1use crate::style::Color;
11use std::marker::PhantomData;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum FlexDirection {
25 #[default]
27 Row,
28 Column,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub enum AlignItems {
37 Start,
39 Center,
41 End,
43 #[default]
45 Stretch,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum JustifyContent {
53 #[default]
55 Start,
56 Center,
58 End,
60 SpaceBetween,
62 SpaceAround,
64 SpaceEvenly,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Default)]
80pub enum Dimension {
81 #[default]
83 Auto,
84 Points(f32),
86 Percent(f32),
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
92pub enum Overflow {
93 #[default]
95 Visible,
96 Hidden,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum TextWrap {
103 Wrap,
105 Truncate,
107 NoWrap,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum BorderStyle {
122 Single,
124 Double,
126 Rounded,
128 Thick,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
134pub struct Edges {
135 pub top: u16,
136 pub right: u16,
137 pub bottom: u16,
138 pub left: u16,
139}
140
141impl Edges {
142 pub fn all(v: u16) -> Self {
144 Self {
145 top: v,
146 right: v,
147 bottom: v,
148 left: v,
149 }
150 }
151 pub fn xy(x: u16, y: u16) -> Self {
153 Self {
154 top: y,
155 right: x,
156 bottom: y,
157 left: x,
158 }
159 }
160}
161
162#[derive(Debug, Clone, Default)]
164pub struct TextStyle {
165 pub fg: Option<Color>,
166 pub bg: Option<Color>,
167 pub bold: bool,
168 pub italic: bool,
169 pub underline: bool,
170 pub reverse: bool,
171 pub dim: bool,
172 pub strikethrough: bool,
173}
174
175#[derive(Debug, Clone, Default)]
177pub struct BoxStyle {
178 pub flex_direction: FlexDirection,
179 pub padding: Edges,
180 pub margin: Edges,
181 pub border: Option<BorderStyle>,
182 pub border_color: Option<Color>,
183 pub gap: u16,
184 pub align_items: AlignItems,
185 pub justify_content: JustifyContent,
186 pub width: Dimension,
187 pub height: Dimension,
188 pub flex_grow: f32,
189 pub flex_shrink: f32,
190 pub flex_basis: Dimension,
191 pub min_width: Dimension,
192 pub max_width: Dimension,
193 pub min_height: Dimension,
194 pub max_height: Dimension,
195 pub bg: Option<Color>,
196 pub overflow: Overflow,
197}
198
199pub enum Element<Msg> {
204 Box(BoxElement<Msg>),
206 Text(TextElement),
208 Spacer,
210 _Phantom(PhantomData<Msg>),
211}
212
213impl<Msg> Element<Msg> {
214 pub fn text(content: impl Into<String>) -> Self {
216 Element::Text(TextElement::new(content))
217 }
218
219 pub fn is_box(&self) -> bool {
221 matches!(self, Element::Box(_))
222 }
223
224 pub fn is_text(&self) -> bool {
226 matches!(self, Element::Text(_))
227 }
228
229 pub fn is_spacer(&self) -> bool {
231 matches!(self, Element::Spacer)
232 }
233
234 pub fn text_content(&self) -> Option<&str> {
236 match self {
237 Element::Text(t) => Some(&t.content),
238 _ => None,
239 }
240 }
241
242 pub fn children(&self) -> Option<&[Element<Msg>]> {
244 match self {
245 Element::Box(b) => Some(&b.children),
246 _ => None,
247 }
248 }
249
250 pub fn child_count(&self) -> usize {
252 match self {
253 Element::Box(b) => b.children.len(),
254 _ => 0,
255 }
256 }
257}
258
259pub struct BoxElement<Msg> {
274 pub children: Vec<Element<Msg>>,
275 pub style: BoxStyle,
276 _phantom: PhantomData<Msg>,
277}
278
279pub struct TextElement {
290 pub content: String,
291 pub style: TextStyle,
292 pub wrap: TextWrap,
293}
294
295impl<Msg> BoxElement<Msg> {
296 pub fn new() -> Self {
297 Self {
298 children: Vec::new(),
299 style: BoxStyle::default(),
300 _phantom: PhantomData,
301 }
302 }
303
304 pub fn row() -> Self {
306 Self::new().direction(FlexDirection::Row)
307 }
308
309 pub fn col() -> Self {
311 Self::new().direction(FlexDirection::Column)
312 }
313
314 pub fn direction(mut self, d: FlexDirection) -> Self {
315 self.style.flex_direction = d;
316 self
317 }
318
319 pub fn child(mut self, el: Element<Msg>) -> Self {
320 self.children.push(el);
321 self
322 }
323
324 pub fn children(mut self, els: Vec<Element<Msg>>) -> Self {
325 self.children = els;
326 self
327 }
328
329 pub fn padding(mut self, all: u16) -> Self {
330 self.style.padding = Edges::all(all);
331 self
332 }
333
334 pub fn padding_xy(mut self, x: u16, y: u16) -> Self {
335 self.style.padding = Edges::xy(x, y);
336 self
337 }
338
339 pub fn margin(mut self, all: u16) -> Self {
340 self.style.margin = Edges::all(all);
341 self
342 }
343
344 pub fn border(mut self, style: BorderStyle) -> Self {
345 self.style.border = Some(style);
346 self
347 }
348
349 pub fn border_color(mut self, c: Color) -> Self {
350 self.style.border_color = Some(c);
351 self
352 }
353
354 pub fn gap(mut self, g: u16) -> Self {
355 self.style.gap = g;
356 self
357 }
358
359 pub fn flex_grow(mut self, g: f32) -> Self {
360 self.style.flex_grow = g;
361 self
362 }
363
364 pub fn flex_shrink(mut self, s: f32) -> Self {
365 self.style.flex_shrink = s;
366 self
367 }
368
369 pub fn flex_basis(mut self, d: Dimension) -> Self {
370 self.style.flex_basis = d;
371 self
372 }
373
374 pub fn width(mut self, d: Dimension) -> Self {
375 self.style.width = d;
376 self
377 }
378
379 pub fn height(mut self, d: Dimension) -> Self {
380 self.style.height = d;
381 self
382 }
383
384 pub fn min_width(mut self, d: Dimension) -> Self {
385 self.style.min_width = d;
386 self
387 }
388
389 pub fn max_width(mut self, d: Dimension) -> Self {
390 self.style.max_width = d;
391 self
392 }
393
394 pub fn min_height(mut self, d: Dimension) -> Self {
395 self.style.min_height = d;
396 self
397 }
398
399 pub fn max_height(mut self, d: Dimension) -> Self {
400 self.style.max_height = d;
401 self
402 }
403
404 pub fn bg(mut self, c: Color) -> Self {
405 self.style.bg = Some(c);
406 self
407 }
408
409 pub fn align_items(mut self, a: AlignItems) -> Self {
410 self.style.align_items = a;
411 self
412 }
413
414 pub fn justify_content(mut self, j: JustifyContent) -> Self {
415 self.style.justify_content = j;
416 self
417 }
418
419 pub fn overflow(mut self, o: Overflow) -> Self {
420 self.style.overflow = o;
421 self
422 }
423}
424
425impl<Msg> Default for BoxElement<Msg> {
426 fn default() -> Self {
427 Self::new()
428 }
429}
430
431impl TextElement {
432 pub fn new(content: impl Into<String>) -> Self {
433 Self {
434 content: content.into(),
435 style: TextStyle::default(),
436 wrap: TextWrap::Wrap,
437 }
438 }
439
440 pub fn fg(mut self, c: Color) -> Self {
441 self.style.fg = Some(c);
442 self
443 }
444
445 pub fn bg(mut self, c: Color) -> Self {
446 self.style.bg = Some(c);
447 self
448 }
449
450 pub fn bold(mut self) -> Self {
451 self.style.bold = true;
452 self
453 }
454
455 pub fn italic(mut self) -> Self {
456 self.style.italic = true;
457 self
458 }
459
460 pub fn underline(mut self) -> Self {
461 self.style.underline = true;
462 self
463 }
464
465 pub fn reverse(mut self) -> Self {
466 self.style.reverse = true;
467 self
468 }
469
470 pub fn dim(mut self) -> Self {
471 self.style.dim = true;
472 self
473 }
474
475 pub fn strikethrough(mut self) -> Self {
476 self.style.strikethrough = true;
477 self
478 }
479
480 pub fn wrap(mut self, w: TextWrap) -> Self {
481 self.wrap = w;
482 self
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn edges_all() {
492 let e = Edges::all(5);
493 assert_eq!(e.top, 5);
494 assert_eq!(e.right, 5);
495 assert_eq!(e.bottom, 5);
496 assert_eq!(e.left, 5);
497 }
498
499 #[test]
500 fn edges_xy() {
501 let e = Edges::xy(3, 7);
502 assert_eq!(e.left, 3);
503 assert_eq!(e.right, 3);
504 assert_eq!(e.top, 7);
505 assert_eq!(e.bottom, 7);
506 }
507
508 #[test]
509 fn text_element_builder() {
510 let t = TextElement::new("hello")
511 .bold()
512 .italic()
513 .reverse()
514 .fg(Color::Red)
515 .bg(Color::Blue);
516 assert_eq!(t.content, "hello");
517 assert!(t.style.bold);
518 assert!(t.style.italic);
519 assert!(t.style.reverse);
520 assert_eq!(t.style.fg, Some(Color::Red));
521 assert_eq!(t.style.bg, Some(Color::Blue));
522 }
523
524 #[test]
525 fn box_element_builder() {
526 let b = BoxElement::<()>::new()
527 .direction(FlexDirection::Column)
528 .padding(2)
529 .gap(1)
530 .border(BorderStyle::Rounded)
531 .bg(Color::Black);
532 assert_eq!(b.style.flex_direction, FlexDirection::Column);
533 assert_eq!(b.style.padding, Edges::all(2));
534 assert_eq!(b.style.gap, 1);
535 assert_eq!(b.style.border, Some(BorderStyle::Rounded));
536 assert_eq!(b.style.bg, Some(Color::Black));
537 }
538
539 #[test]
540 fn box_element_children() {
541 let b = BoxElement::<()>::new()
542 .child(Element::Text(TextElement::new("a")))
543 .child(Element::Text(TextElement::new("b")))
544 .child(Element::Spacer);
545 assert_eq!(b.children.len(), 3);
546 }
547
548 #[test]
549 fn box_element_flex_properties() {
550 let b = BoxElement::<()>::new()
551 .flex_grow(2.0)
552 .flex_shrink(0.5)
553 .flex_basis(Dimension::Points(20.0))
554 .width(Dimension::Points(100.0))
555 .height(Dimension::Percent(50.0))
556 .min_width(Dimension::Points(10.0))
557 .max_width(Dimension::Points(120.0))
558 .min_height(Dimension::Points(4.0))
559 .max_height(Dimension::Points(60.0));
560 assert_eq!(b.style.flex_grow, 2.0);
561 assert_eq!(b.style.flex_shrink, 0.5);
562 assert_eq!(b.style.flex_basis, Dimension::Points(20.0));
563 assert_eq!(b.style.width, Dimension::Points(100.0));
564 assert_eq!(b.style.height, Dimension::Percent(50.0));
565 assert_eq!(b.style.min_width, Dimension::Points(10.0));
566 assert_eq!(b.style.max_width, Dimension::Points(120.0));
567 assert_eq!(b.style.min_height, Dimension::Points(4.0));
568 assert_eq!(b.style.max_height, Dimension::Points(60.0));
569 }
570}