From 711defddb89999dfa3a2786a3ab58db87aa49c0a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 28 Sep 2021 22:15:14 +0200 Subject: [PATCH] Show tooltips above widgets on touch screens --- CHANGELOG.md | 1 + egui/src/containers/popup.rs | 60 ++++++++++++++++++++++++------------ egui/src/input_state.rs | 5 +++ egui/src/response.rs | 4 +-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 513aa39a..3de75cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`eg ### Fixed 🐛 * Fix wrongly sized multiline `TextEdit` in justified layouts. * Fix clip rectangle of windows that don't fit the central area. +* Show tooltips above widgets on touch screens. ## 0.14.2 - 2021-08-28 - Window resize fix diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index db03aad6..e7b42958 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -93,17 +93,23 @@ pub fn show_tooltip_at_pointer( /// Show a tooltip under the given area. /// /// If the tooltip does not fit under the area, it tries to place it above it instead. -pub fn show_tooltip_under( +pub fn show_tooltip_for( ctx: &CtxRef, id: Id, rect: &Rect, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { let expanded_rect = rect.expand2(vec2(2.0, 4.0)); + let (above, position) = if ctx.input().any_touches() { + (true, expanded_rect.left_top()) + } else { + (false, expanded_rect.left_bottom()) + }; show_tooltip_at_avoid_dyn( ctx, id, - Some(expanded_rect.left_bottom()), + Some(position), + above, expanded_rect, Box::new(add_contents), ) @@ -118,10 +124,12 @@ pub fn show_tooltip_at( suggested_position: Option, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { + let above = false; show_tooltip_at_avoid_dyn( ctx, id, suggested_position, + above, Rect::NOTHING, Box::new(add_contents), ) @@ -131,13 +139,14 @@ fn show_tooltip_at_avoid_dyn<'c, R>( ctx: &CtxRef, mut id: Id, suggested_position: Option, + above: bool, mut avoid_rect: Rect, add_contents: Box R + 'c>, ) -> Option { let mut tooltip_rect = Rect::NOTHING; let mut count = 0; - let position = if let Some((stored_id, stored_tooltip_rect, stored_count)) = + let mut position = if let Some((stored_id, stored_tooltip_rect, stored_count)) = ctx.frame_state().tooltip_rect { // if there are multiple tooltips open they should use the same id for the `tooltip_size` caching to work. @@ -145,11 +154,15 @@ fn show_tooltip_at_avoid_dyn<'c, R>( tooltip_rect = stored_tooltip_rect; count = stored_count; avoid_rect = avoid_rect.union(tooltip_rect); - tooltip_rect.left_bottom() + if above { + tooltip_rect.left_top() + } else { + tooltip_rect.left_bottom() + } } else if let Some(position) = suggested_position { position } else if ctx.memory().everything_is_visible() { - Pos2::default() + Pos2::ZERO } else { return None; // No good place for a tooltip :( }; @@ -160,21 +173,30 @@ fn show_tooltip_at_avoid_dyn<'c, R>( .get_or_default::() .tooltip_size(id, count); let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0)); - let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size); - // Place the tooltip above the avoid_rect if necessary. - let new_rect = Rect::from_min_size(position, expected_size); - // Note: We do not use Rect::intersects() since it returns true even if the rects only touch. - let position = if avoid_rect.min.x < new_rect.max.x - && new_rect.min.x < avoid_rect.max.x - && avoid_rect.min.y < new_rect.max.y - && new_rect.min.y < avoid_rect.max.y - { - Pos2::new(position.x, avoid_rect.min.y - expected_size.y) - } else { - position - }; - let position = position.max(ctx.input().screen_rect().left_top()); + if above { + position.y -= expected_size.y; + } + + position = position.at_most(ctx.input().screen_rect().max - expected_size); + + // check if we intersect the avoid_rect + { + let new_rect = Rect::from_min_size(position, expected_size); + + // Note: We do not use Rect::intersects() since it returns true even if the rects only touch. + if new_rect.shrink(1.0).intersects(avoid_rect) { + if above { + // place below instead: + position = avoid_rect.left_bottom(); + } else { + // place above instead: + position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y); + } + } + } + + let position = position.at_least(ctx.input().screen_rect().min); let InnerResponse { inner, response } = show_tooltip_area_dyn(ctx, id, position, add_contents); ctx.memory() diff --git a/egui/src/input_state.rs b/egui/src/input_state.rs index b842b833..3c5e6518 100644 --- a/egui/src/input_state.rs +++ b/egui/src/input_state.rs @@ -266,6 +266,11 @@ impl InputState { } } + /// True if there currently are any fingers touching egui. + pub fn any_touches(&self) -> bool { + !self.touch_states.is_empty() + } + /// Scans `events` for device IDs of touch devices we have not seen before, /// and creates a new `TouchState` for each such device. fn create_touch_states_for_new_devices(&mut self, events: &[Event]) { diff --git a/egui/src/response.rs b/egui/src/response.rs index f9277a4c..f2c08050 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -314,7 +314,7 @@ impl Response { #[doc(alias = "tooltip")] pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { if self.should_show_hover_ui() { - crate::containers::show_tooltip_under( + crate::containers::show_tooltip_for( &self.ctx, self.id.with("__tooltip"), &self.rect, @@ -327,7 +327,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.ctx.rect_contains_pointer(self.layer_id, self.rect) { - crate::containers::show_tooltip_under( + crate::containers::show_tooltip_for( &self.ctx, self.id.with("__tooltip"), &self.rect,