From 5d67f9988c14adfca4a7e67dc7a9ddcd860b1c78 Mon Sep 17 00:00:00 2001 From: Casper Rogild Storm <2248455+casperstorm@users.noreply.github.com> Date: Fri, 22 Apr 2022 13:35:53 +0200 Subject: Implemented Tooltip as Pure --- pure/src/helpers.rs | 11 ++ pure/src/widget.rs | 2 + pure/src/widget/tooltip.rs | 310 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 pure/src/widget/tooltip.rs (limited to 'pure') diff --git a/pure/src/helpers.rs b/pure/src/helpers.rs index 24f6dbaa..2ddab3ae 100644 --- a/pure/src/helpers.rs +++ b/pure/src/helpers.rs @@ -38,6 +38,17 @@ pub fn button<'a, Message, Renderer>( widget::Button::new(content) } +pub fn tooltip<'a, Message, Renderer>( + content: impl Into>, + tooltip: impl ToString, + position: widget::tooltip::Position, +) -> widget::Tooltip<'a, Message, Renderer> +where + Renderer: iced_native::text::Renderer, +{ + widget::Tooltip::new(content, tooltip, position) +} + pub fn text(text: impl Into) -> widget::Text where Renderer: iced_native::text::Renderer, diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 8200f9a7..adce17ea 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -12,6 +12,7 @@ pub mod slider; pub mod svg; pub mod text_input; pub mod toggler; +pub mod tooltip; pub mod tree; mod column; @@ -37,6 +38,7 @@ pub use svg::Svg; pub use text::Text; pub use text_input::TextInput; pub use toggler::Toggler; +pub use tooltip::{Position, Tooltip}; pub use tree::Tree; use iced_native::event::{self, Event}; diff --git a/pure/src/widget/tooltip.rs b/pure/src/widget/tooltip.rs new file mode 100644 index 00000000..f0667e41 --- /dev/null +++ b/pure/src/widget/tooltip.rs @@ -0,0 +1,310 @@ +//! Display a widget over another. +use crate::widget::Tree; +use crate::{Element, Widget}; +use iced_native::event::{self, Event}; +use iced_native::widget::container; +pub use iced_native::widget::Text; +use iced_native::{layout, mouse, overlay}; +use iced_native::{renderer, Size}; +use iced_native::{text, Vector}; +use iced_native::{ + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, +}; + +pub use iced_style::container::{Style, StyleSheet}; + +/// An element to display a widget over another. +#[allow(missing_debug_implementations)] +pub struct Tooltip<'a, Message, Renderer: text::Renderer> { + content: Element<'a, Message, Renderer>, + tooltip: Text, + position: Position, + style_sheet: Box, + gap: u16, + padding: u16, +} + +impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> +where + Renderer: text::Renderer, +{ + /// The default padding of a [`Tooltip`] drawn by this renderer. + const DEFAULT_PADDING: u16 = 5; + + /// Creates an empty [`Tooltip`]. + /// + /// [`Tooltip`]: struct.Tooltip.html + pub fn new( + content: impl Into>, + tooltip: impl ToString, + position: Position, + ) -> Self { + Tooltip { + content: content.into(), + tooltip: Text::new(tooltip.to_string()), + position, + style_sheet: Default::default(), + gap: 0, + padding: Self::DEFAULT_PADDING, + } + } + + /// 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 + } + + /// 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 + } + + /// Sets the style of the [`Tooltip`]. + pub fn style( + mut self, + style_sheet: impl Into>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +/// The position of the tooltip. Defaults to following the cursor. +#[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. + 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<'a, Message, Renderer> Widget + for Tooltip<'a, Message, Renderer> +where + Renderer: text::Renderer, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(std::slice::from_ref(&self.content)) + } + + fn width(&self) -> Length { + self.content.as_widget().width() + } + + fn height(&self) -> Length { + self.content.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.content.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &tree.children[0], + renderer, + inherited_style, + layout, + cursor_position, + viewport, + ); + + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let gap = f32::from(self.gap); + let style = self.style_sheet.style(); + + let defaults = renderer::Style { + text_color: style + .text_color + .unwrap_or(inherited_style.text_color), + }; + + let text_layout = Widget::<(), Renderer>::layout( + &self.tooltip, + renderer, + &layout::Limits::new(Size::ZERO, viewport.size()) + .pad(Padding::new(self.padding)), + ); + + let padding = f32::from(self.padding); + let text_bounds = text_layout.bounds(); + let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; + let y_center = + bounds.y + (bounds.height - text_bounds.height) / 2.0; + + let mut tooltip_bounds = { + let offset = match self.position { + Position::Top => Vector::new( + x_center, + bounds.y - text_bounds.height - gap - padding, + ), + Position::Bottom => Vector::new( + x_center, + bounds.y + bounds.height + gap + padding, + ), + Position::Left => Vector::new( + bounds.x - text_bounds.width - gap - padding, + y_center, + ), + Position::Right => Vector::new( + bounds.x + bounds.width + gap + padding, + y_center, + ), + Position::FollowCursor => Vector::new( + cursor_position.x, + cursor_position.y - text_bounds.height, + ), + }; + + Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: text_bounds.width + padding * 2.0, + height: text_bounds.height + padding * 2.0, + } + }; + + if tooltip_bounds.x < viewport.x { + tooltip_bounds.x = viewport.x; + } else if viewport.x + viewport.width + < tooltip_bounds.x + tooltip_bounds.width + { + tooltip_bounds.x = + viewport.x + viewport.width - tooltip_bounds.width; + } + + if tooltip_bounds.y < viewport.y { + tooltip_bounds.y = viewport.y; + } else if viewport.y + viewport.height + < tooltip_bounds.y + tooltip_bounds.height + { + tooltip_bounds.y = + viewport.y + viewport.height - tooltip_bounds.height; + } + + renderer.with_layer(*viewport, |renderer| { + container::draw_background(renderer, &style, tooltip_bounds); + + Widget::<(), Renderer>::draw( + &self.tooltip, + &tree.children[0], + renderer, + &defaults, + Layout::with_offset( + Vector::new( + tooltip_bounds.x + padding, + tooltip_bounds.y + padding, + ), + &text_layout, + ), + cursor_position, + viewport, + ); + }); + } + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget().overlay( + &mut tree.children[0], + layout, + renderer, + ) + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + text::Renderer, + Message: 'a, +{ + fn from( + tooltip: Tooltip<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(tooltip) + } +} -- cgit From 011b7d11126404eaeff1562e49d81693d4d852b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Apr 2022 20:25:49 +0700 Subject: Reuse `tooltip::Position` from `iced_native` in `iced_pure` --- pure/src/widget/tooltip.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) (limited to 'pure') diff --git a/pure/src/widget/tooltip.rs b/pure/src/widget/tooltip.rs index f0667e41..0155034b 100644 --- a/pure/src/widget/tooltip.rs +++ b/pure/src/widget/tooltip.rs @@ -2,15 +2,18 @@ use crate::widget::Tree; use crate::{Element, Widget}; use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::text; use iced_native::widget::container; -pub use iced_native::widget::Text; -use iced_native::{layout, mouse, overlay}; -use iced_native::{renderer, Size}; -use iced_native::{text, Vector}; +use iced_native::widget::Text; use iced_native::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, + Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, }; +pub use iced_native::widget::tooltip::Position; pub use iced_style::container::{Style, StyleSheet}; /// An element to display a widget over another. @@ -85,21 +88,6 @@ where } } -/// The position of the tooltip. Defaults to following the cursor. -#[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. - 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<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where -- cgit From e33f43af31888b8b0795c2287aed9f26b4e1d699 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 24 Apr 2022 20:45:43 +0700 Subject: Reuse `Tooltip` draw logic in `iced_pure` --- pure/src/widget/tooltip.rs | 118 +++++++++------------------------------------ 1 file changed, 24 insertions(+), 94 deletions(-) (limited to 'pure') diff --git a/pure/src/widget/tooltip.rs b/pure/src/widget/tooltip.rs index 0155034b..75c4d856 100644 --- a/pure/src/widget/tooltip.rs +++ b/pure/src/widget/tooltip.rs @@ -7,14 +7,12 @@ use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::text; -use iced_native::widget::container; +use iced_native::widget::tooltip; use iced_native::widget::Text; -use iced_native::{ - Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, -}; +use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell}; -pub use iced_native::widget::tooltip::Position; pub use iced_style::container::{Style, StyleSheet}; +pub use tooltip::Position; /// An element to display a widget over another. #[allow(missing_debug_implementations)] @@ -173,101 +171,33 @@ where viewport, ); - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { - let gap = f32::from(self.gap); - let style = self.style_sheet.style(); - - let defaults = renderer::Style { - text_color: style - .text_color - .unwrap_or(inherited_style.text_color), - }; - - let text_layout = Widget::<(), Renderer>::layout( - &self.tooltip, - renderer, - &layout::Limits::new(Size::ZERO, viewport.size()) - .pad(Padding::new(self.padding)), - ); - - let padding = f32::from(self.padding); - let text_bounds = text_layout.bounds(); - let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; - let y_center = - bounds.y + (bounds.height - text_bounds.height) / 2.0; - - let mut tooltip_bounds = { - let offset = match self.position { - Position::Top => Vector::new( - x_center, - bounds.y - text_bounds.height - gap - padding, - ), - Position::Bottom => Vector::new( - x_center, - bounds.y + bounds.height + gap + padding, - ), - Position::Left => Vector::new( - bounds.x - text_bounds.width - gap - padding, - y_center, - ), - Position::Right => Vector::new( - bounds.x + bounds.width + gap + padding, - y_center, - ), - Position::FollowCursor => Vector::new( - cursor_position.x, - cursor_position.y - text_bounds.height, - ), - }; - - Rectangle { - x: offset.x - padding, - y: offset.y - padding, - width: text_bounds.width + padding * 2.0, - height: text_bounds.height + padding * 2.0, - } - }; - - if tooltip_bounds.x < viewport.x { - tooltip_bounds.x = viewport.x; - } else if viewport.x + viewport.width - < tooltip_bounds.x + tooltip_bounds.width - { - tooltip_bounds.x = - viewport.x + viewport.width - tooltip_bounds.width; - } - - if tooltip_bounds.y < viewport.y { - tooltip_bounds.y = viewport.y; - } else if viewport.y + viewport.height - < tooltip_bounds.y + tooltip_bounds.height - { - tooltip_bounds.y = - viewport.y + viewport.height - tooltip_bounds.height; - } - - renderer.with_layer(*viewport, |renderer| { - container::draw_background(renderer, &style, tooltip_bounds); + let tooltip = &self.tooltip; + tooltip::draw( + renderer, + inherited_style, + layout, + cursor_position, + viewport, + self.position, + self.gap, + self.padding, + self.style_sheet.as_ref(), + |renderer, limits| { + Widget::<(), Renderer>::layout(tooltip, renderer, limits) + }, + |renderer, defaults, layout, cursor_position, viewport| { Widget::<(), Renderer>::draw( - &self.tooltip, - &tree.children[0], + tooltip, + &Tree::empty(), renderer, - &defaults, - Layout::with_offset( - Vector::new( - tooltip_bounds.x + padding, - tooltip_bounds.y + padding, - ), - &text_layout, - ), + defaults, + layout, cursor_position, viewport, ); - }); - } + }, + ); } fn overlay<'b>( -- cgit