make more InputState properties private

This commit is contained in:
temportalflux
2026-07-11 09:31:07 -04:00
parent 6ce4ebbb2a
commit 344804c232
3 changed files with 77 additions and 64 deletions
+12 -11
View File
@@ -191,7 +191,7 @@ struct InputStateSnapshot {
marked_range: Option<Range<usize>>, marked_range: Option<Range<usize>>,
cursor_position: usize, cursor_position: usize,
logical_lines: Vec<InputLogicalLine>, logical_lines: Vec<InputLogicalLine>,
scroll_offset: Pixels, scroll_distance: Pixels,
line_height: Pixels, line_height: Pixels,
} }
impl InputStateSnapshot { impl InputStateSnapshot {
@@ -201,7 +201,7 @@ impl InputStateSnapshot {
let marked_range = input_state.marked_range().cloned(); let marked_range = input_state.marked_range().cloned();
let cursor_position = input_state.cursor_position(); let cursor_position = input_state.cursor_position();
let logical_lines = input_state.logical_lines.clone(); let logical_lines = input_state.logical_lines.clone();
let scroll_offset = input_state.scroll_offset; let scroll_distance = input_state.distance_from_top();
let line_height = input_state.line_height(); let line_height = input_state.line_height();
let layout_axis = input_state.layout_style().axis(); let layout_axis = input_state.layout_style().axis();
let should_center_placeholder = matches!( let should_center_placeholder = matches!(
@@ -216,7 +216,7 @@ impl InputStateSnapshot {
marked_range, marked_range,
cursor_position, cursor_position,
logical_lines, logical_lines,
scroll_offset, scroll_distance,
line_height, line_height,
} }
} }
@@ -241,6 +241,7 @@ impl<'app> PaintContext<'app> {
) { ) {
let axis = self.snapshot.layout_axis; let axis = self.snapshot.layout_axis;
let bounds = self.bounds; let bounds = self.bounds;
let scroll_distance = self.snapshot.scroll_distance;
window.on_mouse_event({ window.on_mouse_event({
let input = entity.clone(); let input = entity.clone();
move |event: &MouseDownEvent, phase, window, cx| { move |event: &MouseDownEvent, phase, window, cx| {
@@ -257,7 +258,7 @@ impl<'app> PaintContext<'app> {
input.update(cx, |input, cx| { input.update(cx, |input, cx| {
// Converts a screen position to a position relative to the text area origin, adjusted for scroll offset. // Converts a screen position to a position relative to the text area origin, adjusted for scroll offset.
let text_position = (event.position - bounds.origin) let text_position = (event.position - bounds.origin)
.apply_along(axis, |pos| pos + input.scroll_offset); .apply_along(axis, |pos| pos + scroll_distance);
input.on_mouse_down( input.on_mouse_down(
text_position, text_position,
event.click_count, event.click_count,
@@ -293,7 +294,7 @@ impl<'app> PaintContext<'app> {
input.update(cx, |input, cx| { input.update(cx, |input, cx| {
// Converts a screen position to a position relative to the text area origin, adjusted for scroll offset. // Converts a screen position to a position relative to the text area origin, adjusted for scroll offset.
let text_position = (event.position - bounds.origin) let text_position = (event.position - bounds.origin)
.apply_along(axis, |pos| pos + input.scroll_offset); .apply_along(axis, |pos| pos + scroll_distance);
input.on_mouse_move(text_position, cx); input.on_mouse_move(text_position, cx);
}); });
} }
@@ -330,7 +331,7 @@ impl<'app> PaintContext<'app> {
} }
} }
}; };
input.scroll_offset = (input.scroll_offset - delta).clamp(px(0.), max_scroll); input.apply_scroll_delta(delta, max_scroll);
cx.notify(); cx.notify();
}); });
} }
@@ -372,7 +373,7 @@ impl<'app> PaintContext<'app> {
fn paint_selection(&self, window: &mut Window) { fn paint_selection(&self, window: &mut Window) {
let one_line = self.snapshot.logical_lines.len() == 1; let one_line = self.snapshot.logical_lines.len() == 1;
for line in &self.snapshot.logical_lines { for line in &self.snapshot.logical_lines {
let line_y = line.y_offset - self.snapshot.scroll_offset; let line_y = line.y_offset - self.snapshot.scroll_distance;
if !one_line { if !one_line {
if !self.is_line_visible(line) { if !self.is_line_visible(line) {
@@ -442,7 +443,7 @@ impl<'app> PaintContext<'app> {
fn paint_text(&self, window: &mut Window, cx: &mut App) { fn paint_text(&self, window: &mut Window, cx: &mut App) {
for line_layout in &self.snapshot.logical_lines { for line_layout in &self.snapshot.logical_lines {
let line_y = line_layout.y_offset - self.snapshot.scroll_offset; let line_y = line_layout.y_offset - self.snapshot.scroll_distance;
if !self.is_line_visible(line_layout) { if !self.is_line_visible(line_layout) {
continue; continue;
@@ -499,7 +500,7 @@ impl<'app> PaintContext<'app> {
fn find_cursor_position_in_layouts(&self) -> Point<Pixels> { fn find_cursor_position_in_layouts(&self) -> Point<Pixels> {
for line in &self.snapshot.logical_lines { for line in &self.snapshot.logical_lines {
let line_y = line.y_offset - self.snapshot.scroll_offset; let line_y = line.y_offset - self.snapshot.scroll_distance;
if !self.is_line_visible(line) { if !self.is_line_visible(line) {
continue; continue;
@@ -544,7 +545,7 @@ impl<'app> PaintContext<'app> {
} }
fn is_line_visible(&self, line: &InputLogicalLine) -> bool { fn is_line_visible(&self, line: &InputLogicalLine) -> bool {
let line_y = line.y_offset - self.snapshot.scroll_offset; let line_y = line.y_offset - self.snapshot.scroll_distance;
let line_bottom = line_y + self.snapshot.line_height * line.visual_line_count as f32; let line_bottom = line_y + self.snapshot.line_height * line.visual_line_count as f32;
line_bottom >= px(0.) && line_y <= self.bounds.size.height line_bottom >= px(0.) && line_y <= self.bounds.size.height
} }
@@ -565,7 +566,7 @@ impl<'app> PaintContext<'app> {
return; return;
}; };
let line_y = line.y_offset - self.snapshot.scroll_offset; let line_y = line.y_offset - self.snapshot.scroll_distance;
let line_start = line.text_range.start; let line_start = line.text_range.start;
let line_end = line.text_range.end; let line_end = line.text_range.end;
+36 -23
View File
@@ -44,15 +44,15 @@ pub struct InputState {
cached_utf16_len: Option<usize>, cached_utf16_len: Option<usize>,
/// The style of layout (single or multiline). /// The style of layout (single or multiline).
pub(super) layout_style: InputLayoutStyle, layout_style: InputLayoutStyle,
/// The utf-8 character range that is currently selected by the user. /// The utf-8 character range that is currently selected by the user.
/// NOTE: because each input has its own selection state, its trivial for users to have multiple selections active across multiple inputs at the same time. /// NOTE: because each input has its own selection state, its trivial for users to have multiple selections active across multiple inputs at the same time.
/// This could be considered undesirable behavior, and doing so would prompt the question of should there be a mechanism to clear selection when focus is lost. /// This could be considered undesirable behavior, and doing so would prompt the question of should there be a mechanism to clear selection when focus is lost.
pub(super) selected_range: Range<usize>, selected_range: Range<usize>,
/// The direction of the selection_range. Forward means providing in iteration order along `content`. Back means reverse iteration order. /// The direction of the selection_range. Forward means providing in iteration order along `content`. Back means reverse iteration order.
pub(super) selection_direction: NavigationDirection, selection_direction: NavigationDirection,
/// The utf-8 character range of `content` that is currently marked/highlighted. /// The utf-8 character range of `content` that is currently marked/highlighted.
pub(super) marked_range: Option<Range<usize>>, marked_range: Option<Range<usize>>,
// refreshed each update by the element, for conveinent access in mutations and painting // refreshed each update by the element, for conveinent access in mutations and painting
pub(super) layout_data: InputLayoutData, pub(super) layout_data: InputLayoutData,
@@ -68,7 +68,7 @@ pub struct InputState {
/// The number of times the user has clicked `last_click_position`. Used to determine which click behavior to trigger, depending on single, double, or triple clicks. /// The number of times the user has clicked `last_click_position`. Used to determine which click behavior to trigger, depending on single, double, or triple clicks.
click_count: usize, click_count: usize,
/// The distance in pixels from the start of the text a user has scrolled along the layout_style axis (singleline is horizontal, multiline is vertical). /// The distance in pixels from the start of the text a user has scrolled along the layout_style axis (singleline is horizontal, multiline is vertical).
pub(super) scroll_offset: Pixels, scroll_distance: Pixels,
/// The maximum duration between changes to `content` that can be grouped together as a single entry in the history log. /// The maximum duration between changes to `content` that can be grouped together as a single entry in the history log.
history_grouping_interval: Duration, history_grouping_interval: Duration,
@@ -163,7 +163,7 @@ impl InputState {
is_selecting: false, is_selecting: false,
last_click_position: None, last_click_position: None,
click_count: 0, click_count: 0,
scroll_offset: px(0.), scroll_distance: px(0.),
history_grouping_interval: super::DEFAULT_GROUP_INTERVAL, history_grouping_interval: super::DEFAULT_GROUP_INTERVAL,
history_undo_stack: Vec::new(), history_undo_stack: Vec::new(),
@@ -232,6 +232,10 @@ impl InputState {
&self.selected_range &self.selected_range
} }
pub fn selection_direction(&self) -> NavigationDirection {
self.selection_direction
}
/// Sets the selection range directly. /// Sets the selection range directly.
pub fn set_selected_range(&mut self, range: Range<usize>) { pub fn set_selected_range(&mut self, range: Range<usize>) {
let range = range.start.min(self.content.len())..range.end.min(self.content.len()); let range = range.start.min(self.content.len())..range.end.min(self.content.len());
@@ -254,7 +258,7 @@ impl InputState {
/// Returns true if the scroll position is at the top. /// Returns true if the scroll position is at the top.
pub fn at_top(&self) -> bool { pub fn at_top(&self) -> bool {
self.scroll_offset <= px(0.) self.scroll_distance <= px(0.)
} }
/// Returns true if the scroll position is at the bottom. /// Returns true if the scroll position is at the bottom.
@@ -266,7 +270,7 @@ impl InputState {
return true; return true;
} }
self.scroll_offset + visible_height >= content_height self.scroll_distance + visible_height >= content_height
} }
/// Returns the scroll progress as a value from 0.0 (top) to 1.0 (bottom). /// Returns the scroll progress as a value from 0.0 (top) to 1.0 (bottom).
@@ -279,12 +283,12 @@ impl InputState {
return 0.0; return 0.0;
} }
(self.scroll_offset / max_scroll).clamp(0.0, 1.0) (self.scroll_distance / max_scroll).clamp(0.0, 1.0)
} }
/// Returns how far the content is scrolled from the top in pixels. /// Returns how far the content is scrolled from the top in pixels.
pub fn distance_from_top(&self) -> Pixels { pub fn distance_from_top(&self) -> Pixels {
self.scroll_offset.max(px(0.)) self.scroll_distance.max(px(0.))
} }
/// Returns how far the content is from the bottom in pixels. /// Returns how far the content is from the bottom in pixels.
@@ -297,7 +301,7 @@ impl InputState {
return px(0.); return px(0.);
} }
(max_scroll - self.scroll_offset).max(px(0.)) (max_scroll - self.scroll_distance).max(px(0.))
} }
/// Configures how long the input will wait between user-input changes to create new logs in the history for undo/redo. /// Configures how long the input will wait between user-input changes to create new logs in the history for undo/redo.
@@ -347,7 +351,7 @@ impl InputState {
self.cached_utf16_len = Some(cached_len - removed_utf16_len + added_utf16_len); self.cached_utf16_len = Some(cached_len - removed_utf16_len + added_utf16_len);
} }
self.replace_range(range.clone(), &text_to_insert); self.replace_text_at_range(range.clone(), &text_to_insert);
self.selected_range = self.selected_range =
range.start + text_to_insert.len()..range.start + text_to_insert.len(); range.start + text_to_insert.len()..range.start + text_to_insert.len();
@@ -834,8 +838,12 @@ impl InputState {
self.layout_data.line_height self.layout_data.line_height
} }
pub(super) fn set_marked_range(&mut self, range: Option<Range<usize>>) {
self.marked_range = range;
}
/// Replaces the provided utf-8 character range with the provided text /// Replaces the provided utf-8 character range with the provided text
pub(super) fn replace_range(&mut self, range: Range<usize>, text: &str) { pub(super) fn replace_text_at_range(&mut self, range: Range<usize>, text: &str) {
self.content.replace_range(range, &text); self.content.replace_range(range, &text);
} }
@@ -944,6 +952,10 @@ impl InputState {
self.content.len() self.content.len()
} }
pub(super) fn apply_scroll_delta(&mut self, delta: Pixels, max: Pixels) {
self.scroll_distance = (self.scroll_distance - delta).clamp(px(0.), max);
}
pub(super) fn scroll_to_cursor(&mut self) { pub(super) fn scroll_to_cursor(&mut self) {
if self.logical_lines.is_empty() { if self.logical_lines.is_empty() {
return; return;
@@ -971,19 +983,20 @@ impl InputState {
px(0.) px(0.)
}; };
let visible_left = self.scroll_offset; let visible_left = self.scroll_distance;
let visible_right = self.scroll_offset + self.layout_data.available_size.width; let visible_right = self.scroll_distance + self.layout_data.available_size.width;
// Add some padding so cursor isn't right at the edge // Add some padding so cursor isn't right at the edge
let padding = px(2.0); let padding = px(2.0);
if cursor_x < visible_left + padding { if cursor_x < visible_left + padding {
self.scroll_offset = (cursor_x - padding).max(px(0.)); self.scroll_distance = (cursor_x - padding).max(px(0.));
} else if cursor_x > visible_right - padding { } else if cursor_x > visible_right - padding {
self.scroll_offset = cursor_x - self.layout_data.available_size.width + padding; self.scroll_distance =
cursor_x - self.layout_data.available_size.width + padding;
} }
self.scroll_offset = self.scroll_offset.max(px(0.)); self.scroll_distance = self.scroll_distance.max(px(0.));
} }
InputLayoutStyle::MultiLine => { InputLayoutStyle::MultiLine => {
if self.layout_data.available_size.height <= px(0.) { if self.layout_data.available_size.height <= px(0.) {
@@ -1015,18 +1028,18 @@ impl InputState {
line.y_offset line.y_offset
}; };
let visible_top = self.scroll_offset; let visible_top = self.scroll_distance;
let visible_bottom = let visible_bottom =
self.scroll_offset + self.layout_data.available_size.height; self.scroll_distance + self.layout_data.available_size.height;
if cursor_visual_y < visible_top { if cursor_visual_y < visible_top {
self.scroll_offset = cursor_visual_y; self.scroll_distance = cursor_visual_y;
} else if cursor_visual_y + line_height > visible_bottom { } else if cursor_visual_y + line_height > visible_bottom {
self.scroll_offset = (cursor_visual_y + line_height) self.scroll_distance = (cursor_visual_y + line_height)
- self.layout_data.available_size.height; - self.layout_data.available_size.height;
} }
self.scroll_offset = self.scroll_offset.max(px(0.)); self.scroll_distance = self.scroll_distance.max(px(0.));
break; break;
} }
} }
@@ -28,8 +28,8 @@ impl EntityInputHandler for super::InputState {
_cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> Option<UTF16Selection> { ) -> Option<UTF16Selection> {
Some(UTF16Selection { Some(UTF16Selection {
range: self.utf_range_8to16(&self.selected_range), range: self.utf_range_8to16(self.selected_range()),
reversed: self.selection_direction == NavigationDirection::Back, reversed: self.selection_direction() == NavigationDirection::Back,
}) })
} }
@@ -38,13 +38,13 @@ impl EntityInputHandler for super::InputState {
_window: &mut Window, _window: &mut Window,
_cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> Option<Range<usize>> { ) -> Option<Range<usize>> {
self.marked_range self.marked_range()
.as_ref() .as_ref()
.map(|range| self.utf_range_8to16(range)) .map(|range| self.utf_range_8to16(range))
} }
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) { fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.marked_range = None; self.set_marked_range(None);
} }
fn replace_text_in_range( fn replace_text_in_range(
@@ -57,21 +57,21 @@ impl EntityInputHandler for super::InputState {
let range = range_utf16 let range = range_utf16
.as_ref() .as_ref()
.map(|range_utf16| self.utf_range_16to8(range_utf16)) .map(|range_utf16| self.utf_range_16to8(range_utf16))
.or(self.marked_range.clone()) .or(self.marked_range().cloned())
.unwrap_or(self.selected_range.clone()); .unwrap_or(self.selected_range().clone());
let range = range.start.min(self.content().len())..range.end.min(self.content().len()); let range = range.start.min(self.content().len())..range.end.min(self.content().len());
let text_to_insert = self.layout_style.sanitize_content(new_text); let text_to_insert = self.layout_style().sanitize_content(new_text);
// Record patch for undo before modifying content // Record patch for undo before modifying content
self.push_undo_patch(range.clone(), text_to_insert.len()); self.push_undo_patch(range.clone(), text_to_insert.len());
self.push_undo_patch(range.clone(), text_to_insert.len());
self.update_utf16_len(range.clone(), &text_to_insert);
self.replace_range(range.clone(), &text_to_insert);
self.selected_range = self.update_utf16_len(range.clone(), &text_to_insert);
range.start + text_to_insert.len()..range.start + text_to_insert.len(); self.replace_text_at_range(range.clone(), &text_to_insert);
self.marked_range.take(); self.set_selected_range(
range.start + text_to_insert.len()..range.start + text_to_insert.len(),
);
self.set_marked_range(None);
self.layout_data.dirty = true; self.layout_data.dirty = true;
self.pause_cursor_blink(cx); self.pause_cursor_blink(cx);
@@ -90,28 +90,27 @@ impl EntityInputHandler for super::InputState {
let range = range_utf16 let range = range_utf16
.as_ref() .as_ref()
.map(|range_utf16| self.utf_range_16to8(range_utf16)) .map(|range_utf16| self.utf_range_16to8(range_utf16))
.or(self.marked_range.clone()) .or(self.marked_range().cloned())
.unwrap_or(self.selected_range.clone()); .unwrap_or(self.selected_range().clone());
let range = range.start.min(self.content().len())..range.end.min(self.content().len()); let range = range.start.min(self.content().len())..range.end.min(self.content().len());
let text_to_insert = self.layout_style.sanitize_content(new_text); let text_to_insert = self.layout_style().sanitize_content(new_text);
self.update_utf16_len(range.clone(), &text_to_insert); self.update_utf16_len(range.clone(), &text_to_insert);
self.replace_range(range.clone(), &text_to_insert); self.replace_text_at_range(range.clone(), &text_to_insert);
self.set_marked_range(match text_to_insert.is_empty() {
if !text_to_insert.is_empty() { true => None,
self.marked_range = Some(range.start..range.start + text_to_insert.len()); false => Some(range.start..range.start + text_to_insert.len()),
} else { });
self.marked_range = None; self.set_selected_range({
} let new_range = new_selected_range_utf16.as_ref();
let new_range = new_range.map(|range_utf16| self.utf_range_16to8(range_utf16));
self.selected_range = new_selected_range_utf16 let new_range = new_range
.as_ref() .map(|new_range| new_range.start + range.start..new_range.end + range.start);
.map(|range_utf16| self.utf_range_16to8(range_utf16)) new_range.unwrap_or_else(|| {
.map(|new_range| new_range.start + range.start..new_range.end + range.start)
.unwrap_or_else(|| {
range.start + text_to_insert.len()..range.start + text_to_insert.len() range.start + text_to_insert.len()..range.start + text_to_insert.len()
}); })
});
self.layout_data.dirty = true; self.layout_data.dirty = true;
cx.emit(InputStateEvent::TextChanged); cx.emit(InputStateEvent::TextChanged);