diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index 8cdadfbbb9c..b0680fdf203 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -38,7 +38,7 @@ impl CursorRange { /// Select all the text in a galley pub fn select_all(galley: &Galley) -> Self { - Self::two(Cursor::default(), galley.end()) + Self::two(galley.begin(), galley.end()) } pub fn as_ccursor_range(&self) -> CCursorRange { @@ -342,7 +342,7 @@ fn move_single_cursor( Key::ArrowUp => { if modifiers.command { // mac and windows behavior - *cursor = Cursor::default(); + *cursor = galley.begin(); } else { *cursor = galley.cursor_up_one_row(cursor); } @@ -359,7 +359,7 @@ fn move_single_cursor( Key::Home => { if modifiers.ctrl { // windows behavior - *cursor = Cursor::default(); + *cursor = galley.begin(); } else { *cursor = galley.cursor_begin_of_row(cursor); } diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index bdc4736c583..d8adfb83376 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -20,6 +20,27 @@ pub struct TextCursorState { ccursor_range: Option, } +impl From for TextCursorState { + fn from(cursor_range: CursorRange) -> Self { + Self { + cursor_range: Some(cursor_range), + ccursor_range: Some(CCursorRange { + primary: cursor_range.primary.ccursor, + secondary: cursor_range.secondary.ccursor, + }), + } + } +} + +impl From for TextCursorState { + fn from(ccursor_range: CCursorRange) -> Self { + Self { + cursor_range: None, + ccursor_range: Some(ccursor_range), + } + } +} + impl TextCursorState { pub fn is_empty(&self) -> bool { self.cursor_range.is_none() && self.ccursor_range.is_none() @@ -33,7 +54,7 @@ impl TextCursorState { }) } - pub fn range(&mut self, galley: &Galley) -> Option { + pub fn range(&self, galley: &Galley) -> Option { self.cursor_range .map(|cursor_range| { // We only use the PCursor (paragraph number, and character offset within that paragraph). diff --git a/crates/epaint/src/text/cursor.rs b/crates/epaint/src/text/cursor.rs index f06d34cd586..a994aa49a89 100644 --- a/crates/epaint/src/text/cursor.rs +++ b/crates/epaint/src/text/cursor.rs @@ -1,6 +1,8 @@ //! Different types of text cursors, i.e. ways to point into a [`super::Galley`]. -/// Character cursor +/// Character cursor. +/// +/// The default cursor is zero. #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct CCursor { @@ -110,9 +112,12 @@ impl PartialEq for PCursor { } /// All different types of cursors together. +/// /// They all point to the same place, but in their own different ways. /// pcursor/rcursor can also point to after the end of the paragraph/row. /// Does not implement `PartialEq` because you must think which cursor should be equivalent. +/// +/// The default cursor is the zero-cursor, to the first character. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Cursor { diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index e1566becfb7..4e44a064101 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -712,8 +712,25 @@ impl Galley { self.pos_from_pcursor(cursor.pcursor) // pcursor is what TextEdit stores } - /// Cursor at the given position within the galley + /// Cursor at the given position within the galley. + /// + /// A cursor above the galley is considered + /// same as a cursor at the start, + /// and a cursor below the galley is considered + /// same as a cursor at the end. + /// This allows implementing text-selection by dragging above/below the galley. pub fn cursor_from_pos(&self, pos: Vec2) -> Cursor { + if let Some(first_row) = self.rows.first() { + if pos.y < first_row.min_y() { + return self.begin(); + } + } + if let Some(last_row) = self.rows.last() { + if last_row.max_y() < pos.y { + return self.end(); + } + } + let mut best_y_dist = f32::INFINITY; let mut cursor = Cursor::default(); @@ -721,7 +738,7 @@ impl Galley { let mut pcursor_it = PCursor::default(); for (row_nr, row) in self.rows.iter().enumerate() { - let is_pos_within_row = pos.y >= row.min_y() && pos.y <= row.max_y(); + let is_pos_within_row = row.min_y() <= pos.y && pos.y <= row.max_y(); let y_dist = (row.min_y() - pos.y).abs().min((row.max_y() - pos.y).abs()); if is_pos_within_row || y_dist < best_y_dist { best_y_dist = y_dist; @@ -755,12 +772,22 @@ impl Galley { pcursor_it.offset += row.char_count_including_newline(); } } + cursor } } /// ## Cursor positions impl Galley { + /// Cursor to the first character. + /// + /// This is the same as [`Cursor::default`]. + #[inline] + #[allow(clippy::unused_self)] + pub fn begin(&self) -> Cursor { + Cursor::default() + } + /// Cursor to one-past last character. pub fn end(&self) -> Cursor { if self.rows.is_empty() {