1use std::rc::Rc;
2
3use cranpose_foundation::PointerEvent;
4use cranpose_ui::{Brush, DrawCommand, LayoutNodeData, ModifierNodeSlices};
5use cranpose_ui_graphics::{
6 BlendMode, Color, ColorFilter, CompositingStrategy, CornerRadii, DrawPrimitive, GraphicsLayer,
7 Point, Rect, RoundedCornerShape, Size,
8};
9
10pub struct NodeStyle {
11 pub padding: cranpose_ui_graphics::EdgeInsets,
12 pub background: Option<Color>,
13 pub click_actions: Vec<Rc<dyn Fn(Point)>>,
14 pub shape: Option<RoundedCornerShape>,
15 pub pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
16 pub draw_commands: Vec<DrawCommand>,
17 pub graphics_layer: Option<GraphicsLayer>,
18 pub clip_to_bounds: bool,
19}
20
21impl NodeStyle {
22 pub fn from_layout_node(data: &LayoutNodeData) -> Self {
23 let resolved = data.resolved_modifiers;
24 let slices: &ModifierNodeSlices = data.modifier_slices();
25 let pointer_inputs = slices.pointer_inputs().to_vec();
26
27 Self {
28 padding: resolved.padding(),
29 background: None,
30 click_actions: slices.click_handlers().to_vec(),
31 shape: None,
32 pointer_inputs,
33 draw_commands: slices.draw_commands().to_vec(),
34 graphics_layer: slices.graphics_layer(),
35 clip_to_bounds: slices.clip_to_bounds(),
36 }
37 }
38}
39
40pub fn combine_layers(
41 current: GraphicsLayer,
42 modifier_layer: Option<GraphicsLayer>,
43) -> GraphicsLayer {
44 if let Some(layer) = modifier_layer {
45 GraphicsLayer {
46 alpha: (current.alpha * layer.alpha).clamp(0.0, 1.0),
47 scale: current.scale * layer.scale,
48 scale_x: current.scale_x * layer.scale_x,
49 scale_y: current.scale_y * layer.scale_y,
50 rotation_x: current.rotation_x + layer.rotation_x,
51 rotation_y: current.rotation_y + layer.rotation_y,
52 rotation_z: current.rotation_z + layer.rotation_z,
53 camera_distance: layer.camera_distance,
54 transform_origin: layer.transform_origin,
55 translation_x: current.translation_x + layer.translation_x,
56 translation_y: current.translation_y + layer.translation_y,
57 shadow_elevation: layer.shadow_elevation,
58 ambient_shadow_color: layer.ambient_shadow_color,
59 spot_shadow_color: layer.spot_shadow_color,
60 shape: layer.shape,
61 clip: current.clip || layer.clip,
62 color_filter: compose_color_filters(current.color_filter, layer.color_filter),
63 compositing_strategy: layer.compositing_strategy,
64 blend_mode: layer.blend_mode,
65 render_effect: layer.render_effect,
67 backdrop_effect: layer.backdrop_effect,
69 }
70 } else {
71 GraphicsLayer {
72 compositing_strategy: CompositingStrategy::Auto,
73 blend_mode: BlendMode::SrcOver,
74 render_effect: None,
75 backdrop_effect: None,
76 ..current
77 }
78 }
79}
80
81fn layer_scale_x(layer: &GraphicsLayer) -> f32 {
82 layer.scale * layer.scale_x
83}
84
85fn layer_scale_y(layer: &GraphicsLayer) -> f32 {
86 layer.scale * layer.scale_y
87}
88
89pub fn layer_uniform_scale(layer: &GraphicsLayer) -> f32 {
90 layer_scale_x(layer).min(layer_scale_y(layer))
91}
92
93pub fn apply_layer_affine_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
94 let offset_x = rect.x - layer_bounds.x;
95 let offset_y = rect.y - layer_bounds.y;
96 let scale_x = layer_scale_x(layer);
97 let scale_y = layer_scale_y(layer);
98 Rect {
99 x: layer_bounds.x + offset_x * scale_x + layer.translation_x,
100 y: layer_bounds.y + offset_y * scale_y + layer.translation_y,
101 width: rect.width * scale_x,
102 height: rect.height * scale_y,
103 }
104}
105
106fn layer_rotation_pivot(layer_bounds: Rect, layer: &GraphicsLayer) -> (f32, f32) {
107 (
108 layer_bounds.x + layer_bounds.width * layer.transform_origin.pivot_fraction_x,
109 layer_bounds.y + layer_bounds.height * layer.transform_origin.pivot_fraction_y,
110 )
111}
112
113fn layer_has_rotation(layer: &GraphicsLayer) -> bool {
114 layer.rotation_x.abs() > f32::EPSILON
115 || layer.rotation_y.abs() > f32::EPSILON
116 || layer.rotation_z.abs() > f32::EPSILON
117}
118
119fn apply_rotation_and_perspective(
120 point: [f32; 2],
121 pivot: (f32, f32),
122 layer: &GraphicsLayer,
123) -> [f32; 2] {
124 if !layer_has_rotation(layer) {
125 return point;
126 }
127
128 let mut x = point[0] - pivot.0;
129 let mut y = point[1] - pivot.1;
130 let mut z = 0.0f32;
131
132 let (sin_x, cos_x) = layer.rotation_x.to_radians().sin_cos();
133 let (sin_y, cos_y) = layer.rotation_y.to_radians().sin_cos();
134 let (sin_z, cos_z) = layer.rotation_z.to_radians().sin_cos();
135
136 let y_rot_x = y * cos_x - z * sin_x;
137 let z_rot_x = y * sin_x + z * cos_x;
138 y = y_rot_x;
139 z = z_rot_x;
140
141 let x_rot_y = x * cos_y + z * sin_y;
142 let z_rot_y = -x * sin_y + z * cos_y;
143 x = x_rot_y;
144 z = z_rot_y;
145
146 let x_rot_z = x * cos_z - y * sin_z;
147 let y_rot_z = x * sin_z + y * cos_z;
148 x = x_rot_z;
149 y = y_rot_z;
150
151 const CAMERA_DISTANCE_SCALE: f32 = 72.0;
155 let camera_distance = (layer.camera_distance * CAMERA_DISTANCE_SCALE).max(1.0);
156 let denom = (camera_distance - z).max(1.0);
157 let perspective = camera_distance / denom;
158
159 [pivot.0 + x * perspective, pivot.1 + y * perspective]
160}
161
162pub fn apply_layer_to_quad(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> [[f32; 2]; 4] {
163 let affine_rect = apply_layer_affine_to_rect(rect, layer_bounds, layer);
164 let affine_layer_bounds = apply_layer_affine_to_rect(layer_bounds, layer_bounds, layer);
165 let pivot = layer_rotation_pivot(affine_layer_bounds, layer);
166 let quad = [
167 [affine_rect.x, affine_rect.y],
168 [affine_rect.x + affine_rect.width, affine_rect.y],
169 [affine_rect.x, affine_rect.y + affine_rect.height],
170 [
171 affine_rect.x + affine_rect.width,
172 affine_rect.y + affine_rect.height,
173 ],
174 ];
175
176 quad.map(|point| apply_rotation_and_perspective(point, pivot, layer))
177}
178
179pub fn quad_bounds(quad: [[f32; 2]; 4]) -> Rect {
180 let mut min_x = f32::INFINITY;
181 let mut min_y = f32::INFINITY;
182 let mut max_x = f32::NEG_INFINITY;
183 let mut max_y = f32::NEG_INFINITY;
184
185 for [x, y] in quad {
186 min_x = min_x.min(x);
187 min_y = min_y.min(y);
188 max_x = max_x.max(x);
189 max_y = max_y.max(y);
190 }
191
192 Rect {
193 x: min_x,
194 y: min_y,
195 width: (max_x - min_x).max(0.0),
196 height: (max_y - min_y).max(0.0),
197 }
198}
199
200pub fn apply_layer_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
201 quad_bounds(apply_layer_to_quad(rect, layer_bounds, layer))
202}
203
204pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
205 apply_color_filter_to_color(
206 Color(
207 color.0,
208 color.1,
209 color.2,
210 (color.3 * layer.alpha).clamp(0.0, 1.0),
211 ),
212 layer.color_filter,
213 )
214}
215
216fn apply_color_filter_to_color(color: Color, filter: Option<ColorFilter>) -> Color {
217 match filter {
218 Some(filter) => {
219 let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
220 Color(r, g, b, a)
221 }
222 None => color,
223 }
224}
225
226pub fn compose_color_filters(
227 base: Option<ColorFilter>,
228 overlay: Option<ColorFilter>,
229) -> Option<ColorFilter> {
230 match (base, overlay) {
231 (None, None) => None,
232 (Some(filter), None) | (None, Some(filter)) => Some(filter),
233 (Some(filter), Some(next)) => Some(filter.compose(next)),
234 }
235}
236
237pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
238 let scale_x = layer_scale_x(layer);
239 let scale_y = layer_scale_y(layer);
240 let uniform_scale = layer_uniform_scale(layer);
241
242 match brush {
243 Brush::Solid(color) => Brush::solid(apply_layer_to_color(color, layer)),
244 Brush::LinearGradient {
245 colors,
246 stops,
247 mut start,
248 mut end,
249 tile_mode,
250 } => {
251 start.x *= scale_x;
252 start.y *= scale_y;
253 end.x *= scale_x;
254 end.y *= scale_y;
255 Brush::LinearGradient {
256 colors: colors
257 .into_iter()
258 .map(|c| apply_layer_to_color(c, layer))
259 .collect(),
260 stops,
261 start,
262 end,
263 tile_mode,
264 }
265 }
266 Brush::RadialGradient {
267 colors,
268 stops,
269 mut center,
270 mut radius,
271 tile_mode,
272 } => {
273 center.x *= scale_x;
274 center.y *= scale_y;
275 radius *= uniform_scale;
276 Brush::RadialGradient {
277 colors: colors
278 .into_iter()
279 .map(|c| apply_layer_to_color(c, layer))
280 .collect(),
281 stops,
282 center,
283 radius,
284 tile_mode,
285 }
286 }
287 Brush::SweepGradient {
288 colors,
289 stops,
290 mut center,
291 } => {
292 center.x *= scale_x;
293 center.y *= scale_y;
294 Brush::SweepGradient {
295 colors: colors
296 .into_iter()
297 .map(|c| apply_layer_to_color(c, layer))
298 .collect(),
299 stops,
300 center,
301 }
302 }
303 }
304}
305
306pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
307 CornerRadii {
308 top_left: radii.top_left * scale,
309 top_right: radii.top_right * scale,
310 bottom_right: radii.bottom_right * scale,
311 bottom_left: radii.bottom_left * scale,
312 }
313}
314
315#[derive(Clone, Copy)]
316pub enum DrawPlacement {
317 Behind,
318 Overlay,
319}
320
321pub fn primitives_for_placement(
322 command: &DrawCommand,
323 placement: DrawPlacement,
324 size: Size,
325) -> Vec<DrawPrimitive> {
326 let split_with_content = |primitives: Vec<DrawPrimitive>, placement| {
327 let Some(last_content_idx) = primitives
328 .iter()
329 .rposition(|primitive| matches!(primitive, DrawPrimitive::Content))
330 else {
331 return if matches!(placement, DrawPlacement::Overlay) {
332 primitives
333 .into_iter()
334 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
335 .collect()
336 } else {
337 Vec::new()
338 };
339 };
340
341 primitives
342 .into_iter()
343 .enumerate()
344 .filter_map(|(index, primitive)| {
345 if matches!(primitive, DrawPrimitive::Content) {
346 return None;
347 }
348 let is_before = index < last_content_idx;
349 match placement {
350 DrawPlacement::Behind if is_before => Some(primitive),
351 DrawPlacement::Overlay if !is_before => Some(primitive),
352 _ => None,
353 }
354 })
355 .collect()
356 };
357
358 match (placement, command) {
359 (DrawPlacement::Behind, DrawCommand::Behind(func)) => func(size)
360 .into_iter()
361 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
362 .collect(),
363 (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => func(size)
364 .into_iter()
365 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
366 .collect(),
367 (_, DrawCommand::WithContent(func)) => split_with_content(func(size), placement),
368 _ => Vec::new(),
369 }
370}