From a8a79967a4617b69624d03fb6397e78b14992c40 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 24 Jan 2024 15:28:33 +0100 Subject: [PATCH] Add ability to turn off multi-widget text selection --- crates/egui/src/style.rs | 19 +++++++- .../text_selection/label_text_selection.rs | 46 ++++++++++++------- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 6422e654723..1b81c0e5918 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -720,6 +720,12 @@ pub struct Interaction { /// Can you select the text on a [`crate::Label`] by default? pub selectable_labels: bool, + + /// Can the user select text that span multiple labels? + /// + /// The default is `true`, but text seelction can be slightly glitchy, + /// so you may want to disable it. + pub multi_widget_text_select: bool, } /// Controls the visual style (colors etc) of egui. @@ -1120,6 +1126,7 @@ impl Default for Interaction { show_tooltips_only_when_still: true, tooltip_delay: 0.0, selectable_labels: true, + multi_widget_text_select: true, } } } @@ -1580,6 +1587,7 @@ impl Interaction { show_tooltips_only_when_still, tooltip_delay, selectable_labels, + multi_widget_text_select, } = self; ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side")); ui.add( @@ -1590,7 +1598,16 @@ impl Interaction { "Only show tooltips if mouse is still", ); ui.add(Slider::new(tooltip_delay, 0.0..=1.0).text("tooltip_delay")); - ui.checkbox(selectable_labels, "Selectable text in labels"); + + ui.horizontal(|ui| { + ui.checkbox(selectable_labels, "Selectable text in labels"); + if *selectable_labels { + ui.checkbox( + multi_widget_text_select, + "Selectable text across multiple widgets", + ); + } + }); ui.vertical_centered(|ui| reset_button(ui, self)); } diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 975b64eaf63..45730375ba7 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -119,9 +119,6 @@ pub struct LabelSelectionState { /// Have we reached the widget containing the secondary selection? has_reached_secondary: bool, - /// Did we reach the primary cursor before the secondary? - reached_primary_first: bool, // TODO: remove this - /// Accumulated text to copy. text_to_copy: String, last_copied_galley_rect: Option, @@ -140,7 +137,6 @@ impl Default for LabelSelectionState { is_dragging: Default::default(), has_reached_primary: Default::default(), has_reached_secondary: Default::default(), - reached_primary_first: Default::default(), text_to_copy: Default::default(), last_copied_galley_rect: Default::default(), painted_shape_idx: Default::default(), @@ -172,7 +168,6 @@ impl LabelSelectionState { state.selection_bbox_this_frame = Rect::NOTHING; state.any_hovered = false; - state.reached_primary_first = false; state.has_reached_primary = false; state.has_reached_secondary = false; state.text_to_copy.clear(); @@ -312,7 +307,12 @@ impl LabelSelectionState { return TextCursorState::default(); } - if self.is_dragging { + let multi_widget_text_select = ui.style().interaction.multi_widget_text_select; + + let may_select_widget = + multi_widget_text_select || selection.primary.widget_id == response.id; + + if self.is_dragging && may_select_widget { if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { let galley_rect = Rect::from_min_size(galley_pos, galley.size()); let galley_rect = galley_rect.intersect(ui.clip_rect()); @@ -321,10 +321,14 @@ impl LabelSelectionState { .x_range() .intersects(self.selection_bbox_last_frame.x_range()); + let has_reached_primary = + self.has_reached_primary || response.id == selection.primary.widget_id; + let has_reached_secondary = + self.has_reached_secondary || response.id == selection.secondary.widget_id; + let new_primary = if response.contains_pointer() { // Dragging into this widget - easy case: - let cursor = galley.cursor_from_pos(pointer_pos - galley_pos); - Some(cursor) + Some(galley.cursor_from_pos(pointer_pos - galley_pos)) } else if is_in_same_column && !self.has_reached_primary && selection.primary.pos.y <= selection.secondary.pos.y @@ -338,8 +342,8 @@ impl LabelSelectionState { } Some(galley.begin()) } else if is_in_same_column - && self.has_reached_secondary - && self.has_reached_primary + && has_reached_secondary + && has_reached_primary && selection.secondary.pos.y <= selection.primary.pos.y && selection.secondary.pos.y <= galley_rect.bottom() && galley_rect.bottom() <= pointer_pos.y @@ -415,7 +419,6 @@ impl LabelSelectionState { galley.begin().ccursor } else { // Select everything from the cursor onward: - self.reached_primary_first = true; galley.end().ccursor }; TextCursorState::from(CCursorRange { primary, secondary }) @@ -473,9 +476,12 @@ impl LabelSelectionState { if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { if response.contains_pointer() { - // Handle start-if-drag and double-click-to-select: let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - galley_pos); - cursor_state.pointer_interaction(ui, response, cursor_at_pointer, galley, false); + + // This is where we handle start-of-drag and double-click-to-select. + // Actual drag-to-select happens elsewhere. + let dragged = false; + cursor_state.pointer_interaction(ui, response, cursor_at_pointer, galley, dragged); } } @@ -483,8 +489,11 @@ impl LabelSelectionState { let galley_rect = Rect::from_min_size(galley_pos, galley.size()); self.selection_bbox_this_frame = self.selection_bbox_this_frame.union(galley_rect); - // TODO: only if we contain primary cursor! - process_selection_key_events(ui.ctx(), galley, response.id, &mut cursor_range); + if let Some(selection) = &self.selection { + if selection.primary.widget_id == response.id { + process_selection_key_events(ui.ctx(), galley, response.id, &mut cursor_range); + } + } if got_copy_event(ui.ctx()) { self.copy_text(galley_pos, galley, &cursor_range); @@ -506,13 +515,16 @@ impl LabelSelectionState { let secondary_changed = Some(range.secondary) != old_range.map(|r| r.secondary); selection.layer_id = response.layer_id; - if primary_changed { + + if primary_changed || !ui.style().interaction.multi_widget_text_select { selection.primary = WidgetTextCursor::new(widget_id, range.primary, galley_pos, galley); + self.has_reached_primary = true; } - if secondary_changed { + if secondary_changed || !ui.style().interaction.multi_widget_text_select { selection.secondary = WidgetTextCursor::new(widget_id, range.secondary, galley_pos, galley); + self.has_reached_secondary = true; } } else { // Start of a new selection