Skip to content

Commit

Permalink
Fix: dragging to above/below a TextEdit or Label will select text…
Browse files Browse the repository at this point in the history
… to begin/end (#3858)

This also adds a `Galley::begin` method to match `Galley::end` (both
returning cursor positions).

* Part of #3816
  • Loading branch information
emilk authored Jan 22, 2024
1 parent d319489 commit ff39fd6
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 7 deletions.
6 changes: 3 additions & 3 deletions crates/egui/src/text_selection/cursor_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
23 changes: 22 additions & 1 deletion crates/egui/src/text_selection/text_cursor_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ pub struct TextCursorState {
ccursor_range: Option<CCursorRange>,
}

impl From<CursorRange> 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<CCursorRange> 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()
Expand All @@ -33,7 +54,7 @@ impl TextCursorState {
})
}

pub fn range(&mut self, galley: &Galley) -> Option<CursorRange> {
pub fn range(&self, galley: &Galley) -> Option<CursorRange> {
self.cursor_range
.map(|cursor_range| {
// We only use the PCursor (paragraph number, and character offset within that paragraph).
Expand Down
7 changes: 6 additions & 1 deletion crates/epaint/src/text/cursor.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 29 additions & 2 deletions crates/epaint/src/text/text_layout_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,16 +712,33 @@ 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();

let mut ccursor_index = 0;
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;
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit ff39fd6

Please sign in to comment.