Skip to content

Commit

Permalink
Add ability to highlight any widget (#2632)
Browse files Browse the repository at this point in the history
* Add ability to highlight any widget

* Add line to changelog

* Demote the demo to a test
  • Loading branch information
emilk authored Jan 27, 2023
1 parent e7c0547 commit c72bdb7
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `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)).
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).

### Changed 🔧
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
Expand Down
12 changes: 12 additions & 0 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,8 @@ impl Context {
) -> Response {
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 {
ctx: self.clone(),
layer_id,
Expand All @@ -629,6 +631,7 @@ impl Context {
sense,
enabled,
hovered,
highlighted,
clicked: Default::default(),
double_clicked: Default::default(),
triple_clicked: Default::default(),
Expand Down Expand Up @@ -1284,6 +1287,15 @@ impl Context {
pub fn wants_keyboard_input(&self) -> bool {
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
Expand Down
15 changes: 14 additions & 1 deletion crates/egui/src/frame_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ops::RangeInclusive;

use crate::*;
use crate::{id::IdSet, *};

#[derive(Clone, Copy, Debug)]
pub(crate) struct TooltipFrameState {
Expand Down Expand Up @@ -51,6 +51,12 @@ pub(crate) struct FrameState {

#[cfg(feature = "accesskit")]
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 {
Expand All @@ -65,6 +71,8 @@ impl Default for FrameState {
scroll_target: [None, None],
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_this_frame: Default::default(),
highlight_next_frame: Default::default(),
}
}
}
Expand All @@ -81,6 +89,8 @@ impl FrameState {
scroll_target,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_this_frame,
highlight_next_frame,
} = self;

used_ids.clear();
Expand All @@ -90,10 +100,13 @@ impl FrameState {
*tooltip_state = None;
*scroll_delta = input.scroll_delta;
*scroll_target = [None, None];

#[cfg(feature = "accesskit")]
{
*accesskit_state = None;
}

*highlight_this_frame = std::mem::take(highlight_next_frame);
}

/// How much space is still available after panels has been added.
Expand Down
3 changes: 3 additions & 0 deletions crates/egui/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
pub type IdMap<V> = std::collections::HashMap<Id, V, BuilIdHasher>;
25 changes: 25 additions & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
///
/// 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.
// TODO(emilk): we should be using bit sets instead of so many bools
#[derive(Clone)]
pub struct Response {
// CONTEXT:
Expand Down Expand Up @@ -42,6 +43,10 @@ pub struct Response {
#[doc(hidden)]
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.
#[doc(hidden)]
pub clicked: [bool; NUM_POINTER_BUTTONS],
Expand Down Expand Up @@ -90,6 +95,7 @@ impl std::fmt::Debug for Response {
sense,
enabled,
hovered,
highlighted,
clicked,
double_clicked,
triple_clicked,
Expand All @@ -106,6 +112,7 @@ impl std::fmt::Debug for Response {
.field("sense", sense)
.field("enabled", enabled)
.field("hovered", hovered)
.field("highlighted", highlighted)
.field("clicked", clicked)
.field("double_clicked", double_clicked)
.field("triple_clicked", triple_clicked)
Expand Down Expand Up @@ -213,6 +220,12 @@ impl Response {
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 function only returns true if the UI as a whole (e.g. window)
Expand Down Expand Up @@ -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.
pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self {
self.on_disabled_hover_ui(|ui| {
Expand Down Expand Up @@ -688,6 +712,7 @@ impl Response {
sense: self.sense.union(other.sense),
enabled: self.enabled || other.enabled,
hovered: self.hovered || other.hovered,
highlighted: self.highlighted || other.highlighted,
clicked: [
self.clicked[0] || other.clicked[0],
self.clicked[1] || other.clicked[1],
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,9 @@ pub struct Widgets {
/// The style of an interactive widget, such as a button, at rest.
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,

/// The style of an interactive widget as you are clicking or dragging it.
Expand All @@ -592,7 +594,7 @@ impl Widgets {
&self.noninteractive
} else if response.is_pointer_button_down_on() || response.has_focus() {
&self.active
} else if response.hovered() {
} else if response.hovered() | response.highlighted() {
&self.hovered
} else {
&self.inactive
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widgets/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl Widget for Label {
if ui.is_rect_visible(response.rect) {
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)
} else {
Stroke::NONE
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl Default for Tests {
fn default() -> Self {
Self::from_demos(vec![
Box::new(super::tests::CursorTest::default()),
Box::new(super::highlighting::Highlighting::default()),
Box::new(super::tests::IdTest::default()),
Box::new(super::tests::InputTest::default()),
Box::new(super::layout_test::LayoutTest::default()),
Expand Down
37 changes: 37 additions & 0 deletions crates/egui_demo_lib/src/demo/highlighting.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod dancing_strings;
pub mod demo_app_windows;
pub mod drag_and_drop;
pub mod font_book;
pub mod highlighting;
pub mod layout_test;
pub mod misc_demo_window;
pub mod multi_touch;
Expand Down

0 comments on commit c72bdb7

Please sign in to comment.