use std::{ops::Range, rc::Rc};
use cranpose_ui::{Brush, DrawCommand, DrawCommandFn};
use cranpose_ui_graphics::{
Color, ColorFilter, CommandRecording, CornerRadii, DrawPrimitive, GraphicsLayer, Size,
};
pub use crate::graph::quad_bounds;
use crate::layer_transform::{layer_scale_x, layer_scale_y, layer_uniform_scale};
pub fn apply_layer_to_color(color: Color, layer: &GraphicsLayer) -> Color {
apply_paint_to_color(color, layer.alpha, layer.color_filter.as_ref())
}
pub fn apply_paint_to_color(color: Color, alpha: f32, color_filter: Option<&ColorFilter>) -> Color {
let color = color.srgb_8bit();
let color = Color(color.0, color.1, color.2, (color.3 * alpha).clamp(0.0, 1.0));
match color_filter {
Some(filter) => {
let [r, g, b, a] = filter.apply_rgba([color.0, color.1, color.2, color.3]);
Color(r, g, b, a)
}
None => color,
}
}
pub fn compose_color_filters(
base: Option<&ColorFilter>,
overlay: Option<&ColorFilter>,
) -> Option<ColorFilter> {
match (base, overlay) {
(None, None) => None,
(Some(filter), None) | (None, Some(filter)) => Some(filter.clone()),
(Some(filter), Some(next)) => Some(filter.compose(next)),
}
}
pub fn apply_layer_to_brush(brush: Brush, layer: &GraphicsLayer) -> Brush {
if layer.alpha == 1.0
&& layer.color_filter.is_none()
&& layer_scale_x(layer) == 1.0
&& layer_scale_y(layer) == 1.0
{
return map_brush_colors(brush, Color::srgb_8bit);
}
map_brush_colors(scale_brush_geometry(brush, layer), |color| {
apply_layer_to_color(color, layer)
})
}
fn map_brush_colors(brush: Brush, paint: impl Fn(Color) -> Color) -> Brush {
match brush {
Brush::Solid(color) => Brush::solid(paint(color)),
Brush::LinearGradient {
colors,
stops,
start,
end,
tile_mode,
} => Brush::LinearGradient {
colors: colors.into_iter().map(paint).collect(),
stops,
start,
end,
tile_mode,
},
Brush::RadialGradient {
colors,
stops,
center,
radius,
tile_mode,
} => Brush::RadialGradient {
colors: colors.into_iter().map(paint).collect(),
stops,
center,
radius,
tile_mode,
},
Brush::SweepGradient {
colors,
stops,
center,
} => Brush::SweepGradient {
colors: colors.into_iter().map(paint).collect(),
stops,
center,
},
}
}
fn scale_brush_geometry(brush: Brush, layer: &GraphicsLayer) -> Brush {
let scale_x = layer_scale_x(layer);
let scale_y = layer_scale_y(layer);
let uniform_scale = layer_uniform_scale(layer);
match brush {
Brush::Solid(color) => Brush::Solid(color),
Brush::LinearGradient {
colors,
stops,
mut start,
mut end,
tile_mode,
} => {
start.x *= scale_x;
start.y *= scale_y;
end.x *= scale_x;
end.y *= scale_y;
Brush::LinearGradient {
colors,
stops,
start,
end,
tile_mode,
}
}
Brush::RadialGradient {
colors,
stops,
mut center,
mut radius,
tile_mode,
} => {
center.x *= scale_x;
center.y *= scale_y;
radius *= uniform_scale;
Brush::RadialGradient {
colors,
stops,
center,
radius,
tile_mode,
}
}
Brush::SweepGradient {
colors,
stops,
mut center,
} => {
center.x *= scale_x;
center.y *= scale_y;
Brush::SweepGradient {
colors,
stops,
center,
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ResolvedBrush {
Solid(Color),
Other(Brush),
}
impl ResolvedBrush {
pub fn from_brush(brush: Brush) -> Self {
match brush {
Brush::Solid(color) => Self::Solid(color),
other => Self::Other(other),
}
}
pub fn into_brush(self) -> Brush {
match self {
Self::Solid(color) => Brush::Solid(color),
Self::Other(brush) => brush,
}
}
}
pub fn resolve_layer_brush(brush: &Brush, layer: &GraphicsLayer) -> ResolvedBrush {
match brush {
Brush::Solid(color) => {
if layer.alpha == 1.0
&& layer.color_filter.is_none()
&& layer_scale_x(layer) == 1.0
&& layer_scale_y(layer) == 1.0
{
ResolvedBrush::Solid(*color)
} else {
ResolvedBrush::Solid(apply_layer_to_color(*color, layer))
}
}
other => ResolvedBrush::Other(apply_layer_to_brush(other.clone(), layer)),
}
}
pub fn scale_corner_radii(radii: CornerRadii, scale: f32) -> CornerRadii {
CornerRadii {
top_left: radii.top_left * scale,
top_right: radii.top_right * scale,
bottom_right: radii.bottom_right * scale,
bottom_left: radii.bottom_left * scale,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DrawPlacement {
Behind,
Overlay,
}
pub fn primitives_for_placement(
command: &DrawCommand,
placement: DrawPlacement,
size: Size,
) -> Vec<DrawPrimitive> {
let Some((func, split)) = placement_func(command, placement) else {
return Vec::new();
};
let mut recording = CommandRecording::default();
let segments = record_with(func, split, size, &mut recording);
recording.primitives(segments).collect()
}
pub fn recording_for_placement_reusing(
command: &DrawCommand,
placement: DrawPlacement,
size: Size,
storage: impl FnOnce() -> Rc<CommandRecording>,
) -> Option<(Rc<CommandRecording>, Range<u32>)> {
let (func, split) = placement_func(command, placement)?;
let mut shared = storage();
let segments = record_with(func, split, size, Rc::make_mut(&mut shared));
Some((shared, segments))
}
fn placement_func(
command: &DrawCommand,
placement: DrawPlacement,
) -> Option<(&DrawCommandFn, Option<bool>)> {
match (placement, command) {
(DrawPlacement::Behind, DrawCommand::Behind(func))
| (DrawPlacement::Overlay, DrawCommand::Overlay(func)) => Some((func, None)),
(_, DrawCommand::WithContent(func)) => {
Some((func, Some(placement == DrawPlacement::Behind)))
}
_ => None,
}
}
fn record_with(
func: &DrawCommandFn,
split: Option<bool>,
size: Size,
recording: &mut CommandRecording,
) -> Range<u32> {
let mut scope = cranpose_ui::command_draw_scope(size, recording);
func(&mut scope);
scope.finish();
match split {
None => recording.all_segments(),
Some(behind) => recording.content_split(behind),
}
}
#[cfg(test)]
#[path = "tests/style_shared_tests.rs"]
mod tests;