1use std::{ops::Range, rc::Rc};
2
3use cranpose_ui::{Brush, DrawCommand, DrawCommandFn};
4use cranpose_ui_graphics::{
5 Color, ColorFilter, CommandRecording, CornerRadii, DrawPlacement, DrawPrimitive, GraphicsLayer,
6 Size,
7};
8
9pub use crate::graph::quad_bounds;
10use crate::layer_transform::{layer_scale_x, layer_scale_y, layer_uniform_scale};
11
12pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
22 apply_paint_to_color(color, layer.alpha, layer.color_filter.as_ref())
23}
24
25pub fn apply_paint_to_color(color: Color, alpha: f32, color_filter: Option<&ColorFilter>) -> Color {
29 let color = color.srgb_8bit();
30 let color = Color(color.0, color.1, color.2, (color.3 * alpha).clamp(0.0, 1.0));
31 match color_filter {
32 Some(filter) => {
33 let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
34 Color(r, g, b, a)
35 }
36 None => color,
37 }
38}
39
40pub fn compose_color_filters(
42 base: Option<&ColorFilter>,
43 overlay: Option<&ColorFilter>,
44) -> Option<ColorFilter> {
45 match (base, overlay) {
46 (None, None) => None,
47 (Some(filter), None) | (None, Some(filter)) => Some(filter.clone()),
48 (Some(filter), Some(next)) => Some(filter.compose(next)),
49 }
50}
51
52pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
55 if layer.alpha == 1.0
56 && layer.color_filter.is_none()
57 && layer_scale_x(layer) == 1.0
58 && layer_scale_y(layer) == 1.0
59 {
60 return brush.map_colors(Color::srgb_8bit);
61 }
62 scale_brush_geometry(brush, layer).map_colors(|color| apply_layer_to_color(color, layer))
63}
64
65fn scale_brush_geometry(brush: Brush, layer: &GraphicsLayer) -> Brush {
66 let scale_x = layer_scale_x(layer);
67 let scale_y = layer_scale_y(layer);
68 let uniform_scale = layer_uniform_scale(layer);
69
70 match brush {
71 Brush::Solid(color) => Brush::Solid(color),
72 Brush::LinearGradient {
73 colors,
74 stops,
75 mut start,
76 mut end,
77 tile_mode,
78 } => {
79 start.x *= scale_x;
80 start.y *= scale_y;
81 end.x *= scale_x;
82 end.y *= scale_y;
83 Brush::LinearGradient {
84 colors,
85 stops,
86 start,
87 end,
88 tile_mode,
89 }
90 }
91 Brush::RadialGradient {
92 colors,
93 stops,
94 mut center,
95 mut radius,
96 tile_mode,
97 } => {
98 center.x *= scale_x;
99 center.y *= scale_y;
100 radius *= uniform_scale;
101 Brush::RadialGradient {
102 colors,
103 stops,
104 center,
105 radius,
106 tile_mode,
107 }
108 }
109 Brush::SweepGradient {
110 colors,
111 stops,
112 mut center,
113 } => {
114 center.x *= scale_x;
115 center.y *= scale_y;
116 Brush::SweepGradient {
117 colors,
118 stops,
119 center,
120 }
121 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq)]
133pub enum ResolvedBrush {
134 Solid(Color),
136 Other(Brush),
138}
139
140impl ResolvedBrush {
141 pub fn from_brush(brush: Brush) -> Self {
143 match brush {
144 Brush::Solid(color) => Self::Solid(color),
145 other => Self::Other(other),
146 }
147 }
148
149 pub fn into_brush(self) -> Brush {
152 match self {
153 Self::Solid(color) => Brush::Solid(color),
154 Self::Other(brush) => brush,
155 }
156 }
157}
158
159pub fn resolve_layer_brush(brush: &Brush, layer: &GraphicsLayer) -> ResolvedBrush {
164 match brush {
165 Brush::Solid(color) => {
166 if layer.alpha == 1.0
167 && layer.color_filter.is_none()
168 && layer_scale_x(layer) == 1.0
169 && layer_scale_y(layer) == 1.0
170 {
171 ResolvedBrush::Solid(*color)
172 } else {
173 ResolvedBrush::Solid(apply_layer_to_color(*color, layer))
174 }
175 }
176 other => ResolvedBrush::Other(apply_layer_to_brush(other.clone(), layer)),
177 }
178}
179
180pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
182 CornerRadii {
183 top_left: radii.top_left * scale,
184 top_right: radii.top_right * scale,
185 bottom_right: radii.bottom_right * scale,
186 bottom_left: radii.bottom_left * scale,
187 }
188}
189
190pub fn primitives_for_placement(
193 command: &DrawCommand,
194 placement: DrawPlacement,
195 size: Size,
196) -> Vec<DrawPrimitive> {
197 let Some((func, split)) = placement_func(command, placement) else {
198 return Vec::new();
199 };
200 let mut recording = CommandRecording::default();
201 let segments = record_with(func, split, size, &mut recording);
202 recording.primitives(segments).collect()
203}
204
205pub fn recording_for_placement_reusing(
214 command: &DrawCommand,
215 placement: DrawPlacement,
216 size: Size,
217 storage: impl FnOnce() -> Rc<CommandRecording>,
218) -> Option<(Rc<CommandRecording>, Range<u32>)> {
219 let (func, split) = placement_func(command, placement)?;
220 let mut shared = storage();
221 let segments = record_with(func, split, size, Rc::make_mut(&mut shared));
222 Some((shared, segments))
223}
224
225fn placement_func(
228 command: &DrawCommand,
229 placement: DrawPlacement,
230) -> Option<(&DrawCommandFn, Option<DrawPlacement>)> {
231 match (placement, command) {
232 (DrawPlacement::Behind, DrawCommand::Behind(func))
233 | (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => Some((func, None)),
234 (_, DrawCommand::WithContent(func)) => Some((func, Some(placement))),
235 _ => None,
236 }
237}
238
239fn record_with(
240 func: &DrawCommandFn,
241 split: Option<DrawPlacement>,
242 size: Size,
243 recording: &mut CommandRecording,
244) -> Range<u32> {
245 let mut scope = cranpose_ui::command_draw_scope(size, recording);
246 func(&mut scope);
247 scope.finish();
248 match split {
249 None => recording.all_segments(),
250 Some(placement) => recording.content_split(placement),
251 }
252}
253
254#[cfg(test)]
255#[path = "tests/style_shared_tests.rs"]
256mod tests;