1use bevy_ecs::prelude::*;
4use bevy_reflect::prelude::*;
5use bevy_transform::prelude::*;
6use taffy::{BlockContainerStyle, BlockItemStyle, CoreStyle, FlexboxContainerStyle, FlexboxItemStyle};
7
8use crate::node::{ComputedUi, UiCaches};
9
10#[derive(Component, Reflect, Clone)]
12#[require(Transform, ComputedUi)]
13#[reflect(Component, Default)]
14pub struct Ui {
15 pub display: Display,
17 pub box_sizing: BoxSizing,
19 pub overflow_x: Overflow,
21 pub overflow_y: Overflow,
23 pub scrollbar_width: f32,
25 pub position: Position,
27 pub inset: UiBorder,
30 pub size: UiSize,
32 pub min_size: UiSize,
34 pub max_size: UiSize,
36 pub aspect_ratio: Option<f32>,
38 pub margin: UiBorder,
40 pub padding: UiBorder,
42 pub border: UiBorder,
44 pub flex_direction: FlexDirection,
46 pub flex_wrap: FlexWrap,
48 pub gap: UiSize,
50 pub align_content: AlignContent,
52 pub align_items: AlignItems,
54 pub justify_content: JustifyContent,
56 pub flex_basis: Val,
58 pub flex_grow: f32,
60 pub flex_shrink: f32,
62 pub align_self: AlignSelf,
65}
66
67impl Ui {
68 pub const DEFAULT: Self = Self {
70 display: Display::DEFAULT,
71 box_sizing: BoxSizing::DEFAULT,
72 overflow_x: Overflow::DEFAULT,
73 overflow_y: Overflow::DEFAULT,
74 scrollbar_width: 0.,
75 position: Position::DEFAULT,
76 inset: UiBorder::DEFAULT,
77 size: UiSize::DEFAULT,
78 min_size: UiSize::DEFAULT,
79 max_size: UiSize::DEFAULT,
80 aspect_ratio: None,
81 margin: UiBorder::all(Val::Abs(0.)),
82 padding: UiBorder::all(Val::Abs(0.)),
83 border: UiBorder::all(Val::Abs(0.)),
84 flex_direction: FlexDirection::DEFAULT,
85 flex_wrap: FlexWrap::DEFAULT,
86 gap: UiSize::all(Val::Abs(0.)),
87 align_content: AlignContent::DEFAULT,
88 align_items: AlignItems::DEFAULT,
89 justify_content: JustifyContent::DEFAULT,
90 flex_basis: Val::DEFAULT,
91 flex_grow: 0.,
92 flex_shrink: 1.,
93 align_self: AlignSelf::DEFAULT,
94 };
95}
96
97impl Default for Ui {
98 #[inline]
99 fn default() -> Self {
100 Self::DEFAULT
101 }
102}
103
104pub(crate) fn ui_changed(query: Query<Entity, Changed<Ui>>, mut caches: UiCaches) {
105 for e in &query {
106 caches.invalidate(e)
107 }
108}
109
110#[derive(Copy, Clone)]
111pub(crate) struct WithCtx<T> {
112 pub width: f32,
113 pub height: f32,
114 pub item: T,
115}
116
117#[derive(Reflect, Copy, Clone)]
119#[reflect(Default)]
120pub enum Val {
121 Abs(f32),
123 Rel(f32),
125 Vw(f32),
127 Vh(f32),
129 Auto,
131}
132
133impl Val {
134 pub const DEFAULT: Self = Val::Auto;
136}
137
138impl Default for Val {
139 #[inline]
140 fn default() -> Self {
141 Self::DEFAULT
142 }
143}
144
145impl From<WithCtx<Val>> for taffy::Dimension {
146 #[inline]
147 fn from(WithCtx { width, height, item }: WithCtx<Val>) -> Self {
148 match item {
149 Val::Abs(abs) => Self::Length(abs),
150 Val::Rel(rel) => Self::Percent(rel),
151 Val::Vw(w) => Self::Length(width * w),
152 Val::Vh(h) => Self::Length(height * h),
153 Val::Auto => Self::Auto,
154 }
155 }
156}
157
158impl From<WithCtx<Val>> for taffy::LengthPercentage {
159 #[inline]
160 fn from(WithCtx { width, height, item }: WithCtx<Val>) -> Self {
161 match item {
162 Val::Abs(abs) => Self::Length(abs),
163 Val::Rel(rel) => Self::Percent(rel),
164 Val::Vw(w) => Self::Length(width * w),
165 Val::Vh(h) => Self::Length(height * h),
166 Val::Auto => Self::Length(0.),
167 }
168 }
169}
170
171impl From<WithCtx<Val>> for taffy::LengthPercentageAuto {
172 #[inline]
173 fn from(WithCtx { width, height, item }: WithCtx<Val>) -> Self {
174 match item {
175 Val::Abs(abs) => Self::Length(abs),
176 Val::Rel(rel) => Self::Percent(rel),
177 Val::Vw(w) => Self::Length(width * w),
178 Val::Vh(h) => Self::Length(height * h),
179 Val::Auto => Self::Auto,
180 }
181 }
182}
183
184#[derive(Reflect, Copy, Clone)]
186#[reflect(Default)]
187pub struct UiSize {
188 pub width: Val,
190 pub height: Val,
192}
193
194impl UiSize {
195 pub const DEFAULT: Self = Self {
197 width: Val::DEFAULT,
198 height: Val::DEFAULT,
199 };
200}
201
202impl Default for UiSize {
203 #[inline]
204 fn default() -> Self {
205 Self::DEFAULT
206 }
207}
208
209impl UiSize {
210 #[inline]
212 pub const fn new(width: Val, height: Val) -> Self {
213 Self { width, height }
214 }
215
216 #[inline]
218 pub const fn all(value: Val) -> Self {
219 Self::new(value, value)
220 }
221
222 #[inline]
224 pub const fn abs(width: f32, height: f32) -> Self {
225 Self::new(Val::Abs(width), Val::Abs(height))
226 }
227
228 #[inline]
230 pub const fn rel(width: f32, height: f32) -> Self {
231 Self::new(Val::Rel(width), Val::Rel(height))
232 }
233}
234
235impl<T: From<WithCtx<Val>>> From<WithCtx<UiSize>> for taffy::Size<T> {
236 #[inline]
237 fn from(WithCtx { width, height, item }: WithCtx<UiSize>) -> Self {
238 Self {
239 width: WithCtx {
240 width,
241 height,
242 item: item.width,
243 }
244 .into(),
245 height: WithCtx {
246 width,
247 height,
248 item: item.height,
249 }
250 .into(),
251 }
252 }
253}
254
255#[derive(Reflect, Copy, Clone)]
257#[reflect(Default)]
258pub struct UiBorder {
259 pub left: Val,
261 pub right: Val,
263 pub bottom: Val,
265 pub top: Val,
267}
268
269impl UiBorder {
270 pub const DEFAULT: Self = Self {
272 left: Val::DEFAULT,
273 right: Val::DEFAULT,
274 bottom: Val::DEFAULT,
275 top: Val::DEFAULT,
276 };
277
278 #[inline]
280 pub const fn new(left: Val, right: Val, bottom: Val, top: Val) -> Self {
281 Self {
282 left,
283 right,
284 bottom,
285 top,
286 }
287 }
288
289 #[inline]
291 pub const fn all(value: Val) -> Self {
292 Self::new(value, value, value, value)
293 }
294}
295
296impl Default for UiBorder {
297 #[inline]
298 fn default() -> Self {
299 Self::DEFAULT
300 }
301}
302
303impl<T: From<WithCtx<Val>>> From<WithCtx<UiBorder>> for taffy::Rect<T> {
304 #[inline]
305 fn from(
306 WithCtx {
307 width,
308 height,
309 item:
310 UiBorder {
311 left,
312 right,
313 bottom,
314 top,
315 },
316 }: WithCtx<UiBorder>,
317 ) -> Self {
318 Self {
319 left: WithCtx {
320 width,
321 height,
322 item: left,
323 }
324 .into(),
325 right: WithCtx {
326 width,
327 height,
328 item: right,
329 }
330 .into(),
331 bottom: WithCtx {
332 width,
333 height,
334 item: bottom,
335 }
336 .into(),
337 top: WithCtx {
338 width,
339 height,
340 item: top,
341 }
342 .into(),
343 }
344 }
345}
346
347#[derive(Reflect, Copy, Clone)]
349#[reflect(Default)]
350pub enum Display {
351 Flexbox,
353 Block,
355 None,
357}
358
359impl Display {
360 pub const DEFAULT: Self = Display::Flexbox;
362}
363
364impl Default for Display {
365 #[inline]
366 fn default() -> Self {
367 Self::DEFAULT
368 }
369}
370
371impl From<Display> for taffy::BoxGenerationMode {
372 #[inline]
373 fn from(value: Display) -> Self {
374 match value {
375 Display::Flexbox | Display::Block => Self::Normal,
376 Display::None => Self::None,
377 }
378 }
379}
380
381#[derive(Reflect, Copy, Clone, PartialEq, Eq, Debug)]
384#[reflect(Default)]
385pub enum BoxSizing {
386 BorderBox,
389 ContentBox,
392}
393
394impl BoxSizing {
395 pub const DEFAULT: Self = Self::BorderBox;
397}
398
399impl Default for BoxSizing {
400 #[inline]
401 fn default() -> Self {
402 Self::DEFAULT
403 }
404}
405
406impl From<BoxSizing> for taffy::BoxSizing {
407 #[inline]
408 fn from(value: BoxSizing) -> Self {
409 match value {
410 BoxSizing::BorderBox => Self::BorderBox,
411 BoxSizing::ContentBox => Self::ContentBox,
412 }
413 }
414}
415
416#[derive(Reflect, Copy, Clone, PartialEq, Eq, Debug)]
418#[reflect(Default)]
419pub enum Overflow {
420 Visible,
424 Clip,
428 Hidden,
431 Scroll,
436}
437
438impl Overflow {
439 pub const DEFAULT: Self = Self::Visible;
441}
442
443impl Default for Overflow {
444 #[inline]
445 fn default() -> Self {
446 Self::DEFAULT
447 }
448}
449
450impl From<Overflow> for taffy::Overflow {
451 #[inline]
452 fn from(value: Overflow) -> Self {
453 match value {
454 Overflow::Visible => Self::Visible,
455 Overflow::Clip => Self::Clip,
456 Overflow::Hidden => Self::Hidden,
457 Overflow::Scroll => Self::Scroll,
458 }
459 }
460}
461
462#[derive(Reflect, Copy, Clone, PartialEq, Eq)]
464#[reflect(Default)]
465pub enum Position {
466 Relative,
470 Absolute,
474}
475
476impl Position {
477 pub const DEFAULT: Self = Self::Relative;
479}
480
481impl Default for Position {
482 #[inline]
483 fn default() -> Self {
484 Self::DEFAULT
485 }
486}
487
488impl From<Position> for taffy::Position {
489 #[inline]
490 fn from(value: Position) -> Self {
491 match value {
492 Position::Relative => Self::Relative,
493 Position::Absolute => Self::Absolute,
494 }
495 }
496}
497
498impl CoreStyle for WithCtx<&Ui> {
499 #[inline]
500 fn box_generation_mode(&self) -> taffy::BoxGenerationMode {
501 self.item.display.into()
502 }
503
504 #[inline]
505 fn is_block(&self) -> bool {
506 matches!(self.item.display, Display::Block)
507 }
508
509 #[inline]
510 fn box_sizing(&self) -> taffy::BoxSizing {
511 self.item.box_sizing.into()
512 }
513
514 #[inline]
515 fn overflow(&self) -> taffy::Point<taffy::Overflow> {
516 taffy::Point {
517 x: self.item.overflow_y.into(),
518 y: self.item.overflow_y.into(),
519 }
520 }
521
522 #[inline]
523 fn scrollbar_width(&self) -> f32 {
524 self.item.scrollbar_width
525 }
526
527 #[inline]
528 fn position(&self) -> taffy::Position {
529 self.item.position.into()
530 }
531
532 #[inline]
533 fn inset(&self) -> taffy::Rect<taffy::LengthPercentageAuto> {
534 WithCtx {
535 width: self.width,
536 height: self.height,
537 item: self.item.inset,
538 }
539 .into()
540 }
541
542 #[inline]
543 fn size(&self) -> taffy::Size<taffy::Dimension> {
544 WithCtx {
545 width: self.width,
546 height: self.height,
547 item: self.item.size,
548 }
549 .into()
550 }
551
552 #[inline]
553 fn min_size(&self) -> taffy::Size<taffy::Dimension> {
554 WithCtx {
555 width: self.width,
556 height: self.height,
557 item: self.item.min_size,
558 }
559 .into()
560 }
561
562 #[inline]
563 fn max_size(&self) -> taffy::Size<taffy::Dimension> {
564 WithCtx {
565 width: self.width,
566 height: self.height,
567 item: self.item.max_size,
568 }
569 .into()
570 }
571
572 #[inline]
573 fn aspect_ratio(&self) -> Option<f32> {
574 self.item.aspect_ratio
575 }
576
577 #[inline]
578 fn margin(&self) -> taffy::Rect<taffy::LengthPercentageAuto> {
579 WithCtx {
580 width: self.width,
581 height: self.height,
582 item: self.item.margin,
583 }
584 .into()
585 }
586
587 #[inline]
588 fn padding(&self) -> taffy::Rect<taffy::LengthPercentage> {
589 WithCtx {
590 width: self.width,
591 height: self.height,
592 item: self.item.padding,
593 }
594 .into()
595 }
596
597 #[inline]
598 fn border(&self) -> taffy::Rect<taffy::LengthPercentage> {
599 WithCtx {
600 width: self.width,
601 height: self.height,
602 item: self.item.border,
603 }
604 .into()
605 }
606}
607
608#[derive(Reflect, Copy, Clone, PartialEq, Eq)]
610#[reflect(Default)]
611pub enum FlexDirection {
612 Row,
614 Column,
616 RowReverse,
618 ColumnReverse,
620}
621
622impl FlexDirection {
623 pub const DEFAULT: Self = Self::Row;
625}
626
627impl Default for FlexDirection {
628 #[inline]
629 fn default() -> Self {
630 Self::DEFAULT
631 }
632}
633
634impl From<FlexDirection> for taffy::FlexDirection {
635 #[inline]
636 fn from(value: FlexDirection) -> Self {
637 match value {
638 FlexDirection::Row => Self::Row,
639 FlexDirection::Column => Self::Column,
640 FlexDirection::RowReverse => Self::RowReverse,
641 FlexDirection::ColumnReverse => Self::ColumnReverse,
642 }
643 }
644}
645
646#[derive(Reflect, Copy, Clone, PartialEq, Eq)]
648#[reflect(Default)]
649pub enum FlexWrap {
650 NoWrap,
652 Wrap,
654 WrapReverse,
656}
657
658impl FlexWrap {
659 pub const DEFAULT: Self = Self::NoWrap;
661}
662
663impl Default for FlexWrap {
664 #[inline]
665 fn default() -> Self {
666 Self::DEFAULT
667 }
668}
669
670impl From<FlexWrap> for taffy::FlexWrap {
671 #[inline]
672 fn from(value: FlexWrap) -> Self {
673 match value {
674 FlexWrap::NoWrap => Self::NoWrap,
675 FlexWrap::Wrap => Self::Wrap,
676 FlexWrap::WrapReverse => Self::WrapReverse,
677 }
678 }
679}
680
681#[derive(Reflect, Copy, Clone, PartialEq, Eq)]
685#[reflect(Default)]
686pub enum AlignContent {
687 None,
689 Start,
691 End,
693 FlexStart,
695 FlexEnd,
697 Center,
699 Stretch,
701 SpaceBetween,
704 SpaceEvenly,
707 SpaceAround,
710}
711
712impl AlignContent {
713 pub const DEFAULT: Self = Self::None;
715}
716
717impl Default for AlignContent {
718 #[inline]
719 fn default() -> Self {
720 Self::None
721 }
722}
723
724impl From<AlignContent> for Option<taffy::AlignContent> {
725 #[inline]
726 fn from(value: AlignContent) -> Self {
727 match value {
728 AlignContent::None => None,
729 AlignContent::Start => Some(taffy::AlignContent::Start),
730 AlignContent::End => Some(taffy::AlignContent::End),
731 AlignContent::FlexStart => Some(taffy::AlignContent::FlexStart),
732 AlignContent::FlexEnd => Some(taffy::AlignContent::FlexEnd),
733 AlignContent::Center => Some(taffy::AlignContent::Center),
734 AlignContent::Stretch => Some(taffy::AlignContent::Stretch),
735 AlignContent::SpaceBetween => Some(taffy::AlignContent::SpaceBetween),
736 AlignContent::SpaceEvenly => Some(taffy::AlignContent::SpaceEvenly),
737 AlignContent::SpaceAround => Some(taffy::AlignContent::SpaceAround),
738 }
739 }
740}
741
742#[derive(Reflect, Copy, Clone, PartialEq, Eq)]
746#[reflect(Default)]
747pub enum AlignItems {
748 None,
750 Start,
752 End,
754 FlexStart,
756 FlexEnd,
758 Center,
760 Baseline,
762 Stretch,
764}
765
766impl AlignItems {
767 pub const DEFAULT: Self = Self::None;
769}
770
771impl Default for AlignItems {
772 #[inline]
773 fn default() -> Self {
774 Self::DEFAULT
775 }
776}
777
778impl From<AlignItems> for Option<taffy::AlignItems> {
779 #[inline]
780 fn from(value: AlignItems) -> Self {
781 match value {
782 AlignItems::None => None,
783 AlignItems::Start => Some(taffy::AlignItems::Start),
784 AlignItems::End => Some(taffy::AlignItems::End),
785 AlignItems::FlexStart => Some(taffy::AlignItems::FlexStart),
786 AlignItems::FlexEnd => Some(taffy::AlignItems::FlexEnd),
787 AlignItems::Center => Some(taffy::AlignItems::Center),
788 AlignItems::Baseline => Some(taffy::AlignItems::Baseline),
789 AlignItems::Stretch => Some(taffy::AlignItems::Stretch),
790 }
791 }
792}
793
794pub type JustifyContent = AlignContent;
798
799impl FlexboxContainerStyle for WithCtx<&Ui> {
800 #[inline]
801 fn flex_direction(&self) -> taffy::FlexDirection {
802 self.item.flex_direction.into()
803 }
804
805 #[inline]
806 fn flex_wrap(&self) -> taffy::FlexWrap {
807 self.item.flex_wrap.into()
808 }
809
810 #[inline]
811 fn gap(&self) -> taffy::Size<taffy::LengthPercentage> {
812 WithCtx {
813 width: self.width,
814 height: self.height,
815 item: self.item.gap,
816 }
817 .into()
818 }
819
820 #[inline]
821 fn align_content(&self) -> Option<taffy::AlignContent> {
822 self.item.align_content.into()
823 }
824
825 #[inline]
826 fn align_items(&self) -> Option<taffy::AlignItems> {
827 self.item.align_items.into()
828 }
829
830 #[inline]
831 fn justify_content(&self) -> Option<taffy::JustifyContent> {
832 self.item.justify_content.into()
833 }
834}
835
836pub type AlignSelf = AlignItems;
838
839impl FlexboxItemStyle for WithCtx<&Ui> {
840 #[inline]
841 fn flex_basis(&self) -> taffy::Dimension {
842 WithCtx {
843 width: self.width,
844 height: self.height,
845 item: self.item.flex_basis,
846 }
847 .into()
848 }
849
850 #[inline]
851 fn flex_grow(&self) -> f32 {
852 self.item.flex_grow
853 }
854
855 #[inline]
856 fn flex_shrink(&self) -> f32 {
857 self.item.flex_shrink
858 }
859
860 #[inline]
861 fn align_self(&self) -> Option<taffy::AlignSelf> {
862 self.item.align_self.into()
863 }
864}
865
866impl BlockContainerStyle for WithCtx<&Ui> {
867 #[inline]
868 fn text_align(&self) -> taffy::TextAlign {
869 taffy::TextAlign::Auto
870 }
871}
872
873impl BlockItemStyle for WithCtx<&Ui> {
874 #[inline]
875 fn is_table(&self) -> bool {
876 false
877 }
878}