1use crate::{
2 AnyElement, Bounds, DispatchPhase, Element, ElementId, Hoverable, IdentifiedElement,
3 IntoAnyElement, LayoutId, MouseDownEvent, MouseUpEvent, ParentElement, Pixels, SharedString,
4 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: Styled + Element> ClickableElement<E> {
57 pub fn new(mut child: E) -> Self {
58 let cascade_slot = child.style_cascade().reserve();
59 ClickableElement {
60 child,
61 listeners: Default::default(),
62 active_style: Default::default(),
63 cascade_slot,
64 }
65 }
66
67 pub fn replace_child<E2: Element<ViewState = E::ViewState>>(
68 self,
69 replace: impl FnOnce(E) -> E2,
70 ) -> ClickableElement<E2> {
71 ClickableElement {
72 child: replace(self.child),
73 listeners: self.listeners,
74 active_style: self.active_style,
75 cascade_slot: self.cascade_slot,
76 }
77 }
78}
79
80impl<E> IntoAnyElement<E::ViewState> for ClickableElement<E>
81where
82 E: Styled + Element,
83{
84 fn into_any(self) -> AnyElement<E::ViewState> {
85 AnyElement::new(self)
86 }
87}
88
89impl<E> Element for ClickableElement<E>
90where
91 E: Styled + Element,
92{
93 type ViewState = E::ViewState;
94 type ElementState = ClickableElementState<E::ElementState>;
95
96 fn id(&self) -> Option<ElementId> {
97 self.child.id()
98 }
99
100 fn layout(
101 &mut self,
102 state: &mut Self::ViewState,
103 element_state: Option<Self::ElementState>,
104 cx: &mut ViewContext<Self::ViewState>,
105 ) -> (LayoutId, Self::ElementState) {
106 if let Some(element_state) = element_state {
107 if element_state.mouse_down.lock().is_some() {
108 self.child
109 .style_cascade()
110 .set(self.cascade_slot, Some(self.active_style.clone()));
111 }
112
113 let (layout_id, child_state) =
114 self.child
115 .layout(state, Some(element_state.child_state), cx);
116 (
117 layout_id,
118 ClickableElementState {
119 mouse_down: element_state.mouse_down,
120 child_state,
121 },
122 )
123 } else {
124 let (layout_id, child_state) = self.child.layout(state, None, cx);
125 (
126 layout_id,
127 ClickableElementState {
128 mouse_down: Default::default(),
129 child_state,
130 },
131 )
132 }
133 }
134
135 fn paint(
136 &mut self,
137 bounds: Bounds<Pixels>,
138 state: &mut Self::ViewState,
139 element_state: &mut Self::ElementState,
140 cx: &mut ViewContext<Self::ViewState>,
141 ) {
142 if !self.listeners.is_empty() || self.active_style.is_some() {
143 if let Some(mouse_down) = element_state.mouse_down.lock().clone() {
144 self.child
145 .style_cascade()
146 .set(self.cascade_slot, Some(self.active_style.clone()));
147 let listeners = self.listeners.clone();
148 let mouse_down_mutex = element_state.mouse_down.clone();
149 cx.on_mouse_event(move |view, up: &MouseUpEvent, phase, cx| {
150 if phase == DispatchPhase::Bubble && bounds.contains_point(up.position) {
151 for listener in &*listeners {
152 listener(
153 view,
154 &MouseClickEvent {
155 down: mouse_down.clone(),
156 up: up.clone(),
157 },
158 cx,
159 );
160 }
161 }
162
163 mouse_down_mutex.lock().take();
164 cx.notify();
165 });
166 } else {
167 let mouse_down_mutex = element_state.mouse_down.clone();
168 cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
169 if phase == DispatchPhase::Bubble && bounds.contains_point(down.position) {
170 *mouse_down_mutex.lock() = Some(down.clone());
171 cx.notify();
172 }
173 });
174 }
175 }
176
177 self.child
178 .paint(bounds, state, &mut element_state.child_state, cx);
179 }
180}
181
182impl<E: Styled + IdentifiedElement> IdentifiedElement for ClickableElement<E> {}
183
184impl<E> ParentElement for ClickableElement<E>
185where
186 E: Styled + ParentElement,
187{
188 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
189 self.child.children_mut()
190 }
191
192 fn group_mut(&mut self) -> &mut Option<SharedString> {
193 self.child.group_mut()
194 }
195}
196
197impl<E> Styled for ClickableElement<E>
198where
199 E: Styled + Element,
200{
201 fn style_cascade(&mut self) -> &mut crate::StyleCascade {
202 self.child.style_cascade()
203 }
204
205 fn computed_style(&mut self) -> &crate::Style {
206 self.child.computed_style()
207 }
208}
209
210impl<E> Hoverable for ClickableElement<E>
211where
212 E: Element + Hoverable,
213{
214 fn hover_style(&mut self) -> &mut StyleRefinement {
215 self.child.hover_style()
216 }
217}
218
219impl<E> Clickable for ClickableElement<E>
220where
221 E: Styled + IdentifiedElement,
222{
223 fn active_style(&mut self) -> &mut StyleRefinement {
224 &mut self.active_style
225 }
226
227 fn listeners(&mut self) -> &mut ClickListeners<Self::ViewState> {
228 &mut self.listeners
229 }
230}