diff --git a/Cargo.toml b/Cargo.toml index 75499df9eb..f3a9676f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ members = [ "examples/svg", "examples/todos", "examples/tour", + "examples/tooltip", ] [dependencies] diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml new file mode 100644 index 0000000000..1171de00ba --- /dev/null +++ b/examples/tooltip/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tooltip" +version = "0.1.0" +authors = ["Yusuf Bera Ertan "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/tooltip/README.md b/examples/tooltip/README.md new file mode 100644 index 0000000000..4ccf657877 --- /dev/null +++ b/examples/tooltip/README.md @@ -0,0 +1,14 @@ +## Tooltip + +A tooltip. + +It displays and positions a widget on another based on cursor position. + +The __[`main`]__ file contains all the code of the example. + +You can run it with `cargo run`: +``` +cargo run --package tooltip +``` + +[`main`]: src/main.rs diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs new file mode 100644 index 0000000000..6e2c4dd418 --- /dev/null +++ b/examples/tooltip/src/main.rs @@ -0,0 +1,123 @@ +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 { + 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)] +struct Message; + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Tooltip - Iced") + } + + fn update(&mut self, _message: Message) {} + + fn view(&mut self) -> Element { + 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, + ); + + 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() + .center_y() + .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/graphics/src/widget.rs b/graphics/src/widget.rs index 159ca91bdc..190ea9c05d 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -20,6 +20,7 @@ pub mod scrollable; pub mod slider; pub mod svg; pub mod text_input; +pub mod tooltip; mod column; mod row; @@ -48,6 +49,8 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; pub use column::Column; pub use image::Image; diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs new file mode 100644 index 0000000000..b5b0c55894 --- /dev/null +++ b/graphics/src/widget/tooltip.rs @@ -0,0 +1,37 @@ +//! Decorate content and apply alignment. +use crate::defaults::Defaults; +use crate::{Backend, Renderer}; +use iced_native::{Element, Layout, Point, Rectangle}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` tooltip with a default +/// `Renderer`. +pub type Tooltip<'a, Message, Backend> = + iced_native::Tooltip<'a, Message, Renderer>; + +impl iced_native::tooltip::Renderer for Renderer +where + B: Backend, +{ + type Style = (); + + fn draw( + &mut self, + defaults: &Defaults, + cursor_position: Point, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + viewport: &Rectangle, + ) -> Self::Output { + let (content, mouse_interaction) = content.draw( + self, + &defaults, + content_layout, + cursor_position, + viewport, + ); + + (content, mouse_interaction) + } +} diff --git a/native/src/element.rs b/native/src/element.rs index d6e9639a7e..5e90652412 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -259,8 +259,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) } } @@ -352,11 +355,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)) } } @@ -440,7 +445,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/overlay.rs b/native/src/overlay.rs index ea8bb3845c..20e3ee924f 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -4,6 +4,7 @@ mod element; pub mod menu; pub use element::Element; +use iced_core::Rectangle; pub use menu::Menu; use crate::event::{self, Event}; @@ -35,6 +36,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output; /// Computes the _layout_ hash of the [`Overlay`]. diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 0f44a7810c..fbe05d31c1 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,3 +1,5 @@ +use iced_core::Rectangle; + pub use crate::Overlay; use crate::event::{self, Event}; @@ -74,9 +76,10 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output { self.overlay - .draw(renderer, defaults, layout, cursor_position) + .draw(renderer, defaults, layout, cursor_position, viewport) } /// Computes the _layout_ hash of the [`Element`]. @@ -145,9 +148,10 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Renderer::Output { self.content - .draw(renderer, defaults, layout, cursor_position) + .draw(renderer, defaults, layout, cursor_position, viewport) } fn hash_layout(&self, state: &mut Hasher, position: Point) { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index abac849f3e..aa04f24ff3 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -238,6 +238,7 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, + _viewport: &Rectangle, ) -> Renderer::Output { let primitives = self.container.draw( renderer, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 7a64ac5925..996bdd30fc 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -198,8 +198,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, @@ -334,9 +337,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, @@ -351,6 +356,7 @@ where &Renderer::Defaults::default(), Layout::new(&layer.layout), cursor_position, + &viewport, ); self.overlay = Some(layer); diff --git a/native/src/widget.rs b/native/src/widget.rs index 3677713a1e..1309d6afba 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -36,6 +36,7 @@ pub mod space; pub mod svg; pub mod text; pub mod text_input; +pub mod tooltip; #[doc(no_inline)] pub use button::Button; @@ -71,6 +72,8 @@ pub use svg::Svg; pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; use crate::event::{self, Event}; use crate::layout; @@ -172,6 +175,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 e0e88d31f3..9ee60627e4 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -198,11 +198,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 6576414883..2fc6707eee 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -200,8 +200,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 da3e25fdd2..1619fce65a 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -561,11 +561,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 913cfe96e0..2851562435 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; @@ -189,6 +191,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(); @@ -201,7 +205,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 4110b9bbb7..7536fcddd0 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -272,6 +272,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 b71663bd05..c542aedc0c 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -197,11 +197,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 18cdf16974..86a68f2291 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -401,11 +401,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 new file mode 100644 index 0000000000..cae38d469d --- /dev/null +++ b/native/src/widget/tooltip.rs @@ -0,0 +1,300 @@ +//! Display a widget over another. +use std::hash::Hash; + +use iced_core::Rectangle; + +use crate::{ + 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> { + content: Element<'a, Message, Renderer>, + tooltip: Element<'a, Message, Renderer>, + tooltip_position: TooltipPosition, +} + +impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + /// Creates an empty [`Tooltip`]. + /// + /// [`Tooltip`]: struct.Tooltip.html + pub fn new( + content: T, + tooltip: H, + tooltip_position: TooltipPosition, + ) -> Self + where + T: Into>, + H: Into>, + { + Tooltip { + content: content.into(), + tooltip: tooltip.into(), + tooltip_position, + } + } +} + +/// 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 + for Tooltip<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + self.content.width() + } + + fn height(&self) -> Length { + self.content.height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.content.layout(renderer, limits) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) -> event::Status { + self.content.widget.on_event( + event, + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> Renderer::Output { + renderer.draw( + defaults, + cursor_position, + &self.content, + layout, + viewport, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.content.hash_layout(state); + } + + fn overlay( + &mut self, + layout: Layout<'_>, + overlay_content_bounds: Option, + cursor_position: Point, + ) -> Option> { + 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( + position, + Box::new(Overlay::new(&self.tooltip)), + )) + } else { + None + } + } +} + +struct Overlay<'a, Message, Renderer: self::Renderer> { + content: &'a Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'a, +{ + pub fn new(content: &'a Element<'a, Message, Renderer>) -> Self { + Self { content } + } +} + +impl<'a, Message, Renderer> crate::Overlay + for Overlay<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let space_below = bounds.height - position.y; + let space_above = position.y; + + let limits = layout::Limits::new( + Size::ZERO, + Size::new( + bounds.width - position.x, + if space_below > space_above { + space_below + } else { + space_above + }, + ), + ) + .width(self.content.width()); + + let mut node = self.content.layout(renderer, &limits); + + node.move_to(position - Vector::new(0.0, node.size().height)); + + node + } + + fn hash_layout(&self, state: &mut Hasher, position: Point) { + struct Marker; + std::any::TypeId::of::().hash(state); + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + self.content.hash_layout(state); + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> Renderer::Output { + renderer.draw( + defaults, + cursor_position, + &self.content, + layout, + viewport, + ) + } +} + +/// The renderer of a [`Tooltip`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Tooltip`] in your user interface. +/// +/// [`Tooltip`]: struct.Tooltip.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer { + /// The style supported by this renderer. + type Style: Default; + + /// Draws a [`Tooltip`]. + /// + /// [`Tooltip`]: struct.Tooltip.html + fn draw( + &mut self, + defaults: &Self::Defaults, + cursor_position: Point, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + viewport: &Rectangle, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + self::Renderer, + Message: 'a, +{ + fn from( + column: Tooltip<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(column) + } +} diff --git a/src/widget.rs b/src/widget.rs index edd35d2d84..eac50d576c 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,7 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, Column, Row, Space, Text, + rule, scrollable, slider, text_input, tooltip, Column, Row, Space, + Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -52,7 +53,7 @@ mod platform { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, + svg::Svg, text_input::TextInput, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 177ae1b697..304bb726c3 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -20,6 +20,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod text_input; +pub mod tooltip; #[doc(no_inline)] pub use button::Button; @@ -43,6 +44,8 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use tooltip::Tooltip; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs new file mode 100644 index 0000000000..b7d4f11e8d --- /dev/null +++ b/wgpu/src/widget/tooltip.rs @@ -0,0 +1,6 @@ +//! Display a widget over another. +/// 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;