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 static ref WIDGET_CURSOR_ID: StateId<Var<CursorSource>>;
21 static ref WINDOW_CURSOR_HANDLER_ID: StateId<WeakVarHandle>;
22}
23
24#[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 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#[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}