1use std::ops::Range;
2
3use cranpose_ui::{Brush, DrawCommand, DrawCommandFn};
4use cranpose_ui_graphics::{
5 Color, ColorFilter, CommandRecording, CornerRadii, DrawPrimitive, GraphicsLayer, Size,
6};
7
8pub use crate::graph::quad_bounds;
9use crate::layer_transform::{layer_scale_x, layer_scale_y, layer_uniform_scale};
10
11pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
21 apply_paint_to_color(color, layer.alpha, layer.color_filter.as_ref())
22}
23
24pub fn apply_paint_to_color(color: Color, alpha: f32, color_filter: Option<&ColorFilter>) -> Color {
28 let color = color.srgb_8bit();
29 let color = Color(color.0, color.1, color.2, (color.3 * alpha).clamp(0.0, 1.0));
30 match color_filter {
31 Some(filter) => {
32 let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
33 Color(r, g, b, a)
34 }
35 None => color,
36 }
37}
38
39pub fn compose_color_filters(
41 base: Option<&ColorFilter>,
42 overlay: Option<&ColorFilter>,
43) -> Option<ColorFilter> {
44 match (base, overlay) {
45 (None, None) => None,
46 (Some(filter), None) | (None, Some(filter)) => Some(filter.clone()),
47 (Some(filter), Some(next)) => Some(filter.compose(next)),
48 }
49}
50
51pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
54 if layer.alpha == 1.0
55 && layer.color_filter.is_none()
56 && layer_scale_x(layer) == 1.0
57 && layer_scale_y(layer) == 1.0
58 {
59 return map_brush_colors(brush, Color::srgb_8bit);
60 }
61 map_brush_colors(scale_brush_geometry(brush, layer), |color| {
62 apply_layer_to_color(color, layer)
63 })
64}
65
66fn map_brush_colors(brush: Brush, paint: impl Fn(Color) -> Color) -> Brush {
67 match brush {
68 Brush::Solid(color) => Brush::solid(paint(color)),
69 Brush::LinearGradient {
70 colors,
71 stops,
72 start,
73 end,
74 tile_mode,
75 } => Brush::LinearGradient {
76 colors: colors.into_iter().map(paint).collect(),
77 stops,
78 start,
79 end,
80 tile_mode,
81 },
82 Brush::RadialGradient {
83 colors,
84 stops,
85 center,
86 radius,
87 tile_mode,
88 } => Brush::RadialGradient {
89 colors: colors.into_iter().map(paint).collect(),
90 stops,
91 center,
92 radius,
93 tile_mode,
94 },
95 Brush::SweepGradient {
96 colors,
97 stops,
98 center,
99 } => Brush::SweepGradient {
100 colors: colors.into_iter().map(paint).collect(),
101 stops,
102 center,
103 },
104 }
105}
106
107fn scale_brush_geometry(brush: Brush, layer: &GraphicsLayer) -> Brush {
108 let scale_x = layer_scale_x(layer);
109 let scale_y = layer_scale_y(layer);
110 let uniform_scale = layer_uniform_scale(layer);
111
112 match brush {
113 Brush::Solid(color) => Brush::Solid(color),
114 Brush::LinearGradient {
115 colors,
116 stops,
117 mut start,
118 mut end,
119 tile_mode,
120 } => {
121 start.x *= scale_x;
122 start.y *= scale_y;
123 end.x *= scale_x;
124 end.y *= scale_y;
125 Brush::LinearGradient {
126 colors,
127 stops,
128 start,
129 end,
130 tile_mode,
131 }
132 }
133 Brush::RadialGradient {
134 colors,
135 stops,
136 mut center,
137 mut radius,
138 tile_mode,
139 } => {
140 center.x *= scale_x;
141 center.y *= scale_y;
142 radius *= uniform_scale;
143 Brush::RadialGradient {
144 colors,
145 stops,
146 center,
147 radius,
148 tile_mode,
149 }
150 }
151 Brush::SweepGradient {
152 colors,
153 stops,
154 mut center,
155 } => {
156 center.x *= scale_x;
157 center.y *= scale_y;
158 Brush::SweepGradient {
159 colors,
160 stops,
161 center,
162 }
163 }
164 }
165}
166
167#[derive(Clone, Debug, PartialEq)]
175pub enum ResolvedBrush {
176 Solid(Color),
178 Other(Brush),
180}
181
182impl ResolvedBrush {
183 pub fn from_brush(brush: Brush) -> Self {
185 match brush {
186 Brush::Solid(color) => Self::Solid(color),
187 other => Self::Other(other),
188 }
189 }
190
191 pub fn into_brush(self) -> Brush {
194 match self {
195 Self::Solid(color) => Brush::Solid(color),
196 Self::Other(brush) => brush,
197 }
198 }
199}
200
201pub fn resolve_layer_brush(brush: &Brush, layer: &GraphicsLayer) -> ResolvedBrush {
206 match brush {
207 Brush::Solid(color) => {
208 if layer.alpha == 1.0
209 && layer.color_filter.is_none()
210 && layer_scale_x(layer) == 1.0
211 && layer_scale_y(layer) == 1.0
212 {
213 ResolvedBrush::Solid(*color)
214 } else {
215 ResolvedBrush::Solid(apply_layer_to_color(*color, layer))
216 }
217 }
218 other => ResolvedBrush::Other(apply_layer_to_brush(other.clone(), layer)),
219 }
220}
221
222pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
224 CornerRadii {
225 top_left: radii.top_left * scale,
226 top_right: radii.top_right * scale,
227 bottom_right: radii.bottom_right * scale,
228 bottom_left: radii.bottom_left * scale,
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
234pub enum DrawPlacement {
235 Behind,
237 Overlay,
239}
240
241pub fn primitives_for_placement(
244 command: &DrawCommand,
245 placement: DrawPlacement,
246 size: Size,
247) -> Vec<DrawPrimitive> {
248 recording_for_placement_reusing(command, placement, size, CommandRecording::default)
249 .map(|(recording, segments)| recording.primitives(segments).collect())
250 .unwrap_or_default()
251}
252
253pub fn recording_for_placement_reusing(
260 command: &DrawCommand,
261 placement: DrawPlacement,
262 size: Size,
263 storage: impl FnOnce() -> CommandRecording,
264) -> Option<(CommandRecording, Range<u32>)> {
265 let record = move |func: &DrawCommandFn| {
266 let mut scope = cranpose_ui::command_draw_scope_reusing(size, storage());
267 func(&mut scope);
268 scope.finish()
269 };
270 match (placement, command) {
271 (DrawPlacement::Behind, DrawCommand::Behind(func))
272 | (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => {
273 let recording = record(func);
274 let segments = recording.all_segments();
275 Some((recording, segments))
276 }
277 (_, DrawCommand::WithContent(func)) => {
278 let recording = record(func);
279 let segments = recording.content_split(placement == DrawPlacement::Behind);
280 Some((recording, segments))
281 }
282 _ => None,
283 }
284}
285
286#[cfg(test)]
287#[path = "tests/style_shared_tests.rs"]
288mod tests;