Merge pull request #1692 from tarkah/fix/tooltip-overlay

Use overlay for tooltip
This commit is contained in:
Héctor Ramón 2023-07-12 03:59:15 +02:00 committed by GitHub
commit 9f2be29a28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,16 +1,15 @@
//! Display a widget over another. //! Display a widget over another.
use crate::container; use crate::container;
use crate::core;
use crate::core::event::{self, Event}; use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout}; use crate::core::layout::{self, Layout};
use crate::core::mouse; use crate::core::mouse;
use crate::core::overlay; use crate::core::overlay;
use crate::core::renderer; use crate::core::renderer;
use crate::core::text; use crate::core::text;
use crate::core::widget::Tree; use crate::core::widget::{self, Widget};
use crate::core::{ use crate::core::{
Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
Vector, Widget, Vector,
}; };
use crate::Text; use crate::Text;
@ -107,14 +106,22 @@ where
Renderer: text::Renderer, Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, Renderer::Theme: container::StyleSheet + crate::text::StyleSheet,
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<widget::Tree> {
vec![Tree::new(&self.content)] vec![widget::Tree::new(&self.content)]
} }
fn diff(&self, tree: &mut Tree) { fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(std::slice::from_ref(&self.content)) tree.diff_children(std::slice::from_ref(&self.content))
} }
fn state(&self) -> widget::tree::State {
widget::tree::State::new(State::default())
}
fn tag(&self) -> widget::tree::Tag {
widget::tree::Tag::of::<State>()
}
fn width(&self) -> Length { fn width(&self) -> Length {
self.content.as_widget().width() self.content.as_widget().width()
} }
@ -133,7 +140,7 @@ where
fn on_event( fn on_event(
&mut self, &mut self,
tree: &mut Tree, tree: &mut widget::Tree,
event: Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
@ -141,6 +148,13 @@ where
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) -> event::Status { ) -> event::Status {
let state = tree.state.downcast_mut::<State>();
*state = cursor
.position_over(layout.bounds())
.map(|cursor_position| State::Hovered { cursor_position })
.unwrap_or_default();
self.content.as_widget_mut().on_event( self.content.as_widget_mut().on_event(
&mut tree.children[0], &mut tree.children[0],
event, event,
@ -154,7 +168,7 @@ where
fn mouse_interaction( fn mouse_interaction(
&self, &self,
tree: &Tree, tree: &widget::Tree,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
@ -171,7 +185,7 @@ where
fn draw( fn draw(
&self, &self,
tree: &Tree, tree: &widget::Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &Renderer::Theme, theme: &Renderer::Theme,
inherited_style: &renderer::Style, inherited_style: &renderer::Style,
@ -188,50 +202,50 @@ where
cursor, cursor,
viewport, viewport,
); );
let tooltip = &self.tooltip;
draw(
renderer,
theme,
inherited_style,
layout,
cursor,
viewport,
self.position,
self.gap,
self.padding,
self.snap_within_viewport,
&self.style,
|renderer, limits| {
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
},
|renderer, defaults, layout, viewport| {
Widget::<(), Renderer>::draw(
tooltip,
&Tree::empty(),
renderer,
theme,
defaults,
layout,
cursor,
viewport,
);
},
);
} }
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut widget::Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> { ) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content.as_widget_mut().overlay( let state = tree.state.downcast_ref::<State>();
let content = self.content.as_widget_mut().overlay(
&mut tree.children[0], &mut tree.children[0],
layout, layout,
renderer, renderer,
) );
let tooltip = if let State::Hovered { cursor_position } = *state {
Some(overlay::Element::new(
layout.position(),
Box::new(Overlay {
tooltip: &self.tooltip,
cursor_position,
content_bounds: layout.bounds(),
snap_within_viewport: self.snap_within_viewport,
position: self.position,
gap: self.gap,
padding: self.padding,
style: &self.style,
}),
))
} else {
None
};
if content.is_some() || tooltip.is_some() {
Some(
overlay::Group::with_children(
content.into_iter().chain(tooltip).collect(),
)
.overlay(),
)
} else {
None
}
} }
} }
@ -264,84 +278,107 @@ pub enum Position {
Right, Right,
} }
/// Draws a [`Tooltip`]. #[derive(Debug, Clone, Copy, Default)]
pub fn draw<Renderer>( enum State {
renderer: &mut Renderer, #[default]
theme: &Renderer::Theme, Idle,
inherited_style: &renderer::Style, Hovered {
layout: Layout<'_>, cursor_position: Point,
cursor: mouse::Cursor, },
viewport: &Rectangle, }
struct Overlay<'a, 'b, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{
tooltip: &'b Text<'a, Renderer>,
cursor_position: Point,
content_bounds: Rectangle,
snap_within_viewport: bool,
position: Position, position: Position,
gap: f32, gap: f32,
padding: f32, padding: f32,
snap_within_viewport: bool, style: &'b <Renderer::Theme as container::StyleSheet>::Style,
style: &<Renderer::Theme as container::StyleSheet>::Style, }
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
draw_text: impl FnOnce(&mut Renderer, &renderer::Style, Layout<'_>, &Rectangle), impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
) where for Overlay<'a, 'b, Renderer>
Renderer: core::Renderer, where
Renderer::Theme: container::StyleSheet, Renderer: text::Renderer,
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
{ {
use container::StyleSheet; fn layout(
&self,
renderer: &Renderer,
bounds: Size,
_position: Point,
) -> layout::Node {
let viewport = Rectangle::with_size(bounds);
let bounds = layout.bounds(); let text_layout = Widget::<(), Renderer>::layout(
self.tooltip,
if let Some(cursor_position) = cursor.position_over(bounds) {
let style = theme.appearance(style);
let defaults = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};
let text_layout = layout_text(
renderer, renderer,
&layout::Limits::new( &layout::Limits::new(
Size::ZERO, Size::ZERO,
snap_within_viewport self.snap_within_viewport
.then(|| viewport.size()) .then(|| viewport.size())
.unwrap_or(Size::INFINITY), .unwrap_or(Size::INFINITY),
) )
.pad(Padding::new(padding)), .pad(Padding::new(self.padding)),
); );
let text_bounds = text_layout.bounds(); let text_bounds = text_layout.bounds();
let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; let x_center = self.content_bounds.x
let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0; + (self.content_bounds.width - text_bounds.width) / 2.0;
let y_center = self.content_bounds.y
+ (self.content_bounds.height - text_bounds.height) / 2.0;
let mut tooltip_bounds = { let mut tooltip_bounds = {
let offset = match position { let offset = match self.position {
Position::Top => Vector::new( Position::Top => Vector::new(
x_center, x_center,
bounds.y - text_bounds.height - gap - padding, self.content_bounds.y
- text_bounds.height
- self.gap
- self.padding,
), ),
Position::Bottom => Vector::new( Position::Bottom => Vector::new(
x_center, x_center,
bounds.y + bounds.height + gap + padding, self.content_bounds.y
+ self.content_bounds.height
+ self.gap
+ self.padding,
), ),
Position::Left => Vector::new( Position::Left => Vector::new(
bounds.x - text_bounds.width - gap - padding, self.content_bounds.x
- text_bounds.width
- self.gap
- self.padding,
y_center, y_center,
), ),
Position::Right => Vector::new( Position::Right => Vector::new(
bounds.x + bounds.width + gap + padding, self.content_bounds.x
+ self.content_bounds.width
+ self.gap
+ self.padding,
y_center, y_center,
), ),
Position::FollowCursor => Vector::new( Position::FollowCursor => Vector::new(
cursor_position.x, self.cursor_position.x,
cursor_position.y - text_bounds.height, self.cursor_position.y - text_bounds.height,
), ),
}; };
Rectangle { Rectangle {
x: offset.x - padding, x: offset.x - self.padding,
y: offset.y - padding, y: offset.y - self.padding,
width: text_bounds.width + padding * 2.0, width: text_bounds.width + self.padding * 2.0,
height: text_bounds.height + padding * 2.0, height: text_bounds.height + self.padding * 2.0,
} }
}; };
if snap_within_viewport { if self.snap_within_viewport {
if tooltip_bounds.x < viewport.x { if tooltip_bounds.x < viewport.x {
tooltip_bounds.x = viewport.x; tooltip_bounds.x = viewport.x;
} else if viewport.x + viewport.width } else if viewport.x + viewport.width
@ -361,21 +398,49 @@ pub fn draw<Renderer>(
} }
} }
renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| { layout::Node::with_children(
container::draw_background(renderer, &style, tooltip_bounds); tooltip_bounds.size(),
vec![text_layout.translate(Vector::new(self.padding, self.padding))],
)
.translate(Vector::new(tooltip_bounds.x, tooltip_bounds.y))
}
draw_text( fn draw(
renderer, &self,
&defaults, renderer: &mut Renderer,
Layout::with_offset( theme: &<Renderer as renderer::Renderer>::Theme,
Vector::new( inherited_style: &renderer::Style,
tooltip_bounds.x + padding, layout: Layout<'_>,
tooltip_bounds.y + padding, cursor_position: mouse::Cursor,
), ) {
&text_layout, let style = <Renderer::Theme as container::StyleSheet>::appearance(
), theme, self.style,
viewport, );
)
}); container::draw_background(renderer, &style, layout.bounds());
let defaults = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};
Widget::<(), Renderer>::draw(
self.tooltip,
&widget::Tree::empty(),
renderer,
theme,
&defaults,
layout.children().next().unwrap(),
cursor_position,
&Rectangle::with_size(Size::INFINITY),
);
}
fn is_over(
&self,
_layout: Layout<'_>,
_renderer: &Renderer,
_cursor_position: Point,
) -> bool {
false
} }
} }