Skip to content

Commit

Permalink
Add a tooltip view and widget (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc authored Nov 19, 2023
1 parent 98dd4e4 commit c8c6454
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 12 deletions.
8 changes: 6 additions & 2 deletions examples/widget-gallery/src/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use floem::{
cosmic_text::{Style as FontStyle, Weight},
peniko::Color,
view::View,
views::{label, Decorators},
views::{label, static_label, Decorators},
widgets::tooltip,
};

use crate::form::{form, form_item};
Expand All @@ -11,7 +12,10 @@ pub fn label_view() -> impl View {
form({
(
form_item("Simple Label:".to_string(), 120.0, || {
label(move || "This is a simple label".to_owned())
tooltip(
label(move || "This is a simple label".to_owned()),
static_label("This is a tooltip for the label."),
)
}),
form_item("Styled Label:".to_string(), 120.0, || {
label(move || "This is a styled label".to_owned()).style(|s| {
Expand Down
29 changes: 19 additions & 10 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,7 @@ pub trait View {
id_path: Option<&[Id]>,
event: Event,
) -> EventPropagation {
let mut handled = false;
self.for_each_child_rev_mut(&mut |child| {
handled |= cx.view_event(child, id_path, event.clone()).is_processed();
handled
});
if handled {
EventPropagation::Stop
} else {
EventPropagation::Continue
}
default_event(self, cx, id_path, event)
}

/// `View`-specific implementation. Will be called in the [`View::paint_main`] entry point method.
Expand Down Expand Up @@ -298,6 +289,24 @@ pub fn default_compute_layout<V: View + ?Sized>(
layout_rect
}

pub fn default_event<V: View + ?Sized>(
view: &mut V,
cx: &mut EventCx,
id_path: Option<&[Id]>,
event: Event,
) -> EventPropagation {
let mut handled = false;
view.for_each_child_rev_mut(&mut |child| {
handled |= cx.view_event(child, id_path, event.clone()).is_processed();
handled
});
if handled {
EventPropagation::Stop
} else {
EventPropagation::Continue
}
}

pub(crate) fn paint_bg(
cx: &mut PaintCx,
computed_style: &Style,
Expand Down
3 changes: 3 additions & 0 deletions src/views/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub use scroll::{scroll, Scroll};
mod tab;
pub use tab::*;

mod tooltip;
pub use tooltip::*;

mod stack;
pub use stack::*;

Expand Down
141 changes: 141 additions & 0 deletions src/views/tooltip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use kurbo::Point;
use std::time::Duration;
use taffy::style::Display;

use crate::{
action::{exec_after, TimerToken},
context::{EventCx, StyleCx},
event::Event,
id::Id,
prop, prop_extracter,
style::DisplayProp,
view::{default_event, View, ViewData},
EventPropagation,
};

prop!(pub Delay: f64 {} = 0.6);

prop_extracter! {
TooltipStyle {
delay: Delay,
}
}

/// A view that displays a tooltip for its child.
pub struct Tooltip {
data: ViewData,
hover: Option<(Point, TimerToken)>,
visible: bool,
child: Box<dyn View>,
tip: Box<dyn View>,
style: TooltipStyle,
}

/// A view that displays a tooltip for its child.
pub fn tooltip<V: View + 'static, T: View + 'static>(child: V, tip: T) -> Tooltip {
Tooltip {
data: ViewData::new(Id::next()),
child: Box::new(child),
tip: Box::new(tip),
hover: None,
visible: false,
style: Default::default(),
}
}

impl View for Tooltip {
fn view_data(&self) -> &ViewData {
&self.data
}

fn view_data_mut(&mut self) -> &mut ViewData {
&mut self.data
}

fn for_each_child<'a>(&'a self, for_each: &mut dyn FnMut(&'a dyn View) -> bool) {
for_each(&self.child);
for_each(&self.tip);
}

fn for_each_child_mut<'a>(&'a mut self, for_each: &mut dyn FnMut(&'a mut dyn View) -> bool) {
for_each(&mut self.child);
for_each(&mut self.tip);
}

fn for_each_child_rev_mut<'a>(
&'a mut self,
for_each: &mut dyn FnMut(&'a mut dyn View) -> bool,
) {
for_each(&mut self.tip);
for_each(&mut self.child);
}

fn debug_name(&self) -> std::borrow::Cow<'static, str> {
"Tooltip".into()
}

fn update(&mut self, cx: &mut crate::context::UpdateCx, state: Box<dyn std::any::Any>) {
if let Ok(token) = state.downcast::<TimerToken>() {
if self.hover.map(|(_, t)| t) == Some(*token) {
self.visible = true;
cx.request_style(self.tip.id());
cx.request_layout(self.tip.id());
}
}
}

fn style(&mut self, cx: &mut StyleCx<'_>) {
self.style.read(cx);

cx.style_view(&mut self.child);
cx.style_view(&mut self.tip);

let tip_view = cx.app_state_mut().view_state(self.tip.id());
tip_view.combined_style = tip_view
.combined_style
.clone()
.set(
DisplayProp,
if self.visible {
Display::Flex
} else {
Display::None
},
)
.absolute()
.inset_left(self.hover.map(|(p, _)| p.x).unwrap_or(0.0))
.inset_top(self.hover.map(|(p, _)| p.y).unwrap_or(0.0))
.z_index(100);
}

fn event(
&mut self,
cx: &mut EventCx,
id_path: Option<&[Id]>,
event: Event,
) -> EventPropagation {
match &event {
Event::PointerMove(e) => {
if !self.visible {
let id = self.id();
let token =
exec_after(Duration::from_secs_f64(self.style.delay()), move |token| {
id.update_state(token, false);
});
self.hover = Some((e.pos, token));
}
}
Event::PointerLeave => {
self.hover = None;
if self.visible {
self.visible = false;
cx.request_style(self.tip.id());
cx.request_layout(self.tip.id());
}
}
_ => {}
}

default_event(self, cx, id_path, event)
}
}
16 changes: 16 additions & 0 deletions src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub use checkbox::*;
mod toggle_button;
pub use toggle_button::*;

mod tooltip;
pub use tooltip::*;

pub mod slider;

mod button;
Expand Down Expand Up @@ -170,6 +173,19 @@ pub(crate) fn default_theme() -> Theme {
.set(slider::CircleRad, PxPct::Pct(100.))
.set(slider::BarExtends, false)
})
.class(TooltipClass, |s| {
s.border(0.5)
.border_color(Color::rgb8(140, 140, 140))
.color(Color::rgb8(80, 80, 80))
.border_radius(2.0)
.padding(padding)
.margin(10.0)
.background(Color::WHITE_SMOKE)
.box_shadow_blur(2.0)
.box_shadow_h_offset(2.0)
.box_shadow_v_offset(2.0)
.box_shadow_color(Color::BLACK.with_alpha_factor(0.2))
})
.font_size(FONT_SIZE)
.color(Color::BLACK);

Expand Down
11 changes: 11 additions & 0 deletions src/widgets/tooltip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::{
style_class,
view::View,
views::{self, container, Decorators},
};

style_class!(pub TooltipClass);

pub fn tooltip<V: View + 'static, T: View + 'static>(child: V, tip: T) -> impl View {
views::tooltip(child, container(tip).class(TooltipClass))
}

0 comments on commit c8c6454

Please sign in to comment.