From 3a5639675640c820b7c07dcf39e6b0240e721468 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 31 May 2024 17:05:02 +0200 Subject: [PATCH 01/10] WIP: interact with contents of tooltips --- crates/egui/src/containers/popup.rs | 7 ++++++- crates/egui/src/layers.rs | 2 +- crates/egui/src/response.rs | 31 ++++++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 661de610712..76f1c6dd27c 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -135,7 +135,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>( .pivot(pivot) .fixed_pos(anchor) .default_width(ctx.style().spacing.tooltip_width) - .interactable(false) + // .interactable(false) .show(ctx, |ui| { Frame::popup(&ctx.style()).show_dyn(ui, add_contents).inner }); @@ -234,6 +234,11 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool { }) } +pub fn tooltip_area_state(ctx: &Context, widget_id: Id) -> Option { + let primary_tooltip_area_id = tooltip_id(widget_id, 0); + AreaState::load(ctx, primary_tooltip_area_id) +} + /// Helper for [`popup_above_or_below_widget`]. pub fn popup_below_widget( ui: &Ui, diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index b9b1fb5a5c3..5e5ad0838db 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -49,7 +49,7 @@ impl Order { | Self::Middle | Self::Foreground | Self::Debug => true, - Self::Tooltip => false, + Self::Tooltip => true, } } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 8697a9743a5..7cceb4092ac 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -2,8 +2,8 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, - menu, ComboBox, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, - WidgetText, + menu, tooltip_area_state, ComboBox, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, + WidgetRect, WidgetText, }; // ---------------------------------------------------------------------------- @@ -570,6 +570,31 @@ impl Response { return true; } + let is_tooltip_open = self.is_tooltip_open(); + + if is_tooltip_open { + if let Some(area) = tooltip_area_state(&self.ctx, self.id) { + // We keep the tooltip open if hovered, + // or if the pointer is on its way to it, + // so that the user can interact with the tooltip + // (i.e. click links that are in it). + + let rect = area.rect(); + let pointer_in_area_or_on_the_way_there = self.ctx.input(|i| { + if let Some(pos) = i.pointer.hover_pos() { + rect.contains(pos) + || rect.intersects_ray(pos, i.pointer.velocity().normalized()) + } else { + false + } + }); + + if pointer_in_area_or_on_the_way_there { + return true; + } + } + } + // Fast early-outs: if self.enabled { if !self.hovered || !self.ctx.input(|i| i.pointer.has_pointer()) { @@ -605,7 +630,7 @@ impl Response { let tooltip_was_recently_shown = when_was_a_toolip_last_shown .map_or(false, |time| ((now - time) as f32) < tooltip_grace_time); - if !tooltip_was_recently_shown && !self.is_tooltip_open() { + if !tooltip_was_recently_shown && !is_tooltip_open { if self.ctx.style().interaction.show_tooltips_only_when_still { // We only show the tooltip when the mouse pointer is still. if !self.ctx.input(|i| i.pointer.is_still()) { From 4a91aa7a6c51b821b06a357e550ffd09c9db8dcc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 20:37:12 +0200 Subject: [PATCH 02/10] Make `ViewportState` public and readable --- crates/egui/src/context.rs | 47 +++++++++++++++++++++++----------- crates/egui/src/frame_state.rs | 36 +++++++++++++------------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 9471349999c..9354a9fee23 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -198,36 +198,39 @@ impl ContextImpl { // ---------------------------------------------------------------------------- -/// State stored per viewport +/// State stored per viewport. +/// +/// Mostly for internal use. +/// Things here may move and change without warning. #[derive(Default)] -struct ViewportState { +pub struct ViewportState { /// The type of viewport. /// /// This will never be [`ViewportClass::Embedded`], /// since those don't result in real viewports. - class: ViewportClass, + pub class: ViewportClass, /// The latest delta - builder: ViewportBuilder, + pub builder: ViewportBuilder, /// The user-code that shows the GUI, used for deferred viewports. /// /// `None` for immediate viewports. - viewport_ui_cb: Option>, + pub viewport_ui_cb: Option>, - input: InputState, + pub input: InputState, /// State that is collected during a frame and then cleared - frame_state: FrameState, + pub frame_state: FrameState, /// Has this viewport been updated this frame? - used: bool, + pub used: bool, /// Written to during the frame. - widgets_this_frame: WidgetRects, + pub widgets_this_frame: WidgetRects, /// Read - widgets_prev_frame: WidgetRects, + pub widgets_prev_frame: WidgetRects, /// State related to repaint scheduling. repaint: ViewportRepaintInfo, @@ -236,20 +239,20 @@ struct ViewportState { // Updated at the start of the frame: // /// Which widgets are under the pointer? - hits: WidgetHits, + pub hits: WidgetHits, /// What widgets are being interacted with this frame? /// /// Based on the widgets from last frame, and input in this frame. - interact_widgets: InteractionSnapshot, + pub interact_widgets: InteractionSnapshot, // ---------------------- // The output of a frame: // - graphics: GraphicLayers, + pub graphics: GraphicLayers, // Most of the things in `PlatformOutput` are not actually viewport dependent. - output: PlatformOutput, - commands: Vec, + pub output: PlatformOutput, + pub commands: Vec, } /// What called [`Context::request_repaint`]? @@ -3092,6 +3095,20 @@ impl Context { self.read(|ctx| ctx.parent_viewport_id()) } + /// Read the state of the current viewport. + pub fn viewport(&self, reader: impl FnOnce(&ViewportState) -> R) -> R { + self.write(|ctx| reader(ctx.viewport())) + } + + /// Read the state of a specific current viewport. + pub fn viewport_for( + &self, + viewport_id: ViewportId, + reader: impl FnOnce(&ViewportState) -> R, + ) -> R { + self.write(|ctx| reader(ctx.viewport_for(viewport_id))) + } + /// For integrations: Set this to render a sync viewport. /// /// This will only set the callback for the current thread, diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 28c8c364b78..1a4f5a71161 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -1,18 +1,18 @@ use crate::{id::IdSet, *}; #[derive(Clone, Debug, Default)] -pub(crate) struct TooltipFrameState { +pub struct TooltipFrameState { pub widget_tooltips: IdMap, } impl TooltipFrameState { - pub(crate) fn clear(&mut self) { + pub fn clear(&mut self) { self.widget_tooltips.clear(); } } #[derive(Clone, Copy, Debug)] -pub(crate) struct PerWidgetTooltipState { +pub struct PerWidgetTooltipState { /// Bounding rectangle for all widget and all previous tooltips. pub bounding_rect: Rect, @@ -22,37 +22,37 @@ pub(crate) struct PerWidgetTooltipState { #[cfg(feature = "accesskit")] #[derive(Clone)] -pub(crate) struct AccessKitFrameState { - pub(crate) node_builders: IdMap, - pub(crate) parent_stack: Vec, +pub struct AccessKitFrameState { + pub node_builders: IdMap, + pub parent_stack: Vec, } /// State that is collected during a frame and then cleared. /// Short-term (single frame) memory. #[derive(Clone)] -pub(crate) struct FrameState { +pub struct FrameState { /// All [`Id`]s that were used this frame. - pub(crate) used_ids: IdMap, + pub used_ids: IdMap, /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] does not change this. /// This is the area available to Window's. - pub(crate) available_rect: Rect, + pub available_rect: Rect, /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] retracts from this. - pub(crate) unused_rect: Rect, + pub unused_rect: Rect, /// How much space is used by panels. - pub(crate) used_by_panels: Rect, + pub used_by_panels: Rect, /// If a tooltip has been shown this frame, where was it? /// This is used to prevent multiple tooltips to cover each other. /// Reset at the start of each frame. - pub(crate) tooltip_state: TooltipFrameState, + pub tooltip_state: TooltipFrameState, /// The current scroll area should scroll to this range (horizontal, vertical). - pub(crate) scroll_target: [Option<(Rangef, Option)>; 2], + pub scroll_target: [Option<(Rangef, Option)>; 2], /// The current scroll area should scroll by this much. /// @@ -63,19 +63,19 @@ pub(crate) struct FrameState { /// /// A positive Y-value indicates the content is being moved down, /// as when swiping down on a touch-screen or track-pad with natural scrolling. - pub(crate) scroll_delta: Vec2, + pub scroll_delta: Vec2, #[cfg(feature = "accesskit")] - pub(crate) accesskit_state: Option, + pub accesskit_state: Option, /// Highlight these widgets this next frame. Read from this. - pub(crate) highlight_this_frame: IdSet, + pub highlight_this_frame: IdSet, /// Highlight these widgets the next frame. Write to this. - pub(crate) highlight_next_frame: IdSet, + pub highlight_next_frame: IdSet, #[cfg(debug_assertions)] - pub(crate) has_debug_viewed_this_frame: bool, + pub has_debug_viewed_this_frame: bool, } impl Default for FrameState { From 58affefc8f652e48b2a8c449f13a82ce372d4cc9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 20:45:16 +0200 Subject: [PATCH 03/10] Only keep tooltip open if it contains interactive widgets --- crates/egui/src/containers/popup.rs | 16 +++++---- crates/egui/src/response.rs | 50 +++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 76f1c6dd27c..f43437a8e38 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -135,8 +135,15 @@ fn show_tooltip_at_avoid_dyn<'c, R>( .pivot(pivot) .fixed_pos(anchor) .default_width(ctx.style().spacing.tooltip_width) - // .interactable(false) + .interactable(false) // Only affects the actual area, i.e. clicking and dragging it. The content can still be interactive. .show(ctx, |ui| { + // By default the text in tooltips aren't selectable. + // This means that most tooltips aren't interactable, + // which also mean they won't stick around so you can click them. + // Only tooltips that have actual interactive stuff (buttons, links, …) + // will stick around when you try to click them. + ui.style_mut().interaction.selectable_labels = false; + Frame::popup(&ctx.style()).show_dyn(ui, add_contents).inner }); @@ -147,7 +154,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>( inner } -fn tooltip_id(widget_id: Id, tooltip_count: usize) -> Id { +pub fn tooltip_id(widget_id: Id, tooltip_count: usize) -> Id { widget_id.with(tooltip_count) } @@ -234,11 +241,6 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool { }) } -pub fn tooltip_area_state(ctx: &Context, widget_id: Id) -> Option { - let primary_tooltip_area_id = tooltip_id(widget_id, 0); - AreaState::load(ctx, primary_tooltip_area_id) -} - /// Helper for [`popup_above_or_below_widget`]. pub fn popup_below_widget( ui: &Ui, diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 7cceb4092ac..de7fdb5a9f1 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -2,7 +2,7 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, - menu, tooltip_area_state, ComboBox, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, + menu, AreaState, ComboBox, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, Ui, WidgetRect, WidgetText, }; @@ -520,6 +520,18 @@ impl Response { /// For that, use [`Self::on_disabled_hover_ui`] instead. /// /// If you call this multiple times the tooltips will stack underneath the previous ones. + /// + /// The widget can contain interactive widgets, such as buttons and links. + /// If so, it will stay open as the user moves their pointer over it. + /// By default, the text of a tooltip is NOT selectable (i.e. interactive), + /// but you can change this by setting [`style::Interaction::selectable_labels` from within the tooltip: + /// + /// ``` + /// # let ui = &mut egui::Ui::__test(); + /// ui.label("Hover me").on_hover_ui(|ui| { + /// ui.style_mut().interaction.selectable_labels = true; + /// ui.label("This text can be selected"); + /// }); #[doc(alias = "tooltip")] pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { if self.enabled && self.should_show_hover_ui() { @@ -573,24 +585,34 @@ impl Response { let is_tooltip_open = self.is_tooltip_open(); if is_tooltip_open { - if let Some(area) = tooltip_area_state(&self.ctx, self.id) { + let tooltip_id = crate::tooltip_id(self.id, 0); // TODO: correct index + let layer_id = LayerId::new(Order::Tooltip, tooltip_id); + + let tooltip_has_interactive_widget = self.ctx.viewport(|vp| { + vp.widgets_prev_frame + .get_layer(layer_id) + .any(|w| w.sense.interactive()) + }); + + if tooltip_has_interactive_widget { // We keep the tooltip open if hovered, // or if the pointer is on its way to it, // so that the user can interact with the tooltip // (i.e. click links that are in it). - - let rect = area.rect(); - let pointer_in_area_or_on_the_way_there = self.ctx.input(|i| { - if let Some(pos) = i.pointer.hover_pos() { - rect.contains(pos) - || rect.intersects_ray(pos, i.pointer.velocity().normalized()) - } else { - false + if let Some(area) = AreaState::load(&self.ctx, tooltip_id) { + let rect = area.rect(); + let pointer_in_area_or_on_the_way_there = self.ctx.input(|i| { + if let Some(pos) = i.pointer.hover_pos() { + rect.contains(pos) + || rect.intersects_ray(pos, i.pointer.velocity().normalized()) + } else { + false + } + }); + + if pointer_in_area_or_on_the_way_there { + return true; } - }); - - if pointer_in_area_or_on_the_way_there { - return true; } } } From 792fdba4c9902c091ce2e90b807e565de8c6a2c3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 20:46:10 +0200 Subject: [PATCH 04/10] Improve `test_size_pass` --- tests/test_size_pass/src/main.rs | 64 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/tests/test_size_pass/src/main.rs b/tests/test_size_pass/src/main.rs index f841cd1d9b8..ab9ad17e500 100644 --- a/tests/test_size_pass/src/main.rs +++ b/tests/test_size_pass/src/main.rs @@ -8,9 +8,56 @@ fn main() -> eframe::Result<()> { let options = eframe::NativeOptions::default(); eframe::run_simple_native("My egui App", options, move |ctx, _frame| { + // A bottom panel to force the tooltips to consider if the fit below or under the widget: + egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.label("Single tooltips:"); + for i in 0..3 { + ui.label(format!("Hover label {i} for a tooltip")) + .on_hover_text("There is some text here"); + } + }); + ui.vertical(|ui| { + ui.label("Double tooltips:"); + for i in 0..3 { + ui.label(format!("Hover label {i} for two tooltips")) + .on_hover_text("First tooltip") + .on_hover_text("Second tooltip"); + } + }); + }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| { + ui.label("Hover for tooltip") + .on_hover_text("This is a rather long tooltip that needs careful positioning."); + }); + }); + egui::CentralPanel::default().show(ctx, |ui| { - if ui.button("Reset egui memory").clicked() { - ctx.memory_mut(|mem| *mem = Default::default()); + ui.horizontal(|ui| { + if ui.button("Reset egui memory").clicked() { + ctx.memory_mut(|mem| *mem = Default::default()); + } + + ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| { + ui.label("Hover for tooltip").on_hover_text( + "This is a rather long tooltip that needs careful positioning.", + ); + ui.label("Hover for interactive tooltip").on_hover_ui(|ui| { + ui.label("This tooltip has a button:"); + let _ = ui.button("Clicking me does nothing"); + }); + }); + }); + + let has_tooltip = ui + .label("This label has a tooltip at the mouse cursor") + .on_hover_text_at_pointer("Told you!") + .is_tooltip_open(); + + let response = ui.label("This label gets a tooltip when the previous label is hovered"); + if has_tooltip { + response.show_tooltip_text("The ever-present tooltip!"); } ui.separator(); @@ -43,6 +90,19 @@ fn main() -> eframe::Result<()> { alternatives.len(), |i| alternatives[i], ); + + egui::ComboBox::from_id_source("combo") + .selected_text("ComboBox") + .width(100.0) + .show_ui(ui, |ui| { + ui.ctx() + .debug_painter() + .debug_rect(ui.max_rect(), egui::Color32::RED, ""); + + ui.label("Hello"); + ui.label("World"); + ui.label("Hellooooooooooooooooooooooooo"); + }); }); }) } From ffb0cc17743ade0aa07d57c752fbab135cc9a4b7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 20:46:30 +0200 Subject: [PATCH 05/10] Change default tooltip width from 600 to 500 points --- crates/egui/src/style.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index fd1c13a8aab..f306a73dae2 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1092,7 +1092,7 @@ impl Default for Spacing { icon_width_inner: 8.0, icon_spacing: 4.0, default_area_size: vec2(600.0, 400.0), - tooltip_width: 600.0, + tooltip_width: 500.0, menu_width: 400.0, menu_spacing: 2.0, combo_height: 200.0, From baba04fd7ac087b6ae3e3244092ffb735c49cf02 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 21:11:03 +0200 Subject: [PATCH 06/10] Use correct id for tooltip --- crates/egui/src/containers/popup.rs | 11 +++++++++++ crates/egui/src/response.rs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index f43437a8e38..c397a2de2ba 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -154,6 +154,17 @@ fn show_tooltip_at_avoid_dyn<'c, R>( inner } +/// What is the id of the next tooltip for this widget? +pub fn next_tooltip_id(ctx: &Context, widget_id: Id) -> Id { + let tooltip_count = ctx.frame_state(|fs| { + fs.tooltip_state + .widget_tooltips + .get(&widget_id) + .map_or(0, |state| state.tooltip_count) + }); + tooltip_id(widget_id, tooltip_count) +} + pub fn tooltip_id(widget_id: Id, tooltip_count: usize) -> Id { widget_id.with(tooltip_count) } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index de7fdb5a9f1..b16d9c4f1aa 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -585,7 +585,7 @@ impl Response { let is_tooltip_open = self.is_tooltip_open(); if is_tooltip_open { - let tooltip_id = crate::tooltip_id(self.id, 0); // TODO: correct index + let tooltip_id = crate::next_tooltip_id(&self.ctx, self.id); let layer_id = LayerId::new(Order::Tooltip, tooltip_id); let tooltip_has_interactive_widget = self.ctx.viewport(|vp| { From 5349ac558bdb85bffd0c1fd0a64d588ec0869464 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 21:11:14 +0200 Subject: [PATCH 07/10] Fix match statement --- crates/egui/src/layers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 5e5ad0838db..768099e23d6 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -48,8 +48,8 @@ impl Order { | Self::PanelResizeLine | Self::Middle | Self::Foreground + | Self::Tooltip | Self::Debug => true, - Self::Tooltip => true, } } From 22c89248449dd7da8a3fc4fedda339485b25a16b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 21:11:26 +0200 Subject: [PATCH 08/10] Change text on button in Misc Demos --- crates/egui_demo_lib/src/demo/misc_demo_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 6b73fe4048b..98c3973711a 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -264,7 +264,7 @@ impl Widgets { ui.label("This tooltip was created with"); ui.monospace(".on_hover_ui(…)"); }); - let _ = ui.button("A button you can never press"); + let _ = ui.button("A button you can press, but that does nothing."); }; let disabled_tooltip_ui = |ui: &mut Ui| { ui.heading("Different tooltip when widget is disabled"); From 3ff9a18625c3fb3099f6b79c70276898e614c38b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 2 Jun 2024 21:15:48 +0200 Subject: [PATCH 09/10] Fix doctest --- crates/egui/src/response.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index b16d9c4f1aa..045b28a25a9 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -527,11 +527,13 @@ impl Response { /// but you can change this by setting [`style::Interaction::selectable_labels` from within the tooltip: /// /// ``` - /// # let ui = &mut egui::Ui::__test(); + /// # egui::__run_test_ui(|ui| { /// ui.label("Hover me").on_hover_ui(|ui| { /// ui.style_mut().interaction.selectable_labels = true; /// ui.label("This text can be selected"); /// }); + /// # }); + /// ``` #[doc(alias = "tooltip")] pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { if self.enabled && self.should_show_hover_ui() { From 9f813df1457f3634549690558086324dd9a165de Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Jun 2024 10:47:38 +0200 Subject: [PATCH 10/10] Create a new tooltip demo --- .../src/demo/demo_app_windows.rs | 1 + .../src/demo/misc_demo_window.rs | 31 +------ crates/egui_demo_lib/src/demo/mod.rs | 1 + crates/egui_demo_lib/src/demo/tooltips.rs | 85 +++++++++++++++++++ 4 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 crates/egui_demo_lib/src/demo/tooltips.rs diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index dc0e91f1b47..74c5edefb52 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -42,6 +42,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 98c3973711a..6cd46723fac 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -233,7 +233,6 @@ fn label_ui(ui: &mut egui::Ui) { #[cfg_attr(feature = "serde", serde(default))] pub struct Widgets { angle: f32, - enabled: bool, password: String, } @@ -241,7 +240,6 @@ impl Default for Widgets { fn default() -> Self { Self { angle: std::f32::consts::TAU / 3.0, - enabled: true, password: "hunter2".to_owned(), } } @@ -249,38 +247,11 @@ impl Default for Widgets { impl Widgets { pub fn ui(&mut self, ui: &mut Ui) { - let Self { - angle, - enabled, - password, - } = self; + let Self { angle, password } = self; ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file_line!()); }); - let tooltip_ui = |ui: &mut Ui| { - ui.heading("The name of the tooltip"); - ui.horizontal(|ui| { - ui.label("This tooltip was created with"); - ui.monospace(".on_hover_ui(…)"); - }); - let _ = ui.button("A button you can press, but that does nothing."); - }; - let disabled_tooltip_ui = |ui: &mut Ui| { - ui.heading("Different tooltip when widget is disabled"); - ui.horizontal(|ui| { - ui.label("This tooltip was created with"); - ui.monospace(".on_disabled_hover_ui(…)"); - }); - }; - ui.checkbox(enabled, "Enabled"); - ui.add_enabled( - *enabled, - egui::Label::new("Tooltips can be more than just simple text."), - ) - .on_hover_ui(tooltip_ui) - .on_disabled_hover_ui(disabled_tooltip_ui); - ui.separator(); ui.horizontal(|ui| { diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index 8a911c9d68b..724134f86c6 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -32,6 +32,7 @@ pub mod tests; pub mod text_edit; pub mod text_layout; pub mod toggle_switch; +pub mod tooltips; pub mod widget_gallery; pub mod window_options; diff --git a/crates/egui_demo_lib/src/demo/tooltips.rs b/crates/egui_demo_lib/src/demo/tooltips.rs new file mode 100644 index 00000000000..f40edd5418d --- /dev/null +++ b/crates/egui_demo_lib/src/demo/tooltips.rs @@ -0,0 +1,85 @@ +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Tooltips { + enabled: bool, +} + +impl Default for Tooltips { + fn default() -> Self { + Self { enabled: true } + } +} + +impl super::Demo for Tooltips { + fn name(&self) -> &'static str { + "🗖 Tooltips" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use super::View as _; + let window = egui::Window::new("Tooltips") + .constrain(false) // So we can test how tooltips behave close to the screen edge + .resizable(false) + .scroll(false) + .open(open); + window.show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for Tooltips { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.spacing_mut().item_spacing.y = 8.0; + + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file_line!()); + }); + + ui.label("All labels in this demo have tooltips.") + .on_hover_text("Yes, even this one."); + + ui.label("Some widgets have multiple tooltips!") + .on_hover_text("The first tooltip.") + .on_hover_text("The second tooltip."); + + ui.label("Tooltips can contain interactive widgets.") + .on_hover_ui(|ui| { + ui.label("This tooltip contains a link:"); + ui.hyperlink_to("www.egui.rs", "https://www.egui.rs/") + .on_hover_text("The tooltip has a tooltip in it!"); + }); + + ui.label("You can put selectable text in tooltips too.") + .on_hover_ui(|ui| { + ui.style_mut().interaction.selectable_labels = true; + ui.label("You can select this text."); + }); + + ui.separator(); // --------------------------------------------------------- + + let tooltip_ui = |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + ui.label("This tooltip was created with"); + ui.code(".on_hover_ui(…)"); + }); + }; + let disabled_tooltip_ui = |ui: &mut egui::Ui| { + ui.label("A fifferent tooltip when widget is disabled."); + ui.horizontal(|ui| { + ui.label("This tooltip was created with"); + ui.code(".on_disabled_hover_ui(…)"); + }); + }; + + ui.label("You can have different tooltips depending on whether or not a widget is enabled or not:") + .on_hover_text("Check the tooltip of the button below, and see how it changes dependning on whether or not it is enabled."); + + ui.horizontal(|ui| { + ui.checkbox(&mut self.enabled, "Enabled") + .on_hover_text("Controls whether or not the button below is enabled."); + + ui.add_enabled(self.enabled, egui::Button::new("Sometimes clickable")) + .on_hover_ui(tooltip_ui) + .on_disabled_hover_ui(disabled_tooltip_ui); + }); + } +}