1pub use crate::paint::PaintStyle;
15
16#[derive(Clone, Copy, Debug, Default, PartialEq)]
22pub enum Length {
23 Px(f32),
25 Percent(f32),
27 #[default]
29 Auto,
30}
31
32impl From<f32> for Length {
33 fn from(v: f32) -> Self {
34 Self::Px(v)
35 }
36}
37
38impl From<i32> for Length {
39 fn from(v: i32) -> Self {
40 Self::Px(v as f32)
41 }
42}
43
44impl From<&str> for Length {
45 fn from(s: &str) -> Self {
48 let t = s.trim();
49 if t == "auto" {
50 return Self::Auto;
51 }
52 if let Some(n) = t.strip_suffix('%') {
53 return Self::Percent(parse_number(n, s) / 100.0);
54 }
55 let n = t.strip_suffix("px").unwrap_or(t);
56 Self::Px(parse_number(n, s))
57 }
58}
59
60fn parse_number(n: &str, whole: &str) -> f32 {
61 n.trim().parse::<f32>().unwrap_or_else(|_| {
62 panic!("egui-reactor: cannot parse {whole:?} as a length (expected \"auto\", \"50%\", \"12px\" or \"12\")")
63 })
64}
65
66impl Length {
67 pub fn to_dimension(self) -> taffy::Dimension {
69 match self {
70 Self::Px(v) => taffy::Dimension::length(v),
71 Self::Percent(v) => taffy::Dimension::percent(v),
72 Self::Auto => taffy::Dimension::auto(),
73 }
74 }
75
76 pub fn to_length_percentage_auto(self) -> taffy::LengthPercentageAuto {
78 match self {
79 Self::Px(v) => taffy::LengthPercentageAuto::length(v),
80 Self::Percent(v) => taffy::LengthPercentageAuto::percent(v),
81 Self::Auto => taffy::LengthPercentageAuto::auto(),
82 }
83 }
84
85 pub fn to_length_percentage(self) -> taffy::LengthPercentage {
87 match self {
88 Self::Px(v) => taffy::LengthPercentage::length(v),
89 Self::Percent(v) => taffy::LengthPercentage::percent(v),
90 Self::Auto => taffy::LengthPercentage::length(0.0),
91 }
92 }
93}
94
95macro_rules! str_enum {
97 (
98 $(#[$meta:meta])*
99 pub enum $name:ident {
100 $( $(#[$vmeta:meta])* $variant:ident = $text:literal ),* $(,)?
101 }
102 ) => {
103 $(#[$meta])*
104 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
105 pub enum $name {
106 $( $(#[$vmeta])* $variant, )*
107 }
108
109 impl $name {
110 pub fn parse(s: &str) -> Option<Self> {
112 match s.trim() {
113 $( $text => Some(Self::$variant), )*
114 _ => None,
115 }
116 }
117 }
118
119 impl From<&str> for $name {
120 fn from(s: &str) -> Self {
123 Self::parse(s).unwrap_or_else(|| {
124 panic!(
125 "egui-reactor: {:?} is not a valid {} (expected one of: {})",
126 s,
127 stringify!($name),
128 [$($text),*].join(", "),
129 )
130 })
131 }
132 }
133 };
134}
135
136str_enum! {
137 pub enum Direction {
139 #[default] Row = "row",
140 Column = "column",
141 RowReverse = "row-reverse",
142 ColumnReverse = "column-reverse",
143 }
144}
145
146str_enum! {
147 pub enum Justify {
149 #[default] Normal = "normal",
150 Start = "start",
151 End = "end",
152 FlexStart = "flex-start",
153 FlexEnd = "flex-end",
154 Center = "center",
155 Stretch = "stretch",
156 SpaceBetween = "space-between",
157 SpaceEvenly = "space-evenly",
158 SpaceAround = "space-around",
159 }
160}
161
162str_enum! {
163 pub enum Align {
165 #[default] Normal = "normal",
166 Start = "start",
167 End = "end",
168 FlexStart = "flex-start",
169 FlexEnd = "flex-end",
170 Center = "center",
171 Baseline = "baseline",
172 Stretch = "stretch",
173 }
174}
175
176str_enum! {
177 pub enum Display {
179 #[default] Flex = "flex",
180 Grid = "grid",
181 Block = "block",
182 None = "none",
183 }
184}
185
186pub type AlignSelf = Align;
188
189impl Direction {
190 pub fn to_taffy(self) -> taffy::FlexDirection {
192 match self {
193 Self::Row => taffy::FlexDirection::Row,
194 Self::Column => taffy::FlexDirection::Column,
195 Self::RowReverse => taffy::FlexDirection::RowReverse,
196 Self::ColumnReverse => taffy::FlexDirection::ColumnReverse,
197 }
198 }
199}
200
201impl Justify {
202 pub fn to_taffy(self) -> Option<taffy::AlignContent> {
204 Some(match self {
205 Self::Normal => return None,
206 Self::Start => taffy::AlignContent::Start,
207 Self::End => taffy::AlignContent::End,
208 Self::FlexStart => taffy::AlignContent::FlexStart,
209 Self::FlexEnd => taffy::AlignContent::FlexEnd,
210 Self::Center => taffy::AlignContent::Center,
211 Self::Stretch => taffy::AlignContent::Stretch,
212 Self::SpaceBetween => taffy::AlignContent::SpaceBetween,
213 Self::SpaceEvenly => taffy::AlignContent::SpaceEvenly,
214 Self::SpaceAround => taffy::AlignContent::SpaceAround,
215 })
216 }
217}
218
219impl Align {
220 pub fn to_taffy(self) -> Option<taffy::AlignItems> {
222 Some(match self {
223 Self::Normal => return None,
224 Self::Start => taffy::AlignItems::Start,
225 Self::End => taffy::AlignItems::End,
226 Self::FlexStart => taffy::AlignItems::FlexStart,
227 Self::FlexEnd => taffy::AlignItems::FlexEnd,
228 Self::Center => taffy::AlignItems::Center,
229 Self::Baseline => taffy::AlignItems::Baseline,
230 Self::Stretch => taffy::AlignItems::Stretch,
231 })
232 }
233}
234
235impl Display {
236 pub fn to_taffy(self) -> taffy::Display {
238 match self {
239 Self::Flex => taffy::Display::Flex,
240 Self::Grid => taffy::Display::Grid,
241 Self::Block => taffy::Display::Block,
242 Self::None => taffy::Display::None,
243 }
244 }
245}
246
247#[derive(Clone, Copy, Debug, Default, PartialEq)]
255pub struct ItemStyle {
256 pub w: Option<Length>,
258 pub h: Option<Length>,
260 pub min_w: Option<Length>,
262 pub min_h: Option<Length>,
264 pub max_w: Option<Length>,
266 pub max_h: Option<Length>,
268 pub grow: Option<f32>,
270 pub shrink: Option<f32>,
272 pub basis: Option<Length>,
274 pub align_self: Option<AlignSelf>,
276 pub m: Option<Length>,
278 pub mx: Option<Length>,
280 pub my: Option<Length>,
282 pub mt: Option<Length>,
284 pub mr: Option<Length>,
286 pub mb: Option<Length>,
288 pub ml: Option<Length>,
290 pub p: Option<Length>,
292 pub px: Option<Length>,
294 pub py: Option<Length>,
296 pub pt: Option<Length>,
298 pub pr: Option<Length>,
300 pub pb: Option<Length>,
302 pub pl: Option<Length>,
304 pub col_span: Option<u16>,
306 pub row_span: Option<u16>,
308 pub paint: PaintStyle,
310}
311
312macro_rules! length_setters {
313 ($( $(#[$meta:meta])* $name:ident ),* $(,)?) => {
314 $(
315 $(#[$meta])*
316 #[must_use]
317 pub fn $name(mut self, v: impl Into<Length>) -> Self {
318 self.$name = Some(v.into());
319 self
320 }
321 )*
322 };
323}
324
325impl ItemStyle {
326 length_setters!(
327 w,
329 h,
331 min_w,
333 min_h,
335 max_w,
337 max_h,
339 basis,
341 m,
343 mx,
345 my,
347 mt,
349 mr,
351 mb,
353 ml,
355 p,
357 px,
359 py,
361 pt,
363 pr,
365 pb,
367 pl,
369 );
370
371 #[must_use]
373 pub fn grow(mut self, v: f32) -> Self {
374 self.grow = Some(v);
375 self
376 }
377
378 #[must_use]
380 pub fn shrink(mut self, v: f32) -> Self {
381 self.shrink = Some(v);
382 self
383 }
384
385 #[must_use]
387 pub fn align_self(mut self, v: impl Into<AlignSelf>) -> Self {
388 self.align_self = Some(v.into());
389 self
390 }
391
392 #[must_use]
394 pub fn col_span(mut self, v: u16) -> Self {
395 self.col_span = Some(v);
396 self
397 }
398
399 #[must_use]
401 pub fn row_span(mut self, v: u16) -> Self {
402 self.row_span = Some(v);
403 self
404 }
405
406 #[must_use]
408 pub fn bg(mut self, v: impl Into<egui::Color32>) -> Self {
409 self.paint = self.paint.bg(v);
410 self
411 }
412
413 #[must_use]
415 pub fn border(mut self, v: impl Into<egui::Stroke>) -> Self {
416 self.paint = self.paint.border(v);
417 self
418 }
419
420 #[must_use]
422 pub fn radius(mut self, v: f32) -> Self {
423 self.paint = self.paint.radius(v);
424 self
425 }
426
427 #[must_use]
429 pub fn shadow(mut self, v: bool) -> Self {
430 self.paint = self.paint.shadow(v);
431 self
432 }
433
434 #[must_use]
436 pub fn custom_shadow(mut self, v: egui::Shadow) -> Self {
437 self.paint = self.paint.custom_shadow(v);
438 self
439 }
440
441 #[must_use]
443 pub fn opacity(mut self, v: f32) -> Self {
444 self.paint = self.paint.opacity(v);
445 self
446 }
447
448 pub fn padding_px(&self) -> Option<[f32; 4]> {
461 let sides = [self.p, self.px, self.py, self.pt, self.pr, self.pb, self.pl];
462 if sides.iter().all(Option::is_none) {
463 return None;
464 }
465 let rect = resolve_rect(
466 sides,
467 |v| match v {
468 Length::Px(v) => Some(v),
469 _ => None,
470 },
471 Some(0.0),
472 );
473 Some([rect.top?, rect.right?, rect.bottom?, rect.left?])
474 }
475
476 #[must_use]
482 pub fn without_padding(mut self) -> Self {
483 self.p = None;
484 self.px = None;
485 self.py = None;
486 self.pt = None;
487 self.pr = None;
488 self.pb = None;
489 self.pl = None;
490 self
491 }
492
493 pub fn to_taffy(&self) -> taffy::Style {
495 let dim = |v: Option<Length>| {
496 v.map(Length::to_dimension)
497 .unwrap_or(taffy::Dimension::auto())
498 };
499 let mut style = taffy::Style {
500 size: taffy::Size {
501 width: dim(self.w),
502 height: dim(self.h),
503 },
504 min_size: taffy::Size {
505 width: dim(self.min_w),
506 height: dim(self.min_h),
507 },
508 max_size: taffy::Size {
509 width: dim(self.max_w),
510 height: dim(self.max_h),
511 },
512 margin: resolve_rect(
513 [self.m, self.mx, self.my, self.mt, self.mr, self.mb, self.ml],
514 Length::to_length_percentage_auto,
515 taffy::LengthPercentageAuto::length(0.0),
516 ),
517 padding: resolve_rect(
518 [self.p, self.px, self.py, self.pt, self.pr, self.pb, self.pl],
519 Length::to_length_percentage,
520 taffy::LengthPercentage::length(0.0),
521 ),
522 ..Default::default()
523 };
524 if let Some(grow) = self.grow {
525 style.flex_grow = grow;
526 }
527 if let Some(shrink) = self.shrink {
528 style.flex_shrink = shrink;
529 }
530 if let Some(basis) = self.basis {
531 style.flex_basis = basis.to_dimension();
532 }
533 if let Some(align_self) = self.align_self {
534 style.align_self = align_self.to_taffy();
535 }
536 if let Some(span) = self.col_span {
537 style.grid_column = taffy::style_helpers::span(span);
538 }
539 if let Some(span) = self.row_span {
540 style.grid_row = taffy::style_helpers::span(span);
541 }
542 if let Some(border) = self.paint.border {
547 style.border = taffy::Rect::length(border.width);
548 }
549 style
550 }
551}
552
553fn resolve_rect<T: Copy>(
558 [all, x, y, top, right, bottom, left]: [Option<Length>; 7],
559 convert: impl Fn(Length) -> T,
560 zero: T,
561) -> taffy::Rect<T> {
562 let pick = |specific: Option<Length>, axis: Option<Length>| {
563 specific.or(axis).or(all).map(&convert).unwrap_or(zero)
564 };
565 taffy::Rect {
566 top: pick(top, y),
567 right: pick(right, x),
568 bottom: pick(bottom, y),
569 left: pick(left, x),
570 }
571}
572
573#[derive(Clone, Copy, Debug, Default, PartialEq)]
575pub struct Gap {
576 pub column: f32,
578 pub row: f32,
580}
581
582impl From<f32> for Gap {
583 fn from(v: f32) -> Self {
584 Self { column: v, row: v }
585 }
586}
587
588impl From<i32> for Gap {
589 fn from(v: i32) -> Self {
590 Self::from(v as f32)
591 }
592}
593
594impl From<(f32, f32)> for Gap {
595 fn from((column, row): (f32, f32)) -> Self {
596 Self { column, row }
597 }
598}
599
600#[derive(Clone, Debug, Default, PartialEq)]
602pub struct ContainerStyle {
603 pub display: Display,
605 pub direction: Direction,
607 pub wrap: bool,
609 pub justify: Justify,
611 pub align: Align,
613 pub align_content: Option<Justify>,
615 pub gap: (f32, f32),
617 pub cols: Option<u16>,
619}
620
621impl ContainerStyle {
622 #[must_use]
624 pub fn display(mut self, v: impl Into<Display>) -> Self {
625 self.display = v.into();
626 self
627 }
628
629 #[must_use]
631 pub fn direction(mut self, v: impl Into<Direction>) -> Self {
632 self.direction = v.into();
633 self
634 }
635
636 #[must_use]
638 pub fn wrap(mut self, v: bool) -> Self {
639 self.wrap = v;
640 self
641 }
642
643 #[must_use]
645 pub fn justify(mut self, v: impl Into<Justify>) -> Self {
646 self.justify = v.into();
647 self
648 }
649
650 #[must_use]
652 pub fn align(mut self, v: impl Into<Align>) -> Self {
653 self.align = v.into();
654 self
655 }
656
657 #[must_use]
659 pub fn align_content(mut self, v: impl Into<Justify>) -> Self {
660 self.align_content = Some(v.into());
661 self
662 }
663
664 #[must_use]
666 pub fn gap(mut self, v: impl Into<Gap>) -> Self {
667 let gap = v.into();
668 self.gap = (gap.column, gap.row);
669 self
670 }
671
672 #[must_use]
674 pub fn gaps(mut self, column: f32, row: f32) -> Self {
675 self.gap = (column, row);
676 self
677 }
678
679 #[must_use]
681 pub fn cols(mut self, v: u16) -> Self {
682 self.cols = Some(v);
683 self
684 }
685
686 pub fn merge(&self, item: &ItemStyle) -> taffy::Style {
691 let mut style = item.to_taffy();
692 style.display = self.display.to_taffy();
693 style.flex_direction = self.direction.to_taffy();
694 style.flex_wrap = if self.wrap {
695 taffy::FlexWrap::Wrap
696 } else {
697 taffy::FlexWrap::NoWrap
698 };
699 style.justify_content = self.justify.to_taffy();
700 style.align_items = self.align.to_taffy();
701 style.align_content = self.align_content.and_then(Justify::to_taffy);
702 style.gap = taffy::Size {
703 width: taffy::LengthPercentage::length(self.gap.0),
704 height: taffy::LengthPercentage::length(self.gap.1),
705 };
706 if self.display == Display::Grid
707 && let Some(cols) = self.cols
708 {
709 style.grid_template_columns = taffy::style_helpers::evenly_sized_tracks(cols);
710 }
711 style
712 }
713}
714
715pub fn root_style() -> taffy::Style {
741 ContainerStyle::default()
742 .direction("column")
743 .merge(&ItemStyle::default().w("100%").h("100%"))
744}