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))]
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 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#[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}