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/// [`CursorImg`]: zng_ext_window::CursorImg
30#[property(CONTEXT, default(CursorIcon::Default))]
31pub fn cursor(child: impl IntoUiNode, cursor: impl IntoVar<CursorSource>) -> UiNode {
32    let cursor = cursor.into_var();
33    match_node(child, move |_, op| match op {
34        UiNodeOp::Init => {
35            // setup a single handler for the window, each widget
36            // that sets cursor holds the handle
37            let id = WINDOW.id();
38            let h = WINDOW.with_state_mut(|mut s| match s.entry(*WINDOW_CURSOR_HANDLER_ID) {
39                state_map::StateMapEntry::Occupied(mut e) => {
40                    if let Some(h) = e.get().upgrade() {
41                        h
42                    } else {
43                        let h = cursor_impl(id);
44                        e.insert(h.downgrade());
45                        h
46                    }
47                }
48                state_map::StateMapEntry::Vacant(e) => {
49                    let h = cursor_impl(id);
50                    e.insert(h.downgrade());
51                    h
52                }
53            });
54            WIDGET.push_var_handle(h);
55        }
56        UiNodeOp::Info { info } => {
57            info.set_meta(*WIDGET_CURSOR_ID, cursor.current_context());
58        }
59        _ => {}
60    })
61}
62fn cursor_impl(id: WindowId) -> VarHandle {
63    let mut _binding = VarHandle::dummy();
64    let mut current_top = None;
65    MOUSE_HOVERED_EVENT.hook(move |args| {
66        if let Some(t) = &args.target
67            && t.window_id() == id
68            && let Some(info) = WINDOWS.widget_tree(id).unwrap().get(t.widget_id())
69        {
70            let mut vars = None;
71            macro_rules! vars {
72                () => {
73                    vars.get_or_insert_with(|| WINDOWS.vars(id).unwrap())
74                };
75            }
76            for info in info.self_and_ancestors() {
77                if let Some(cap) = &args.capture
78                    && !cap.allows((id, info.id()))
79                {
80                    continue;
81                }
82                if let Some(cursor) = info.meta().get(*WIDGET_CURSOR_ID) {
83                    let top = Some(info.id());
84                    if current_top != top {
85                        current_top = top;
86
87                        _binding = cursor.set_bind(&vars!().cursor());
88                    }
89                    return true;
90                }
91            }
92
93            if current_top.take().is_some() {
94                _binding = VarHandle::dummy();
95                vars!().cursor().set(CursorIcon::Default);
96            }
97        }
98        true
99    })
100}
101
102/// Defines how click events are generated for the widget.
103///
104/// Setting this to `None` will cause the widget to inherit the parent mode, or [`ClickMode::default`] if
105/// no parent sets the click mode.
106///
107/// [`ClickMode::default`]: zng_ext_input::mouse::ClickMode::default
108#[property(CONTEXT, default(None))]
109pub fn click_mode(child: impl IntoUiNode, mode: impl IntoVar<Option<ClickMode>>) -> UiNode {
110    let mode = mode.into_var();
111
112    match_node(child, move |_, op| match op {
113        UiNodeOp::Init => {
114            WIDGET.sub_var_info(&mode);
115        }
116        UiNodeOp::Info { info } => {
117            info.set_click_mode(mode.get());
118        }
119        _ => {}
120    })
121}