Skip to main content

pfx_live/flat/
style.rs

1use super::icons::Icons;
2use super::sdf::{self, Field};
3use super::{
4    BLUR, DASHED, Dash, Draw, FILL_SOLID, FILL_VERTICAL, Fill, Fit, Instance, KIND_ARC,
5    KIND_CIRCLE, KIND_RECT, KIND_RING, Material, Matrix, Shadow, Shape, Srgba, Stroke, pack_plain,
6};
7
8pub const BEVEL: u32 = 1 << 24;
9
10pub const STYLE_CALL: &str =
11    "        fill = flat_bevel_over(v, kind, p, field, inverse, fill, fill_cover);\n";
12
13pub const STYLE_WGSL: &str = r#"
14const FLAT_BEVEL: u32 = 16777216u;
15
16fn flat_bevel_light(v: FlatVaryings, kind: u32, p: vec2f, inverse: mat2x2f) -> f32 {
17    let toward = v.material.yz;
18    if (kind == FLAT_RECT) {
19        let s = vec2f(flat_sign(p.x), flat_sign(p.y));
20        let q = abs(p) - max(v.shape.xy - vec2f(flat_corner(p, v.corners)), vec2f(0.0));
21        if (q.x > 0.0 && q.y > 0.0) {
22            return flat_coverage(vec3f(-dot(s * q, toward), -toward), inverse);
23        }
24        let x_lit = select(0.0, 1.0, s.x * toward.x > 0.0);
25        let y_lit = select(0.0, 1.0, s.y * toward.y > 0.0);
26        let x_side = flat_coverage(vec3f(q.y - q.x, -s.x, s.y), inverse);
27        return mix(y_lit, x_lit, x_side);
28    }
29    var side = 1.0;
30    if (kind == FLAT_RING || kind == FLAT_ARC) {
31        side = flat_sign(length(p) - v.shape.x);
32    }
33    return flat_coverage(vec3f(-side * dot(p, toward), -side * toward), inverse);
34}
35
36fn flat_bevel_over(v: FlatVaryings, kind: u32, p: vec2f, field: vec3f, inverse: mat2x2f, fill: vec4f, fill_cover: f32) -> vec4f {
37    if ((v.info.x & FLAT_BEVEL) == 0u) {
38        return fill;
39    }
40    let inner = flat_coverage(vec3f(field.x + v.material.x, field.yz), inverse);
41    let band = max(fill_cover - inner, 0.0);
42    let colour = mix(v.frame_y, v.frame_x, flat_bevel_light(v, kind, p, inverse));
43    let paint = vec4f(colour.rgb * colour.a, colour.a);
44    let share = select(0.0, band / fill_cover, fill_cover > 0.0);
45    return paint * band + fill * (1.0 - paint.a * share);
46}
47"#;
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub enum Style {
51    Outline(Outline),
52    Bevel(Bevel),
53    Flat,
54}
55
56impl Style {
57    pub fn outline(width: f32) -> Self {
58        Self::Outline(Outline::solid(width))
59    }
60
61    pub fn ghost(width: f32, on: f32, off: f32, alpha: f32) -> Self {
62        Self::Outline(Outline::ghost(width, on, off, alpha))
63    }
64
65    pub fn bevel(width: f32, light: Srgba, dark: Srgba) -> Self {
66        Self::Bevel(Bevel::new(width, light, dark))
67    }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq)]
71pub struct Outline {
72    pub width: f32,
73    pub dash: Option<Dash>,
74    pub colour: Option<Srgba>,
75    pub alpha: f32,
76}
77
78impl Outline {
79    pub fn solid(width: f32) -> Self {
80        Self {
81            width,
82            dash: None,
83            colour: None,
84            alpha: 1.0,
85        }
86    }
87
88    pub fn dashed(width: f32, on: f32, off: f32) -> Self {
89        Self {
90            dash: Some(Dash {
91                on,
92                off,
93                phase: 0.0,
94            }),
95            ..Self::solid(width)
96        }
97    }
98
99    pub fn ghost(width: f32, on: f32, off: f32, alpha: f32) -> Self {
100        Self {
101            alpha,
102            ..Self::dashed(width, on, off)
103        }
104    }
105
106    pub fn colour(mut self, colour: Srgba) -> Self {
107        self.colour = Some(colour);
108        self
109    }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct Bevel {
114    pub width: f32,
115    pub light: Srgba,
116    pub dark: Srgba,
117    pub toward: [f32; 2],
118}
119
120impl Bevel {
121    pub fn new(width: f32, light: Srgba, dark: Srgba) -> Self {
122        Self {
123            width,
124            light,
125            dark,
126            toward: [-1.0, -1.0],
127        }
128    }
129
130    pub fn toward(mut self, toward: [f32; 2]) -> Self {
131        self.toward = toward;
132        self
133    }
134}
135
136pub fn restyle(draws: &mut [Draw], style: Option<Style>) {
137    for draw in draws {
138        draw.style = style;
139    }
140}
141
142pub fn restyle_group(draws: &mut [Draw], group: u16, style: Option<Style>) {
143    for draw in draws.iter_mut().filter(|draw| draw.group == Some(group)) {
144        draw.style = style;
145    }
146}
147
148fn axes(transform: &Matrix) -> [f32; 4] {
149    [
150        transform[0][0],
151        transform[0][1],
152        transform[1][0],
153        transform[1][1],
154    ]
155}
156
157fn mean_scale(transform: &Matrix) -> f32 {
158    let [a, b, c, d] = axes(transform);
159    (a * d - c * b).abs().sqrt()
160}
161
162fn paint_colour(draw: &Draw) -> Option<Srgba> {
163    draw.stroke
164        .filter(|stroke| stroke.width > 0.0)
165        .map(|stroke| stroke.colour)
166        .or(match draw.fill {
167            Fill::None => None,
168            Fill::Solid(colour) => Some(colour),
169            Fill::Vertical(top, _) => Some(top),
170            Fill::Gradient(gradient) => gradient.stops().next().map(|(_, colour)| Srgba(colour)),
171            Fill::Image(image) => Some(image.tint),
172        })
173}
174
175pub fn resolve(draw: &Draw) -> Draw {
176    let mut out = *draw;
177    out.style = None;
178    let Some(style) = draw.style else {
179        return out;
180    };
181    if matches!(draw.shape, Shape::Text(_)) {
182        return out;
183    }
184    out.shadow = match draw.shadow {
185        Shadow::Glow(glow) => Shadow::Glow(glow),
186        _ => Shadow::None,
187    };
188    out.material = Material::default();
189    if let Style::Outline(outline) = style {
190        out.fill = Fill::None;
191        out.pattern = None;
192        let scale = mean_scale(&draw.transform);
193        let colour = outline.colour.or_else(|| paint_colour(draw));
194        out.stroke = match colour {
195            Some(colour)
196                if scale > 0.0
197                    && scale.is_finite()
198                    && outline.width > 0.0
199                    && outline.width.is_finite() =>
200            {
201                Some(Stroke {
202                    colour: colour.alpha(colour.0[3] * outline.alpha.clamp(0.0, 1.0)),
203                    width: outline.width / scale,
204                    dash: outline.dash.map(|dash| Dash {
205                        on: dash.on / scale,
206                        off: dash.off / scale,
207                        phase: dash.phase / scale,
208                    }),
209                })
210            }
211            _ => None,
212        };
213    }
214    out
215}
216
217pub(super) fn pack(
218    draw: &Draw,
219    style: Style,
220    fit: &Fit,
221    icons: Option<&Icons>,
222) -> Option<Instance> {
223    let resolved = resolve(draw);
224    let mut instance = pack_plain(&resolved, fit, icons)?;
225    let kind = instance.kind();
226    match style {
227        Style::Outline(_) => {
228            if let Some(dash) = resolved.stroke.and_then(|stroke| stroke.dash)
229                && matches!(kind, KIND_RING | KIND_ARC)
230                && instance.stroke[0] > 0.0
231                && dash.on > 0.0
232                && dash.off > 0.0
233            {
234                instance.info[0] |= DASHED;
235                instance.stroke[1] = dash.on;
236                instance.stroke[2] = dash.off;
237                instance.stroke[3] = dash.phase;
238            }
239        }
240        Style::Bevel(bevel) => {
241            if instance.info[0] & (FILL_SOLID | FILL_VERTICAL) != 0
242                && instance.info[0] & BLUR == 0
243                && matches!(kind, KIND_RECT | KIND_CIRCLE | KIND_RING | KIND_ARC)
244                && bevel.width > 0.0
245                && bevel.width.is_finite()
246            {
247                let [a, b, c, d] = axes(&draw.transform);
248                let det = a * d - c * b;
249                let unit = det.abs().sqrt() * fit.scale;
250                let pixels = (bevel.width * fit.scale).round().max(1.0);
251                let toward = unit_or(bevel.toward, [0.0, -1.0]);
252                let local = unit_or(
253                    [
254                        (d * toward[0] - c * toward[1]) / det,
255                        (-b * toward[0] + a * toward[1]) / det,
256                    ],
257                    [0.0, -1.0],
258                );
259                instance.material = [pixels / unit, local[0], local[1], 0.0];
260                instance.frame[0] = bevel.light.0;
261                instance.frame[1] = bevel.dark.0;
262                instance.info[0] |= BEVEL;
263            }
264        }
265        Style::Flat => {}
266    }
267    Some(instance)
268}
269
270fn unit_or(v: [f32; 2], fallback: [f32; 2]) -> [f32; 2] {
271    let length = (v[0] * v[0] + v[1] * v[1]).sqrt();
272    if length > 0.0 && length.is_finite() {
273        [v[0] / length, v[1] / length]
274    } else {
275        fallback
276    }
277}
278
279fn sign(value: f32) -> f32 {
280    if value >= 0.0 { 1.0 } else { -1.0 }
281}
282
283fn bevel_light(instance: &Instance, p: [f32; 2], inverse: [[f32; 2]; 2]) -> f32 {
284    let toward = [instance.material[1], instance.material[2]];
285    let shape = instance.shape;
286    match instance.kind() {
287        KIND_RECT => {
288            let s = [sign(p[0]), sign(p[1])];
289            let radius = sdf::corner(p, instance.corners);
290            let q = [
291                p[0].abs() - (shape[0] - radius).max(0.0),
292                p[1].abs() - (shape[1] - radius).max(0.0),
293            ];
294            if q[0] > 0.0 && q[1] > 0.0 {
295                let lean = s[0] * q[0] * toward[0] + s[1] * q[1] * toward[1];
296                return sdf::coverage([-lean, -toward[0], -toward[1]], inverse);
297            }
298            let x_lit = if s[0] * toward[0] > 0.0 { 1.0 } else { 0.0 };
299            let y_lit = if s[1] * toward[1] > 0.0 { 1.0 } else { 0.0 };
300            let x_side = sdf::coverage([q[1] - q[0], -s[0], s[1]], inverse);
301            y_lit + (x_lit - y_lit) * x_side
302        }
303        kind => {
304            let side = if matches!(kind, KIND_RING | KIND_ARC) {
305                sign((p[0] * p[0] + p[1] * p[1]).sqrt() - shape[0])
306            } else {
307                1.0
308            };
309            let lean = side * (p[0] * toward[0] + p[1] * toward[1]);
310            sdf::coverage([-lean, -side * toward[0], -side * toward[1]], inverse)
311        }
312    }
313}
314
315pub fn bevel_over(
316    instance: &Instance,
317    p: [f32; 2],
318    field: Field,
319    inverse: [[f32; 2]; 2],
320    fill: [f32; 4],
321    fill_cover: f32,
322) -> [f32; 4] {
323    if instance.info[0] & BEVEL == 0 {
324        return fill;
325    }
326    let inner = sdf::coverage(
327        [field[0] + instance.material[0], field[1], field[2]],
328        inverse,
329    );
330    let band = (fill_cover - inner).max(0.0);
331    let towards = bevel_light(instance, p, inverse);
332    let dark = instance.frame[1];
333    let light = instance.frame[0];
334    let colour: [f32; 4] = std::array::from_fn(|i| dark[i] + (light[i] - dark[i]) * towards);
335    let paint = [
336        colour[0] * colour[3],
337        colour[1] * colour[3],
338        colour[2] * colour[3],
339        colour[3],
340    ];
341    let share = if fill_cover > 0.0 {
342        band / fill_cover
343    } else {
344        0.0
345    };
346    std::array::from_fn(|i| paint[i] * band + fill[i] * (1.0 - paint[3] * share))
347}