Skip to main content

lumen_core/
style_overrides.rs

1//! The values runtime systems hold on an element over what its styles say.
2//!
3//! An element's styles are a function of the cascade: the user-agent and skin
4//! sheets, the app's stylesheet, what its markup declares and what a script
5//! set with `set_style`. A restyle recomputes them from all of that, so a
6//! class removed takes with it every property only that class set.
7//!
8//! Some values are not the cascade's to decide. A hidden `<if>` is
9//! `display: none` while its signal is false; an open popup sits where the
10//! edge-flip put it; a virtualized row sits in its slot; a hovered button
11//! fades toward its tint. The system that owns such a value holds it here,
12//! in [`StyleOverrides`], instead of writing the live component behind the
13//! cascade's back. The live components are always the cascade's result with
14//! every held value laid over it, so a restyle that recomputes the cascade
15//! keeps them: it records the new resting values here and leaves the held
16//! fields alone, and when a system lets go the field drops back to what the
17//! cascade says now, not what it said when the system took hold.
18//!
19//! Holders stack by [`StyleOwner::RANK`]: a press tint over a hover tint over
20//! a toggle's checked fill over the cascade. A holder that blends toward its
21//! own value from whatever is under it (a tint fading in) reads that through
22//! [`StyleOverrides::below`].
23
24use std::any::TypeId;
25use std::ops::DerefMut;
26
27use bevy_ecs::component::Component;
28use bevy_ecs::world::EntityWorldMut;
29
30use crate::components::{
31    Border, Color, Display, Edges, Fill, Length, Opacity, Position, ShadowSpec, Style, TextStyle,
32    Visuals,
33};
34
35bitflags::bitflags! {
36    /// The style values a runtime system can hold on an element.
37    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
38    pub struct StyleFields: u16 {
39        /// `Style::display`.
40        const DISPLAY = 1 << 0;
41        /// `Style::position`.
42        const POSITION = 1 << 1;
43        /// `Style::inset`, all four sides.
44        const INSET = 1 << 2;
45        /// `Style::width`.
46        const WIDTH = 1 << 3;
47        /// `Style::height`.
48        const HEIGHT = 1 << 4;
49        /// `Style::min_height`.
50        const MIN_HEIGHT = 1 << 5;
51        /// `Visuals::fill`.
52        const FILL = 1 << 6;
53        /// `Visuals::shadows`.
54        const SHADOWS = 1 << 7;
55        /// `Visuals::border`.
56        const BORDER = 1 << 8;
57        /// The [`Opacity`] component, absent meaning fully opaque.
58        const OPACITY = 1 << 9;
59        /// `TextStyle::color`.
60        const TEXT_COLOR = 1 << 10;
61        /// Every field that lives on [`Style`].
62        const LAYOUT = Self::DISPLAY.bits()
63            | Self::POSITION.bits()
64            | Self::INSET.bits()
65            | Self::WIDTH.bits()
66            | Self::HEIGHT.bits()
67            | Self::MIN_HEIGHT.bits();
68        /// Every field that lives on [`Visuals`].
69        const VISUALS = Self::FILL.bits() | Self::SHADOWS.bits() | Self::BORDER.bits();
70    }
71}
72
73/// A value for some of the [`StyleFields`]; `None` leaves a field out.
74#[derive(Clone, Debug, Default, PartialEq)]
75pub struct StyleValues {
76    /// `Style::display`.
77    pub display: Option<Display>,
78    /// `Style::position`.
79    pub position: Option<Position>,
80    /// `Style::inset`.
81    pub inset: Option<Edges>,
82    /// `Style::width`.
83    pub width: Option<Length>,
84    /// `Style::height`.
85    pub height: Option<Length>,
86    /// `Style::min_height`.
87    pub min_height: Option<Length>,
88    /// `Visuals::fill`.
89    pub fill: Option<Option<Fill>>,
90    /// `Visuals::shadows`.
91    pub shadows: Option<Vec<ShadowSpec>>,
92    /// `Visuals::border`.
93    pub border: Option<Option<Border>>,
94    /// The [`Opacity`] component; `Some(None)` is no component.
95    pub opacity: Option<Option<f32>>,
96    /// `TextStyle::color`.
97    pub text_color: Option<Color>,
98}
99
100/// The live components of one element, read when a holder takes a field
101/// nobody held yet: at that moment the live value is the cascade's.
102#[derive(Clone, Copy, Debug, Default)]
103pub struct Live<'a> {
104    /// The element's layout style.
105    pub style: Option<&'a Style>,
106    /// The element's paint.
107    pub visuals: Option<&'a Visuals>,
108    /// The element's text style.
109    pub text: Option<&'a TextStyle>,
110    /// The element's opacity; absent is fully opaque.
111    pub opacity: Option<&'a Opacity>,
112}
113
114impl StyleValues {
115    /// The fields this sets.
116    pub fn fields(&self) -> StyleFields {
117        let mut f = StyleFields::empty();
118        f.set(StyleFields::DISPLAY, self.display.is_some());
119        f.set(StyleFields::POSITION, self.position.is_some());
120        f.set(StyleFields::INSET, self.inset.is_some());
121        f.set(StyleFields::WIDTH, self.width.is_some());
122        f.set(StyleFields::HEIGHT, self.height.is_some());
123        f.set(StyleFields::MIN_HEIGHT, self.min_height.is_some());
124        f.set(StyleFields::FILL, self.fill.is_some());
125        f.set(StyleFields::SHADOWS, self.shadows.is_some());
126        f.set(StyleFields::BORDER, self.border.is_some());
127        f.set(StyleFields::OPACITY, self.opacity.is_some());
128        f.set(StyleFields::TEXT_COLOR, self.text_color.is_some());
129        f
130    }
131
132    /// Read `fields` off the live components. A field whose component is
133    /// missing is left out, except opacity, whose absence is a value.
134    pub fn read(fields: StyleFields, live: &Live<'_>) -> Self {
135        let mut v = Self::default();
136        if let Some(s) = live.style {
137            v.display = fields.contains(StyleFields::DISPLAY).then_some(s.display);
138            v.position = fields.contains(StyleFields::POSITION).then_some(s.position);
139            v.inset = fields.contains(StyleFields::INSET).then_some(s.inset);
140            v.width = fields.contains(StyleFields::WIDTH).then_some(s.width);
141            v.height = fields.contains(StyleFields::HEIGHT).then_some(s.height);
142            v.min_height = fields
143                .contains(StyleFields::MIN_HEIGHT)
144                .then_some(s.min_height);
145        }
146        if let Some(vis) = live.visuals {
147            v.fill = fields.contains(StyleFields::FILL).then(|| vis.fill.clone());
148            v.shadows = fields
149                .contains(StyleFields::SHADOWS)
150                .then(|| vis.shadows.clone());
151            v.border = fields.contains(StyleFields::BORDER).then_some(vis.border);
152        }
153        if fields.contains(StyleFields::OPACITY) {
154            v.opacity = Some(live.opacity.map(|o| o.0));
155        }
156        if let Some(t) = live.text {
157            v.text_color = fields.contains(StyleFields::TEXT_COLOR).then_some(t.color);
158        }
159        v
160    }
161
162    /// Lay `top` over these values: every field it sets wins.
163    pub fn overlay(&mut self, top: &StyleValues) {
164        if top.display.is_some() {
165            self.display = top.display;
166        }
167        if top.position.is_some() {
168            self.position = top.position;
169        }
170        if top.inset.is_some() {
171            self.inset = top.inset;
172        }
173        if top.width.is_some() {
174            self.width = top.width;
175        }
176        if top.height.is_some() {
177            self.height = top.height;
178        }
179        if top.min_height.is_some() {
180            self.min_height = top.min_height;
181        }
182        if top.fill.is_some() {
183            self.fill.clone_from(&top.fill);
184        }
185        if top.shadows.is_some() {
186            self.shadows.clone_from(&top.shadows);
187        }
188        if top.border.is_some() {
189            self.border = top.border;
190        }
191        if top.opacity.is_some() {
192            self.opacity = top.opacity;
193        }
194        if top.text_color.is_some() {
195            self.text_color = top.text_color;
196        }
197    }
198
199    /// Keep only `fields`.
200    pub fn restricted(mut self, fields: StyleFields) -> Self {
201        let keep = |f: StyleFields| fields.contains(f);
202        if !keep(StyleFields::DISPLAY) {
203            self.display = None;
204        }
205        if !keep(StyleFields::POSITION) {
206            self.position = None;
207        }
208        if !keep(StyleFields::INSET) {
209            self.inset = None;
210        }
211        if !keep(StyleFields::WIDTH) {
212            self.width = None;
213        }
214        if !keep(StyleFields::HEIGHT) {
215            self.height = None;
216        }
217        if !keep(StyleFields::MIN_HEIGHT) {
218            self.min_height = None;
219        }
220        if !keep(StyleFields::FILL) {
221            self.fill = None;
222        }
223        if !keep(StyleFields::SHADOWS) {
224            self.shadows = None;
225        }
226        if !keep(StyleFields::BORDER) {
227            self.border = None;
228        }
229        if !keep(StyleFields::OPACITY) {
230            self.opacity = None;
231        }
232        if !keep(StyleFields::TEXT_COLOR) {
233            self.text_color = None;
234        }
235        self
236    }
237
238    /// Write the layout fields this sets onto `style`, touching only the
239    /// ones that differ so an unchanged value marks nothing changed.
240    pub fn write_style(&self, style: &mut impl DerefMut<Target = Style>) {
241        if let Some(d) = self.display
242            && style.display != d
243        {
244            style.display = d;
245        }
246        if let Some(p) = self.position
247            && style.position != p
248        {
249            style.position = p;
250        }
251        if let Some(i) = self.inset
252            && !edges_eq(&style.inset, &i)
253        {
254            style.inset = i;
255        }
256        if let Some(w) = self.width
257            && style.width != w
258        {
259            style.width = w;
260        }
261        if let Some(h) = self.height
262            && style.height != h
263        {
264            style.height = h;
265        }
266        if let Some(h) = self.min_height
267            && style.min_height != h
268        {
269            style.min_height = h;
270        }
271    }
272
273    /// Write the paint fields this sets onto `visuals`, touching only the
274    /// ones that differ.
275    pub fn write_visuals(&self, visuals: &mut impl DerefMut<Target = Visuals>) {
276        if let Some(f) = &self.fill
277            && visuals.fill != *f
278        {
279            visuals.fill.clone_from(f);
280        }
281        if let Some(s) = &self.shadows
282            && visuals.shadows != *s
283        {
284            visuals.shadows.clone_from(s);
285        }
286        if let Some(b) = self.border
287            && visuals.border != b
288        {
289            visuals.border = b;
290        }
291    }
292
293    /// Write the text color this sets onto `text`, if it differs.
294    pub fn write_text(&self, text: &mut impl DerefMut<Target = TextStyle>) {
295        if let Some(c) = self.text_color
296            && text.color != c
297        {
298            text.color = c;
299        }
300    }
301
302    /// What to do with the [`Opacity`] component to make it hold the
303    /// opacity this sets: `None` leaves it as it is, `Some(None)` removes
304    /// it, `Some(Some(o))` writes `o`.
305    pub fn opacity_change(&self, current: Option<&Opacity>) -> Option<Option<Opacity>> {
306        let want = self.opacity?;
307        match (want, current) {
308            (None, None) => None,
309            (Some(o), Some(cur)) if cur.0 == o => None,
310            (want, _) => Some(want.map(Opacity)),
311        }
312    }
313}
314
315/// Inset sides compared the way layout reads them: `NaN` is `auto`, and
316/// `auto` equals `auto`.
317fn edges_eq(a: &Edges, b: &Edges) -> bool {
318    let side = |x: f32, y: f32| (x.is_nan() && y.is_nan()) || x == y;
319    side(a.left, b.left)
320        && side(a.right, b.right)
321        && side(a.top, b.top)
322        && side(a.bottom, b.bottom)
323        && a.inline_start == b.inline_start
324        && a.inline_end == b.inline_end
325        && a.block_start == b.block_start
326        && a.block_end == b.block_end
327        && a.pct_left == b.pct_left
328        && a.pct_right == b.pct_right
329        && a.pct_top == b.pct_top
330        && a.pct_bottom == b.pct_bottom
331}
332
333/// A runtime system that holds style values on elements.
334///
335/// Implemented on a type the system owns (usually the component that marks
336/// what it drives), which names its layer in [`StyleOverrides`].
337pub trait StyleOwner: 'static {
338    /// Where the owner's values sit: a higher rank lays over a lower one.
339    ///
340    /// The shipped systems use, lowest first: 10 for a CSS `transition`
341    /// tween, 20 for a widget's own state fill (a toggle's checked track,
342    /// a selected tab) and for the layout holders, 30 for the `:disabled`
343    /// / `:drag-over` state patches, 40 for the hover tint, 50 for the
344    /// press tint, 60 for the hover and focus borders.
345    const RANK: u8;
346}
347
348#[derive(Clone, Debug)]
349struct Claim {
350    owner: TypeId,
351    rank: u8,
352    values: StyleValues,
353}
354
355/// The values runtime systems hold on one element; see the module docs.
356///
357/// Carried by every element a holder can act on (its marker components
358/// require it), so a holder writes here in place rather than through a
359/// deferred insert another holder could race.
360#[derive(Component, Clone, Debug, Default)]
361pub struct StyleOverrides {
362    /// The cascade's value for every field some claim holds.
363    resting: StyleValues,
364    /// Ascending by rank; equal ranks keep the order they arrived in.
365    claims: Vec<Claim>,
366}
367
368impl StyleOverrides {
369    /// Every field some holder holds.
370    pub fn held(&self) -> StyleFields {
371        self.claims
372            .iter()
373            .fold(StyleFields::empty(), |f, c| f | c.values.fields())
374    }
375
376    /// True when nothing is held.
377    pub fn is_empty(&self) -> bool {
378        self.claims.is_empty()
379    }
380
381    /// What `O` holds, if anything.
382    pub fn held_by<O: StyleOwner>(&self) -> Option<&StyleValues> {
383        let owner = TypeId::of::<O>();
384        self.claims
385            .iter()
386            .find(|c| c.owner == owner)
387            .map(|c| &c.values)
388    }
389
390    /// What the cascade resolved for the held fields.
391    pub fn resting(&self) -> &StyleValues {
392        &self.resting
393    }
394
395    /// Hold `values` for `O`, replacing whatever `O` held before.
396    ///
397    /// `live` is read for a field nobody held yet: until then the live
398    /// value is the cascade's. Returns the composed value of every field
399    /// whose top layer may have moved (what `O` held before and holds now),
400    /// to write onto the live components.
401    pub fn hold<O: StyleOwner>(&mut self, values: StyleValues, live: &Live<'_>) -> StyleValues {
402        self.hold_reading::<O>(values, |fields| StyleValues::read(fields, live))
403    }
404
405    /// [`Self::hold`] with the live values read by `read`, which is handed
406    /// the fields nobody held yet.
407    fn hold_reading<O: StyleOwner>(
408        &mut self,
409        values: StyleValues,
410        read: impl FnOnce(StyleFields) -> StyleValues,
411    ) -> StyleValues {
412        let owner = TypeId::of::<O>();
413        let before = self.held();
414        let fresh = values.fields() - before;
415        if !fresh.is_empty() {
416            self.resting.overlay(&read(fresh).restricted(fresh));
417        }
418        let previous = match self.claims.iter_mut().find(|c| c.owner == owner) {
419            Some(claim) => std::mem::replace(&mut claim.values, values).fields(),
420            None => {
421                let at = self
422                    .claims
423                    .iter()
424                    .position(|c| c.rank > O::RANK)
425                    .unwrap_or(self.claims.len());
426                self.claims.insert(
427                    at,
428                    Claim {
429                        owner,
430                        rank: O::RANK,
431                        values,
432                    },
433                );
434                StyleFields::empty()
435            }
436        };
437        let affected = previous | self.held();
438        let out = self.composed(affected);
439        self.drop_unheld_resting();
440        out
441    }
442
443    /// Stop holding anything for `O`. Returns the composed value of every
444    /// field it held, which for a field nobody else holds is the cascade's,
445    /// to write onto the live components.
446    pub fn release<O: StyleOwner>(&mut self) -> StyleValues {
447        let owner = TypeId::of::<O>();
448        let Some(at) = self.claims.iter().position(|c| c.owner == owner) else {
449            return StyleValues::default();
450        };
451        let fields = self.claims.remove(at).values.fields();
452        let out = self.composed(fields);
453        self.drop_unheld_resting();
454        out
455    }
456
457    /// The value of `fields` under `O`'s layer: the cascade's, with every
458    /// lower-ranked holder laid over it. A field nobody holds reads `live`.
459    pub fn below<O: StyleOwner>(&self, fields: StyleFields, live: &Live<'_>) -> StyleValues {
460        let owner = TypeId::of::<O>();
461        let held = self.held();
462        let mut out = StyleValues::read(fields - held, live);
463        out.overlay(&self.resting.clone().restricted(fields & held));
464        for claim in &self.claims {
465            if claim.rank > O::RANK || claim.owner == owner {
466                break;
467            }
468            out.overlay(&claim.values.clone().restricted(fields));
469        }
470        out
471    }
472
473    /// Record what a restyle resolved: `fresh` is the cascade's value for
474    /// every field. Returns the composed value of every held field, to lay
475    /// over `fresh` before it is written onto the live components.
476    pub fn rebase(&mut self, fresh: &StyleValues) -> StyleValues {
477        let held = self.held();
478        self.resting = fresh.clone().restricted(held);
479        self.composed(held)
480    }
481
482    /// The top value of each of `fields`: the cascade's with every holder
483    /// laid over it in rank order.
484    fn composed(&self, fields: StyleFields) -> StyleValues {
485        let mut out = self.resting.clone().restricted(fields);
486        for claim in &self.claims {
487            out.overlay(&claim.values.clone().restricted(fields));
488        }
489        out
490    }
491
492    /// Forget the resting value of a field nobody holds any more, so the
493    /// next holder reads it fresh off the live components.
494    fn drop_unheld_resting(&mut self) {
495        let held = self.held();
496        self.resting = std::mem::take(&mut self.resting).restricted(held);
497    }
498}
499
500/// Hold `values` for `O` on one element and write what it shows now: the
501/// form for a holder that reaches the element through the world rather than
502/// through a query. Inserts the [`StyleOverrides`] the element lacks.
503pub fn hold_on<O: StyleOwner>(ent: &mut EntityWorldMut<'_>, values: StyleValues) {
504    let live = StyleValues::read(
505        values.fields(),
506        &Live {
507            style: ent.get::<Style>(),
508            visuals: ent.get::<Visuals>(),
509            text: ent.get::<TextStyle>(),
510            opacity: ent.get::<Opacity>(),
511        },
512    );
513    let mut overrides = ent.take::<StyleOverrides>().unwrap_or_default();
514    let out = overrides.hold_reading::<O>(values, |_| live);
515    ent.insert(overrides);
516    write_on(ent, &out);
517}
518
519/// Stop holding anything for `O` on one element and write what it shows
520/// now; see [`hold_on`].
521pub fn release_on<O: StyleOwner>(ent: &mut EntityWorldMut<'_>) {
522    let Some(mut overrides) = ent.get_mut::<StyleOverrides>() else {
523        return;
524    };
525    let out = overrides.release::<O>();
526    write_on(ent, &out);
527}
528
529/// Write `values` onto an element's live components, touching only what
530/// differs.
531pub fn write_on(ent: &mut EntityWorldMut<'_>, values: &StyleValues) {
532    let fields = values.fields();
533    if fields.intersects(StyleFields::LAYOUT)
534        && let Some(mut style) = ent.get_mut::<Style>()
535    {
536        values.write_style(&mut style);
537    }
538    if fields.intersects(StyleFields::VISUALS)
539        && let Some(mut visuals) = ent.get_mut::<Visuals>()
540    {
541        values.write_visuals(&mut visuals);
542    }
543    if fields.contains(StyleFields::TEXT_COLOR)
544        && let Some(mut text) = ent.get_mut::<TextStyle>()
545    {
546        values.write_text(&mut text);
547    }
548    match values.opacity_change(ent.get::<Opacity>()) {
549        Some(Some(o)) => {
550            ent.insert(o);
551        }
552        Some(None) => {
553            ent.remove::<Opacity>();
554        }
555        None => {}
556    }
557}
558
559#[cfg(test)]
560mod tests {
561    use super::*;
562
563    struct Low;
564    impl StyleOwner for Low {
565        const RANK: u8 = 10;
566    }
567    struct High;
568    impl StyleOwner for High {
569        const RANK: u8 = 50;
570    }
571
572    fn solid(r: f32) -> Option<Fill> {
573        Some(Fill::Solid(Color::rgb(r, 0.0, 0.0)))
574    }
575
576    fn visuals(r: f32) -> Visuals {
577        Visuals {
578            fill: solid(r),
579            ..Visuals::default()
580        }
581    }
582
583    #[test]
584    fn a_held_value_survives_a_rebase_and_lets_go_to_the_new_resting_value() {
585        let mut ov = StyleOverrides::default();
586        let live = visuals(0.1);
587        let out = ov.hold::<Low>(
588            StyleValues {
589                fill: Some(solid(0.5)),
590                ..StyleValues::default()
591            },
592            &Live {
593                visuals: Some(&live),
594                ..Live::default()
595            },
596        );
597        assert_eq!(out.fill, Some(solid(0.5)));
598        assert_eq!(ov.resting().fill, Some(solid(0.1)));
599
600        // The cascade moves the resting fill while the value is held.
601        let out = ov.rebase(&StyleValues {
602            fill: Some(solid(0.2)),
603            display: Some(Display::Flex),
604            ..StyleValues::default()
605        });
606        assert_eq!(out.fill, Some(solid(0.5)), "the held value stays on top");
607        assert_eq!(out.display, None, "only held fields come back");
608
609        let out = ov.release::<Low>();
610        assert_eq!(
611            out.fill,
612            Some(solid(0.2)),
613            "letting go lands on the new value"
614        );
615        assert!(ov.is_empty());
616        assert_eq!(ov.resting().fill, None);
617    }
618
619    #[test]
620    fn holders_stack_by_rank_and_below_reads_the_layer_under_one() {
621        let mut ov = StyleOverrides::default();
622        let live = visuals(0.1);
623        let l = Live {
624            visuals: Some(&live),
625            ..Live::default()
626        };
627        ov.hold::<High>(
628            StyleValues {
629                fill: Some(solid(0.9)),
630                ..StyleValues::default()
631            },
632            &l,
633        );
634        let out = ov.hold::<Low>(
635            StyleValues {
636                fill: Some(solid(0.5)),
637                ..StyleValues::default()
638            },
639            &l,
640        );
641        assert_eq!(out.fill, Some(solid(0.9)), "the higher rank stays on top");
642        assert_eq!(
643            ov.below::<High>(StyleFields::FILL, &l).fill,
644            Some(solid(0.5)),
645            "under the high layer sits the low one"
646        );
647        assert_eq!(
648            ov.below::<Low>(StyleFields::FILL, &l).fill,
649            Some(solid(0.1)),
650            "under the low layer sits the cascade"
651        );
652        let out = ov.release::<High>();
653        assert_eq!(out.fill, Some(solid(0.5)));
654    }
655
656    #[test]
657    fn an_unheld_field_reads_live_below_every_layer() {
658        let ov = StyleOverrides::default();
659        let style = Style {
660            display: Display::None,
661            ..Style::default()
662        };
663        let below = ov.below::<Low>(
664            StyleFields::DISPLAY,
665            &Live {
666                style: Some(&style),
667                ..Live::default()
668            },
669        );
670        assert_eq!(below.display, Some(Display::None));
671    }
672
673    #[test]
674    fn opacity_absent_is_a_value() {
675        let mut ov = StyleOverrides::default();
676        let out = ov.hold::<Low>(
677            StyleValues {
678                opacity: Some(Some(0.0)),
679                ..StyleValues::default()
680            },
681            &Live::default(),
682        );
683        assert_eq!(out.opacity_change(None), Some(Some(Opacity(0.0))));
684        let out = ov.release::<Low>();
685        assert_eq!(
686            out.opacity_change(Some(&Opacity(0.0))),
687            Some(None),
688            "the cascade had no opacity, so letting go removes it"
689        );
690    }
691}