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