Merge pull request #1692 from tarkah/fix/tooltip-overlay
Use overlay for tooltip
This commit is contained in:
commit
9f2be29a28
1 changed files with 166 additions and 101 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue