Skip to content

Commit

Permalink
add Tooltip widget with simple example
Browse files Browse the repository at this point in the history
  • Loading branch information
yusdacra committed Jul 28, 2020
1 parent 51c22f2 commit c138eb8
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ members = [
"examples/svg",
"examples/todos",
"examples/tour",
"examples/tooltip",
]

[dependencies]
Expand Down
9 changes: 9 additions & 0 deletions examples/tooltip/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "tooltip"
version = "0.1.0"
authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"]
edition = "2018"
publish = false

[dependencies]
iced = { path = "../..", features = ["debug"] }
14 changes: 14 additions & 0 deletions examples/tooltip/README.md
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions examples/tooltip/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use iced::{Container, Element, Length, Sandbox, Settings, Text, Tooltip};

pub fn main() {
Example::run(Settings::default())
}

#[derive(Default)]
struct Example {}

#[derive(Debug, Clone, Copy)]
enum Message {}

impl Sandbox for Example {
type Message = Message;

fn new() -> Self {
Self::default()
}

fn title(&self) -> String {
String::from("Tooltip - Iced")
}

fn update(&mut self, _message: Message) {}

fn view(&mut self) -> Element<Message> {
let tooltip = Tooltip::new(Text::new("hello").size(60))
.hover_content(Text::new("hello but smaller").size(30));

Container::new(tooltip)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y()
.into()
}
}
3 changes: 3 additions & 0 deletions graphics/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
pub mod tooltip;

mod column;
mod row;
Expand All @@ -45,6 +46,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;
Expand Down
31 changes: 31 additions & 0 deletions graphics/src/widget/tooltip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Decorate content and apply alignment.
use crate::defaults::Defaults;
use crate::{Backend, Renderer};
use iced_native::{Element, Layout, Point};

/// An element decorating some content.
///
/// This is an alias of an `iced_native` tooltip with a default
/// `Renderer`.
pub type Tooltip<'a, Message, Backend> =
iced_native::Tooltip<'a, Message, Renderer<Backend>>;

impl<B> iced_native::tooltip::Renderer for Renderer<B>
where
B: Backend,
{
type Style = ();

fn draw<Message>(
&mut self,
defaults: &Defaults,
cursor_position: Point,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let (content, mouse_interaction) =
content.draw(self, &defaults, content_layout, cursor_position);

(content, mouse_interaction)
}
}
3 changes: 3 additions & 0 deletions native/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,6 +70,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::{layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point};

Expand Down
239 changes: 239 additions & 0 deletions native/src/widget/tooltip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! Display a widget over another.
use std::hash::Hash;

use crate::{
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> {
state: State,
content: Element<'a, Message, Renderer>,
hover_content: Option<Element<'a, Message, Renderer>>,
}

impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
/// Creates an empty [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
Tooltip {
state: Default::default(),
content: content.into(),
hover_content: None,
}
}

/// Sets the hover_content of the [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
pub fn hover_content<T>(mut self, hover_content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
self.hover_content = Some(hover_content.into());
self
}
}

#[derive(Debug, Default)]
struct State {
cursor_position: Point,
is_hovered: bool,
}

impl<'a, Message, Renderer> Widget<Message, Renderer>
for Tooltip<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.content.width()
}

fn height(&self) -> Length {
self.content.height()
}

fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content.layout(renderer, limits)
}

fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
match event {
Event::Mouse(iced_core::mouse::Event::CursorMoved { x, y }) => {
self.state.cursor_position.x = x;
self.state.cursor_position.y = y;
if layout.bounds().contains(self.state.cursor_position) {
self.state.is_hovered = true;
} else {
self.state.is_hovered = false;
}
}
_ => (),
}
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,
) -> Renderer::Output {
renderer.draw(defaults, cursor_position, &self.content, layout)
}

fn hash_layout(&self, state: &mut Hasher) {
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);

self.content.hash_layout(state);
}

fn overlay(
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
if layout.bounds().contains(self.state.cursor_position) {
Some(overlay::Element::new(
self.state.cursor_position,
Box::new(Overlay::new(self.hover_content.as_ref()?)),
))
} else {
None
}
}
}

struct Overlay<'a, Message, Renderer: self::Renderer> {
content: &'a Element<'a, Message, Renderer>,
}

impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
where
Message: 'a,
Renderer: 'a,
{
pub fn new(content: &'a Element<'a, Message, Renderer>) -> Self {
Self { content }
}
}

impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
for Overlay<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn layout(
&self,
renderer: &Renderer,
bounds: Size,
position: Point,
) -> layout::Node {
let space_below = bounds.height - position.y;
let space_above = position.y;

let limits = layout::Limits::new(
Size::ZERO,
Size::new(
bounds.width - position.x,
if space_below > space_above {
space_below
} else {
space_above
},
),
)
.width(self.content.width());

let mut node = self.content.layout(renderer, &limits);

node.move_to(position - Vector::new(0.0, node.size().height));

node
}

fn hash_layout(&self, state: &mut Hasher, position: Point) {
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);

(position.x as u32).hash(state);
(position.y as u32).hash(state);
self.content.hash_layout(state);
}

fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(defaults, cursor_position, &self.content, layout)
}
}

/// The renderer of a [`Tooltip`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Tooltip`] in your user interface.
///
/// [`Tooltip`]: struct.Tooltip.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;

/// Draws a [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
cursor_position: Point,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
) -> Self::Output;
}

impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Message: 'a,
{
fn from(
column: Tooltip<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(column)
}
}
4 changes: 2 additions & 2 deletions src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
mod platform {
pub use crate::renderer::widget::{
button, checkbox, container, pane_grid, pick_list, progress_bar, radio,
scrollable, slider, text_input, Column, Row, Space, Text,
scrollable, slider, text_input, tooltip, Column, Row, Space, Text,
};

#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
Expand All @@ -47,7 +47,7 @@ mod platform {
button::Button, checkbox::Checkbox, container::Container, image::Image,
pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar,
radio::Radio, scrollable::Scrollable, slider::Slider, svg::Svg,
text_input::TextInput,
text_input::TextInput, tooltip::Tooltip,
};

#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
Expand Down
Loading

0 comments on commit c138eb8

Please sign in to comment.