Use bitfield instead of bools in `Response` and `Sense` (#5556)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> Closes <https://github.com/emilk/egui/issues/3862>. Factoring the `bool` members of `Response` into a bitfield, the size of `Response` is now 96 bytes (down from 104). I gave `Sense` the same treatment, however this has no effects on `Response` due to padding. I've decided not to pursue `PointerState`, as it is quite large (_many_ members that are sized and aligned to multiples of 8 bytes), so I don't expect any noticeable benefit from making handful of `bool`s slightly leaner. In any case, the changes to `Sense` are already quite a bit more intrusive than those to `Response`. The previous implementation overloaded the names of the attributes `click` and `drag` with similarly named methods that _construct_ `Sense` with the corresponding flag set. Now, that the attributes can no longer be accessed directly, I had to introduce methods with new names (`senses_click()`, `senses_drag()` and `is_focusable()`). I don't think this is the cleanest solution: the old methods are essentially redundant now that the named constants like `Sense::CLICK` exist. I did however not want to needlessly break backwards compatibility. I am happy to revert it (or go further 🙂) if there are concerns.
This commit is contained in:
parent
0fac8eadfc
commit
35860418ac
|
|
@ -1260,6 +1260,7 @@ dependencies = [
|
|||
"accesskit",
|
||||
"ahash",
|
||||
"backtrace",
|
||||
"bitflags 2.6.0",
|
||||
"document-features",
|
||||
"emath",
|
||||
"epaint",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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.
|
||||
|
|
@ -1213,11 +1214,13 @@ 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.
|
||||
pub(crate) fn get_response(&self, widget_rect: WidgetRect) -> Response {
|
||||
use response::Flags;
|
||||
|
||||
let WidgetRect {
|
||||
id,
|
||||
layer_id,
|
||||
|
|
@ -1237,61 +1240,72 @@ 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: Flags::empty(),
|
||||
interact_pointer_pos: None,
|
||||
changed: false,
|
||||
intrinsic_size: None,
|
||||
};
|
||||
|
||||
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.contains_pointer = viewport.interact_widgets.contains_pointer.contains(&id);
|
||||
res.flags.set(
|
||||
Flags::CONTAINS_POINTER,
|
||||
viewport.interact_widgets.contains_pointer.contains(&id),
|
||||
);
|
||||
|
||||
let input = &viewport.input;
|
||||
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))
|
||||
{
|
||||
// Space/enter works like a primary click for e.g. selected buttons
|
||||
res.fake_primary_click = true;
|
||||
res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true);
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
if enabled
|
||||
&& sense.click
|
||||
&& sense.senses_click()
|
||||
&& input.has_accesskit_action_request(id, accesskit::Action::Click)
|
||||
{
|
||||
res.fake_primary_click = true;
|
||||
res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true);
|
||||
}
|
||||
|
||||
if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched {
|
||||
res.long_touched = true;
|
||||
if enabled && sense.senses_click() && Some(id) == viewport.interact_widgets.long_touched
|
||||
{
|
||||
res.flags.set(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(
|
||||
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(
|
||||
Flags::HOVERED,
|
||||
viewport.interact_widgets.hovered.contains(&id),
|
||||
);
|
||||
res.flags.set(
|
||||
Flags::DRAGGED,
|
||||
Some(id) == viewport.interact_widgets.dragged,
|
||||
);
|
||||
res.flags.set(
|
||||
Flags::DRAG_STARTED,
|
||||
Some(id) == viewport.interact_widgets.drag_started,
|
||||
);
|
||||
res.flags.set(
|
||||
Flags::DRAG_STOPPED,
|
||||
Some(id) == viewport.interact_widgets.drag_stopped,
|
||||
);
|
||||
}
|
||||
|
||||
let clicked = Some(id) == viewport.interact_widgets.clicked;
|
||||
|
|
@ -1304,20 +1318,22 @@ impl Context {
|
|||
any_press = true;
|
||||
}
|
||||
PointerEvent::Released { click, .. } => {
|
||||
if enabled && sense.click && clicked && click.is_some() {
|
||||
res.clicked = true;
|
||||
if enabled && sense.senses_click() && clicked && click.is_some() {
|
||||
res.flags.set(Flags::CLICKED, true);
|
||||
}
|
||||
|
||||
res.is_pointer_button_down_on = false;
|
||||
res.dragged = false;
|
||||
res.flags.set(Flags::IS_POINTER_BUTTON_DOWN_ON, false);
|
||||
res.flags.set(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 +1346,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(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);
|
||||
}
|
||||
|
|
@ -2152,11 +2168,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
|
||||
|
|
@ -3131,7 +3148,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, "");
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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`!
|
||||
|
|
|
|||
|
|
@ -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<Pos2>,
|
||||
|
||||
/// 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,73 @@ 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<Vec2>,
|
||||
|
||||
#[doc(hidden)]
|
||||
pub flags: Flags,
|
||||
}
|
||||
|
||||
/// A bit set for various boolean properties of `Response`.
|
||||
#[doc(hidden)]
|
||||
#[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 +151,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 +164,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 +172,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 +180,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 +204,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 +227,7 @@ impl Response {
|
|||
let pointer = &i.pointer;
|
||||
|
||||
if pointer.any_click() {
|
||||
if self.contains_pointer || self.hovered {
|
||||
if self.contains_pointer() || self.hovered() {
|
||||
false
|
||||
} else if let Some(pos) = pointer.interact_pos() {
|
||||
!self.interact_rect.contains(pos)
|
||||
|
|
@ -242,7 +245,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 +254,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 +267,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 +319,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 +327,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 +357,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 +369,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 +381,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.
|
||||
|
|
@ -422,7 +425,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);
|
||||
|
|
@ -460,7 +463,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 +495,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 +513,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 +522,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 +550,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 +558,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 +572,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 +728,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 +737,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 +820,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 +891,7 @@ impl Response {
|
|||
rect: self.rect,
|
||||
interact_rect: self.interact_rect,
|
||||
sense: self.sense | sense,
|
||||
enabled: self.enabled,
|
||||
enabled: self.enabled(),
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
|
@ -951,7 +954,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 +986,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 {
|
||||
|
|
@ -992,10 +995,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1125,9 +1128,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 +1160,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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
self.union(rhs)
|
||||
pub fn senses_click(&self) -> bool {
|
||||
self.contains(Self::CLICK)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOrAssign for Sense {
|
||||
#[inline]
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = self.union(rhs);
|
||||
pub fn senses_drag(&self) -> bool {
|
||||
self.contains(Self::DRAG)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_focusable(&self) -> bool {
|
||||
self.contains(Self::FOCUSABLE)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ impl LabelSelectionState {
|
|||
) -> Vec<RowVertexIndices> {
|
||||
let widget_id = response.id;
|
||||
|
||||
if response.hovered {
|
||||
if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::Text);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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,8 @@ 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 -= 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
|
||||
|
||||
|
|
@ -740,14 +740,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 +794,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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue