From 9c3b59ada89e465dced2839a43417f2c90f07a8a Mon Sep 17 00:00:00 2001 From: Pol Welter Date: Wed, 1 Jan 2025 11:38:48 +0100 Subject: [PATCH 1/4] Refactor Response to use bit set --- Cargo.lock | 1 + Cargo.toml | 1 + crates/egui/Cargo.toml | 1 + crates/egui/src/context.rs | 79 +++--- crates/egui/src/response.rs | 224 +++++++++--------- .../text_selection/label_text_selection.rs | 2 +- crates/egui/src/ui.rs | 4 +- crates/egui/src/widgets/button.rs | 2 +- crates/egui/src/widgets/drag_value.rs | 4 +- crates/egui/src/widgets/slider.rs | 4 +- crates/egui/src/widgets/text_edit/builder.rs | 14 +- 11 files changed, 175 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 009aeb73130..2f3085cb3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1260,6 +1260,7 @@ dependencies = [ "accesskit", "ahash", "backtrace", + "bitflags 2.6.0", "document-features", "emath", "epaint", diff --git a/Cargo.toml b/Cargo.toml index 1dcae33b6ed..71655115166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ ahash = { version = "0.8.11", default-features = false, features = [ "std", ] } backtrace = "0.3" +bitflags = "2.6" bytemuck = "1.7.2" criterion = { version = "0.5.1", default-features = false } dify = { version = "0.7", default-features = false } diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index a0f11fdfa32..d0407b1c338 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -84,6 +84,7 @@ emath = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false } ahash.workspace = true +bitflags.workspace = true nohash-hasher.workspace = true profiling.workspace = true diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 3ecf42ca46b..e24dd0aa385 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -30,7 +30,7 @@ use crate::{ os::OperatingSystem, output::FullOutput, pass_state::PassState, - resize, scroll_area, + resize, response, scroll_area, util::IdTypeMap, viewport::ViewportClass, Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, @@ -1213,7 +1213,7 @@ impl Context { #[deprecated = "Use Response.contains_pointer or Context::read_response instead"] pub fn widget_contains_pointer(&self, id: Id) -> bool { self.read_response(id) - .map_or(false, |response| response.contains_pointer) + .map_or(false, |response| response.contains_pointer()) } /// Do all interaction for an existing widget, without (re-)registering it. @@ -1237,26 +1237,21 @@ impl Context { rect, interact_rect, sense, - enabled, - contains_pointer: false, - hovered: false, - highlighted, - clicked: false, - fake_primary_click: false, - long_touched: false, - drag_started: false, - dragged: false, - drag_stopped: false, - is_pointer_button_down_on: false, + flags: response::Flags::empty(), interact_pointer_pos: None, - changed: false, intrinsic_size: None, }; + res.flags.set(response::Flags::ENABLED, enabled); + res.flags.set(response::Flags::HIGHLIGHTED, highlighted); + self.write(|ctx| { let viewport = ctx.viewports.entry(ctx.viewport_id()).or_default(); - res.contains_pointer = viewport.interact_widgets.contains_pointer.contains(&id); + res.flags.set( + response::Flags::CONTAINS_POINTER, + viewport.interact_widgets.contains_pointer.contains(&id), + ); let input = &viewport.input; let memory = &mut ctx.memory; @@ -1267,7 +1262,7 @@ impl Context { && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { // Space/enter works like a primary click for e.g. selected buttons - res.fake_primary_click = true; + res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true); } #[cfg(feature = "accesskit")] @@ -1275,23 +1270,38 @@ impl Context { && sense.click && input.has_accesskit_action_request(id, accesskit::Action::Click) { - res.fake_primary_click = true; + res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true); } if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched { - res.long_touched = true; + res.flags.set(response::Flags::LONG_TOUCHED, true); } let interaction = memory.interaction(); - res.is_pointer_button_down_on = interaction.potential_click_id == Some(id) - || interaction.potential_drag_id == Some(id); + res.flags.set( + response::Flags::IS_POINTER_BUTTON_DOWN_ON, + interaction.potential_click_id == Some(id) + || interaction.potential_drag_id == Some(id), + ); - if res.enabled { - res.hovered = viewport.interact_widgets.hovered.contains(&id); - res.dragged = Some(id) == viewport.interact_widgets.dragged; - res.drag_started = Some(id) == viewport.interact_widgets.drag_started; - res.drag_stopped = Some(id) == viewport.interact_widgets.drag_stopped; + if res.enabled() { + res.flags.set( + response::Flags::HOVERED, + viewport.interact_widgets.hovered.contains(&id), + ); + res.flags.set( + response::Flags::DRAGGED, + Some(id) == viewport.interact_widgets.dragged, + ); + res.flags.set( + response::Flags::DRAG_STARTED, + Some(id) == viewport.interact_widgets.drag_started, + ); + res.flags.set( + response::Flags::DRAG_STOPPED, + Some(id) == viewport.interact_widgets.drag_stopped, + ); } let clicked = Some(id) == viewport.interact_widgets.clicked; @@ -1305,19 +1315,22 @@ impl Context { } PointerEvent::Released { click, .. } => { if enabled && sense.click && clicked && click.is_some() { - res.clicked = true; + res.flags.set(response::Flags::CLICKED, true); } - res.is_pointer_button_down_on = false; - res.dragged = false; + res.flags + .set(response::Flags::IS_POINTER_BUTTON_DOWN_ON, false); + res.flags.set(response::Flags::DRAGGED, false); } } } // is_pointer_button_down_on is false when released, but we want interact_pointer_pos // to still work. - let is_interacted_with = - res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped; + let is_interacted_with = res.is_pointer_button_down_on() + || res.long_touched() + || clicked + || res.drag_stopped(); if is_interacted_with { res.interact_pointer_pos = input.pointer.interact_pos(); if let (Some(to_global), Some(pos)) = ( @@ -1330,10 +1343,10 @@ impl Context { if input.pointer.any_down() && !is_interacted_with { // We don't hover widgets while interacting with *other* widgets: - res.hovered = false; + res.flags.set(response::Flags::HOVERED, false); } - let pointer_pressed_elsewhere = any_press && !res.hovered; + let pointer_pressed_elsewhere = any_press && !res.hovered(); if pointer_pressed_elsewhere && memory.has_focus(id) { memory.surrender_focus(id); } @@ -3131,7 +3144,7 @@ impl Context { // TODO(emilk): `Sense::hover_highlight()` let response = ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click())); - if response.hovered && is_visible { + if response.hovered() && is_visible { ui.ctx() .debug_painter() .debug_rect(area.rect(), Color32::RED, ""); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index c65d9ca8c71..3cc99796afb 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -9,14 +9,14 @@ use crate::{ /// The result of adding a widget to a [`Ui`]. /// -/// A [`Response`] lets you know whether or not a widget is being hovered, clicked or dragged. +/// A [`Response`] lets you know whether a widget is being hovered, clicked or dragged. /// It also lets you easily show a tooltip on hover. /// /// Whenever something gets added to a [`Ui`], a [`Response`] object is returned. /// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts. /// /// ⚠️ The `Response` contains a clone of [`Context`], and many methods lock the `Context`. -/// It can therefor be a deadlock to use `Context` from within a context-locking closures, +/// It can therefore be a deadlock to use `Context` from within a context-locking closures, /// such as [`Context::input`]. #[derive(Clone, Debug)] pub struct Response { @@ -50,78 +50,12 @@ pub struct Response { /// (that is handled by the `Painter` directly). pub sense: Sense, - /// Was the widget enabled? - /// If `false`, there was no interaction attempted (not even hover). - #[doc(hidden)] - pub enabled: bool, - // OUT: - /// The pointer is above this widget with no other blocking it. - #[doc(hidden)] - pub contains_pointer: bool, - - /// The pointer is hovering above this widget or the widget was clicked/tapped this frame. - #[doc(hidden)] - pub hovered: bool, - - /// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`]. - #[doc(hidden)] - pub highlighted: bool, - - /// This widget was clicked this frame. - /// - /// Which pointer and how many times we don't know, - /// and ask [`crate::InputState`] about at runtime. - /// - /// This is only set to true if the widget was clicked - /// by an actual mouse. - #[doc(hidden)] - pub clicked: bool, - - /// This widget should act as if clicked due - /// to something else than a click. - /// - /// This is set to true if the widget has keyboard focus and - /// the user hit the Space or Enter key. - #[doc(hidden)] - pub fake_primary_click: bool, - - /// This widget was long-pressed on a touch screen to simulate a secondary click. - #[doc(hidden)] - pub long_touched: bool, - - /// The widget started being dragged this frame. - #[doc(hidden)] - pub drag_started: bool, - - /// The widget is being dragged. - #[doc(hidden)] - pub dragged: bool, - - /// The widget was being dragged, but now it has been released. - #[doc(hidden)] - pub drag_stopped: bool, - - /// Is the pointer button currently down on this widget? - /// This is true if the pointer is pressing down or dragging a widget - #[doc(hidden)] - pub is_pointer_button_down_on: bool, - - /// Where the pointer (mouse/touch) were when when this widget was clicked or dragged. + /// Where the pointer (mouse/touch) were when this widget was clicked or dragged. /// `None` if the widget is not being interacted with. #[doc(hidden)] pub interact_pointer_pos: Option, - /// Was the underlying data changed? - /// - /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. - /// Always `false` for something like a [`Button`](crate::Button). - /// - /// Note that this can be `true` even if the user did not interact with the widget, - /// for instance if an existing slider value was clamped to the given range. - #[doc(hidden)] - pub changed: bool, - /// The intrinsic / desired size of the widget. /// /// For a button, this will be the size of the label + the frames padding, @@ -133,6 +67,71 @@ pub struct Response { /// for improved layouting. /// See for instance [`egui_flex`](https://github.com/lucasmerlin/hello_egui/tree/main/crates/egui_flex). pub intrinsic_size: Option, + + #[doc(hidden)] + pub(crate) flags: Flags, +} + +#[derive(Copy, Clone, Debug)] +pub struct Flags(u16); + +bitflags::bitflags! { + impl Flags: u16 { + /// Was the widget enabled? + /// If `false`, there was no interaction attempted (not even hover). + const ENABLED = 1<<0; + + /// The pointer is above this widget with no other blocking it. + const CONTAINS_POINTER = 1<<1; + + /// The pointer is hovering above this widget or the widget was clicked/tapped this frame. + const HOVERED = 1<<2; + + /// The widget is highlighted via a call to [`Response::highlight`] or + /// [`Context::highlight_widget`]. + const HIGHLIGHTED = 1<<3; + + /// This widget was clicked this frame. + /// + /// Which pointer and how many times we don't know, + /// and ask [`crate::InputState`] about at runtime. + /// + /// This is only set to true if the widget was clicked + /// by an actual mouse. + const CLICKED = 1<<4; + + /// This widget should act as if clicked due + /// to something else than a click. + /// + /// This is set to true if the widget has keyboard focus and + /// the user hit the Space or Enter key. + const FAKE_PRIMARY_CLICKED = 1<<5; + + /// This widget was long-pressed on a touch screen to simulate a secondary click. + const LONG_TOUCHED = 1<<6; + + /// The widget started being dragged this frame. + const DRAG_STARTED = 1<<7; + + /// The widget is being dragged. + const DRAGGED = 1<<8; + + /// The widget was being dragged, but now it has been released. + const DRAG_STOPPED = 1<<9; + + /// Is the pointer button currently down on this widget? + /// This is true if the pointer is pressing down or dragging a widget + const IS_POINTER_BUTTON_DOWN_ON = 1<<10; + + /// Was the underlying data changed? + /// + /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. + /// Always `false` for something like a [`Button`](crate::Button). + /// + /// Note that this can be `true` even if the user did not interact with the widget, + /// for instance if an existing slider value was clamped to the given range. + const CHANGED = 1<<11; + } } impl Response { @@ -150,7 +149,7 @@ impl Response { /// You can use [`Self::interact`] to sense more things *after* adding a widget. #[inline(always)] pub fn clicked(&self) -> bool { - self.fake_primary_click || self.clicked_by(PointerButton::Primary) + self.flags.contains(Flags::FAKE_PRIMARY_CLICKED) || self.clicked_by(PointerButton::Primary) } /// Returns true if this widget was clicked this frame by the given mouse button. @@ -163,7 +162,7 @@ impl Response { /// Use [`Self::secondary_clicked`] instead to also detect that. #[inline] pub fn clicked_by(&self, button: PointerButton) -> bool { - self.clicked && self.ctx.input(|i| i.pointer.button_clicked(button)) + self.flags.contains(Flags::CLICKED) && self.ctx.input(|i| i.pointer.button_clicked(button)) } /// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button). @@ -171,7 +170,7 @@ impl Response { /// This also returns true if the widget was pressed-and-held on a touch screen. #[inline] pub fn secondary_clicked(&self) -> bool { - self.long_touched || self.clicked_by(PointerButton::Secondary) + self.flags.contains(Flags::LONG_TOUCHED) || self.clicked_by(PointerButton::Secondary) } /// Was this long-pressed on a touch screen? @@ -179,7 +178,7 @@ impl Response { /// Usually you want to check [`Self::secondary_clicked`] instead. #[inline] pub fn long_touched(&self) -> bool { - self.long_touched + self.flags.contains(Flags::LONG_TOUCHED) } /// Returns true if this widget was clicked this frame by the middle mouse button. @@ -203,13 +202,15 @@ impl Response { /// Returns true if this widget was double-clicked this frame by the given button. #[inline] pub fn double_clicked_by(&self, button: PointerButton) -> bool { - self.clicked && self.ctx.input(|i| i.pointer.button_double_clicked(button)) + self.flags.contains(Flags::CLICKED) + && self.ctx.input(|i| i.pointer.button_double_clicked(button)) } /// Returns true if this widget was triple-clicked this frame by the given button. #[inline] pub fn triple_clicked_by(&self, button: PointerButton) -> bool { - self.clicked && self.ctx.input(|i| i.pointer.button_triple_clicked(button)) + self.flags.contains(Flags::CLICKED) + && self.ctx.input(|i| i.pointer.button_triple_clicked(button)) } /// `true` if there was a click *outside* the rect of this widget. @@ -224,7 +225,10 @@ impl Response { let pointer = &i.pointer; if pointer.any_click() { - if self.contains_pointer || self.hovered { + if self + .flags + .intersects(Flags::CONTAINS_POINTER | Flags::HOVERED) + { false } else if let Some(pos) = pointer.interact_pos() { !self.interact_rect.contains(pos) @@ -242,7 +246,7 @@ impl Response { /// and the widget should be drawn in a gray disabled look. #[inline(always)] pub fn enabled(&self) -> bool { - self.enabled + self.flags.contains(Flags::ENABLED) } /// The pointer is hovering above this widget or the widget was clicked/tapped this frame. @@ -251,7 +255,7 @@ impl Response { /// `hovered` is always `false` for disabled widgets. #[inline(always)] pub fn hovered(&self) -> bool { - self.hovered + self.flags.contains(Flags::HOVERED) } /// Returns true if the pointer is contained by the response rect, and no other widget is covering it. @@ -264,14 +268,14 @@ impl Response { /// [`Self::contains_pointer`] also checks that no other widget is covering this response rectangle. #[inline(always)] pub fn contains_pointer(&self) -> bool { - self.contains_pointer + self.flags.contains(Flags::CONTAINS_POINTER) } /// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`]. #[doc(hidden)] #[inline(always)] pub fn highlighted(&self) -> bool { - self.highlighted + self.flags.contains(Flags::HIGHLIGHTED) } /// This widget has the keyboard focus (i.e. is receiving key presses). @@ -316,7 +320,7 @@ impl Response { self.ctx.memory_mut(|mem| mem.surrender_focus(self.id)); } - /// Did a drag on this widgets begin this frame? + /// Did a drag on this widget begin this frame? /// /// This is only true if the widget sense drags. /// If the widget also senses clicks, this will only become true if the pointer has moved a bit. @@ -324,10 +328,10 @@ impl Response { /// This will only be true for a single frame. #[inline] pub fn drag_started(&self) -> bool { - self.drag_started + self.flags.contains(Flags::DRAG_STARTED) } - /// Did a drag on this widgets by the button begin this frame? + /// Did a drag on this widget by the button begin this frame? /// /// This is only true if the widget sense drags. /// If the widget also senses clicks, this will only become true if the pointer has moved a bit. @@ -354,7 +358,7 @@ impl Response { /// You can use [`Self::interact`] to sense more things *after* adding a widget. #[inline(always)] pub fn dragged(&self) -> bool { - self.dragged + self.flags.contains(Flags::DRAGGED) } /// See [`Self::dragged`]. @@ -366,7 +370,7 @@ impl Response { /// The widget was being dragged, but now it has been released. #[inline] pub fn drag_stopped(&self) -> bool { - self.drag_stopped + self.flags.contains(Flags::DRAG_STOPPED) } /// The widget was being dragged by the button, but now it has been released. @@ -378,7 +382,7 @@ impl Response { #[inline] #[deprecated = "Renamed 'drag_stopped'"] pub fn drag_released(&self) -> bool { - self.drag_stopped + self.drag_stopped() } /// The widget was being dragged by the button, but now it has been released. @@ -460,7 +464,7 @@ impl Response { } } - /// Where the pointer (mouse/touch) were when when this widget was clicked or dragged. + /// Where the pointer (mouse/touch) were when this widget was clicked or dragged. /// /// `None` if the widget is not being interacted with. #[inline] @@ -492,7 +496,7 @@ impl Response { /// This could also be thought of as "is this widget being interacted with?". #[inline(always)] pub fn is_pointer_button_down_on(&self) -> bool { - self.is_pointer_button_down_on + self.flags.contains(Flags::IS_POINTER_BUTTON_DOWN_ON) } /// Was the underlying data changed? @@ -510,7 +514,7 @@ impl Response { /// for instance if an existing slider value was clamped to the given range. #[inline(always)] pub fn changed(&self) -> bool { - self.changed + self.flags.contains(Flags::CHANGED) } /// Report the data shown by this widget changed. @@ -519,10 +523,10 @@ impl Response { /// e.g. checkboxes, sliders etc. /// /// This should be called when the *content* changes, but not when the view does. - /// So we call this when the text of a [`crate::TextEdit`], but not when the cursors changes. + /// So we call this when the text of a [`crate::TextEdit`], but not when the cursor changes. #[inline(always)] pub fn mark_changed(&mut self) { - self.changed = true; + self.flags.set(Flags::CHANGED, true); } /// Show this UI if the widget was hovered (i.e. a tooltip). @@ -547,7 +551,7 @@ impl Response { /// ``` #[doc(alias = "tooltip")] pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { - if self.enabled && self.should_show_hover_ui() { + if self.flags.contains(Flags::ENABLED) && self.should_show_hover_ui() { self.show_tooltip_ui(add_contents); } self @@ -555,7 +559,7 @@ impl Response { /// Show this UI when hovering if the widget is disabled. pub fn on_disabled_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { - if !self.enabled && self.should_show_hover_ui() { + if !self.enabled() && self.should_show_hover_ui() { crate::containers::show_tooltip_for( &self.ctx, self.layer_id, @@ -569,7 +573,7 @@ impl Response { /// Like `on_hover_ui`, but show the ui next to cursor. pub fn on_hover_ui_at_pointer(self, add_contents: impl FnOnce(&mut Ui)) -> Self { - if self.enabled && self.should_show_hover_ui() { + if self.enabled() && self.should_show_hover_ui() { crate::containers::show_tooltip_at_pointer( &self.ctx, self.layer_id, @@ -725,8 +729,8 @@ impl Response { } // Fast early-outs: - if self.enabled { - if !self.hovered || !self.ctx.input(|i| i.pointer.has_pointer()) { + if self.enabled() { + if !self.hovered() || !self.ctx.input(|i| i.pointer.has_pointer()) { return false; } } else if !self.ctx.rect_contains_pointer(self.layer_id, self.rect) { @@ -734,7 +738,7 @@ impl Response { } // There is a tooltip_delay before showing the first tooltip, - // but once one tooltips is show, moving the mouse cursor to + // but once one tooltip is show, moving the mouse cursor to // another widget should show the tooltip for that widget right away. // Let the user quickly move over some dead space to hover the next thing @@ -817,7 +821,7 @@ impl Response { #[inline] pub fn highlight(mut self) -> Self { self.ctx.highlight_widget(self.id); - self.highlighted = true; + self.flags.set(Flags::HIGHLIGHTED, true); self } @@ -888,7 +892,7 @@ impl Response { rect: self.rect, interact_rect: self.interact_rect, sense: self.sense | sense, - enabled: self.enabled, + enabled: self.enabled(), }, true, ) @@ -951,7 +955,7 @@ impl Response { Some(OutputEvent::TripleClicked(make_info())) } else if self.gained_focus() { Some(OutputEvent::FocusGained(make_info())) - } else if self.changed { + } else if self.changed() { Some(OutputEvent::ValueChanged(make_info())) } else { None @@ -983,7 +987,7 @@ impl Response { #[cfg(feature = "accesskit")] pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) { - if !self.enabled { + if !self.enabled() { builder.set_disabled(); } builder.set_bounds(accesskit::Rect { @@ -1125,9 +1129,9 @@ impl Response { pub fn paint_debug_info(&self) { self.ctx.debug_painter().debug_rect( self.rect, - if self.hovered { + if self.hovered() { crate::Color32::DARK_GREEN - } else if self.enabled { + } else if self.enabled() { crate::Color32::BLUE } else { crate::Color32::RED @@ -1157,20 +1161,8 @@ impl Response { rect: self.rect.union(other.rect), interact_rect: self.interact_rect.union(other.interact_rect), sense: self.sense.union(other.sense), - enabled: self.enabled || other.enabled, - contains_pointer: self.contains_pointer || other.contains_pointer, - hovered: self.hovered || other.hovered, - highlighted: self.highlighted || other.highlighted, - clicked: self.clicked || other.clicked, - fake_primary_click: self.fake_primary_click || other.fake_primary_click, - long_touched: self.long_touched || other.long_touched, - drag_started: self.drag_started || other.drag_started, - dragged: self.dragged || other.dragged, - drag_stopped: self.drag_stopped || other.drag_stopped, - is_pointer_button_down_on: self.is_pointer_button_down_on - || other.is_pointer_button_down_on, + flags: self.flags | other.flags, interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos), - changed: self.changed || other.changed, intrinsic_size: None, } } diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index fe5eac00e78..e19a40e4953 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -484,7 +484,7 @@ impl LabelSelectionState { ) -> Vec { let widget_id = response.id; - if response.hovered { + if response.hovered() { ui.ctx().set_cursor_icon(CursorIcon::Text); } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index acc08ca379f..879177139bc 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2079,7 +2079,7 @@ impl Ui { // only touch `*radians` if we actually changed the degree value if degrees != radians.to_degrees() { *radians = degrees.to_radians(); - response.changed = true; + response.mark_changed(); } response @@ -2102,7 +2102,7 @@ impl Ui { // only touch `*radians` if we actually changed the value if taus != *radians / TAU { *radians = taus * TAU; - response.changed = true; + response.mark_changed(); } response diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 088800e45ce..701e89b1cb3 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -387,7 +387,7 @@ impl Widget for Button<'_> { } if let Some(cursor) = ui.visuals().interact_cursor { - if response.hovered { + if response.hovered() { ui.ctx().set_cursor_icon(cursor); } } diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index a5b8c25b2f8..175fdcc5a4b 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -660,7 +660,9 @@ impl<'a> Widget for DragValue<'a> { response }; - response.changed = get(&mut get_set_value) != old_value; + if get(&mut get_set_value) != old_value { + response.mark_changed(); + } response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value)); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 71f4c8499c0..acf0359a8b9 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -946,7 +946,9 @@ impl<'a> Slider<'a> { self.slider_ui(ui, &response); let value = self.get_value(); - response.changed = value != old_value; + if value != old_value { + response.mark_changed(); + } response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text())); #[cfg(feature = "accesskit")] diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index be96c00fad4..387d632e58f 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -7,7 +7,7 @@ use crate::{ epaint, os::OperatingSystem, output::OutputEvent, - text_selection, + response, text_selection, text_selection::{ text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange, CursorRange, }, @@ -565,8 +565,10 @@ impl<'t> TextEdit<'t> { let mut response = ui.interact(outer_rect, id, sense); response.intrinsic_size = Some(Vec2::new(desired_width, desired_outer_size.y)); - response.fake_primary_click = false; // Don't sent `OutputEvent::Clicked` when a user presses the space bar - + // Don't sent `OutputEvent::Clicked` when a user presses the space bar + response + .flags + .set(response::Flags::FAKE_PRIMARY_CLICKED, false); let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor @@ -740,14 +742,14 @@ impl<'t> TextEdit<'t> { let primary_cursor_rect = cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height); - if response.changed || selection_changed { + if response.changed() || selection_changed { // Scroll to keep primary cursor in view: ui.scroll_to_rect(primary_cursor_rect + margin, None); } if text.is_mutable() && interactive { let now = ui.ctx().input(|i| i.time); - if response.changed || selection_changed { + if response.changed() || selection_changed { state.last_interaction_time = now; } @@ -794,7 +796,7 @@ impl<'t> TextEdit<'t> { state.clone().store(ui.ctx(), id); - if response.changed { + if response.changed() { response.widget_info(|| { WidgetInfo::text_edit( ui.is_enabled(), From 909d8670e4073280819975323d065c4d8bd83d0a Mon Sep 17 00:00:00 2001 From: Pol Welter Date: Wed, 1 Jan 2025 14:25:27 +0100 Subject: [PATCH 2/4] Rewrite Sense as bitset --- crates/egui/src/containers/modal.rs | 17 +--- crates/egui/src/context.rs | 25 ++--- crates/egui/src/hit_test.rs | 51 ++++++---- crates/egui/src/interaction.rs | 6 +- crates/egui/src/response.rs | 6 +- crates/egui/src/sense.rs | 99 +++++++------------ .../src/text_selection/text_cursor_state.rs | 2 +- crates/egui/src/widgets/label.rs | 2 +- 8 files changed, 91 insertions(+), 117 deletions(-) diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index 521b9dedba4..8bf5966752c 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -86,11 +86,7 @@ impl Modal { response, } = area.show(ctx, |ui| { let bg_rect = ui.ctx().screen_rect(); - let bg_sense = Sense { - click: true, - drag: true, - focusable: false, - }; + let bg_sense = Sense::CLICK | Sense::DRAG; let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect)); backdrop.set_min_size(bg_rect.size()); ui.painter().rect_filled(bg_rect, 0.0, backdrop_color); @@ -101,14 +97,9 @@ impl Modal { // We need the extra scope with the sense since frame can't have a sense and since we // need to prevent the clicks from passing through to the backdrop. let inner = ui - .scope_builder( - UiBuilder::new().sense(Sense { - click: true, - drag: true, - focusable: false, - }), - |ui| frame.show(ui, content).inner, - ) + .scope_builder(UiBuilder::new().sense(Sense::CLICK | Sense::DRAG), |ui| { + frame.show(ui, content).inner + }) .inner; (inner, backdrop_response) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index e24dd0aa385..023d9100341 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1151,8 +1151,9 @@ impl Context { /// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)). #[allow(clippy::too_many_arguments)] pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response { - let interested_in_focus = - w.enabled && w.sense.focusable && self.memory(|mem| mem.allows_interaction(w.layer_id)); + let interested_in_focus = w.enabled + && w.sense.is_focusable() + && self.memory(|mem| mem.allows_interaction(w.layer_id)); // Remember this widget self.write(|ctx| { @@ -1173,7 +1174,7 @@ impl Context { self.memory_mut(|mem| mem.surrender_focus(w.id)); } - if w.sense.interactive() || w.sense.focusable { + if w.sense.interactive() || w.sense.is_focusable() { self.check_for_id_clash(w.id, w.rect, "widget"); } @@ -1181,7 +1182,7 @@ impl Context { let res = self.get_response(w); #[cfg(feature = "accesskit")] - if allow_focus && w.sense.focusable { + if allow_focus && w.sense.is_focusable() { // Make sure anything that can receive focus has an AccessKit node. // TODO(mwcampbell): For nodes that are filled from widget info, // some information is written to the node twice. @@ -1257,7 +1258,7 @@ impl Context { let memory = &mut ctx.memory; if enabled - && sense.click + && sense.senses_click() && memory.has_focus(id) && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { @@ -1267,13 +1268,14 @@ impl Context { #[cfg(feature = "accesskit")] if enabled - && sense.click + && sense.senses_click() && input.has_accesskit_action_request(id, accesskit::Action::Click) { res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true); } - if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched { + if enabled && sense.senses_click() && Some(id) == viewport.interact_widgets.long_touched + { res.flags.set(response::Flags::LONG_TOUCHED, true); } @@ -1314,7 +1316,7 @@ impl Context { any_press = true; } PointerEvent::Released { click, .. } => { - if enabled && sense.click && clicked && click.is_some() { + if enabled && sense.senses_click() && clicked && click.is_some() { res.flags.set(response::Flags::CLICKED, true); } @@ -2165,11 +2167,12 @@ impl Context { let painter = Painter::new(self.clone(), *layer_id, Rect::EVERYTHING); for rect in rects { if rect.sense.interactive() { - let (color, text) = if rect.sense.click && rect.sense.drag { + let (color, text) = if rect.sense.senses_click() && rect.sense.senses_drag() + { (Color32::from_rgb(0x88, 0, 0x88), "click+drag") - } else if rect.sense.click { + } else if rect.sense.senses_click() { (Color32::from_rgb(0x88, 0, 0), "click") - } else if rect.sense.drag { + } else if rect.sense.senses_drag() { (Color32::from_rgb(0, 0, 0x88), "drag") } else { // unreachable since we only show interactive diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index 8741f5f8b41..81c8abbb87c 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -2,7 +2,7 @@ use ahash::HashMap; use emath::TSTransform; -use crate::{ahash, emath, LayerId, Pos2, Rect, WidgetRect, WidgetRects}; +use crate::{ahash, emath, LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects}; /// Result of a hit-test against [`WidgetRects`]. /// @@ -128,8 +128,8 @@ pub fn hit_test( // the `enabled` flag everywhere: for w in &mut close { if !w.enabled { - w.sense.click = false; - w.sense.drag = false; + w.sense -= Sense::CLICK; + w.sense -= Sense::DRAG; } } @@ -158,11 +158,11 @@ pub fn hit_test( restore_widget_rect(wr); } if let Some(wr) = &mut hits.drag { - debug_assert!(wr.sense.drag); + debug_assert!(wr.sense.senses_drag()); restore_widget_rect(wr); } if let Some(wr) = &mut hits.click { - debug_assert!(wr.sense.click); + debug_assert!(wr.sense.senses_click()); restore_widget_rect(wr); } } @@ -179,8 +179,16 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { #![allow(clippy::collapsible_else_if)] // First find the best direct hits: - let hit_click = find_closest_within(close.iter().copied().filter(|w| w.sense.click), pos, 0.0); - let hit_drag = find_closest_within(close.iter().copied().filter(|w| w.sense.drag), pos, 0.0); + let hit_click = find_closest_within( + close.iter().copied().filter(|w| w.sense.senses_click()), + pos, + 0.0, + ); + let hit_drag = find_closest_within( + close.iter().copied().filter(|w| w.sense.senses_drag()), + pos, + 0.0, + ); match (hit_click, hit_drag) { (None, None) => { @@ -190,14 +198,14 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { close .iter() .copied() - .filter(|w| w.sense.click || w.sense.drag), + .filter(|w| w.sense.senses_click() || w.sense.senses_drag()), pos, ); if let Some(closest) = closest { WidgetHits { - click: closest.sense.click.then_some(closest), - drag: closest.sense.drag.then_some(closest), + click: closest.sense.senses_click().then_some(closest), + drag: closest.sense.senses_drag().then_some(closest), ..Default::default() } } else { @@ -218,9 +226,12 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { // or a moveable window. // It could also be something small, like a slider, or panel resize handle. - let closest_click = find_closest(close.iter().copied().filter(|w| w.sense.click), pos); + let closest_click = find_closest( + close.iter().copied().filter(|w| w.sense.senses_click()), + pos, + ); if let Some(closest_click) = closest_click { - if closest_click.sense.drag { + if closest_click.sense.senses_drag() { // We have something close that sense both clicks and drag. // Should we use it over the direct drag-hit? if hit_drag @@ -244,7 +255,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { } } } else { - // These is a close pure-click widget. + // This is a close pure-click widget. // However, we should be careful to only return two different widgets // when it is absolutely not going to confuse the user. if hit_drag @@ -277,7 +288,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { close .iter() .copied() - .filter(|w| w.sense.drag && w.id != hit_drag.id), + .filter(|w| w.sense.senses_drag() && w.id != hit_drag.id), pos, ); @@ -331,7 +342,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { let click_is_on_top_of_drag = drag_idx < click_idx; if click_is_on_top_of_drag { - if hit_click.sense.drag { + if hit_click.sense.senses_drag() { // The top thing senses both clicks and drags. WidgetHits { click: Some(hit_click), @@ -349,7 +360,7 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits { } } } else { - if hit_drag.sense.click { + if hit_drag.sense.senses_click() { // The top thing senses both clicks and drags. WidgetHits { click: Some(hit_drag), @@ -393,7 +404,7 @@ fn find_closest_within( if dist_sq == closest_dist_sq { // It's a tie! Pick the thin candidate over the thick one. // This makes it easier to hit a thin resize-handle, for instance: - if should_prioritizie_hits_on_back(closest.interact_rect, widget.interact_rect) { + if should_prioritize_hits_on_back(closest.interact_rect, widget.interact_rect) { continue; } } @@ -409,12 +420,12 @@ fn find_closest_within( closest } -/// Should we prioritizie hits on `back` over those on `front`? +/// Should we prioritize hits on `back` over those on `front`? /// /// `back` should be behind the `front` widget. /// /// Returns true if `back` is a small hit-target and `front` is not. -fn should_prioritizie_hits_on_back(back: Rect, front: Rect) -> bool { +fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool { if front.contains_rect(back) { return false; // back widget is fully occluded; no way to hit it } @@ -484,7 +495,7 @@ mod tests { assert_eq!(hits.click.unwrap().id, Id::new("click-and-drag")); assert_eq!(hits.drag.unwrap().id, Id::new("click-and-drag")); - // Close hit - should still ignore the drag-background so as not to confuse the userr: + // Close hit - should still ignore the drag-background so as not to confuse the user: let hits = hit_test_on_close(&widgets, pos2(105.0, 5.0)); assert_eq!(hits.click.unwrap().id, Id::new("click-and-drag")); assert_eq!(hits.drag.unwrap().id, Id::new("click-and-drag")); diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 22ec8ebc5a5..b6b63e19d47 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -192,14 +192,14 @@ pub(crate) fn interact( // Check if we started dragging something new: if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) { if widget.enabled { - let is_dragged = if widget.sense.click && widget.sense.drag { + let is_dragged = if widget.sense.senses_click() && widget.sense.senses_drag() { // This widget is sensitive to both clicks and drags. // When the mouse first is pressed, it could be either, // so we postpone the decision until we know. input.pointer.is_decidedly_dragging() } else { // This widget is just sensitive to drags, so we can mark it as dragged right away: - widget.sense.drag + widget.sense.senses_drag() }; if is_dragged { @@ -271,7 +271,7 @@ pub(crate) fn interact( let mut hovered: IdSet = hits.click.iter().chain(&hits.drag).map(|w| w.id).collect(); for w in &hits.contains_pointer { - let is_interactive = w.sense.click || w.sense.drag; + let is_interactive = w.sense.senses_click() || w.sense.senses_drag(); if is_interactive { // The only interactive widgets we mark as hovered are the ones // in `hits.click` and `hits.drag`! diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 3cc99796afb..9e81c542499 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -426,7 +426,7 @@ impl Response { crate::DragAndDrop::set_payload(&self.ctx, payload); } - if self.hovered() && !self.sense.click { + if self.hovered() && !self.sense.senses_click() { // Things that can be drag-dropped should use the Grab cursor icon, // but if the thing is _also_ clickable, that can be annoying. self.ctx.set_cursor_icon(CursorIcon::Grab); @@ -996,10 +996,10 @@ impl Response { x1: self.rect.max.x.into(), y1: self.rect.max.y.into(), }); - if self.sense.focusable { + if self.sense.is_focusable() { builder.add_action(accesskit::Action::Focus); } - if self.sense.click { + if self.sense.senses_click() { builder.add_action(accesskit::Action::Click); } } diff --git a/crates/egui/src/sense.rs b/crates/egui/src/sense.rs index 1b6394b2cc3..464db311f81 100644 --- a/crates/egui/src/sense.rs +++ b/crates/egui/src/sense.rs @@ -1,36 +1,37 @@ /// What sort of interaction is a widget sensitive to? #[derive(Clone, Copy, Eq, PartialEq)] // #[cfg_attr(feature = "serde", derive(serde::Serialize))] -pub struct Sense { - /// Buttons, sliders, windows, … - pub click: bool, +pub struct Sense(u8); - /// Sliders, windows, scroll bars, scroll areas, … - pub drag: bool, +bitflags::bitflags! { + impl Sense: u8 { - /// This widget wants focus. - /// - /// Anything interactive + labels that can be focused - /// for the benefit of screen readers. - pub focusable: bool, + const HOVER = 0; + + /// Buttons, sliders, windows, … + const CLICK = 1<<0; + + /// Sliders, windows, scroll bars, scroll areas, … + const DRAG = 1<<1; + + /// This widget wants focus. + /// + /// Anything interactive + labels that can be focused + /// for the benefit of screen readers. + const FOCUSABLE = 1<<2; + } } impl std::fmt::Debug for Sense { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - click, - drag, - focusable, - } = self; - write!(f, "Sense {{")?; - if *click { + if self.senses_click() { write!(f, " click")?; } - if *drag { + if self.senses_drag() { write!(f, " drag")?; } - if *focusable { + if self.is_focusable() { write!(f, " focusable")?; } write!(f, " }}") @@ -42,42 +43,26 @@ impl Sense { #[doc(alias = "none")] #[inline] pub fn hover() -> Self { - Self { - click: false, - drag: false, - focusable: false, - } + Self::empty() } /// Senses no clicks or drags, but can be focused with the keyboard. /// Used for labels that can be focused for the benefit of screen readers. #[inline] pub fn focusable_noninteractive() -> Self { - Self { - click: false, - drag: false, - focusable: true, - } + Self::FOCUSABLE } /// Sense clicks and hover, but not drags. #[inline] pub fn click() -> Self { - Self { - click: true, - drag: false, - focusable: true, - } + Self::CLICK | Self::FOCUSABLE } /// Sense drags and hover, but not clicks. #[inline] pub fn drag() -> Self { - Self { - click: false, - drag: true, - focusable: true, - } + Self::DRAG | Self::FOCUSABLE } /// Sense both clicks, drags and hover (e.g. a slider or window). @@ -90,43 +75,27 @@ impl Sense { /// See [`crate::PointerState::is_decidedly_dragging`] for details. #[inline] pub fn click_and_drag() -> Self { - Self { - click: true, - drag: true, - focusable: true, - } - } - - /// The logical "or" of two [`Sense`]s. - #[must_use] - #[inline] - pub fn union(self, other: Self) -> Self { - Self { - click: self.click | other.click, - drag: self.drag | other.drag, - focusable: self.focusable | other.focusable, - } + Self::CLICK | Self::FOCUSABLE | Self::DRAG } /// Returns true if we sense either clicks or drags. #[inline] pub fn interactive(&self) -> bool { - self.click || self.drag + self.intersects(Self::CLICK | Self::DRAG) } -} -impl std::ops::BitOr for Sense { - type Output = Self; + #[inline] + pub fn senses_click(&self) -> bool { + self.contains(Self::CLICK) + } #[inline] - fn bitor(self, rhs: Self) -> Self { - self.union(rhs) + pub fn senses_drag(&self) -> bool { + self.contains(Self::DRAG) } -} -impl std::ops::BitOrAssign for Sense { #[inline] - fn bitor_assign(&mut self, rhs: Self) { - *self = self.union(rhs); + pub fn is_focusable(&self) -> bool { + self.contains(Self::FOCUSABLE) } } diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index dc75c7dd864..61407353a85 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -122,7 +122,7 @@ impl TextCursorState { secondary: galley.from_ccursor(ccursor_range.secondary), })); true - } else if response.sense.drag { + } else if response.sense.senses_drag() { if response.hovered() && ui.input(|i| i.pointer.any_pressed()) { // The start of a drag (or a click). if ui.input(|i| i.modifiers.shift) { diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index b6ade45ae30..eb3e3840e79 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -146,7 +146,7 @@ impl Label { } else { Sense::click() }; - select_sense.focusable = false; // Don't move focus to labels with TAB key. + select_sense -= Sense::FOCUSABLE; // Don't move focus to labels with TAB key. sense = sense.union(select_sense); } From c86e0af763e5000ce6b9c8762fcd46495004064c Mon Sep 17 00:00:00 2001 From: Pol Welter Date: Wed, 1 Jan 2025 20:40:12 +0100 Subject: [PATCH 3/4] Add bitflags to the list of dependencies in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 247ad32d90c..2abccd5d2f3 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,7 @@ Light Theme: * [`ahash`](https://crates.io/crates/ahash) * [`nohash-hasher`](https://crates.io/crates/nohash-hasher) * [`parking_lot`](https://crates.io/crates/parking_lot) +* [`bitflags`](https://crates.io/crates/bitflags) Heavier dependencies are kept out of `egui`, even as opt-in. No code that isn't fully Wasm-friendly is part of `egui`. From 4052b1822cee9d8393b72fa1625a8b8ae60aff68 Mon Sep 17 00:00:00 2001 From: Pol Welter Date: Mon, 6 Jan 2025 14:28:59 +0100 Subject: [PATCH 4/4] Address reviewer's comments --- README.md | 2 +- crates/egui/src/context.rs | 35 ++++++++++---------- crates/egui/src/response.rs | 9 +++-- crates/egui/src/widgets/text_edit/builder.rs | 4 +-- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 2abccd5d2f3..b28cc995d67 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,9 @@ Light Theme: * [`ab_glyph`](https://crates.io/crates/ab_glyph) * [`ahash`](https://crates.io/crates/ahash) +* [`bitflags`](https://crates.io/crates/bitflags) * [`nohash-hasher`](https://crates.io/crates/nohash-hasher) * [`parking_lot`](https://crates.io/crates/parking_lot) -* [`bitflags`](https://crates.io/crates/bitflags) Heavier dependencies are kept out of `egui`, even as opt-in. No code that isn't fully Wasm-friendly is part of `egui`. diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 023d9100341..b696d338c21 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1219,6 +1219,8 @@ impl Context { /// Do all interaction for an existing widget, without (re-)registering it. pub(crate) fn get_response(&self, widget_rect: WidgetRect) -> Response { + use response::Flags; + let WidgetRect { id, layer_id, @@ -1238,19 +1240,19 @@ impl Context { rect, interact_rect, sense, - flags: response::Flags::empty(), + flags: Flags::empty(), interact_pointer_pos: None, intrinsic_size: None, }; - res.flags.set(response::Flags::ENABLED, enabled); - res.flags.set(response::Flags::HIGHLIGHTED, highlighted); + res.flags.set(Flags::ENABLED, enabled); + res.flags.set(Flags::HIGHLIGHTED, highlighted); self.write(|ctx| { let viewport = ctx.viewports.entry(ctx.viewport_id()).or_default(); res.flags.set( - response::Flags::CONTAINS_POINTER, + Flags::CONTAINS_POINTER, viewport.interact_widgets.contains_pointer.contains(&id), ); @@ -1263,7 +1265,7 @@ impl Context { && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { // Space/enter works like a primary click for e.g. selected buttons - res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true); + res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true); } #[cfg(feature = "accesskit")] @@ -1271,37 +1273,37 @@ impl Context { && sense.senses_click() && input.has_accesskit_action_request(id, accesskit::Action::Click) { - res.flags.set(response::Flags::FAKE_PRIMARY_CLICKED, true); + res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true); } if enabled && sense.senses_click() && Some(id) == viewport.interact_widgets.long_touched { - res.flags.set(response::Flags::LONG_TOUCHED, true); + res.flags.set(Flags::LONG_TOUCHED, true); } let interaction = memory.interaction(); res.flags.set( - response::Flags::IS_POINTER_BUTTON_DOWN_ON, + Flags::IS_POINTER_BUTTON_DOWN_ON, interaction.potential_click_id == Some(id) || interaction.potential_drag_id == Some(id), ); if res.enabled() { res.flags.set( - response::Flags::HOVERED, + Flags::HOVERED, viewport.interact_widgets.hovered.contains(&id), ); res.flags.set( - response::Flags::DRAGGED, + Flags::DRAGGED, Some(id) == viewport.interact_widgets.dragged, ); res.flags.set( - response::Flags::DRAG_STARTED, + Flags::DRAG_STARTED, Some(id) == viewport.interact_widgets.drag_started, ); res.flags.set( - response::Flags::DRAG_STOPPED, + Flags::DRAG_STOPPED, Some(id) == viewport.interact_widgets.drag_stopped, ); } @@ -1317,12 +1319,11 @@ impl Context { } PointerEvent::Released { click, .. } => { if enabled && sense.senses_click() && clicked && click.is_some() { - res.flags.set(response::Flags::CLICKED, true); + res.flags.set(Flags::CLICKED, true); } - res.flags - .set(response::Flags::IS_POINTER_BUTTON_DOWN_ON, false); - res.flags.set(response::Flags::DRAGGED, false); + res.flags.set(Flags::IS_POINTER_BUTTON_DOWN_ON, false); + res.flags.set(Flags::DRAGGED, false); } } } @@ -1345,7 +1346,7 @@ impl Context { if input.pointer.any_down() && !is_interacted_with { // We don't hover widgets while interacting with *other* widgets: - res.flags.set(response::Flags::HOVERED, false); + res.flags.set(Flags::HOVERED, false); } let pointer_pressed_elsewhere = any_press && !res.hovered(); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 9e81c542499..73e77516812 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -69,9 +69,11 @@ pub struct Response { pub intrinsic_size: Option, #[doc(hidden)] - pub(crate) flags: Flags, + pub flags: Flags, } +/// A bit set for various boolean properties of `Response`. +#[doc(hidden)] #[derive(Copy, Clone, Debug)] pub struct Flags(u16); @@ -225,10 +227,7 @@ impl Response { let pointer = &i.pointer; if pointer.any_click() { - if self - .flags - .intersects(Flags::CONTAINS_POINTER | Flags::HOVERED) - { + if self.contains_pointer() || self.hovered() { false } else if let Some(pos) = pointer.interact_pos() { !self.interact_rect.contains(pos) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 387d632e58f..5c3b080f12e 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -566,9 +566,7 @@ impl<'t> TextEdit<'t> { response.intrinsic_size = Some(Vec2::new(desired_width, desired_outer_size.y)); // Don't sent `OutputEvent::Clicked` when a user presses the space bar - response - .flags - .set(response::Flags::FAKE_PRIMARY_CLICKED, false); + response.flags -= response::Flags::FAKE_PRIMARY_CLICKED; let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor