1use cranpose_core::{run_in_mutable_snapshot, NodeId};
4use cranpose_foundation::{PointerEvent, PointerEventKind};
5use cranpose_render_common::{HitTestTarget, RenderScene};
6use cranpose_ui_graphics::{Brush, Color, Point, Rect, RoundedCornerShape};
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::rc::Rc;
10
11#[derive(Clone)]
12pub struct DrawShape {
13 pub rect: Rect,
14 pub brush: Brush,
15 pub shape: Option<RoundedCornerShape>,
16 pub z_index: usize,
17 pub clip: Option<Rect>,
18}
19
20#[derive(Clone)]
21pub struct TextDraw {
22 pub rect: Rect,
23 pub text: String,
24 pub color: Color,
25 pub scale: f32,
26 pub z_index: usize,
27 pub clip: Option<Rect>,
28}
29
30#[derive(Clone)]
31pub enum ClickAction {
32 Simple(Rc<RefCell<dyn FnMut()>>),
33 WithPoint(Rc<dyn Fn(Point)>),
34}
35
36impl ClickAction {
37 pub(crate) fn invoke(&self, rect: Rect, x: f32, y: f32) {
38 match self {
39 ClickAction::Simple(handler) => (handler.borrow_mut())(),
40 ClickAction::WithPoint(handler) => handler(Point {
41 x: x - rect.x,
42 y: y - rect.y,
43 }),
44 }
45 }
46}
47
48#[derive(Clone)]
49pub struct HitRegion {
50 pub node_id: NodeId,
51 pub rect: Rect,
52 pub shape: Option<RoundedCornerShape>,
53 pub click_actions: Vec<ClickAction>,
54 pub pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
55 pub z_index: usize,
56 pub hit_clip: Option<Rect>,
57}
58
59impl HitTestTarget for HitRegion {
60 fn dispatch(&self, event: PointerEvent) {
61 let x = event.global_position.x;
62 let y = event.global_position.y;
63 let kind = event.kind;
64
65 let local = Point {
66 x: x - self.rect.x,
67 y: y - self.rect.y,
68 };
69
70 let local_event = event.copy_with_local_position(local);
71
72 let has_pointer_inputs = !self.pointer_inputs.is_empty();
73 let has_click_actions = kind == PointerEventKind::Down && !self.click_actions.is_empty();
74
75 if !has_pointer_inputs && !has_click_actions {
76 return;
77 }
78
79 if let Err(err) = run_in_mutable_snapshot(|| {
80 for handler in self.pointer_inputs.iter() {
81 if local_event.is_consumed() {
83 break;
84 }
85 handler(local_event.clone());
86 }
87
88 if kind == PointerEventKind::Down && !local_event.is_consumed() {
90 for action in &self.click_actions {
91 action.invoke(self.rect, x, y);
92 }
93 }
94 }) {
95 log::error!(
96 "failed to apply mutable snapshot for pointer event {:?} at ({}, {}): {}",
97 kind,
98 x,
99 y,
100 err
101 );
102 }
103 }
104
105 fn node_id(&self) -> NodeId {
106 self.node_id
107 }
108}
109
110impl HitRegion {
111 pub fn contains(&self, x: f32, y: f32) -> bool {
112 if let Some(clip) = self.hit_clip {
113 if !clip.contains(x, y) {
114 return false;
115 }
116 }
117 if let Some(shape) = self.shape {
118 point_in_rounded_rect(x, y, self.rect, shape)
119 } else {
120 self.rect.contains(x, y)
121 }
122 }
123}
124
125pub struct Scene {
126 pub shapes: Vec<DrawShape>,
127 pub texts: Vec<TextDraw>,
128 pub hits: Vec<HitRegion>,
129 node_index: HashMap<NodeId, HitRegion>,
131 next_z: usize,
132}
133
134impl Scene {
135 pub fn new() -> Self {
136 Self {
137 shapes: Vec::new(),
138 texts: Vec::new(),
139 hits: Vec::new(),
140 node_index: HashMap::new(),
141 next_z: 0,
142 }
143 }
144
145 pub fn push_shape(
146 &mut self,
147 rect: Rect,
148 brush: Brush,
149 shape: Option<RoundedCornerShape>,
150 clip: Option<Rect>,
151 ) {
152 let z_index = self.next_z;
153 self.next_z += 1;
154 self.shapes.push(DrawShape {
155 rect,
156 brush,
157 shape,
158 z_index,
159 clip,
160 });
161 }
162
163 pub fn push_text(
164 &mut self,
165 rect: Rect,
166 text: String,
167 color: Color,
168 scale: f32,
169 clip: Option<Rect>,
170 ) {
171 let z_index = self.next_z;
172 self.next_z += 1;
173 self.texts.push(TextDraw {
174 rect,
175 text,
176 color,
177 scale,
178 z_index,
179 clip,
180 });
181 }
182
183 pub fn push_hit(
184 &mut self,
185 node_id: NodeId,
186 rect: Rect,
187 shape: Option<RoundedCornerShape>,
188 click_actions: Vec<ClickAction>,
189 pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
190 hit_clip: Option<Rect>,
191 ) {
192 if click_actions.is_empty() && pointer_inputs.is_empty() {
193 return;
194 }
195 let z_index = self.next_z;
196 self.next_z += 1;
197 let hit_region = HitRegion {
198 node_id,
199 rect,
200 shape,
201 click_actions,
202 pointer_inputs,
203 z_index,
204 hit_clip,
205 };
206 self.node_index.insert(node_id, hit_region.clone());
208 self.hits.push(hit_region);
209 }
210}
211
212impl Default for Scene {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218impl RenderScene for Scene {
219 type HitTarget = HitRegion;
220
221 fn clear(&mut self) {
222 self.shapes.clear();
223 self.texts.clear();
224 self.hits.clear();
225 self.node_index.clear();
226 self.next_z = 0;
227 }
228
229 fn hit_test(&self, x: f32, y: f32) -> Vec<Self::HitTarget> {
230 let mut hits: Vec<_> = self
231 .hits
232 .iter()
233 .filter(|hit| hit.contains(x, y))
234 .cloned()
235 .collect();
236
237 hits.sort_by(|a, b| b.z_index.cmp(&a.z_index));
239 hits
240 }
241
242 fn find_target(&self, node_id: NodeId) -> Option<Self::HitTarget> {
243 self.node_index.get(&node_id).cloned()
245 }
246}
247
248fn point_in_rounded_rect(x: f32, y: f32, rect: Rect, shape: RoundedCornerShape) -> bool {
250 if !rect.contains(x, y) {
251 return false;
252 }
253
254 let local_x = x - rect.x;
255 let local_y = y - rect.y;
256
257 let radii = shape.resolve(rect.width, rect.height);
259 let tl = radii.top_left;
260 let tr = radii.top_right;
261 let bl = radii.bottom_left;
262 let br = radii.bottom_right;
263
264 if local_x < tl && local_y < tl {
266 let dx = tl - local_x;
267 let dy = tl - local_y;
268 return dx * dx + dy * dy <= tl * tl;
269 }
270
271 if local_x > rect.width - tr && local_y < tr {
273 let dx = local_x - (rect.width - tr);
274 let dy = tr - local_y;
275 return dx * dx + dy * dy <= tr * tr;
276 }
277
278 if local_x < bl && local_y > rect.height - bl {
280 let dx = bl - local_x;
281 let dy = local_y - (rect.height - bl);
282 return dx * dx + dy * dy <= bl * bl;
283 }
284
285 if local_x > rect.width - br && local_y > rect.height - br {
287 let dx = local_x - (rect.width - br);
288 let dy = local_y - (rect.height - br);
289 return dx * dx + dy * dy <= br * br;
290 }
291
292 true
293}