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, RoundedCornerShape, Size,
8};
9
10use crate::layer_transform::{layer_scale_x, layer_scale_y, layer_uniform_scale};
11
12pub struct NodeStyle {
13 pub padding: cranpose_ui_graphics::EdgeInsets,
14 pub background: Option<Color>,
15 pub click_actions: Vec<Rc<dyn Fn(Point)>>,
16 pub shape: Option<RoundedCornerShape>,
17 pub pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
18 pub draw_commands: Vec<DrawCommand>,
19 pub graphics_layer: Option<GraphicsLayer>,
20 pub clip_to_bounds: bool,
21}
22
23impl NodeStyle {
24 pub fn from_layout_node(data: &LayoutNodeData) -> Self {
25 let resolved = data.resolved_modifiers;
26 let slices: &ModifierNodeSlices = data.modifier_slices();
27 let pointer_inputs = slices.pointer_inputs().to_vec();
28
29 Self {
30 padding: resolved.padding(),
31 background: None,
32 click_actions: slices.click_handlers().to_vec(),
33 shape: None,
34 pointer_inputs,
35 draw_commands: slices.draw_commands().to_vec(),
36 graphics_layer: slices.graphics_layer(),
37 clip_to_bounds: slices.clip_to_bounds(),
38 }
39 }
40}
41
42pub fn combine_layers(
43 current: GraphicsLayer,
44 modifier_layer: Option<GraphicsLayer>,
45) -> GraphicsLayer {
46 if let Some(layer) = modifier_layer {
47 GraphicsLayer {
48 alpha: (current.alpha * layer.alpha).clamp(0.0, 1.0),
49 scale: current.scale * layer.scale,
50 scale_x: current.scale_x * layer.scale_x,
51 scale_y: current.scale_y * layer.scale_y,
52 rotation_x: current.rotation_x + layer.rotation_x,
53 rotation_y: current.rotation_y + layer.rotation_y,
54 rotation_z: current.rotation_z + layer.rotation_z,
55 camera_distance: layer.camera_distance,
56 transform_origin: layer.transform_origin,
57 translation_x: current.translation_x + layer.translation_x,
58 translation_y: current.translation_y + layer.translation_y,
59 shadow_elevation: layer.shadow_elevation,
60 ambient_shadow_color: layer.ambient_shadow_color,
61 spot_shadow_color: layer.spot_shadow_color,
62 shape: layer.shape,
63 clip: current.clip || layer.clip,
64 color_filter: compose_color_filters(current.color_filter, layer.color_filter),
65 compositing_strategy: layer.compositing_strategy,
66 blend_mode: layer.blend_mode,
67 render_effect: layer.render_effect,
69 backdrop_effect: layer.backdrop_effect,
71 }
72 } else {
73 GraphicsLayer {
74 compositing_strategy: CompositingStrategy::Auto,
75 blend_mode: BlendMode::SrcOver,
76 render_effect: None,
77 backdrop_effect: None,
78 ..current
79 }
80 }
81}
82
83pub use crate::graph::quad_bounds;
84
85pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
86 apply_color_filter_to_color(
87 Color(
88 color.0,
89 color.1,
90 color.2,
91 (color.3 * layer.alpha).clamp(0.0, 1.0),
92 ),
93 layer.color_filter,
94 )
95}
96
97fn apply_color_filter_to_color(color: Color, filter: Option<ColorFilter>) -> Color {
98 match filter {
99 Some(filter) => {
100 let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
101 Color(r, g, b, a)
102 }
103 None => color,
104 }
105}
106
107pub fn compose_color_filters(
108 base: Option<ColorFilter>,
109 overlay: Option<ColorFilter>,
110) -> Option<ColorFilter> {
111 match (base, overlay) {
112 (None, None) => None,
113 (Some(filter), None) | (None, Some(filter)) => Some(filter),
114 (Some(filter), Some(next)) => Some(filter.compose(next)),
115 }
116}
117
118pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
119 let scale_x = layer_scale_x(layer);
120 let scale_y = layer_scale_y(layer);
121 let uniform_scale = layer_uniform_scale(layer);
122
123 match brush {
124 Brush::Solid(color) => Brush::solid(apply_layer_to_color(color, layer)),
125 Brush::LinearGradient {
126 colors,
127 stops,
128 mut start,
129 mut end,
130 tile_mode,
131 } => {
132 start.x *= scale_x;
133 start.y *= scale_y;
134 end.x *= scale_x;
135 end.y *= scale_y;
136 Brush::LinearGradient {
137 colors: colors
138 .into_iter()
139 .map(|c| apply_layer_to_color(c, layer))
140 .collect(),
141 stops,
142 start,
143 end,
144 tile_mode,
145 }
146 }
147 Brush::RadialGradient {
148 colors,
149 stops,
150 mut center,
151 mut radius,
152 tile_mode,
153 } => {
154 center.x *= scale_x;
155 center.y *= scale_y;
156 radius *= uniform_scale;
157 Brush::RadialGradient {
158 colors: colors
159 .into_iter()
160 .map(|c| apply_layer_to_color(c, layer))
161 .collect(),
162 stops,
163 center,
164 radius,
165 tile_mode,
166 }
167 }
168 Brush::SweepGradient {
169 colors,
170 stops,
171 mut center,
172 } => {
173 center.x *= scale_x;
174 center.y *= scale_y;
175 Brush::SweepGradient {
176 colors: colors
177 .into_iter()
178 .map(|c| apply_layer_to_color(c, layer))
179 .collect(),
180 stops,
181 center,
182 }
183 }
184 }
185}
186
187pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
188 CornerRadii {
189 top_left: radii.top_left * scale,
190 top_right: radii.top_right * scale,
191 bottom_right: radii.bottom_right * scale,
192 bottom_left: radii.bottom_left * scale,
193 }
194}
195
196#[derive(Clone, Copy)]
197pub enum DrawPlacement {
198 Behind,
199 Overlay,
200}
201
202pub fn primitives_for_placement(
203 command: &DrawCommand,
204 placement: DrawPlacement,
205 size: Size,
206) -> Vec<DrawPrimitive> {
207 let split_with_content = |primitives: Vec<DrawPrimitive>, placement| {
208 let Some(last_content_idx) = primitives
209 .iter()
210 .rposition(|primitive| matches!(primitive, DrawPrimitive::Content))
211 else {
212 return if matches!(placement, DrawPlacement::Overlay) {
213 primitives
214 .into_iter()
215 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
216 .collect()
217 } else {
218 Vec::new()
219 };
220 };
221
222 primitives
223 .into_iter()
224 .enumerate()
225 .filter_map(|(index, primitive)| {
226 if matches!(primitive, DrawPrimitive::Content) {
227 return None;
228 }
229 let is_before = index < last_content_idx;
230 match placement {
231 DrawPlacement::Behind if is_before => Some(primitive),
232 DrawPlacement::Overlay if !is_before => Some(primitive),
233 _ => None,
234 }
235 })
236 .collect()
237 };
238
239 match (placement, command) {
240 (DrawPlacement::Behind, DrawCommand::Behind(func)) => func(size)
241 .into_iter()
242 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
243 .collect(),
244 (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => func(size)
245 .into_iter()
246 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
247 .collect(),
248 (_, DrawCommand::WithContent(func)) => split_with_content(func(size), placement),
249 _ => Vec::new(),
250 }
251}