Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: dragging to above/below a TextEdit or Label will select text to begin/end #3858

Merged
merged 3 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading