Add ability to highlight any widget (#2632)

* Add ability to highlight any widget

* Add line to changelog

* Demote the demo to a test
This commit is contained in:
Emil Ernerfeldt 2023-01-27 23:36:14 +01:00 committed by GitHub
parent e7c0547e23
commit c72bdb77b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 4 deletions

View file

@ -24,6 +24,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)). * Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)).
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)). * Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)). * You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
### Changed 🔧 ### Changed 🔧
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)). * Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).

View file

@ -621,6 +621,8 @@ impl Context {
) -> Response { ) -> Response {
let hovered = hovered && enabled; // can't even hover disabled widgets let hovered = hovered && enabled; // can't even hover disabled widgets
let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id));
let mut response = Response { let mut response = Response {
ctx: self.clone(), ctx: self.clone(),
layer_id, layer_id,
@ -629,6 +631,7 @@ impl Context {
sense, sense,
enabled, enabled,
hovered, hovered,
highlighted,
clicked: Default::default(), clicked: Default::default(),
double_clicked: Default::default(), double_clicked: Default::default(),
triple_clicked: Default::default(), triple_clicked: Default::default(),
@ -1284,6 +1287,15 @@ impl Context {
pub fn wants_keyboard_input(&self) -> bool { pub fn wants_keyboard_input(&self) -> bool {
self.memory(|m| m.interaction.focus.focused().is_some()) self.memory(|m| m.interaction.focus.focused().is_some())
} }
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
///
/// The highlight takes on frame to take effect if you call this after the widget has been fully rendered.
///
/// See also [`Response::highlight`].
pub fn highlight_widget(&self, id: Id) {
self.frame_state_mut(|fs| fs.highlight_next_frame.insert(id));
}
} }
// Ergonomic methods to forward some calls often used in 'if let' without holding the borrow // Ergonomic methods to forward some calls often used in 'if let' without holding the borrow

View file

@ -1,6 +1,6 @@
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use crate::*; use crate::{id::IdSet, *};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) struct TooltipFrameState { pub(crate) struct TooltipFrameState {
@ -51,6 +51,12 @@ pub(crate) struct FrameState {
#[cfg(feature = "accesskit")] #[cfg(feature = "accesskit")]
pub(crate) accesskit_state: Option<AccessKitFrameState>, pub(crate) accesskit_state: Option<AccessKitFrameState>,
/// Highlight these widgets this next frame. Read from this.
pub(crate) highlight_this_frame: IdSet,
/// Highlight these widgets the next frame. Write to this.
pub(crate) highlight_next_frame: IdSet,
} }
impl Default for FrameState { impl Default for FrameState {
@ -65,6 +71,8 @@ impl Default for FrameState {
scroll_target: [None, None], scroll_target: [None, None],
#[cfg(feature = "accesskit")] #[cfg(feature = "accesskit")]
accesskit_state: None, accesskit_state: None,
highlight_this_frame: Default::default(),
highlight_next_frame: Default::default(),
} }
} }
} }
@ -81,6 +89,8 @@ impl FrameState {
scroll_target, scroll_target,
#[cfg(feature = "accesskit")] #[cfg(feature = "accesskit")]
accesskit_state, accesskit_state,
highlight_this_frame,
highlight_next_frame,
} = self; } = self;
used_ids.clear(); used_ids.clear();
@ -90,10 +100,13 @@ impl FrameState {
*tooltip_state = None; *tooltip_state = None;
*scroll_delta = input.scroll_delta; *scroll_delta = input.scroll_delta;
*scroll_target = [None, None]; *scroll_target = [None, None];
#[cfg(feature = "accesskit")] #[cfg(feature = "accesskit")]
{ {
*accesskit_state = None; *accesskit_state = None;
} }
*highlight_this_frame = std::mem::take(highlight_next_frame);
} }
/// How much space is still available after panels has been added. /// How much space is still available after panels has been added.

View file

@ -168,5 +168,8 @@ impl std::hash::BuildHasher for BuilIdHasher {
} }
} }
/// `IdSet` is a `HashSet<Id>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
pub type IdSet = std::collections::HashSet<Id, BuilIdHasher>;
/// `IdMap<V>` is a `HashMap<Id, V>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing. /// `IdMap<V>` is a `HashMap<Id, V>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
pub type IdMap<V> = std::collections::HashMap<Id, V, BuilIdHasher>; pub type IdMap<V> = std::collections::HashMap<Id, V, BuilIdHasher>;

View file

@ -13,6 +13,7 @@ use crate::{
/// ///
/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned. /// 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. /// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
// TODO(emilk): we should be using bit sets instead of so many bools
#[derive(Clone)] #[derive(Clone)]
pub struct Response { pub struct Response {
// CONTEXT: // CONTEXT:
@ -42,6 +43,10 @@ pub struct Response {
#[doc(hidden)] #[doc(hidden)]
pub hovered: bool, pub hovered: bool,
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
#[doc(hidden)]
pub highlighted: bool,
/// The pointer clicked this thing this frame. /// The pointer clicked this thing this frame.
#[doc(hidden)] #[doc(hidden)]
pub clicked: [bool; NUM_POINTER_BUTTONS], pub clicked: [bool; NUM_POINTER_BUTTONS],
@ -90,6 +95,7 @@ impl std::fmt::Debug for Response {
sense, sense,
enabled, enabled,
hovered, hovered,
highlighted,
clicked, clicked,
double_clicked, double_clicked,
triple_clicked, triple_clicked,
@ -106,6 +112,7 @@ impl std::fmt::Debug for Response {
.field("sense", sense) .field("sense", sense)
.field("enabled", enabled) .field("enabled", enabled)
.field("hovered", hovered) .field("hovered", hovered)
.field("highlighted", highlighted)
.field("clicked", clicked) .field("clicked", clicked)
.field("double_clicked", double_clicked) .field("double_clicked", double_clicked)
.field("triple_clicked", triple_clicked) .field("triple_clicked", triple_clicked)
@ -213,6 +220,12 @@ impl Response {
self.hovered self.hovered
} }
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
#[doc(hidden)]
pub fn highlighted(&self) -> bool {
self.highlighted
}
/// This widget has the keyboard focus (i.e. is receiving key presses). /// This widget has the keyboard focus (i.e. is receiving key presses).
/// ///
/// This function only returns true if the UI as a whole (e.g. window) /// This function only returns true if the UI as a whole (e.g. window)
@ -454,6 +467,17 @@ impl Response {
}) })
} }
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
///
/// The highlight takes on frame to take effect if you call this after the widget has been fully rendered.
///
/// See also [`Context::highlight_widget`].
pub fn highlight(mut self) -> Self {
self.ctx.highlight_widget(self.id);
self.highlighted = true;
self
}
/// Show this text when hovering if the widget is disabled. /// Show this text when hovering if the widget is disabled.
pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self { pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self {
self.on_disabled_hover_ui(|ui| { self.on_disabled_hover_ui(|ui| {
@ -688,6 +712,7 @@ impl Response {
sense: self.sense.union(other.sense), sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled, enabled: self.enabled || other.enabled,
hovered: self.hovered || other.hovered, hovered: self.hovered || other.hovered,
highlighted: self.highlighted || other.highlighted,
clicked: [ clicked: [
self.clicked[0] || other.clicked[0], self.clicked[0] || other.clicked[0],
self.clicked[1] || other.clicked[1], self.clicked[1] || other.clicked[1],

View file

@ -576,7 +576,9 @@ pub struct Widgets {
/// The style of an interactive widget, such as a button, at rest. /// The style of an interactive widget, such as a button, at rest.
pub inactive: WidgetVisuals, pub inactive: WidgetVisuals,
/// The style of an interactive widget while you hover it. /// The style of an interactive widget while you hover it, or when it is highlighted.
///
/// See [`Response::hovered`], [`Response::highlighted`] and [`Response::highlight`].
pub hovered: WidgetVisuals, pub hovered: WidgetVisuals,
/// The style of an interactive widget as you are clicking or dragging it. /// The style of an interactive widget as you are clicking or dragging it.
@ -592,7 +594,7 @@ impl Widgets {
&self.noninteractive &self.noninteractive
} else if response.is_pointer_button_down_on() || response.has_focus() { } else if response.is_pointer_button_down_on() || response.has_focus() {
&self.active &self.active
} else if response.hovered() { } else if response.hovered() | response.highlighted() {
&self.hovered &self.hovered
} else { } else {
&self.inactive &self.inactive

View file

@ -173,7 +173,7 @@ impl Widget for Label {
if ui.is_rect_visible(response.rect) { if ui.is_rect_visible(response.rect) {
let response_color = ui.style().interact(&response).text_color(); let response_color = ui.style().interact(&response).text_color();
let underline = if response.has_focus() { let underline = if response.has_focus() || response.highlighted() {
Stroke::new(1.0, response_color) Stroke::new(1.0, response_color)
} else { } else {
Stroke::NONE Stroke::NONE

View file

@ -90,6 +90,7 @@ impl Default for Tests {
fn default() -> Self { fn default() -> Self {
Self::from_demos(vec![ Self::from_demos(vec![
Box::new(super::tests::CursorTest::default()), Box::new(super::tests::CursorTest::default()),
Box::new(super::highlighting::Highlighting::default()),
Box::new(super::tests::IdTest::default()), Box::new(super::tests::IdTest::default()),
Box::new(super::tests::InputTest::default()), Box::new(super::tests::InputTest::default()),
Box::new(super::layout_test::LayoutTest::default()), Box::new(super::layout_test::LayoutTest::default()),

View file

@ -0,0 +1,37 @@
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Highlighting {}
impl super::Demo for Highlighting {
fn name(&self) -> &'static str {
"Highlighting"
}
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.default_width(320.0)
.open(open)
.show(ctx, |ui| {
use super::View as _;
self.ui(ui);
});
}
}
impl super::View for Highlighting {
fn ui(&mut self, ui: &mut egui::Ui) {
ui.label("This demo demonstrates highlighting a widget.");
ui.add_space(4.0);
let label_response = ui.label("Hover me to highlight the button!");
ui.add_space(4.0);
let mut button_response = ui.button("Hover the button to highlight the label!");
if label_response.hovered() {
button_response = button_response.highlight();
}
if button_response.hovered() {
label_response.highlight();
}
}
}

View file

@ -12,6 +12,7 @@ pub mod dancing_strings;
pub mod demo_app_windows; pub mod demo_app_windows;
pub mod drag_and_drop; pub mod drag_and_drop;
pub mod font_book; pub mod font_book;
pub mod highlighting;
pub mod layout_test; pub mod layout_test;
pub mod misc_demo_window; pub mod misc_demo_window;
pub mod multi_touch; pub mod multi_touch;