diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs index bf927271..feef27f4 100644 --- a/examples/tooltip/src/main.rs +++ b/examples/tooltip/src/main.rs @@ -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) @@ -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 { @@ -30,6 +34,9 @@ impl Tooltip { self.position = position; } + Message::ToggleImmediate(is_immediate) => { + self.is_immediate = is_immediate; + } } } @@ -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() } } diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 8518b07c..a5207ed1 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -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::*; } } @@ -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::{ @@ -72,6 +76,7 @@ pub struct Tooltip< gap: f32, padding: f32, snap_within_viewport: bool, + delay: Duration, class: Theme::Class<'a>, } @@ -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 @@ -98,6 +106,7 @@ where gap: 0.0, padding: Self::DEFAULT_PADDING, snap_within_viewport: true, + delay: Self::DEFAULT_DELAY, class: Theme::default(), } } @@ -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; @@ -206,23 +223,46 @@ where | Event::Window(window::Event::RedrawRequested(_)) = event { let state = tree.state.downcast_mut::(); - let previous_state = *state; - let was_idle = *state == State::Idle; + let now = Instant::now(); + let cursor_position = cursor.position_over(layout.bounds()); - *state = cursor - .position_over(layout.bounds()) - .map(|cursor_position| State::Hovered { cursor_position }) - .unwrap_or_default(); + match (*state, cursor_position) { + (State::Idle, Some(cursor_position)) => { + if self.delay == Duration::ZERO { + *state = State::Open { cursor_position }; + shell.invalidate_layout(); + } else { + *state = State::Hovered { at: now }; + } - let is_idle = *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.request_redraw_at(now + self.delay); + } + (State::Hovered { .. }, None) => { + *state = State::Idle; + } + (State::Hovered { at, .. }, _) if at.elapsed() < self.delay => { + shell.request_redraw_at(now + self.delay - at.elapsed()); + } + (State::Hovered { .. }, Some(cursor_position)) => { + *state = State::Open { cursor_position }; + shell.invalidate_layout(); + } + ( + State::Open { + cursor_position: last_position, + }, + Some(cursor_position), + ) if self.position == Position::FollowCursor + && last_position != cursor_position => + { + *state = State::Open { cursor_position }; + shell.request_redraw(); + } + (State::Open { .. }, None) => { + *state = State::Idle; + shell.invalidate_layout(); + } + (State::Open { .. }, Some(_)) | (State::Idle, None) => (), } } @@ -296,7 +336,7 @@ where translation, ); - let tooltip = if let State::Hovered { cursor_position } = *state { + let tooltip = if let State::Open { cursor_position } = *state { Some(overlay::Element::new(Box::new(Overlay { position: layout.position() + translation, tooltip: &mut self.tooltip, @@ -361,6 +401,9 @@ enum State { #[default] Idle, Hovered { + at: Instant, + }, + Open { cursor_position: Point, }, }