From a19f89d3a6af2804f2ac4e30f6d639b56a9bebfd Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Tue, 28 Jul 2020 18:07:46 +0300 Subject: feat(native): add Tooltip widget --- Cargo.toml | 1 + examples/tooltip/Cargo.toml | 9 + examples/tooltip/README.md | 14 ++ examples/tooltip/src/main.rs | 123 ++++++++++++++ graphics/src/widget.rs | 3 + graphics/src/widget/tooltip.rs | 37 ++++ native/src/element.rs | 14 +- native/src/overlay.rs | 2 + native/src/overlay/element.rs | 8 +- native/src/overlay/menu.rs | 1 + native/src/user_interface.rs | 16 +- native/src/widget.rs | 5 + native/src/widget/column.rs | 10 +- native/src/widget/container.rs | 8 +- native/src/widget/pane_grid.rs | 6 +- native/src/widget/pane_grid/content.rs | 7 +- native/src/widget/pick_list.rs | 2 + native/src/widget/row.rs | 10 +- native/src/widget/scrollable.rs | 8 +- native/src/widget/tooltip.rs | 300 +++++++++++++++++++++++++++++++++ src/widget.rs | 5 +- wgpu/src/widget.rs | 3 + wgpu/src/widget/tooltip.rs | 6 + 23 files changed, 580 insertions(+), 18 deletions(-) create mode 100644 examples/tooltip/Cargo.toml create mode 100644 examples/tooltip/README.md create mode 100644 examples/tooltip/src/main.rs create mode 100644 graphics/src/widget/tooltip.rs create mode 100644 native/src/widget/tooltip.rs create mode 100644 wgpu/src/widget/tooltip.rs 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 "] +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 { + 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>; + +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 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, + 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 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, ) -> 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 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, + _cursor_position: Point, ) -> Option> { 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, + 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 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, + 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 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, + 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 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, + 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 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, + _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 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, + 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 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, + 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 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( + 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 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; -- cgit From 81c75c15249b608dd8a6d47e25f96feb10ca68da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:09:16 +0100 Subject: Change `Tooltip` to support `Text` only for now --- examples/tooltip/src/main.rs | 114 +++++++++---------- graphics/src/widget/tooltip.rs | 76 ++++++++++++- native/src/element.rs | 14 +-- native/src/layout.rs | 7 +- native/src/user_interface.rs | 15 +-- native/src/widget.rs | 2 - native/src/widget/column.rs | 10 +- native/src/widget/container.rs | 8 +- native/src/widget/pane_grid.rs | 6 +- native/src/widget/pane_grid/content.rs | 7 +- native/src/widget/pick_list.rs | 2 - native/src/widget/row.rs | 10 +- native/src/widget/scrollable.rs | 8 +- native/src/widget/tooltip.rs | 194 +++++---------------------------- wgpu/src/widget/tooltip.rs | 2 +- 15 files changed, 172 insertions(+), 303 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 6e2c4dd4..a49caa70 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -1,6 +1,7 @@ +use iced::tooltip::{self, Tooltip}; use iced::{ - button, tooltip::TooltipPosition, Button, Column, Container, Element, - Length, Row, Sandbox, Settings, Text, Tooltip, + button, Button, Column, Container, Element, HorizontalAlignment, Length, + Row, Sandbox, Settings, Text, VerticalAlignment, }; pub fn main() { @@ -9,11 +10,11 @@ pub fn main() { #[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, + top: button::State, + bottom: button::State, + right: button::State, + left: button::State, + follow_cursor: button::State, } #[derive(Debug, Clone, Copy)] @@ -33,52 +34,39 @@ impl Sandbox for Example { 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( + let top = + tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top); + + let bottom = tooltip( "Tooltip at bottom", - &mut self.tooltip_bottom_button_state, - TooltipPosition::Bottom, + &mut self.bottom, + tooltip::Position::Bottom, ); - let tooltip_right = tooltip_builder( + + let left = + tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left); + + let right = tooltip( "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, + &mut self.right, + tooltip::Position::Right, ); let fixed_tooltips = Row::with_children(vec![ - tooltip_top.into(), - tooltip_bottom.into(), - tooltip_left.into(), - tooltip_right.into(), + top.into(), + bottom.into(), + left.into(), + 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, + .spacing(50); + + let follow_cursor = tooltip( + "Tooltip follows cursor", + &mut self.follow_cursor, + tooltip::Position::FollowCursor, ); let content = Column::with_children(vec![ @@ -88,36 +76,42 @@ impl Sandbox for Example { .center_x() .center_y() .into(), - cursor_tooltip_area.into(), + follow_cursor.into(), ]) .width(Length::Fill) - .height(Length::Fill); + .height(Length::Fill) + .spacing(50); Container::new(content) .width(Length::Fill) .height(Length::Fill) .center_x() .center_y() + .padding(50) .into() } } -fn tooltip_builder<'a>( +fn tooltip<'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: tooltip::Position, +) -> Element<'a, Message> { + Tooltip::new( + Button::new( + button_state, + Text::new(label) + .size(40) + .width(Length::Fill) + .height(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + ) + .on_press(Message) + .width(Length::Fill) + .height(Length::Fill), + Text::new("Tooltip"), position, - )) - .center_x() - .center_y() - .width(Length::Fill) - .height(Length::Fill) -} - -fn tooltip() -> Text { - Text::new("Tooltip").size(20) + ) + .into() } diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index b5b0c558..0b4d1d2f 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -1,7 +1,10 @@ //! Decorate content and apply alignment. +use crate::backend::{self, Backend}; use crate::defaults::Defaults; -use crate::{Backend, Renderer}; -use iced_native::{Element, Layout, Point, Rectangle}; +use crate::{Primitive, Renderer, Vector}; + +use iced_native::layout::{self, Layout}; +use iced_native::{Element, Point, Rectangle, Size, Text}; /// An element decorating some content. /// @@ -10,9 +13,11 @@ use iced_native::{Element, Layout, Point, Rectangle}; pub type Tooltip<'a, Message, Backend> = iced_native::Tooltip<'a, Message, Renderer>; +pub use iced_native::tooltip::Position; + impl iced_native::tooltip::Renderer for Renderer where - B: Backend, + B: Backend + backend::Text, { type Style = (); @@ -20,10 +25,14 @@ where &mut self, defaults: &Defaults, cursor_position: Point, - content: &Element<'_, Message, Self>, content_layout: Layout<'_>, viewport: &Rectangle, + content: &Element<'_, Message, Self>, + tooltip: &Text, + position: Position, ) -> Self::Output { + let bounds = content_layout.bounds(); + let (content, mouse_interaction) = content.draw( self, &defaults, @@ -32,6 +41,63 @@ where viewport, ); - (content, mouse_interaction) + if bounds.contains(cursor_position) { + use iced_native::Widget; + + let tooltip_layout = Widget::<(), Self>::layout( + tooltip, + self, + &layout::Limits::new(Size::ZERO, viewport.size()), + ); + + let tooltip_bounds = tooltip_layout.bounds(); + + let x_center = + bounds.x + (bounds.width - tooltip_bounds.width) / 2.0; + + let y_center = + bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; + + let offset = match position { + Position::Top => { + Vector::new(x_center, bounds.y - tooltip_bounds.height) + } + Position::Bottom => { + Vector::new(x_center, bounds.y + bounds.height) + } + Position::Left => { + Vector::new(bounds.x - tooltip_bounds.width, y_center) + } + Position::Right => { + Vector::new(bounds.x + bounds.width, y_center) + } + Position::FollowCursor => Vector::new( + cursor_position.x, + cursor_position.y - tooltip_bounds.height, + ), + }; + + let (tooltip, _) = Widget::<(), Self>::draw( + tooltip, + self, + defaults, + Layout::with_offset(offset, &tooltip_layout), + cursor_position, + viewport, + ); + + ( + Primitive::Clip { + bounds: *viewport, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Group { + primitives: vec![content, tooltip], + }), + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } } } diff --git a/native/src/element.rs b/native/src/element.rs index 5e906524..d6e9639a 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -259,11 +259,8 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.widget - .overlay(layout, overlay_content_bounds, cursor_position) + self.widget.overlay(layout) } } @@ -355,13 +352,11 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(layout, overlay_content_bounds, cursor_position) + .overlay(layout) .map(move |overlay| overlay.map(mapper)) } } @@ -445,10 +440,7 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.element - .overlay(layout, overlay_content_bounds, cursor_position) + self.element.overlay(layout) } } diff --git a/native/src/layout.rs b/native/src/layout.rs index 6d144902..b4b4a021 100644 --- a/native/src/layout.rs +++ b/native/src/layout.rs @@ -19,11 +19,14 @@ pub struct Layout<'a> { } impl<'a> Layout<'a> { - pub(crate) fn new(node: &'a Node) -> Self { + /// Creates a new [`Layout`] for the given [`Node`] at the origin. + pub fn new(node: &'a Node) -> Self { Self::with_offset(Vector::new(0.0, 0.0), node) } - pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { + /// Creates a new [`Layout`] for the given [`Node`] with the provided offset + /// from the origin. + pub fn with_offset(offset: Vector, node: &'a Node) -> Self { let bounds = node.bounds(); Self { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 996bdd30..d1835b65 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -198,11 +198,8 @@ where messages: &mut Vec, ) -> Vec { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay( - Layout::new(&self.base.layout), - self.overlay.as_ref().map(|l| l.layout.bounds()), - cursor_position, - ) { + self.root.overlay(Layout::new(&self.base.layout)) + { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, @@ -337,11 +334,9 @@ 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), - self.overlay.as_ref().map(|l| l.layout.bounds()), - cursor_position, - ) { + let overlay = if let Some(mut overlay) = + self.root.overlay(Layout::new(&self.base.layout)) + { let layer = Self::overlay_layer( self.overlay.take(), self.bounds, diff --git a/native/src/widget.rs b/native/src/widget.rs index 1309d6af..d5c353df 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -175,8 +175,6 @@ 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 9ee60627..e0e88d31 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -198,19 +198,11 @@ 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, - overlay_content_bounds, - cursor_position, - ) - }) + .filter_map(|(child, layout)| child.widget.overlay(layout)) .next() } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 2fc6707e..65764148 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -200,14 +200,8 @@ where fn overlay( &mut self, layout: Layout<'_>, - overlay_content_bounds: Option, - cursor_position: Point, ) -> Option> { - self.content.overlay( - layout.children().next().unwrap(), - overlay_content_bounds, - cursor_position, - ) + self.content.overlay(layout.children().next().unwrap()) } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 0a7d818d..c6fe4b60 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -558,15 +558,11 @@ 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, overlay_content_bounds, cursor_position) - }) + .filter_map(|((_, pane), layout)| pane.overlay(layout)) .next() } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 28515624..913cfe96 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,5 +1,3 @@ -use iced_core::Rectangle; - use crate::container; use crate::event::{self, Event}; use crate::layout; @@ -191,8 +189,6 @@ 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(); @@ -205,8 +201,7 @@ where layout }; - self.body - .overlay(body_layout, overlay_content_bounds, cursor_position) + self.body.overlay(body_layout) } } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 6c424d28..74f4508e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -274,8 +274,6 @@ 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 c542aedc..b71663bd 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -197,19 +197,11 @@ 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, - overlay_content_bounds, - cursor_position, - ) - }) + .filter_map(|(child, layout)| child.widget.overlay(layout)) .next() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 86a68f22..18cdf169 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -401,17 +401,11 @@ 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_content_bounds, - cursor_position, - ) + .overlay(layout.children().next().unwrap()) .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 cae38d46..72d03c1a 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -3,46 +3,43 @@ use std::hash::Hash; use iced_core::Rectangle; +use crate::widget::text::{self, Text}; use crate::{ - event, layout, overlay, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Size, Vector, Widget, + event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Widget, }; /// An element to display a widget over another. #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: self::Renderer> { +pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { content: Element<'a, Message, Renderer>, - tooltip: Element<'a, Message, Renderer>, - tooltip_position: TooltipPosition, + tooltip: Text, + position: Position, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::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>, - { + pub fn new( + content: impl Into>, + tooltip: Text, + position: Position, + ) -> Self { Tooltip { content: content.into(), - tooltip: tooltip.into(), - tooltip_position, + tooltip, + position, } } } /// The position of the tooltip. Defaults to following the cursor. -#[derive(Debug, PartialEq)] -pub enum TooltipPosition { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Position { /// The tooltip will follow the cursor. FollowCursor, /// The tooltip will appear on the top of the widget. @@ -55,16 +52,10 @@ pub enum TooltipPosition { Right, } -impl Default for TooltipPosition { - fn default() -> Self { - TooltipPosition::FollowCursor - } -} - impl<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer, { fn width(&self) -> Length { self.content.width() @@ -109,12 +100,15 @@ where cursor_position: Point, viewport: &Rectangle, ) -> Renderer::Output { - renderer.draw( + self::Renderer::draw( + renderer, defaults, cursor_position, - &self.content, layout, viewport, + &self.content, + &self.tooltip, + self.position, ) } @@ -124,142 +118,6 @@ where 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`]. @@ -269,7 +127,7 @@ where /// /// [`Tooltip`]: struct.Tooltip.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer { +pub trait Renderer: crate::Renderer + text::Renderer { /// The style supported by this renderer. type Style: Default; @@ -280,16 +138,18 @@ pub trait Renderer: crate::Renderer { &mut self, defaults: &Self::Defaults, cursor_position: Point, - content: &Element<'_, Message, Self>, content_layout: Layout<'_>, viewport: &Rectangle, + content: &Element<'_, Message, Self>, + tooltip: &Text, + position: Position, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer, + Renderer: 'a + self::Renderer + text::Renderer, Message: 'a, { fn from( diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs index b7d4f11e..89ab3a15 100644 --- a/wgpu/src/widget/tooltip.rs +++ b/wgpu/src/widget/tooltip.rs @@ -3,4 +3,4 @@ pub type Tooltip<'a, Message> = iced_native::Tooltip<'a, Message, crate::Renderer>; -pub use iced_native::tooltip::TooltipPosition; +pub use iced_native::tooltip::Position; -- cgit From 9f60a256fc909dda0c30e301020d03d7ec28d722 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:12:00 +0100 Subject: Remove `viewport` from `Overlay::draw` for now --- native/src/overlay.rs | 2 -- native/src/overlay/element.rs | 8 ++------ native/src/overlay/menu.rs | 1 - native/src/user_interface.rs | 1 - 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 20e3ee92..ea8bb384 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -4,7 +4,6 @@ mod element; pub mod menu; pub use element::Element; -use iced_core::Rectangle; pub use menu::Menu; use crate::event::{self, Event}; @@ -36,7 +35,6 @@ 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 fbe05d31..0f44a781 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,5 +1,3 @@ -use iced_core::Rectangle; - pub use crate::Overlay; use crate::event::{self, Event}; @@ -76,10 +74,9 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - viewport: &Rectangle, ) -> Renderer::Output { self.overlay - .draw(renderer, defaults, layout, cursor_position, viewport) + .draw(renderer, defaults, layout, cursor_position) } /// Computes the _layout_ hash of the [`Element`]. @@ -148,10 +145,9 @@ where defaults: &Renderer::Defaults, layout: Layout<'_>, cursor_position: Point, - viewport: &Rectangle, ) -> Renderer::Output { self.content - .draw(renderer, defaults, layout, cursor_position, viewport) + .draw(renderer, defaults, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher, position: Point) { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index c920e86e..5ad1391f 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -239,7 +239,6 @@ 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 d1835b65..7a64ac59 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -351,7 +351,6 @@ where &Renderer::Defaults::default(), Layout::new(&layer.layout), cursor_position, - &viewport, ); self.overlay = Some(layer); -- cgit From 2f766b73413fe60cd881e139fa0e84a0f0134d91 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:16:37 +0100 Subject: Introduce `Tooltip::gap` to control spacing --- examples/tooltip/src/main.rs | 1 + graphics/src/widget/tooltip.rs | 16 ++++++++++------ native/src/widget/tooltip.rs | 10 ++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index a49caa70..3677dc75 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -113,5 +113,6 @@ fn tooltip<'a>( Text::new("Tooltip"), position, ) + .gap(10) .into() } diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 0b4d1d2f..26b18507 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -30,6 +30,7 @@ where content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + gap: u16, ) -> Self::Output { let bounds = content_layout.bounds(); @@ -58,18 +59,21 @@ where let y_center = bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; + let gap = f32::from(gap); + let offset = match position { - Position::Top => { - Vector::new(x_center, bounds.y - tooltip_bounds.height) - } + Position::Top => Vector::new( + x_center, + bounds.y - tooltip_bounds.height - gap, + ), Position::Bottom => { - Vector::new(x_center, bounds.y + bounds.height) + Vector::new(x_center, bounds.y + bounds.height + gap) } Position::Left => { - Vector::new(bounds.x - tooltip_bounds.width, y_center) + Vector::new(bounds.x - tooltip_bounds.width - gap, y_center) } Position::Right => { - Vector::new(bounds.x + bounds.width, y_center) + Vector::new(bounds.x + bounds.width + gap, y_center) } Position::FollowCursor => Vector::new( cursor_position.x, diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 72d03c1a..6da7b0ca 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -15,6 +15,7 @@ pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { content: Element<'a, Message, Renderer>, tooltip: Text, position: Position, + gap: u16, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> @@ -33,8 +34,15 @@ where content: content.into(), tooltip, position, + gap: 0, } } + + /// Sets the gap between the content and its [`Tooltip`]. + pub fn gap(mut self, gap: u16) -> Self { + self.gap = gap; + self + } } /// The position of the tooltip. Defaults to following the cursor. @@ -109,6 +117,7 @@ where &self.content, &self.tooltip, self.position, + self.gap, ) } @@ -143,6 +152,7 @@ pub trait Renderer: crate::Renderer + text::Renderer { content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + gap: u16, ) -> Self::Output; } -- cgit From 9d4996cbab012559c73852145b9968f6b101663a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 03:18:47 +0100 Subject: Export `Tooltip` in `iced_glow` --- glow/src/widget.rs | 3 +++ glow/src/widget/tooltip.rs | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 glow/src/widget/tooltip.rs diff --git a/glow/src/widget.rs b/glow/src/widget.rs index b5c84c56..5481216a 100644 --- a/glow/src/widget.rs +++ b/glow/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/glow/src/widget/tooltip.rs b/glow/src/widget/tooltip.rs new file mode 100644 index 00000000..89ab3a15 --- /dev/null +++ b/glow/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::Position; -- cgit From 4e923290ccb38dc9cee05592554f98f1f0f12966 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 04:00:35 +0100 Subject: Add `style` and `padding` to `Tooltip` --- examples/tooltip/src/main.rs | 20 ++++++++++ graphics/src/widget/tooltip.rs | 86 ++++++++++++++++++++++++++++++------------ native/src/widget/tooltip.rs | 40 ++++++++++++++++---- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index 3677dc75..b10b8b50 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -114,5 +114,25 @@ fn tooltip<'a>( position, ) .gap(10) + .padding(20) + .style(style::Tooltip) .into() } + +mod style { + use iced::container; + use iced::Color; + + pub struct Tooltip; + + impl container::StyleSheet for Tooltip { + fn style(&self) -> container::Style { + container::Style { + text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)), + background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()), + border_radius: 12.0, + ..container::Style::default() + } + } + } +} diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs index 26b18507..51b465a5 100644 --- a/graphics/src/widget/tooltip.rs +++ b/graphics/src/widget/tooltip.rs @@ -1,8 +1,9 @@ //! Decorate content and apply alignment. use crate::backend::{self, Backend}; -use crate::defaults::Defaults; +use crate::defaults::{self, Defaults}; use crate::{Primitive, Renderer, Vector}; +use iced_native::container; use iced_native::layout::{self, Layout}; use iced_native::{Element, Point, Rectangle, Size, Text}; @@ -19,7 +20,7 @@ impl iced_native::tooltip::Renderer for Renderer where B: Backend + backend::Text, { - type Style = (); + const DEFAULT_PADDING: u16 = 5; fn draw( &mut self, @@ -30,10 +31,10 @@ where content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + style_sheet: &::Style, gap: u16, + padding: u16, ) -> Self::Output { - let bounds = content_layout.bounds(); - let (content, mouse_interaction) = content.draw( self, &defaults, @@ -42,13 +43,26 @@ where viewport, ); + let bounds = content_layout.bounds(); + if bounds.contains(cursor_position) { use iced_native::Widget; + let gap = f32::from(gap); + let padding = f32::from(padding); + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + let tooltip_layout = Widget::<(), Self>::layout( tooltip, self, - &layout::Limits::new(Size::ZERO, viewport.size()), + &layout::Limits::new(Size::ZERO, viewport.size()) + .pad(f32::from(padding)), ); let tooltip_bounds = tooltip_layout.bounds(); @@ -59,22 +73,23 @@ where let y_center = bounds.y + (bounds.height - tooltip_bounds.height) / 2.0; - let gap = f32::from(gap); - let offset = match position { Position::Top => Vector::new( x_center, - bounds.y - tooltip_bounds.height - gap, + bounds.y - tooltip_bounds.height - gap - padding, + ), + Position::Bottom => Vector::new( + x_center, + bounds.y + bounds.height + gap + padding, + ), + Position::Left => Vector::new( + bounds.x - tooltip_bounds.width - gap - padding, + y_center, + ), + Position::Right => Vector::new( + bounds.x + bounds.width + gap + padding, + y_center, ), - Position::Bottom => { - Vector::new(x_center, bounds.y + bounds.height + gap) - } - Position::Left => { - Vector::new(bounds.x - tooltip_bounds.width - gap, y_center) - } - Position::Right => { - Vector::new(bounds.x + bounds.width + gap, y_center) - } Position::FollowCursor => Vector::new( cursor_position.x, cursor_position.y - tooltip_bounds.height, @@ -84,19 +99,42 @@ where let (tooltip, _) = Widget::<(), Self>::draw( tooltip, self, - defaults, + &defaults, Layout::with_offset(offset, &tooltip_layout), cursor_position, viewport, ); + let tooltip_bounds = Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: tooltip_bounds.width + padding * 2.0, + height: tooltip_bounds.height + padding * 2.0, + }; + ( - Primitive::Clip { - bounds: *viewport, - offset: Vector::new(0, 0), - content: Box::new(Primitive::Group { - primitives: vec![content, tooltip], - }), + Primitive::Group { + primitives: vec![ + content, + Primitive::Clip { + bounds: *viewport, + offset: Vector::new(0, 0), + content: Box::new( + if let Some(background) = + crate::container::background( + tooltip_bounds, + &style, + ) + { + Primitive::Group { + primitives: vec![background, tooltip], + } + } else { + tooltip + }, + ), + }, + ], }, mouse_interaction, ) diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 6da7b0ca..56559827 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -3,6 +3,7 @@ use std::hash::Hash; use iced_core::Rectangle; +use crate::widget::container; use crate::widget::text::{self, Text}; use crate::{ event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, @@ -11,16 +12,18 @@ use crate::{ /// An element to display a widget over another. #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: self::Renderer + text::Renderer> { +pub struct Tooltip<'a, Message, Renderer: self::Renderer> { content: Element<'a, Message, Renderer>, tooltip: Text, position: Position, + style: ::Style, gap: u16, + padding: u16, } impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer, { /// Creates an empty [`Tooltip`]. /// @@ -34,15 +37,32 @@ where content: content.into(), tooltip, position, + style: Default::default(), gap: 0, + padding: Renderer::DEFAULT_PADDING, } } + /// Sets the style of the [`Tooltip`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } + /// Sets the gap between the content and its [`Tooltip`]. pub fn gap(mut self, gap: u16) -> Self { self.gap = gap; self } + + /// Sets the padding of the [`Tooltip`]. + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } } /// The position of the tooltip. Defaults to following the cursor. @@ -63,7 +83,7 @@ pub enum Position { impl<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where - Renderer: self::Renderer + text::Renderer, + Renderer: self::Renderer, { fn width(&self) -> Length { self.content.width() @@ -117,7 +137,9 @@ where &self.content, &self.tooltip, self.position, + &self.style, self.gap, + self.padding, ) } @@ -136,9 +158,11 @@ where /// /// [`Tooltip`]: struct.Tooltip.html /// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + text::Renderer { - /// The style supported by this renderer. - type Style: Default; +pub trait Renderer: + crate::Renderer + text::Renderer + container::Renderer +{ + /// The default padding of a [`Tooltip`] drawn by this renderer. + const DEFAULT_PADDING: u16; /// Draws a [`Tooltip`]. /// @@ -152,14 +176,16 @@ pub trait Renderer: crate::Renderer + text::Renderer { content: &Element<'_, Message, Self>, tooltip: &Text, position: Position, + style: &::Style, gap: u16, + padding: u16, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + text::Renderer, + Renderer: 'a + self::Renderer, Message: 'a, { fn from( -- cgit From 5e2743361bf0a2e10c0e14638a9b2818c9e5e364 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 23 Feb 2021 04:02:55 +0100 Subject: Generate new layers only for clip primitives in `Layer::generate` --- graphics/src/layer.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index ab40b114..7dce1d4c 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -82,7 +82,12 @@ impl<'a> Layer<'a> { let mut layers = vec![first_layer]; - Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive); + Self::process_primitive( + &mut layers, + Vector::new(0.0, 0.0), + primitive, + 0, + ); layers } @@ -91,13 +96,19 @@ impl<'a> Layer<'a> { layers: &mut Vec, translation: Vector, primitive: &'a Primitive, + current_layer: usize, ) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - Self::process_primitive(layers, translation, primitive) + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) } } Primitive::Text { @@ -109,7 +120,7 @@ impl<'a> Layer<'a> { horizontal_alignment, vertical_alignment, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.text.push(Text { content, @@ -128,7 +139,7 @@ impl<'a> Layer<'a> { border_width, border_color, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; // TODO: Move some of these computations to the GPU (?) layer.quads.push(Quad { @@ -146,7 +157,7 @@ impl<'a> Layer<'a> { }); } Primitive::Mesh2D { buffers, size } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), @@ -167,7 +178,7 @@ impl<'a> Layer<'a> { offset, content, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let translated_bounds = *bounds + translation; // Only draw visible content @@ -175,16 +186,15 @@ impl<'a> Layer<'a> { layer.bounds.intersection(&translated_bounds) { let clip_layer = Layer::new(clip_bounds); - let new_layer = Layer::new(layer.bounds); - layers.push(clip_layer); + Self::process_primitive( layers, translation - Vector::new(offset.x as f32, offset.y as f32), content, + layers.len() - 1, ); - layers.push(new_layer); } } Primitive::Translate { @@ -195,13 +205,19 @@ impl<'a> Layer<'a> { layers, translation + *new_translation, &content, + current_layer, ); } Primitive::Cached { cache } => { - Self::process_primitive(layers, translation, &cache); + Self::process_primitive( + layers, + translation, + &cache, + current_layer, + ); } Primitive::Image { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Raster { handle: handle.clone(), @@ -209,7 +225,7 @@ impl<'a> Layer<'a> { }); } Primitive::Svg { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Vector { handle: handle.clone(), -- cgit From 2736e4ca35f17a92768f0be682acf6da3b574cb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 24 Feb 2021 00:59:29 +0100 Subject: Hide `Text` as an implementation detail of `Tooltip` --- examples/tooltip/src/main.rs | 6 +++--- native/src/widget/tooltip.rs | 30 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index b10b8b50..d6c8b8e1 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -110,11 +110,11 @@ fn tooltip<'a>( .on_press(Message) .width(Length::Fill) .height(Length::Fill), - Text::new("Tooltip"), + "Tooltip", position, ) - .gap(10) - .padding(20) + .gap(5) + .padding(10) .style(style::Tooltip) .into() } diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 56559827..ab07868c 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -30,12 +30,12 @@ where /// [`Tooltip`]: struct.Tooltip.html pub fn new( content: impl Into>, - tooltip: Text, + tooltip: impl ToString, position: Position, ) -> Self { Tooltip { content: content.into(), - tooltip, + tooltip: Text::new(tooltip.to_string()), position, style: Default::default(), gap: 0, @@ -43,12 +43,17 @@ where } } - /// Sets the style of the [`Tooltip`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { - self.style = style.into(); + /// Sets the size of the text of the [`Tooltip`]. + pub fn size(mut self, size: u16) -> Self { + self.tooltip = self.tooltip.size(size); + self + } + + /// Sets the font of the [`Tooltip`]. + /// + /// [`Font`]: Renderer::Font + pub fn font(mut self, font: impl Into) -> Self { + self.tooltip = self.tooltip.font(font); self } @@ -63,6 +68,15 @@ where self.padding = padding; self } + + /// Sets the style of the [`Tooltip`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } } /// The position of the tooltip. Defaults to following the cursor. -- cgit