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 --- native/src/widget/tooltip.rs | 300 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 native/src/widget/tooltip.rs (limited to 'native/src/widget/tooltip.rs') 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) + } +} -- 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 --- native/src/widget/tooltip.rs | 194 ++++++------------------------------------- 1 file changed, 27 insertions(+), 167 deletions(-) (limited to 'native/src/widget/tooltip.rs') 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( -- 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 --- native/src/widget/tooltip.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'native/src/widget/tooltip.rs') 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 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` --- native/src/widget/tooltip.rs | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) (limited to 'native/src/widget/tooltip.rs') 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 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` --- native/src/widget/tooltip.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'native/src/widget/tooltip.rs') 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 From 21971e0037c2ddcb96fd48ea96332445de4137bb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 10 Mar 2021 01:59:02 +0100 Subject: Make `Clipboard` argument in `Widget` trait mutable --- native/src/widget/tooltip.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'native/src/widget/tooltip.rs') diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index ab07868c..276afd41 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -120,17 +120,17 @@ where event: Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec, renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, ) -> event::Status { self.content.widget.on_event( event, layout, cursor_position, - messages, renderer, clipboard, + messages, ) } -- cgit