1use crate::{
2 AnyElement, Bounds, DispatchPhase, Element, ElementId, ElementKind, Hoverable,
3 IdentifiedElement, IntoAnyElement, LayoutId, LayoutNode, MouseDownEvent, MouseUpEvent, Pixels,
4 SharedString, StyleRefinement, Styled, ViewContext,
5};
6use parking_lot::Mutex;
7use refineable::CascadeSlot;
8use smallvec::SmallVec;
9use std::sync::Arc;
10
11pub trait Clickable: Element + Sized {
12 fn active_style(&mut self) -> &mut StyleRefinement;
13 fn listeners(&mut self) -> &mut ClickListeners<Self::ViewState>;
14
15 fn on_click(
16 &mut self,
17 f: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext<Self::ViewState>)
18 + 'static
19 + Send
20 + Sync,
21 ) where
22 Self: Sized,
23 {
24 self.listeners().push(Arc::new(f));
25 }
26
27 fn active(mut self, f: impl FnOnce(&mut StyleRefinement) -> &mut StyleRefinement) -> Self
28 where
29 Self: Sized,
30 {
31 f(self.active_style());
32 self
33 }
34}
35
36pub type ClickListeners<V> =
37 SmallVec<[Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync>; 1]>;
38
39pub struct ClickableElementState<E: 'static + Send + Sync> {
40 mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
41 child_state: E,
42}
43
44pub struct MouseClickEvent {
45 pub down: MouseDownEvent,
46 pub up: MouseUpEvent,
47}
48
49pub struct ClickableElement<E: Element> {
50 child: E,
51 listeners: ClickListeners<E::ViewState>,
52 active_style: StyleRefinement,
53 cascade_slot: CascadeSlot,
54}
55
56impl<E: Element> ClickableElement<E> {
57 pub fn replace_child<E2: Element<ViewState = E::ViewState>>(
58 self,
59 replace: impl FnOnce(E) -> E2,
60 ) -> ClickableElement<E2> {
61 ClickableElement {
62 child: replace(self.child),
63 listeners: self.listeners,
64 active_style: self.active_style,
65 cascade_slot: self.cascade_slot,
66 }
67 }
68}
69
70impl<E> IntoAnyElement<E::ViewState> for ClickableElement<E>
71where
72 E: Styled + Element,
73{
74 fn into_any(self) -> AnyElement<E::ViewState> {
75 AnyElement::new(self)
76 }
77}
78
79impl<E> Element for ClickableElement<E>
80where
81 E: Styled + Element,
82{
83 type ViewState = E::ViewState;
84 type ElementState = ClickableElementState<E::ElementState>;
85
86 fn id(&self) -> Option<ElementId> {
87 self.child.id()
88 }
89
90 fn layout(
91 &mut self,
92 state: &mut Self::ViewState,
93 element_state: Option<Self::ElementState>,
94 cx: &mut ViewContext<Self::ViewState>,
95 ) -> (LayoutId, Self::ElementState) {
96 if let Some(element_state) = element_state {
97 if element_state.mouse_down.lock().is_some() {
98 self.child
99 .style_cascade()
100 .set(self.cascade_slot, Some(self.active_style.clone()));
101 }
102
103 let (layout_id, child_state) =
104 self.child
105 .layout(state, Some(element_state.child_state), cx);
106 (
107 layout_id,
108 ClickableElementState {
109 mouse_down: element_state.mouse_down,
110 child_state,
111 },
112 )
113 } else {
114 let (layout_id, child_state) = self.child.layout(state, None, cx);
115 (
116 layout_id,
117 ClickableElementState {
118 mouse_down: Default::default(),
119 child_state,
120 },
121 )
122 }
123 }
124
125 fn paint(
126 &mut self,
127 bounds: Bounds<Pixels>,
128 state: &mut Self::ViewState,
129 element_state: &mut Self::ElementState,
130 cx: &mut ViewContext<Self::ViewState>,
131 ) {
132 if !self.listeners.is_empty() || self.active_style.is_some() {
133 if let Some(mouse_down) = element_state.mouse_down.lock().clone() {
134 self.child
135 .style_cascade()
136 .set(self.cascade_slot, Some(self.active_style.clone()));
137 let listeners = self.listeners.clone();
138 let mouse_down_mutex = element_state.mouse_down.clone();
139 cx.on_mouse_event(move |view, up: &MouseUpEvent, phase, cx| {
140 if phase == DispatchPhase::Bubble && bounds.contains_point(up.position) {
141 for listener in &*listeners {
142 listener(
143 view,
144 &MouseClickEvent {
145 down: mouse_down.clone(),
146 up: up.clone(),
147 },
148 cx,
149 );
150 }
151 }
152
153 mouse_down_mutex.lock().take();
154 cx.notify();
155 });
156 } else {
157 let mouse_down_mutex = element_state.mouse_down.clone();
158 cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
159 if phase == DispatchPhase::Bubble && bounds.contains_point(down.position) {
160 *mouse_down_mutex.lock() = Some(down.clone());
161 cx.notify();
162 }
163 });
164 }
165 }
166
167 self.child
168 .paint(bounds, state, &mut element_state.child_state, cx);
169 }
170}
171
172impl<E: Styled + IdentifiedElement> IdentifiedElement for ClickableElement<E> {}
173
174impl<E, K> LayoutNode<E::ViewState, K> for ClickableElement<E>
175where
176 E: Element + LayoutNode<E::ViewState, K>,
177 K: ElementKind,
178{
179 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<E::ViewState>; 2]> {
180 self.child.children_mut()
181 }
182
183 fn group_mut(&mut self) -> &mut Option<SharedString> {
184 self.child.group_mut()
185 }
186}
187
188impl<E> Styled for ClickableElement<E>
189where
190 E: Styled + Element,
191{
192 fn style_cascade(&mut self) -> &mut crate::StyleCascade {
193 self.child.style_cascade()
194 }
195
196 fn computed_style(&mut self) -> &crate::Style {
197 self.child.computed_style()
198 }
199}
200
201impl<E> Hoverable for ClickableElement<E>
202where
203 E: Element + Hoverable,
204{
205 fn hover_style(&mut self) -> &mut StyleRefinement {
206 self.child.hover_style()
207 }
208}
209
210impl<E> Clickable for ClickableElement<E>
211where
212 E: Styled + IdentifiedElement,
213{
214 fn active_style(&mut self) -> &mut StyleRefinement {
215 &mut self.active_style
216 }
217
218 fn listeners(&mut self) -> &mut ClickListeners<Self::ViewState> {
219 &mut self.listeners
220 }
221}