Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions examples/tooltip/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use iced::Element;
use iced::alignment;
use iced::time::seconds;
use iced::widget::tooltip::Position;
use iced::widget::{button, center, container, tooltip};
use iced::widget::{button, center, checkbox, column, container, tooltip};

pub fn main() -> iced::Result {
iced::run(Tooltip::update, Tooltip::view)
Expand All @@ -9,11 +11,13 @@ pub fn main() -> iced::Result {
#[derive(Default)]
struct Tooltip {
position: Position,
is_immediate: bool,
}

#[derive(Debug, Clone)]
enum Message {
ChangePosition,
ToggleImmediate(bool),
}

impl Tooltip {
Expand All @@ -30,6 +34,9 @@ impl Tooltip {

self.position = position;
}
Message::ToggleImmediate(is_immediate) => {
self.is_immediate = is_immediate;
}
}
}

Expand All @@ -41,9 +48,19 @@ impl Tooltip {
self.position,
)
.gap(10)
.delay(seconds(if self.is_immediate { 0 } else { 2 }))
.style(container::rounded_box);

center(tooltip).into()
let checkbox = checkbox(self.is_immediate)
.label("Show immediately")
.on_toggle(Message::ToggleImmediate);

center(
column![tooltip, checkbox]
.align_x(alignment::Horizontal::Center)
.spacing(10),
)
.into()
}
}

Expand Down
111 changes: 81 additions & 30 deletions widget/src/tooltip.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Tooltips display a hint of information over some element when hovered.
//!
//! By default, the tooltip is only displayed when hovered for 2 seconds.
//! This delay can be adjusted with [`Tooltip::delay`].
//!
//! # Example
//! ```no_run
//! # mod iced { pub mod widget { pub use iced_widget::*; } }
Expand Down Expand Up @@ -27,6 +30,7 @@ use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::text;
use crate::core::time::{Duration, Instant};
use crate::core::widget::{self, Widget};
use crate::core::window;
use crate::core::{
Expand Down Expand Up @@ -72,6 +76,7 @@ pub struct Tooltip<
gap: f32,
padding: f32,
snap_within_viewport: bool,
delay: Duration,
class: Theme::Class<'a>,
}

Expand All @@ -83,6 +88,9 @@ where
/// The default padding of a [`Tooltip`] drawn by this renderer.
const DEFAULT_PADDING: f32 = 5.0;

/// The default delay before a [`Tooltip`] is shown.
const DEFAULT_DELAY: Duration = Duration::from_secs(2);

/// Creates a new [`Tooltip`].
///
/// [`Tooltip`]: struct.Tooltip.html
Expand All @@ -98,6 +106,7 @@ where
gap: 0.0,
padding: Self::DEFAULT_PADDING,
snap_within_viewport: true,
delay: Self::DEFAULT_DELAY,
class: Theme::default(),
}
}
Expand All @@ -114,6 +123,14 @@ where
self
}

/// Sets the delay before the [`Tooltip`] is shown.
///
/// Set to [`Duration::ZERO`] to be shown immediately.
pub fn delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}

/// Sets whether the [`Tooltip`] is snapped within the viewport.
pub fn snap_within_viewport(mut self, snap: bool) -> Self {
self.snap_within_viewport = snap;
Expand Down Expand Up @@ -206,23 +223,52 @@ where
| Event::Window(window::Event::RedrawRequested(_)) = event
{
let state = tree.state.downcast_mut::<State>();
let previous_state = *state;
let was_idle = *state == State::Idle;

*state = cursor
.position_over(layout.bounds())
.map(|cursor_position| State::Hovered { cursor_position })
.unwrap_or_default();

let is_idle = *state == State::Idle;
let now = Instant::now();
let cursor_position = cursor.position_over(layout.bounds());

match (*state, cursor_position) {
(State::Idle, Some(cursor_position)) => {
*state = State::Hovered {
cursor_position,
at: now,
};

shell.invalidate_layout();
shell.request_redraw_at(now + self.delay);
}
(State::Hovered { .. }, None) => {
*state = State::Idle;

if was_idle != is_idle {
shell.invalidate_layout();
shell.request_redraw();
} else if self.position == Position::FollowCursor
&& *state != previous_state
{
shell.request_redraw();
shell.invalidate_layout();
}
(State::Hovered { at, .. }, Some(cursor_position))
if at.elapsed() < self.delay =>
{
*state = State::Hovered {
at,
cursor_position,
};

shell.request_redraw_at(now + self.delay - at.elapsed());
}
(
State::Hovered {
at,
cursor_position: last_position,
},
Some(cursor_position),
) if self.position == Position::FollowCursor
&& last_position != cursor_position =>
{
*state = State::Hovered {
at,
cursor_position,
};

shell.request_redraw();
}
(State::Hovered { .. }, Some(_)) => (),
(State::Idle, None) => (),
}
}

Expand Down Expand Up @@ -296,21 +342,25 @@ where
translation,
);

let tooltip = if let State::Hovered { cursor_position } = *state {
Some(overlay::Element::new(Box::new(Overlay {
position: layout.position() + translation,
tooltip: &mut self.tooltip,
tree: children.next().unwrap(),
let tooltip = match *state {
State::Hovered {
cursor_position,
content_bounds: layout.bounds(),
snap_within_viewport: self.snap_within_viewport,
positioning: self.position,
gap: self.gap,
padding: self.padding,
class: &self.class,
})))
} else {
None
at,
} if at.elapsed() > self.delay => {
Some(overlay::Element::new(Box::new(Overlay {
position: layout.position() + translation,
tooltip: &mut self.tooltip,
tree: children.next().unwrap(),
cursor_position,
content_bounds: layout.bounds(),
snap_within_viewport: self.snap_within_viewport,
positioning: self.position,
gap: self.gap,
padding: self.padding,
class: &self.class,
})))
}
_ => None,
};

if content.is_some() || tooltip.is_some() {
Expand Down Expand Up @@ -362,6 +412,7 @@ enum State {
Idle,
Hovered {
cursor_position: Point,
at: Instant,
},
}

Expand Down
Loading