diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 8c0ef29e64..6e2c4dd418 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -1,14 +1,23 @@ -use iced::{Container, Element, Length, Sandbox, Settings, Text, Tooltip}; +use iced::{ + button, tooltip::TooltipPosition, Button, Column, Container, Element, + Length, Row, Sandbox, Settings, Text, Tooltip, +}; pub fn main() { Example::run(Settings::default()).unwrap() } #[derive(Default)] -struct Example {} +struct Example { + tooltip_top_button_state: button::State, + tooltip_bottom_button_state: button::State, + tooltip_right_button_state: button::State, + tooltip_left_button_state: button::State, + tooltip_cursor_button_state: button::State, +} #[derive(Debug, Clone, Copy)] -enum Message {} +struct Message; impl Sandbox for Example { type Message = Message; @@ -24,12 +33,67 @@ impl Sandbox for Example { fn update(&mut self, _message: Message) {} fn view(&mut self) -> Element { - let tooltip = Tooltip::new( - Text::new("hello").size(60), - Text::new("hello but smaller").size(30), + let tooltip_top = tooltip_builder( + "Tooltip at top", + &mut self.tooltip_top_button_state, + TooltipPosition::Top, + ); + let tooltip_bottom = tooltip_builder( + "Tooltip at bottom", + &mut self.tooltip_bottom_button_state, + TooltipPosition::Bottom, + ); + let tooltip_right = tooltip_builder( + "Tooltip at right", + &mut self.tooltip_right_button_state, + TooltipPosition::Right, + ); + let tooltip_left = tooltip_builder( + "Tooltip at left", + &mut self.tooltip_left_button_state, + TooltipPosition::Left, ); - Container::new(tooltip) + let fixed_tooltips = Row::with_children(vec![ + tooltip_top.into(), + tooltip_bottom.into(), + tooltip_left.into(), + tooltip_right.into(), + ]) + .width(Length::Fill) + .height(Length::Fill) + .align_items(iced::Align::Center) + .spacing(120); + + let cursor_tooltip_area = Tooltip::new( + Button::new( + &mut self.tooltip_cursor_button_state, + Container::new(Text::new("Tooltip follows cursor").size(40)) + .center_y() + .center_x() + .width(Length::Fill) + .height(Length::Fill), + ) + .on_press(Message) + .width(Length::Fill) + .height(Length::Fill), + tooltip(), + TooltipPosition::FollowCursor, + ); + + let content = Column::with_children(vec![ + Container::new(fixed_tooltips) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into(), + cursor_tooltip_area.into(), + ]) + .width(Length::Fill) + .height(Length::Fill); + + Container::new(content) .width(Length::Fill) .height(Length::Fill) .center_x() @@ -37,3 +101,23 @@ impl Sandbox for Example { .into() } } + +fn tooltip_builder<'a>( + label: &str, + button_state: &'a mut button::State, + position: TooltipPosition, +) -> Container<'a, Message> { + Container::new(Tooltip::new( + Button::new(button_state, Text::new(label).size(40)).on_press(Message), + tooltip(), + position, + )) + .center_x() + .center_y() + .width(Length::Fill) + .height(Length::Fill) +} + +fn tooltip() -> Text { + Text::new("Tooltip").size(20) +} diff --git a/native/src/element.rs b/native/src/element.rs index 9703a7dbaf..6616632d99 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -281,8 +281,11 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.widget.overlay(layout) + self.widget + .overlay(layout, overlay_content_bounds, cursor_position) } } @@ -374,11 +377,13 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(layout) + .overlay(layout, overlay_content_bounds, cursor_position) .map(move |overlay| overlay.map(mapper)) } } @@ -462,7 +467,10 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.element.overlay(layout) + self.element + .overlay(layout, overlay_content_bounds, cursor_position) } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index dc994e91b6..f717df1f38 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -208,8 +208,11 @@ where messages: &mut Vec, ) -> Vec { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) - { + self.root.overlay( + Layout::new(&self.base.layout), + self.overlay.as_ref().map(|l| l.layout.bounds()), + cursor_position, + ) { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, @@ -345,9 +348,11 @@ where ) -> Renderer::Output { let viewport = Rectangle::with_size(self.bounds); - let overlay = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) - { + let overlay = if let Some(mut overlay) = self.root.overlay( + Layout::new(&self.base.layout), + self.overlay.as_ref().map(|l| l.layout.bounds()), + cursor_position, + ) { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, diff --git a/native/src/widget.rs b/native/src/widget.rs index 4ff19dbda0..6add39060f 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -196,6 +196,8 @@ where fn overlay( &mut self, _layout: Layout<'_>, + _overlay_content_bounds: Option, + _cursor_position: Point, ) -> Option> { None } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 42a9e734b7..fe92f29458 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -216,11 +216,19 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay( + layout, + overlay_content_bounds, + cursor_position, + ) + }) .next() } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 419060dbaf..9b5ebdb395 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -222,8 +222,14 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - self.content.overlay(layout.children().next().unwrap()) + self.content.overlay( + layout.children().next().unwrap(), + overlay_content_bounds, + cursor_position, + ) } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index acb43276af..871ddfce0e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -571,11 +571,15 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.elements .iter_mut() .zip(layout.children()) - .filter_map(|((_, pane), layout)| pane.overlay(layout)) + .filter_map(|((_, pane), layout)| { + pane.overlay(layout, overlay_content_bounds, cursor_position) + }) .next() } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 2dac706080..b2789a809b 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,3 +1,5 @@ +use iced_core::Rectangle; + use crate::container; use crate::event::{self, Event}; use crate::layout; @@ -198,6 +200,8 @@ where pub(crate) fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let body_layout = if self.title_bar.is_some() { let mut children = layout.children(); @@ -210,7 +214,8 @@ where layout }; - self.body.overlay(body_layout) + self.body + .overlay(body_layout, overlay_content_bounds, cursor_position) } } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 113197f71d..d565343d1a 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -287,6 +287,8 @@ where fn overlay( &mut self, layout: Layout<'_>, + _overlay_content_bounds: Option, + _cursor_position: Point, ) -> Option> { if *self.is_open { let bounds = layout.bounds(); diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 6b09d0c851..422521d53a 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -216,11 +216,19 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay( + layout, + overlay_content_bounds, + cursor_position, + ) + }) .next() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 92671dddf6..08bf6438ab 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -390,11 +390,17 @@ where fn overlay( &mut self, layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { let Self { content, state, .. } = self; content - .overlay(layout.children().next().unwrap()) + .overlay( + layout.children().next().unwrap(), + overlay_content_bounds, + cursor_position, + ) .map(|overlay| { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 533a64f66f..cae38d469d 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -4,16 +4,16 @@ use std::hash::Hash; use iced_core::Rectangle; use crate::{ - layout, overlay, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Size, Vector, Widget, + event, layout, overlay, Clipboard, Element, Event, Hasher, Layout, Length, + Point, Size, Vector, Widget, }; /// An element to display a widget over another. #[allow(missing_debug_implementations)] pub struct Tooltip<'a, Message, Renderer: self::Renderer> { - state: State, content: Element<'a, Message, Renderer>, - hover_content: Element<'a, Message, Renderer>, + tooltip: Element<'a, Message, Renderer>, + tooltip_position: TooltipPosition, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> @@ -23,22 +23,42 @@ where /// Creates an empty [`Tooltip`]. /// /// [`Tooltip`]: struct.Tooltip.html - pub fn new(content: T, hover_content: T) -> Self + pub fn new( + content: T, + tooltip: H, + tooltip_position: TooltipPosition, + ) -> Self where T: Into>, + H: Into>, { Tooltip { - state: Default::default(), content: content.into(), - hover_content: hover_content.into(), + tooltip: tooltip.into(), + tooltip_position, } } } -#[derive(Debug, Default)] -struct State { - cursor_position: Point, - is_hovered: bool, +/// The position of the tooltip. Defaults to following the cursor. +#[derive(Debug, PartialEq)] +pub enum TooltipPosition { + /// The tooltip will follow the cursor. + FollowCursor, + /// The tooltip will appear on the top of the widget. + Top, + /// The tooltip will appear on the bottom of the widget. + Bottom, + /// The tooltip will appear on the left of the widget. + Left, + /// The tooltip will appear on the right of the widget. + Right, +} + +impl Default for TooltipPosition { + fn default() -> Self { + TooltipPosition::FollowCursor + } } impl<'a, Message, Renderer> Widget @@ -70,14 +90,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { - if layout.bounds().contains(cursor_position) { - self.state.cursor_position = cursor_position; - self.state.is_hovered = true; - } else { - self.state.is_hovered = false; - } - + ) -> event::Status { self.content.widget.on_event( event, layout, @@ -114,12 +127,59 @@ where fn overlay( &mut self, - _layout: Layout<'_>, + layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, ) -> Option> { - if self.state.is_hovered { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let mut position = cursor_position; + + if let Some(content_bounds) = overlay_content_bounds { + if TooltipPosition::FollowCursor != self.tooltip_position { + match self.tooltip_position { + TooltipPosition::Top | TooltipPosition::Bottom => { + let x = bounds.x + bounds.width * 0.5 + - content_bounds.width * 0.5; + + position = match self.tooltip_position { + TooltipPosition::Top => Point::new( + x, + bounds.y - content_bounds.height, + ), + TooltipPosition::Bottom => Point::new( + x, + bounds.y + + bounds.height + + content_bounds.height, + ), + _ => unreachable!(), + }; + } + TooltipPosition::Left | TooltipPosition::Right => { + let y = + bounds.center_y() + content_bounds.height * 0.5; + + position = match self.tooltip_position { + TooltipPosition::Left => Point::new( + bounds.x - content_bounds.width, + y, + ), + TooltipPosition::Right => { + Point::new(bounds.x + bounds.width, y) + } + _ => unreachable!(), + }; + } + _ => {} + } + } + } + Some(overlay::Element::new( - self.state.cursor_position, - Box::new(Overlay::new(&self.hover_content)), + position, + Box::new(Overlay::new(&self.tooltip)), )) } else { None diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs index 66f32f64a8..b7d4f11e8d 100644 --- a/wgpu/src/widget/tooltip.rs +++ b/wgpu/src/widget/tooltip.rs @@ -2,3 +2,5 @@ /// A widget allowing the selection of a single value from a list of options. pub type Tooltip<'a, Message> = iced_native::Tooltip<'a, Message, crate::Renderer>; + +pub use iced_native::tooltip::TooltipPosition;