1use std::ops::Range;
2
3use cranpose_ui::{Brush, DrawCommand, DrawCommandFn};
4use cranpose_ui_graphics::{
5 BlendMode, Color, ColorFilter, CommandRecording, CompositingStrategy, CornerRadii,
6 DrawPrimitive, GraphicsLayer, Size,
7};
8
9use crate::layer_transform::{layer_scale_x, layer_scale_y, layer_uniform_scale};
10
11pub fn combine_layers(
12 current: GraphicsLayer,
13 modifier_layer: Option<GraphicsLayer>,
14) -> GraphicsLayer {
15 if let Some(layer) = modifier_layer {
16 GraphicsLayer {
17 alpha: (current.alpha * layer.alpha).clamp(0.0, 1.0),
18 scale: current.scale * layer.scale,
19 scale_x: current.scale_x * layer.scale_x,
20 scale_y: current.scale_y * layer.scale_y,
21 rotation_x: current.rotation_x + layer.rotation_x,
22 rotation_y: current.rotation_y + layer.rotation_y,
23 rotation_z: current.rotation_z + layer.rotation_z,
24 camera_distance: layer.camera_distance,
25 transform_origin: layer.transform_origin,
26 translation_x: current.translation_x + layer.translation_x,
27 translation_y: current.translation_y + layer.translation_y,
28 shadow_elevation: layer.shadow_elevation,
29 ambient_shadow_color: layer.ambient_shadow_color,
30 spot_shadow_color: layer.spot_shadow_color,
31 shape: layer.shape,
32 clip: current.clip || layer.clip,
33 color_filter: compose_color_filters(current.color_filter, layer.color_filter),
34 compositing_strategy: layer.compositing_strategy,
35 blend_mode: layer.blend_mode,
36 render_effect: layer.render_effect,
37 backdrop_effect: layer.backdrop_effect,
38 }
39 } else {
40 GraphicsLayer {
41 compositing_strategy: CompositingStrategy::Auto,
42 blend_mode: BlendMode::SrcOver,
43 render_effect: None,
44 backdrop_effect: None,
45 ..current
46 }
47 }
48}
49
50pub use crate::graph::quad_bounds;
51
52pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
62 let color = color.srgb_8bit();
63 apply_color_filter_to_color(
64 Color(
65 color.0,
66 color.1,
67 color.2,
68 (color.3 * layer.alpha).clamp(0.0, 1.0),
69 ),
70 layer.color_filter,
71 )
72}
73
74fn apply_color_filter_to_color(color: Color, filter: Option<ColorFilter>) -> Color {
75 match filter {
76 Some(filter) => {
77 let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
78 Color(r, g, b, a)
79 }
80 None => color,
81 }
82}
83
84pub fn compose_color_filters(
85 base: Option<ColorFilter>,
86 overlay: Option<ColorFilter>,
87) -> Option<ColorFilter> {
88 match (base, overlay) {
89 (None, None) => None,
90 (Some(filter), None) | (None, Some(filter)) => Some(filter),
91 (Some(filter), Some(next)) => Some(filter.compose(next)),
92 }
93}
94
95pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
96 if layer.alpha == 1.0
97 && layer.color_filter.is_none()
98 && layer_scale_x(layer) == 1.0
99 && layer_scale_y(layer) == 1.0
100 {
101 return map_brush_colors(brush, Color::srgb_8bit);
102 }
103 map_brush_colors(scale_brush_geometry(brush, layer), |color| {
104 apply_layer_to_color(color, layer)
105 })
106}
107
108fn map_brush_colors(brush: Brush, paint: impl Fn(Color) -> Color) -> Brush {
109 match brush {
110 Brush::Solid(color) => Brush::solid(paint(color)),
111 Brush::LinearGradient {
112 colors,
113 stops,
114 start,
115 end,
116 tile_mode,
117 } => Brush::LinearGradient {
118 colors: colors.into_iter().map(paint).collect(),
119 stops,
120 start,
121 end,
122 tile_mode,
123 },
124 Brush::RadialGradient {
125 colors,
126 stops,
127 center,
128 radius,
129 tile_mode,
130 } => Brush::RadialGradient {
131 colors: colors.into_iter().map(paint).collect(),
132 stops,
133 center,
134 radius,
135 tile_mode,
136 },
137 Brush::SweepGradient {
138 colors,
139 stops,
140 center,
141 } => Brush::SweepGradient {
142 colors: colors.into_iter().map(paint).collect(),
143 stops,
144 center,
145 },
146 }
147}
148
149fn scale_brush_geometry(brush: Brush, layer: &GraphicsLayer) -> Brush {
150 let scale_x = layer_scale_x(layer);
151 let scale_y = layer_scale_y(layer);
152 let uniform_scale = layer_uniform_scale(layer);
153
154 match brush {
155 Brush::Solid(color) => Brush::Solid(color),
156 Brush::LinearGradient {
157 colors,
158 stops,
159 mut start,
160 mut end,
161 tile_mode,
162 } => {
163 start.x *= scale_x;
164 start.y *= scale_y;
165 end.x *= scale_x;
166 end.y *= scale_y;
167 Brush::LinearGradient {
168 colors,
169 stops,
170 start,
171 end,
172 tile_mode,
173 }
174 }
175 Brush::RadialGradient {
176 colors,
177 stops,
178 mut center,
179 mut radius,
180 tile_mode,
181 } => {
182 center.x *= scale_x;
183 center.y *= scale_y;
184 radius *= uniform_scale;
185 Brush::RadialGradient {
186 colors,
187 stops,
188 center,
189 radius,
190 tile_mode,
191 }
192 }
193 Brush::SweepGradient {
194 colors,
195 stops,
196 mut center,
197 } => {
198 center.x *= scale_x;
199 center.y *= scale_y;
200 Brush::SweepGradient {
201 colors,
202 stops,
203 center,
204 }
205 }
206 }
207}
208
209#[derive(Clone, Debug, PartialEq)]
217pub enum ResolvedBrush {
218 Solid(Color),
219 Other(Brush),
221}
222
223impl ResolvedBrush {
224 pub fn from_brush(brush: Brush) -> Self {
225 match brush {
226 Brush::Solid(color) => Self::Solid(color),
227 other => Self::Other(other),
228 }
229 }
230
231 pub fn into_brush(self) -> Brush {
234 match self {
235 Self::Solid(color) => Brush::Solid(color),
236 Self::Other(brush) => brush,
237 }
238 }
239}
240
241pub fn resolve_layer_brush(brush: &Brush, layer: &GraphicsLayer) -> ResolvedBrush {
246 match brush {
247 Brush::Solid(color) => {
248 if layer.alpha == 1.0
249 && layer.color_filter.is_none()
250 && layer_scale_x(layer) == 1.0
251 && layer_scale_y(layer) == 1.0
252 {
253 ResolvedBrush::Solid(*color)
254 } else {
255 ResolvedBrush::Solid(apply_layer_to_color(*color, layer))
256 }
257 }
258 other => ResolvedBrush::Other(apply_layer_to_brush(other.clone(), layer)),
259 }
260}
261
262pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
263 CornerRadii {
264 top_left: radii.top_left * scale,
265 top_right: radii.top_right * scale,
266 bottom_right: radii.bottom_right * scale,
267 bottom_left: radii.bottom_left * scale,
268 }
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
272pub enum DrawPlacement {
273 Behind,
274 Overlay,
275}
276
277pub fn primitives_for_placement(
278 command: &DrawCommand,
279 placement: DrawPlacement,
280 size: Size,
281) -> Vec<DrawPrimitive> {
282 recording_for_placement_reusing(command, placement, size, CommandRecording::default)
283 .map(|(recording, segments)| recording.primitives(segments).collect())
284 .unwrap_or_default()
285}
286
287pub fn recording_for_placement_reusing(
294 command: &DrawCommand,
295 placement: DrawPlacement,
296 size: Size,
297 storage: impl FnOnce() -> CommandRecording,
298) -> Option<(CommandRecording, Range<u32>)> {
299 let record = move |func: &DrawCommandFn| {
300 let mut scope = cranpose_ui::command_draw_scope_reusing(size, storage());
301 func(&mut scope);
302 scope.finish()
303 };
304 match (placement, command) {
305 (DrawPlacement::Behind, DrawCommand::Behind(func))
306 | (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => {
307 let recording = record(func);
308 let segments = recording.all_segments();
309 Some((recording, segments))
310 }
311 (_, DrawCommand::WithContent(func)) => {
312 let recording = record(func);
313 let segments = recording.content_split(placement == DrawPlacement::Behind);
314 Some((recording, segments))
315 }
316 _ => None,
317 }
318}
319
320#[cfg(test)]
321#[path = "tests/style_shared_tests.rs"]
322mod tests;