Clamp text cursor positions in the same places where we used to (#7081)

Closes #7077.

This fixes the problem shown in #7077 where clearing a `TextEdit`
wouldn't reset its cursor position. I've fixed that by adding back the
`TextCursorState::range` method, which clamps the selection range to
that of the passed `Galley`, and calling it in the same places where it
was called before #5785.

(/cc @juancampa)

* [x] I have followed the instructions in the PR template
This commit is contained in:
valadaptive 2025-06-15 19:53:00 -04:00 committed by GitHub
parent df2c16ef0a
commit 54fded362d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 10 deletions

View File

@ -530,7 +530,7 @@ impl LabelSelectionState {
let mut cursor_state = self.cursor_for(ui, response, global_from_galley, galley); let mut cursor_state = self.cursor_for(ui, response, global_from_galley, galley);
let old_range = cursor_state.char_range(); let old_range = cursor_state.range(galley);
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
if response.contains_pointer() { if response.contains_pointer() {
@ -544,7 +544,7 @@ impl LabelSelectionState {
} }
} }
if let Some(mut cursor_range) = cursor_state.char_range() { if let Some(mut cursor_range) = cursor_state.range(galley) {
let galley_rect = global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size()); let galley_rect = global_from_galley * Rect::from_min_size(Pos2::ZERO, galley.size());
self.selection_bbox_this_frame = self.selection_bbox_this_frame.union(galley_rect); self.selection_bbox_this_frame = self.selection_bbox_this_frame.union(galley_rect);
@ -562,7 +562,7 @@ impl LabelSelectionState {
} }
// Look for changes due to keyboard and/or mouse interaction: // Look for changes due to keyboard and/or mouse interaction:
let new_range = cursor_state.char_range(); let new_range = cursor_state.range(galley);
let selection_changed = old_range != new_range; let selection_changed = old_range != new_range;
if let (true, Some(range)) = (selection_changed, new_range) { if let (true, Some(range)) = (selection_changed, new_range) {
@ -632,7 +632,7 @@ impl LabelSelectionState {
} }
} }
let cursor_range = cursor_state.char_range(); let cursor_range = cursor_state.range(galley);
let mut new_vertex_indices = vec![]; let mut new_vertex_indices = vec![];

View File

@ -35,6 +35,16 @@ impl TextCursorState {
self.ccursor_range self.ccursor_range
} }
/// The currently selected range of characters, clamped within the character
/// range of the given [`Galley`].
pub fn range(&self, galley: &Galley) -> Option<CCursorRange> {
self.ccursor_range.map(|mut range| {
range.primary = galley.clamp_cursor(&range.primary);
range.secondary = galley.clamp_cursor(&range.secondary);
range
})
}
/// Sets the currently selected range of characters. /// Sets the currently selected range of characters.
pub fn set_char_range(&mut self, ccursor_range: Option<CCursorRange>) { pub fn set_char_range(&mut self, ccursor_range: Option<CCursorRange>) {
self.ccursor_range = ccursor_range; self.ccursor_range = ccursor_range;
@ -69,7 +79,7 @@ impl TextCursorState {
if response.hovered() && ui.input(|i| i.pointer.any_pressed()) { if response.hovered() && ui.input(|i| i.pointer.any_pressed()) {
// The start of a drag (or a click). // The start of a drag (or a click).
if ui.input(|i| i.modifiers.shift) { if ui.input(|i| i.modifiers.shift) {
if let Some(mut cursor_range) = self.char_range() { if let Some(mut cursor_range) = self.range(galley) {
cursor_range.primary = cursor_at_pointer; cursor_range.primary = cursor_at_pointer;
self.set_char_range(Some(cursor_range)); self.set_char_range(Some(cursor_range));
} else { } else {
@ -81,7 +91,7 @@ impl TextCursorState {
true true
} else if is_being_dragged { } else if is_being_dragged {
// Drag to select text: // Drag to select text:
if let Some(mut cursor_range) = self.char_range() { if let Some(mut cursor_range) = self.range(galley) {
cursor_range.primary = cursor_at_pointer; cursor_range.primary = cursor_at_pointer;
self.set_char_range(Some(cursor_range)); self.set_char_range(Some(cursor_range));
} }

View File

@ -617,7 +617,7 @@ impl TextEdit<'_> {
} }
let mut cursor_range = None; let mut cursor_range = None;
let prev_cursor_range = state.cursor.char_range(); let prev_cursor_range = state.cursor.range(&galley);
if interactive && ui.memory(|mem| mem.has_focus(id)) { if interactive && ui.memory(|mem| mem.has_focus(id)) {
ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter)); ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter));
@ -720,7 +720,7 @@ impl TextEdit<'_> {
let has_focus = ui.memory(|mem| mem.has_focus(id)); let has_focus = ui.memory(|mem| mem.has_focus(id));
if has_focus { if has_focus {
if let Some(cursor_range) = state.cursor.char_range() { if let Some(cursor_range) = state.cursor.range(&galley) {
// Add text selection rectangles to the galley: // Add text selection rectangles to the galley:
paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None); paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None);
} }
@ -742,7 +742,7 @@ impl TextEdit<'_> {
painter.galley(galley_pos, galley.clone(), text_color); painter.galley(galley_pos, galley.clone(), text_color);
if has_focus { if has_focus {
if let Some(cursor_range) = state.cursor.char_range() { if let Some(cursor_range) = state.cursor.range(&galley) {
let primary_cursor_rect = let primary_cursor_rect =
cursor_rect(&galley, &cursor_range.primary, row_height) cursor_rect(&galley, &cursor_range.primary, row_height)
.translate(galley_pos.to_vec2()); .translate(galley_pos.to_vec2());
@ -898,7 +898,7 @@ fn events(
) -> (bool, CCursorRange) { ) -> (bool, CCursorRange) {
let os = ui.ctx().os(); let os = ui.ctx().os();
let mut cursor_range = state.cursor.char_range().unwrap_or(default_cursor_range); let mut cursor_range = state.cursor.range(galley).unwrap_or(default_cursor_range);
// We feed state to the undoer both before and after handling input // We feed state to the undoer both before and after handling input
// so that the undoer creates automatic saves even when there are no events for a while. // so that the undoer creates automatic saves even when there are no events for a while.

View File

@ -1104,6 +1104,10 @@ impl Galley {
} }
} }
pub fn clamp_cursor(&self, cursor: &CCursor) -> CCursor {
self.cursor_from_layout(self.layout_from_cursor(*cursor))
}
pub fn cursor_up_one_row( pub fn cursor_up_one_row(
&self, &self,
cursor: &CCursor, cursor: &CCursor,