Skip to content

Commit

Permalink
Add overlays and use it for tooltips (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc authored Nov 20, 2023
1 parent bb49d88 commit b86ea50
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 68 deletions.
7 changes: 3 additions & 4 deletions examples/widget-gallery/src/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ pub fn label_view() -> impl View {
form({
(
form_item("Simple Label:".to_string(), 120.0, || {
tooltip(
label(move || "This is a simple label".to_owned()),
static_label("This is a tooltip for the label."),
)
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
18 changes: 18 additions & 0 deletions src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ use crate::{
app::{add_app_update_event, AppUpdateEvent},
ext_event::create_ext_action,
file::{FileDialogOptions, FileInfo},
id::Id,
menu::Menu,
update::{UpdateMessage, CENTRAL_UPDATE_MESSAGES},
view::View,
window_handle::{get_current_view, set_current_view},
};

Expand Down Expand Up @@ -161,3 +163,19 @@ pub fn set_ime_allowed(allowed: bool) {
pub fn set_ime_cursor_area(position: Point, size: Size) {
add_update_message(UpdateMessage::SetImeCursorArea { position, size });
}

/// Creates a new overlay on the current window.
pub fn add_overlay<V: View + 'static>(position: Point, view: impl FnOnce(Id) -> V + 'static) -> Id {
let id = Id::next();
add_update_message(UpdateMessage::AddOverlay {
id,
position,
view: Box::new(move || Box::new(view(id))),
});
id
}

/// Removes an overlay from the current window.
pub fn remove_overlay(id: Id) {
add_update_message(UpdateMessage::RemoveOverlay { id });
}
4 changes: 2 additions & 2 deletions src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ pub struct Id(u64);
pub struct IdPath(pub(crate) Vec<Id>);

impl IdPath {
/// Returns the slice of the ids excluding the first id identifying the window.
/// Returns the slice of the ids including the first id identifying the window.
pub(crate) fn dispatch(&self) -> &[Id] {
&self.0[1..]
&self.0[..]
}
}

Expand Down
1 change: 1 addition & 0 deletions src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl CapturedView {
fn find_by_pos(&self, pos: Point) -> Option<&CapturedView> {
self.children
.iter()
.rev()
.filter_map(|child| child.find_by_pos(pos))
.next()
.or_else(|| self.clipped.contains(pos).then_some(self))
Expand Down
9 changes: 9 additions & 0 deletions src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
id::Id,
menu::Menu,
style::{Style, StyleClassRef, StyleSelector},
view::View,
};

thread_local! {
Expand Down Expand Up @@ -113,6 +114,14 @@ pub(crate) enum UpdateMessage {
SetWindowTitle {
title: String,
},
AddOverlay {
id: Id,
position: Point,
view: Box<dyn FnOnce() -> Box<dyn View>>,
},
RemoveOverlay {
id: Id,
},
Inspect,
FocusWindow,
SetImeAllowed {
Expand Down
92 changes: 36 additions & 56 deletions src/views/tooltip.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use kurbo::Point;
use std::time::Duration;
use taffy::style::Display;
use std::{rc::Rc, time::Duration};

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

Expand All @@ -25,21 +23,26 @@ prop_extracter! {
pub struct Tooltip {
data: ViewData,
hover: Option<(Point, TimerToken)>,
visible: bool,
overlay: Option<Id>,
child: Box<dyn View>,
tip: Box<dyn View>,
tip: Rc<dyn Fn() -> Box<dyn View>>,
style: TooltipStyle,
window_origin: Option<Point>,
}

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

Expand All @@ -54,60 +57,37 @@ impl View for Tooltip {

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>) {
fn update(&mut self, _cx: &mut 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());
if let Some(window_origin) = self.window_origin {
if self.hover.map(|(_, t)| t) == Some(*token) {
let tip = self.tip.clone();
self.overlay = Some(add_overlay(
window_origin + self.hover.unwrap().0.to_vec2(),
move |_| tip(),
));
}
}
}
}

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,
Expand All @@ -116,7 +96,7 @@ impl View for Tooltip {
) -> EventPropagation {
match &event {
Event::PointerMove(e) => {
if !self.visible {
if self.overlay.is_none() {
let id = self.id();
let token =
exec_after(Duration::from_secs_f64(self.style.delay()), move |token| {
Expand All @@ -127,10 +107,9 @@ impl View for Tooltip {
}
Event::PointerLeave => {
self.hover = None;
if self.visible {
self.visible = false;
cx.request_style(self.tip.id());
cx.request_layout(self.tip.id());
if let Some(id) = self.overlay {
remove_overlay(id);
self.overlay = None;
}
}
_ => {}
Expand All @@ -139,15 +118,16 @@ impl View for Tooltip {
default_event(self, cx, id_path, event)
}

fn paint(&mut self, cx: &mut PaintCx) {
cx.paint_view(&mut self.child);
fn compute_layout(&mut self, cx: &mut crate::context::ComputeLayoutCx) -> Option<kurbo::Rect> {
self.window_origin = Some(cx.window_origin);
default_compute_layout(self, cx)
}
}

if self.visible {
// Remove clipping for the tooltip.
cx.save();
cx.clear_clip();
cx.paint_view(&mut self.tip);
cx.restore();
impl Drop for Tooltip {
fn drop(&mut self) {
if let Some(id) = self.overlay {
remove_overlay(id)
}
}
}
7 changes: 5 additions & 2 deletions src/widgets/tooltip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use crate::{

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))
pub fn tooltip<V: View + 'static, T: View + 'static>(
child: V,
tip: impl Fn() -> T + 'static,
) -> impl View {
views::tooltip(child, move || container(tip()).class(TooltipClass))
}
Loading

0 comments on commit b86ea50

Please sign in to comment.