Add delay suppport for tooltip widget
This commit is contained in:
parent
ac511f5f68
commit
5ec23c1243
2 changed files with 102 additions and 30 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
use iced::Element;
|
use iced::Element;
|
||||||
use iced::widget::tooltip::Position;
|
use iced::widget::tooltip::Position;
|
||||||
use iced::widget::{button, center, container, tooltip};
|
use iced::widget::{button, center, checkbox, column, container, tooltip};
|
||||||
|
use iced::{alignment, time::Duration};
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
iced::run(Tooltip::update, Tooltip::view)
|
iced::run(Tooltip::update, Tooltip::view)
|
||||||
|
|
@ -9,11 +10,13 @@ pub fn main() -> iced::Result {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Tooltip {
|
struct Tooltip {
|
||||||
position: Position,
|
position: Position,
|
||||||
|
is_immediate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ChangePosition,
|
ChangePosition,
|
||||||
|
SetImmediate(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tooltip {
|
impl Tooltip {
|
||||||
|
|
@ -30,10 +33,17 @@ impl Tooltip {
|
||||||
|
|
||||||
self.position = position;
|
self.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::SetImmediate(is_immediate) => {
|
||||||
|
self.is_immediate = is_immediate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<'_, Message> {
|
fn view(&self) -> Element<'_, Message> {
|
||||||
|
let delay =
|
||||||
|
Duration::from_millis(if self.is_immediate { 0 } else { 2000 });
|
||||||
|
|
||||||
let tooltip = tooltip(
|
let tooltip = tooltip(
|
||||||
button("Press to change position")
|
button("Press to change position")
|
||||||
.on_press(Message::ChangePosition),
|
.on_press(Message::ChangePosition),
|
||||||
|
|
@ -41,9 +51,19 @@ impl Tooltip {
|
||||||
self.position,
|
self.position,
|
||||||
)
|
)
|
||||||
.gap(10)
|
.gap(10)
|
||||||
|
.delay(delay)
|
||||||
.style(container::rounded_box);
|
.style(container::rounded_box);
|
||||||
|
|
||||||
center(tooltip).into()
|
let checkbox = checkbox(self.is_immediate)
|
||||||
|
.label("Show immediately")
|
||||||
|
.on_toggle(Message::SetImmediate);
|
||||||
|
|
||||||
|
center(
|
||||||
|
column![tooltip, checkbox]
|
||||||
|
.align_x(alignment::Horizontal::Center)
|
||||||
|
.spacing(7),
|
||||||
|
)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
//! Tooltips display a hint of information over some element when hovered.
|
//! Tooltips display a hint of information over some element when hovered.
|
||||||
//!
|
//!
|
||||||
|
//! By default, the tooltip is only displayed after a short duration. (This
|
||||||
|
//! delay can be customized.)
|
||||||
|
//!
|
||||||
//! # Example
|
//! # Example
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # mod iced { pub mod widget { pub use iced_widget::*; } }
|
//! # mod iced { pub mod widget { pub use iced_widget::*; } }
|
||||||
|
|
@ -27,6 +30,7 @@ use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
|
use crate::core::time::{Duration, Instant};
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
|
|
@ -34,6 +38,9 @@ use crate::core::{
|
||||||
Shell, Size, Vector,
|
Shell, Size, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Default tooltip delay, 2 seconds. Chosen because it's the default on macOS.
|
||||||
|
const DEFAULT_TOOLTIP_DELAY: Duration = Duration::from_millis(2000);
|
||||||
|
|
||||||
/// An element to display a widget over another.
|
/// An element to display a widget over another.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|
@ -72,6 +79,7 @@ pub struct Tooltip<
|
||||||
gap: f32,
|
gap: f32,
|
||||||
padding: f32,
|
padding: f32,
|
||||||
snap_within_viewport: bool,
|
snap_within_viewport: bool,
|
||||||
|
delay: Duration,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +106,7 @@ where
|
||||||
gap: 0.0,
|
gap: 0.0,
|
||||||
padding: Self::DEFAULT_PADDING,
|
padding: Self::DEFAULT_PADDING,
|
||||||
snap_within_viewport: true,
|
snap_within_viewport: true,
|
||||||
|
delay: DEFAULT_TOOLTIP_DELAY,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +123,13 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the delay before the [`Tooltip`] is shown. Set to 0 milliseconds
|
||||||
|
/// 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.
|
/// Sets whether the [`Tooltip`] is snapped within the viewport.
|
||||||
pub fn snap_within_viewport(mut self, snap: bool) -> Self {
|
pub fn snap_within_viewport(mut self, snap: bool) -> Self {
|
||||||
self.snap_within_viewport = snap;
|
self.snap_within_viewport = snap;
|
||||||
|
|
@ -206,23 +222,54 @@ where
|
||||||
| Event::Window(window::Event::RedrawRequested(_)) = event
|
| Event::Window(window::Event::RedrawRequested(_)) = event
|
||||||
{
|
{
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let previous_state = *state;
|
let now = Instant::now();
|
||||||
let was_idle = *state == State::Idle;
|
let cursor_position = cursor.position_over(layout.bounds());
|
||||||
|
|
||||||
*state = cursor
|
match (&state, cursor_position) {
|
||||||
.position_over(layout.bounds())
|
// Tooltip was idle, but is now hovered.
|
||||||
.map(|cursor_position| State::Hovered { cursor_position })
|
(State::Idle, Some(cursor_position)) => {
|
||||||
.unwrap_or_default();
|
shell.invalidate_layout();
|
||||||
|
shell.request_redraw_at(now + self.delay);
|
||||||
|
*state = State::Hovered {
|
||||||
|
cursor_position,
|
||||||
|
at: now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let is_idle = *state == State::Idle;
|
// Tooltip was active and isn't hovered anymore.
|
||||||
|
(State::Hovered { .. }, None) => {
|
||||||
|
shell.invalidate_layout();
|
||||||
|
shell.request_redraw();
|
||||||
|
*state = State::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
if was_idle != is_idle {
|
// Tooltip is active, but not for long enough.
|
||||||
shell.invalidate_layout();
|
(State::Hovered { at, .. }, Some(cursor_position))
|
||||||
shell.request_redraw();
|
if at.elapsed() < self.delay =>
|
||||||
} else if self.position == Position::FollowCursor
|
{
|
||||||
&& *state != previous_state
|
let when = now + self.delay - at.elapsed();
|
||||||
{
|
shell.request_redraw_at(when);
|
||||||
shell.request_redraw();
|
*state = State::Hovered {
|
||||||
|
at: *at,
|
||||||
|
cursor_position,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltip has been active long enough, and is following the cursor
|
||||||
|
// (thus requiring a redraw)
|
||||||
|
(State::Hovered { at, .. }, Some(cursor_position))
|
||||||
|
if self.position == Position::FollowCursor =>
|
||||||
|
{
|
||||||
|
shell.request_redraw();
|
||||||
|
*state = State::Hovered {
|
||||||
|
at: *at,
|
||||||
|
cursor_position,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// No change in state.
|
||||||
|
(State::Hovered { .. }, Some(_)) => (),
|
||||||
|
(State::Idle, None) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,21 +343,25 @@ where
|
||||||
translation,
|
translation,
|
||||||
);
|
);
|
||||||
|
|
||||||
let tooltip = if let State::Hovered { cursor_position } = *state {
|
let tooltip = match *state {
|
||||||
Some(overlay::Element::new(Box::new(Overlay {
|
State::Hovered {
|
||||||
position: layout.position() + translation,
|
|
||||||
tooltip: &mut self.tooltip,
|
|
||||||
tree: children.next().unwrap(),
|
|
||||||
cursor_position,
|
cursor_position,
|
||||||
content_bounds: layout.bounds(),
|
at,
|
||||||
snap_within_viewport: self.snap_within_viewport,
|
} if at.elapsed() > self.delay => {
|
||||||
positioning: self.position,
|
Some(overlay::Element::new(Box::new(Overlay {
|
||||||
gap: self.gap,
|
position: layout.position() + translation,
|
||||||
padding: self.padding,
|
tooltip: &mut self.tooltip,
|
||||||
class: &self.class,
|
tree: children.next().unwrap(),
|
||||||
})))
|
cursor_position,
|
||||||
} else {
|
content_bounds: layout.bounds(),
|
||||||
None
|
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() {
|
if content.is_some() || tooltip.is_some() {
|
||||||
|
|
@ -362,6 +413,7 @@ enum State {
|
||||||
Idle,
|
Idle,
|
||||||
Hovered {
|
Hovered {
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
|
at: Instant,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue