summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Yusuf Bera Ertan <y.bera003.06@protonmail.com>2020-07-28 18:07:46 +0300
committerLibravatar Yusuf Bera Ertan <y.bera003.06@protonmail.com>2021-02-15 19:37:46 +0300
commita19f89d3a6af2804f2ac4e30f6d639b56a9bebfd (patch)
tree313320abe39ade1214723521b6a5aff56ad2edef
parent4de164dcc7bc3524c8b20f9c734bc1a4ae4c83bc (diff)
downloadiced-a19f89d3a6af2804f2ac4e30f6d639b56a9bebfd.tar.gz
iced-a19f89d3a6af2804f2ac4e30f6d639b56a9bebfd.tar.bz2
iced-a19f89d3a6af2804f2ac4e30f6d639b56a9bebfd.zip
feat(native): add Tooltip widget
-rw-r--r--Cargo.toml1
-rw-r--r--examples/tooltip/Cargo.toml9
-rw-r--r--examples/tooltip/README.md14
-rw-r--r--examples/tooltip/src/main.rs123
-rw-r--r--graphics/src/widget.rs3
-rw-r--r--graphics/src/widget/tooltip.rs37
-rw-r--r--native/src/element.rs14
-rw-r--r--native/src/overlay.rs2
-rw-r--r--native/src/overlay/element.rs8
-rw-r--r--native/src/overlay/menu.rs1
-rw-r--r--native/src/user_interface.rs16
-rw-r--r--native/src/widget.rs5
-rw-r--r--native/src/widget/column.rs10
-rw-r--r--native/src/widget/container.rs8
-rw-r--r--native/src/widget/pane_grid.rs6
-rw-r--r--native/src/widget/pane_grid/content.rs7
-rw-r--r--native/src/widget/pick_list.rs2
-rw-r--r--native/src/widget/row.rs10
-rw-r--r--native/src/widget/scrollable.rs8
-rw-r--r--native/src/widget/tooltip.rs300
-rw-r--r--src/widget.rs5
-rw-r--r--wgpu/src/widget.rs3
-rw-r--r--wgpu/src/widget/tooltip.rs6
23 files changed, 580 insertions, 18 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 75499df9..f3a9676f 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 00000000..1171de00
--- /dev/null
+++ b/examples/tooltip/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "tooltip"
+version = "0.1.0"
+authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"]
+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 00000000..4ccf6578
--- /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 00000000..6e2c4dd4
--- /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<Message> {
+ 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 159ca91b..190ea9c0 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 00000000..b5b0c558
--- /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<Backend>>;
+
+impl<B> iced_native::tooltip::Renderer for Renderer<B>
+where
+ B: Backend,
+{
+ type Style = ();
+
+ fn draw<Message>(
+ &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 d6e9639a..5e906524 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, B, Renderer>> {
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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- 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 ea8bb384..20e3ee92 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 0f44a781..fbe05d31 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 5ad1391f..c920e86e 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -239,6 +239,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 7a64ac59..996bdd30 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -198,8 +198,11 @@ where
messages: &mut Vec<Message>,
) -> Vec<event::Status> {
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 3677713a..1309d6af 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<Rectangle>,
+ _cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
None
}
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index e0e88d31..9ee60627 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
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 65764148..2fc6707e 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- 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 c6fe4b60..0a7d818d 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -558,11 +558,15 @@ where
fn overlay(
&mut self,
layout: Layout<'_>,
+ overlay_content_bounds: Option<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
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 913cfe96..28515624 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
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 74f4508e..6c424d28 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -274,6 +274,8 @@ where
fn overlay(
&mut self,
layout: Layout<'_>,
+ _overlay_content_bounds: Option<Rectangle>,
+ _cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
if *self.is_open {
let bounds = layout.bounds();
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index b71663bd..c542aedc 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
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 18cdf169..86a68f22 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<Rectangle>,
+ cursor_position: Point,
) -> Option<overlay::Element<'_, Message, Renderer>> {
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 00000000..cae38d46
--- /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<T, H>(
+ content: T,
+ tooltip: H,
+ tooltip_position: TooltipPosition,
+ ) -> Self
+ where
+ T: Into<Element<'a, Message, Renderer>>,
+ H: Into<Element<'a, Message, Renderer>>,
+ {
+ 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<Message, Renderer>
+ 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<Message>,
+ 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::<Marker>().hash(state);
+
+ self.content.hash_layout(state);
+ }
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ overlay_content_bounds: Option<Rectangle>,
+ cursor_position: Point,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ 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<Message, Renderer>
+ 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::<Marker>().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<Message>(
+ &mut self,
+ defaults: &Self::Defaults,
+ cursor_position: Point,
+ content: &Element<'_, Message, Self>,
+ content_layout: Layout<'_>,
+ viewport: &Rectangle,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
+ 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 edd35d2d..eac50d57 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 177ae1b6..304bb726 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 00000000..b7d4f11e
--- /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;