Skip to main content

zng_wgt_input/
misc.rs

1use std::sync::atomic::AtomicBool;
2
3use zng_ext_input::mouse::{ClickMode, MOUSE_HOVERED_EVENT, WidgetInfoBuilderMouseExt as _};
4use zng_ext_window::WINDOWS;
5use zng_wgt::prelude::*;
6
7pub use zng_view_api::window::CursorIcon;
8
9pub use zng_ext_window::CursorSource;
10
11#[cfg(feature = "image")]
12pub use zng_ext_window::CursorImg;
13
14context_local! {
15    static CHILD_SETS_CURSOR: AtomicBool = AtomicBool::new(false);
16}
17
18static_id! {
19    // set on info metadata
20    static ref WIDGET_CURSOR_ID: StateId<Var<CursorSource>>;
21    static ref WINDOW_CURSOR_HANDLER_ID: StateId<WeakVarHandle>;
22}
23
24/// Sets the mouse pointer cursor displayed when hovering the widget.
25///
26/// You can set this property to a [`CursorIcon`] for a named platform dependent icon, [`CursorImg`] for a custom image,
27/// or to `false` that converts to [`CursorSource::Hidden`].
28///
29/// The cursor will apply to the widget an all descendants that don't set a cursor.
30///
31/// [`CursorImg`]: zng_ext_window::CursorImg
32#[property(CONTEXT, default(CursorIcon::Default))]
33pub fn cursor(child: impl IntoUiNode, cursor: impl IntoVar<CursorSource>) -> UiNode {
34    let cursor = cursor.into_var();
35    match_node(child, move |_, op| match op {
36        UiNodeOp::Init => {
37            // setup a single handler for the window, each widget
38            // that sets cursor holds the handle
39            let id = WINDOW.id();
40            let h = WINDOW.with_state_mut(|mut s| match s.entry(*WINDOW_CURSOR_HANDLER_ID) {
41                state_map::StateMapEntry::Occupied(mut e) => {
42                    if let Some(h) = e.get().upgrade() {
43                        h
44                    } else {
45                        let h = cursor_impl(id);
46                        e.insert(h.downgrade());
47                        h
48                    }
49                }
50                state_map::StateMapEntry::Vacant(e) => {
51                    let h = cursor_impl(id);
52                    e.insert(h.downgrade());
53                    h
54                }
55            });
56            WIDGET.push_var_handle(h);
57        }
58        UiNodeOp::Info { info } => {
59            info.set_meta(*WIDGET_CURSOR_ID, cursor.current_context());
60        }
61        _ => {}
62    })
63}
64fn cursor_impl(id: WindowId) -> VarHandle {
65    let mut _binding = VarHandle::dummy();
66    let mut current_top = None;
67    MOUSE_HOVERED_EVENT.hook(move |args| {
68        if let Some(t) = &args.target
69            && t.window_id() == id
70            && args.capture_allows((t.window_id(), t.widget_id()))
71            && let Some(info) = WINDOWS.widget_tree(id).unwrap().get(t.widget_id())
72        {
73            let mut vars = None;
74            macro_rules! vars {
75                () => {
76                    vars.get_or_insert_with(|| WINDOWS.vars(id).unwrap())
77                };
78            }
79            for info in info.self_and_ancestors() {
80                if let Some(cursor) = info.meta().get(*WIDGET_CURSOR_ID) {
81                    let top = Some(info.id());
82                    if current_top != top {
83                        current_top = top;
84
85                        _binding = cursor.set_bind(&vars!().cursor());
86                    }
87                    return true;
88                }
89            }
90
91            if current_top.take().is_some() {
92                _binding = VarHandle::dummy();
93                vars!().cursor().set(CursorIcon::Default);
94            }
95        }
96        true
97    })
98}
99
100/// Defines how click events are generated for the widget.
101///
102/// Setting this to `None` will cause the widget to inherit the parent mode, or [`ClickMode::default`] if
103/// no parent sets the click mode.
104///
105/// [`ClickMode::default`]: zng_ext_input::mouse::ClickMode::default
106#[property(CONTEXT, default(None))]
107pub fn click_mode(child: impl IntoUiNode, mode: impl IntoVar<Option<ClickMode>>) -> UiNode {
108    let mode = mode.into_var();
109
110    match_node(child, move |_, op| match op {
111        UiNodeOp::Init => {
112            WIDGET.sub_var_info(&mode);
113        }
114        UiNodeOp::Info { info } => {
115            info.set_click_mode(mode.get());
116        }
117        _ => {}
118    })
119}