1use 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 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
38 pub struct StyleFields: u16 {
39 const DISPLAY = 1 << 0;
41 const POSITION = 1 << 1;
43 const INSET = 1 << 2;
45 const WIDTH = 1 << 3;
47 const HEIGHT = 1 << 4;
49 const MIN_HEIGHT = 1 << 5;
51 const FILL = 1 << 6;
53 const SHADOWS = 1 << 7;
55 const BORDER = 1 << 8;
57 const OPACITY = 1 << 9;
59 const TEXT_COLOR = 1 << 10;
61 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 const VISUALS = Self::FILL.bits() | Self::SHADOWS.bits() | Self::BORDER.bits();
70 }
71}
72
73#[derive(Clone, Debug, Default, PartialEq)]
75pub struct StyleValues {
76 pub display: Option<Display>,
78 pub position: Option<Position>,
80 pub inset: Option<Edges>,
82 pub width: Option<Length>,
84 pub height: Option<Length>,
86 pub min_height: Option<Length>,
88 pub fill: Option<Option<Fill>>,
90 pub shadows: Option<Vec<ShadowSpec>>,
92 pub border: Option<Option<Border>>,
94 pub opacity: Option<Option<f32>>,
96 pub text_color: Option<Color>,
98}
99
100#[derive(Clone, Copy, Debug, Default)]
103pub struct Live<'a> {
104 pub style: Option<&'a Style>,
106 pub visuals: Option<&'a Visuals>,
108 pub text: Option<&'a TextStyle>,
110 pub opacity: Option<&'a Opacity>,
112}
113
114impl StyleValues {
115 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 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 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 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 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 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 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 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
315fn 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
333pub trait StyleOwner: 'static {
338 const RANK: u8;
346}
347
348#[derive(Clone, Debug)]
349struct Claim {
350 owner: TypeId,
351 rank: u8,
352 values: StyleValues,
353}
354
355#[derive(Component, Clone, Debug, Default)]
361pub struct StyleOverrides {
362 resting: StyleValues,
364 claims: Vec<Claim>,
366}
367
368impl StyleOverrides {
369 pub fn held(&self) -> StyleFields {
371 self.claims
372 .iter()
373 .fold(StyleFields::empty(), |f, c| f | c.values.fields())
374 }
375
376 pub fn is_empty(&self) -> bool {
378 self.claims.is_empty()
379 }
380
381 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 pub fn resting(&self) -> &StyleValues {
392 &self.resting
393 }
394
395 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 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 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 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 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 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 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
500pub 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
519pub 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
529pub 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 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}