rearrange element quad building

This commit is contained in:
temportalflux
2026-07-11 09:31:07 -04:00
parent 962f89c4a2
commit e7e4c9adf4
2 changed files with 147 additions and 133 deletions
+140 -131
View File
@@ -183,19 +183,27 @@ impl PrepaintElement {
}
}
// NOTE: This value is not currently practical. Current estimation is:
// 1 for caret, 1 for ime, n for each real line that may be wrapped,
// n for selection (at least 1 per line that is wrapped that it encapsulates)
const STACK_ALLOCATED_ELEMENTS: usize = 3usize;
#[doc(hidden)]
pub struct LayoutState<State> {
pub state: Entity<State>,
state: Entity<State>,
}
struct InteractivityPrepaint {
hitbox: Option<Hitbox>,
scroll_offset: Point<Pixels>,
inner_bounds: Bounds<Pixels>,
}
#[doc(hidden)]
pub struct PrepaintState {
pub hitbox: Option<Hitbox>,
pub inner_bounds: Bounds<Pixels>,
pub focus_handle: FocusHandle,
pub elements: SmallVec<[PrepaintElement; 3]>,
pub scroll_offset: Point<Pixels>,
pub caret_visible: bool,
interactivity: InteractivityPrepaint,
focus_handle: FocusHandle,
elements: SmallVec<[PrepaintElement; STACK_ALLOCATED_ELEMENTS]>,
}
impl Element for EditableTextElement {
@@ -421,11 +429,6 @@ impl Element for EditableTextElement {
state.layout_data.size.unwrap_or_else(|| bounds.size)
};
struct InteractivityPrepaint {
hitbox: Option<Hitbox>,
scroll_offset: Point<Pixels>,
inner_bounds: Bounds<Pixels>,
}
let prepaint = self.interactivity().prepaint(
global_id,
inspector_id,
@@ -460,20 +463,134 @@ impl Element for EditableTextElement {
}
},
);
let state = request_layout.state.read(cx);
let focus_handle = state.focus_handle(cx);
let elements = self.build_elements(state, &prepaint, window);
PrepaintState {
interactivity: prepaint,
focus_handle,
elements,
}
}
fn paint(
&mut self,
global_id: Option<&gpui::GlobalElementId>,
inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
if let Some(hitbox) = &prepaint.interactivity.hitbox {
window.set_cursor_style(CursorStyle::IBeam, hitbox);
}
let accepts_input = self.accepts_input;
let inner_bounds = prepaint.interactivity.inner_bounds;
let to_local_position = -(bounds.origin + prepaint.interactivity.scroll_offset);
let perform_paint = |style: &Style, window: &mut Window, cx: &mut App| {
if style.display == Display::None {
return;
}
if accepts_input {
let ime_handler =
ElementInputHandler::new(inner_bounds, request_layout.state.clone());
window.handle_input(&prepaint.focus_handle, ime_handler, cx);
}
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseDownEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if !bounds.contains(&event.position) {
return;
}
if event.button != MouseButton::Left {
return;
}
let text_position = event.position + to_local_position;
state.update(cx, |state, cx| {
state.on_mouse_down(event, text_position, window, cx);
});
}
});
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseUpEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if event.button != MouseButton::Left {
return;
}
state.update(cx, |state, cx| {
state.on_mouse_up(event, window, cx);
});
}
});
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseMoveEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
let text_position = event.position + to_local_position;
state.update(cx, |state, cx| {
state.on_mouse_move(event, text_position, window, cx);
});
}
});
let line_h = window.line_height();
let mut lines = Vec::with_capacity(prepaint.elements.len());
for element in prepaint.elements.drain(..) {
match element {
PrepaintElement::Line { line, point, align } => {
let _ = line.paint(point, line_h, align, Some(bounds), window, cx);
lines.push(line);
}
PrepaintElement::Quad(quad) => window.paint_quad(quad),
}
}
};
self.interactivity().paint(
global_id,
inspector_id,
bounds.clone(),
prepaint.interactivity.hitbox.as_ref(),
window,
cx,
perform_paint,
);
}
}
impl EditableTextElement {
fn build_elements(
&self,
state: &EditableTextState,
prepaint: &InteractivityPrepaint,
window: &mut Window,
) -> SmallVec<[PrepaintElement; STACK_ALLOCATED_ELEMENTS]> {
let InteractivityPrepaint {
hitbox,
hitbox: _,
scroll_offset,
inner_bounds,
} = prepaint;
let state = request_layout.state.read(cx);
let focus_handle = state.focus_handle(cx);
let caret_pos = state.caret_pos();
let selection = state.selected_range();
let ime_range = state.marked_range();
// TODO: Cursor blinking
let cursor_visible = true; // input.cursor_visible();
let mut elements = SmallVec::new();
@@ -561,11 +678,11 @@ impl Element for EditableTextElement {
}
}
// TODO: doesnt render caret when the caret is at the end of the document.
// contains_position doesnt include the last character because then scroll-to-caret is incorrect,
// but we still need to account for "caret is at the end of the last line"
let is_cursor_in_line = segment.contains_position(caret_pos);
if is_cursor_in_line && let Some(wrapped) = &segment.wrapped_line {
// TODO: when the cursor is functionally at a character that is on the next line
// (a line that spans multiple rows), the cursor displays at the end of the previous
// row instead of the start of the next row.
let local_offset = caret_pos.saturating_sub(segment.text_range.start);
let caret_px = wrapped
.position_for_index(local_offset, line_height)
@@ -574,9 +691,7 @@ impl Element for EditableTextElement {
}
}
let is_focused = focus_handle.is_focused(window);
if is_focused
&& cursor_visible
if state.is_caret_visible(window)
&& let Some(carent_point) = caret_point
{
const CURSOR_WIDTH: f32 = 2.0;
@@ -590,113 +705,7 @@ impl Element for EditableTextElement {
elements.push(PrepaintElement::Quad(quad));
}
PrepaintState {
hitbox,
inner_bounds,
focus_handle,
elements,
scroll_offset,
caret_visible: cursor_visible,
}
}
fn paint(
&mut self,
global_id: Option<&gpui::GlobalElementId>,
inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
if let Some(hitbox) = &prepaint.hitbox {
window.set_cursor_style(CursorStyle::IBeam, hitbox);
}
let accepts_input = self.accepts_input;
let inner_bounds = prepaint.inner_bounds;
let to_local_position = -(bounds.origin + prepaint.scroll_offset);
let perform_paint = |style: &Style, window: &mut Window, cx: &mut App| {
if style.display == Display::None {
return;
}
if accepts_input {
let ime_handler =
ElementInputHandler::new(inner_bounds, request_layout.state.clone());
window.handle_input(&prepaint.focus_handle, ime_handler, cx);
}
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseDownEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if !bounds.contains(&event.position) {
return;
}
if event.button != MouseButton::Left {
return;
}
let text_position = event.position + to_local_position;
state.update(cx, |state, cx| {
state.on_mouse_down(event, text_position, window, cx);
});
}
});
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseUpEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if event.button != MouseButton::Left {
return;
}
state.update(cx, |state, cx| {
state.on_mouse_up(event, window, cx);
});
}
});
window.on_mouse_event({
let state = request_layout.state.clone();
move |event: &MouseMoveEvent, phase, window, cx| {
if phase != DispatchPhase::Bubble {
return;
}
let text_position = event.position + to_local_position;
state.update(cx, |state, cx| {
state.on_mouse_move(event, text_position, window, cx);
});
}
});
let line_h = window.line_height();
let mut lines = Vec::with_capacity(prepaint.elements.len());
for element in prepaint.elements.drain(..) {
match element {
PrepaintElement::Line { line, point, align } => {
let _ = line.paint(point, line_h, align, Some(bounds), window, cx);
lines.push(line);
}
PrepaintElement::Quad(quad) => window.paint_quad(quad),
}
}
};
self.interactivity().paint(
global_id,
inspector_id,
bounds.clone(),
prepaint.hitbox.as_ref(),
window,
cx,
perform_paint,
);
elements
}
}
@@ -275,8 +275,6 @@ impl EditableTextState {
// point will be relative to content_size, and may or may not be within the current scroll_bounds
let point = self.find_point_for_character_position(self.caret_pos());
println!("{point:?} ?= {:?}", self.layout_data.scroll_bounds);
// this scroll_offset diverges from the rest of gpui, as it is stored in the
// positive real number space (interactivity stores it in the negatives)
let mut scroll_offset = Cow::Borrowed(&self.layout_data.scroll_bounds.origin);
@@ -530,6 +528,13 @@ impl EditableTextState {
}
}
impl EditableTextState {
pub fn is_caret_visible(&self, window: &Window) -> bool {
// TODO: Cursor blinking
self.focus_handle.is_focused(window)
}
}
impl EntityInputHandler for EditableTextState {
fn text_for_range(
&mut self,