1use std::hash::Hash;
8use std::path::PathBuf;
9use std::rc::Rc;
10use std::time::Duration;
11
12use crabshell_reactive::{Memo, Signal};
13
14use crate::cx::Cx;
15use crate::input::{Handler, Handlers};
16use crate::keyboard::KeyEvent;
17use crate::keyed::{self, KeyedList, KeyedMount};
18use crate::prop::{IntoProp, Prop};
19use crate::screencopy::ScreencopyProps;
20use crate::scroll::ScrollProps;
21use crate::style::Style;
22use crate::text_input::InputProps;
23
24pub trait Widget: 'static {
44 fn into_element(self: Box<Self>) -> Element;
49}
50
51pub struct Element {
54 pub(crate) name: &'static str,
55 pub(crate) style: Option<Prop<Style>>,
56 pub(crate) kind: ElementKind,
57 pub(crate) children: Vec<ElementChild>,
58}
59
60pub(crate) enum ElementKind {
61 Flex(FlexProps),
62 Stack,
63 Spacer,
64 Text(TextProps),
65 Icon(IconProps),
66 Image(ImageProps),
67 Capture(ScreencopyProps),
68 Button(Box<ButtonProps>),
70 Scroll(ScrollProps),
71 Input(Box<InputProps>),
72}
73
74pub(crate) enum Child {
77 One(Box<dyn Widget>),
78 Keyed(KeyedList),
79}
80
81pub(crate) enum ElementChild {
83 One(Box<Element>),
84 Keyed(KeyedMount),
85}
86
87fn describe(children: Vec<Child>) -> Vec<ElementChild> {
90 children
91 .into_iter()
92 .map(|child| match child {
93 Child::One(widget) => ElementChild::One(Box::new(widget.into_element())),
94 Child::Keyed(list) => ElementChild::Keyed(list.into_mount()),
95 })
96 .collect()
97}
98
99pub(crate) struct FlexProps {
100 pub(crate) column: bool,
101 pub(crate) grow: bool,
104 pub(crate) gap: Option<Prop<u32>>,
105 pub(crate) padding_vertical: Option<Prop<u32>>,
106 pub(crate) padding_horizontal: Option<Prop<u32>>,
107 pub(crate) align: Option<Prop<Align>>,
108 pub(crate) justify: Option<Prop<Justify>>,
109}
110
111pub(crate) struct TextProps {
112 pub(crate) text: Prop<String>,
113 pub(crate) ellipsis: Option<Prop<bool>>,
114 pub(crate) max_lines: Option<Prop<u32>>,
115}
116
117pub(crate) struct IconProps {
118 pub(crate) source: Prop<IconSource>,
119 pub(crate) size: Option<Prop<u32>>,
120}
121
122pub(crate) struct ImageProps {
123 pub(crate) source: Prop<PathBuf>,
124 pub(crate) width: Option<Prop<u32>>,
125 pub(crate) height: Option<Prop<u32>>,
126 pub(crate) fit: Option<Prop<Fit>>,
127}
128
129#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
132pub enum Fit {
133 #[default]
135 Fill,
136 Contain,
138 Cover,
141}
142
143pub(crate) struct ButtonProps {
144 pub(crate) hover_style: Option<Prop<Style>>,
145 pub(crate) pressed_style: Option<Prop<Style>>,
146 pub(crate) focus_style: Option<Prop<Style>>,
147 pub(crate) hovered: Option<Signal<bool>>,
148 pub(crate) handlers: Handlers,
149}
150
151#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
154pub enum Align {
155 Start,
157 Center,
159 End,
161 #[default]
163 Stretch,
164}
165
166#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
169pub enum Justify {
170 #[default]
172 Start,
173 Center,
175 End,
177 SpaceBetween,
179 SpaceAround,
181 SpaceEvenly,
183}
184
185#[derive(Clone, Debug, PartialEq)]
187pub enum IconSource {
188 Name(String),
191 File(PathBuf),
193 Pixels(IconPixels),
195}
196
197#[derive(Clone, Debug, PartialEq)]
200pub struct IconPixels {
201 pub width: u32,
203 pub height: u32,
205 pub data: Rc<[u8]>,
207}
208
209macro_rules! container_methods {
211 () => {
212 pub fn child(mut self, child: impl Widget) -> Self {
214 self.children.push(Child::One(Box::new(child)));
215 self
216 }
217
218 pub fn children_keyed<T, K, W>(
230 mut self,
231 items: impl FnMut() -> Vec<T> + 'static,
232 key: impl FnMut(&T) -> K + 'static,
233 view: impl FnMut(&Cx, Memo<T>) -> W + 'static,
234 ) -> Self
235 where
236 T: Clone + PartialEq + 'static,
237 K: Eq + Hash + 'static,
238 W: Widget,
239 {
240 self
241 .children
242 .push(Child::Keyed(keyed::mount(items, key, view)));
243 self
244 }
245
246 pub fn exit(mut self, exit: impl IntoProp<Duration>) -> Self {
257 let last = self
258 .children
259 .iter_mut()
260 .rev()
261 .find_map(|child| match child {
262 Child::Keyed(list) => Some(list),
263 Child::One(_) => None,
264 });
265 match last {
266 Some(list) => list.exit = Some(exit.into_prop()),
267 None => tracing::warn!("`exit` was called before any `children_keyed`; it does nothing"),
268 }
269 self
270 }
271 };
272}
273
274macro_rules! style_method {
276 () => {
277 pub fn style(mut self, style: impl IntoProp<Style>) -> Self {
279 self.style = Some(style.into_prop());
280 self
281 }
282 };
283}
284
285macro_rules! flex_widget {
287 ($name:ident, $column:literal, $doc:literal) => {
288 #[doc = $doc]
289 pub struct $name {
293 flex: FlexProps,
294 style: Option<Prop<Style>>,
295 children: Vec<Child>,
296 }
297
298 impl $name {
299 pub fn new() -> Self {
301 $name {
302 flex: FlexProps {
303 column: $column,
304 grow: false,
305 gap: None,
306 padding_vertical: None,
307 padding_horizontal: None,
308 align: None,
309 justify: None,
310 },
311 style: None,
312 children: Vec::new(),
313 }
314 }
315
316 pub fn gap(mut self, gap: impl IntoProp<u32>) -> Self {
318 self.flex.gap = Some(gap.into_prop());
319 self
320 }
321
322 pub fn padding(
326 mut self,
327 vertical: impl IntoProp<u32>,
328 horizontal: impl IntoProp<u32>,
329 ) -> Self {
330 self.flex.padding_vertical = Some(vertical.into_prop());
331 self.flex.padding_horizontal = Some(horizontal.into_prop());
332 self
333 }
334
335 pub fn align(mut self, align: impl IntoProp<Align>) -> Self {
337 self.flex.align = Some(align.into_prop());
338 self
339 }
340
341 pub fn justify(mut self, justify: impl IntoProp<Justify>) -> Self {
343 self.flex.justify = Some(justify.into_prop());
344 self
345 }
346
347 container_methods!();
348 style_method!();
349 }
350
351 impl Default for $name {
352 fn default() -> Self {
353 Self::new()
354 }
355 }
356
357 impl Widget for $name {
358 fn into_element(self: Box<Self>) -> Element {
359 Element {
360 name: stringify!($name),
361 style: self.style,
362 kind: ElementKind::Flex(self.flex),
363 children: describe(self.children),
364 }
365 }
366 }
367 };
368}
369
370flex_widget!(
371 Row,
372 false,
373 "Lays its children out left to right, like a CSS flexbox row."
374);
375flex_widget!(
376 Column,
377 true,
378 "Lays its children out top to bottom, like a CSS flexbox column."
379);
380
381pub struct Stack {
384 style: Option<Prop<Style>>,
385 children: Vec<Child>,
386}
387
388impl Stack {
389 pub fn new() -> Stack {
391 Stack {
392 style: None,
393 children: Vec::new(),
394 }
395 }
396
397 container_methods!();
398 style_method!();
399}
400
401impl Default for Stack {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407impl Widget for Stack {
408 fn into_element(self: Box<Self>) -> Element {
409 Element {
410 name: "Stack",
411 style: self.style,
412 kind: ElementKind::Stack,
413 children: describe(self.children),
414 }
415 }
416}
417
418pub struct Spacer;
421
422impl Spacer {
423 pub fn new() -> Spacer {
425 Spacer
426 }
427}
428
429impl Default for Spacer {
430 fn default() -> Self {
431 Self::new()
432 }
433}
434
435impl Widget for Spacer {
436 fn into_element(self: Box<Self>) -> Element {
437 Element {
438 name: "Spacer",
439 style: None,
440 kind: ElementKind::Spacer,
441 children: Vec::new(),
442 }
443 }
444}
445
446pub struct Text {
452 text: TextProps,
453 style: Option<Prop<Style>>,
454}
455
456impl Text {
457 pub fn new(text: impl IntoProp<String>) -> Text {
459 Text {
460 text: TextProps {
461 text: text.into_prop(),
462 ellipsis: None,
463 max_lines: None,
464 },
465 style: None,
466 }
467 }
468
469 pub fn ellipsis(mut self, ellipsis: impl IntoProp<bool>) -> Text {
472 self.text.ellipsis = Some(ellipsis.into_prop());
473 self
474 }
475
476 pub fn max_lines(mut self, max_lines: impl IntoProp<u32>) -> Text {
480 self.text.max_lines = Some(max_lines.into_prop());
481 self
482 }
483
484 style_method!();
485}
486
487impl Widget for Text {
488 fn into_element(self: Box<Self>) -> Element {
489 Element {
490 name: "Text",
491 style: self.style,
492 kind: ElementKind::Text(self.text),
493 children: Vec::new(),
494 }
495 }
496}
497
498pub struct Icon {
500 icon: IconProps,
501 style: Option<Prop<Style>>,
502}
503
504impl Icon {
505 pub const DEFAULT_SIZE: u32 = 16;
507
508 pub fn new(source: impl IntoProp<IconSource>) -> Icon {
511 Icon {
512 icon: IconProps {
513 source: source.into_prop(),
514 size: None,
515 },
516 style: None,
517 }
518 }
519
520 pub fn size(mut self, size: impl IntoProp<u32>) -> Icon {
522 self.icon.size = Some(size.into_prop());
523 self
524 }
525
526 style_method!();
527}
528
529pub struct Image {
532 image: ImageProps,
533 style: Option<Prop<Style>>,
534}
535
536impl Image {
537 pub const DEFAULT_SIZE: u32 = 64;
539
540 pub fn new(source: impl IntoProp<PathBuf>) -> Image {
543 Image {
544 image: ImageProps {
545 source: source.into_prop(),
546 width: None,
547 height: None,
548 fit: None,
549 },
550 style: None,
551 }
552 }
553
554 pub fn width(mut self, width: impl IntoProp<u32>) -> Image {
556 self.image.width = Some(width.into_prop());
557 self
558 }
559
560 pub fn height(mut self, height: impl IntoProp<u32>) -> Image {
562 self.image.height = Some(height.into_prop());
563 self
564 }
565
566 pub fn fit(mut self, fit: impl IntoProp<Fit>) -> Image {
568 self.image.fit = Some(fit.into_prop());
569 self
570 }
571
572 style_method!();
573}
574
575impl Widget for Image {
576 fn into_element(self: Box<Self>) -> Element {
577 Element {
578 name: "Image",
579 style: self.style,
580 kind: ElementKind::Image(self.image),
581 children: Vec::new(),
582 }
583 }
584}
585
586impl Widget for Icon {
587 fn into_element(self: Box<Self>) -> Element {
588 Element {
589 name: "Icon",
590 style: self.style,
591 kind: ElementKind::Icon(self.icon),
592 children: Vec::new(),
593 }
594 }
595}
596
597pub struct Button {
604 button: ButtonProps,
605 style: Option<Prop<Style>>,
606 child: Box<dyn Widget>,
607}
608
609impl Button {
610 pub fn new(child: impl Widget) -> Button {
612 Button {
613 child: Box::new(child),
614 button: ButtonProps {
615 hover_style: None,
616 pressed_style: None,
617 focus_style: None,
618 hovered: None,
619 handlers: Handlers::default(),
620 },
621 style: None,
622 }
623 }
624
625 pub fn hover_style(mut self, style: impl IntoProp<Style>) -> Button {
628 self.button.hover_style = Some(style.into_prop());
629 self
630 }
631
632 pub fn hovered(mut self, hovered: Signal<bool>) -> Button {
639 self.button.hovered = Some(hovered);
640 self
641 }
642
643 pub fn pressed_style(mut self, style: impl IntoProp<Style>) -> Button {
646 self.button.pressed_style = Some(style.into_prop());
647 self
648 }
649
650 pub fn focus_style(mut self, style: impl IntoProp<Style>) -> Button {
653 self.button.focus_style = Some(style.into_prop());
654 self
655 }
656
657 pub fn on_key(mut self, handler: impl Fn(&Cx, &KeyEvent) -> bool + 'static) -> Button {
662 self.button.handlers.key = Some(Rc::new(Handler::new(handler)));
663 self
664 }
665
666 pub fn on_press(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
670 self.button.handlers.press = Some(Rc::new(Handler::new(handler)));
671 self
672 }
673
674 pub fn on_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
677 self.button.handlers.click = Some(Rc::new(Handler::new(handler)));
678 self
679 }
680
681 pub fn on_right_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
684 self.button.handlers.right_click = Some(Rc::new(Handler::new(handler)));
685 self
686 }
687
688 pub fn on_middle_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
691 self.button.handlers.middle_click = Some(Rc::new(Handler::new(handler)));
692 self
693 }
694
695 pub fn on_scroll(mut self, handler: impl Fn(&Cx, i32) + 'static) -> Button {
700 self.button.handlers.scroll = Some(Rc::new(Handler::new(handler)));
701 self
702 }
703
704 style_method!();
705}
706
707impl Widget for Button {
708 fn into_element(self: Box<Self>) -> Element {
709 Element {
710 name: "Button",
711 style: self.style,
712 kind: ElementKind::Button(Box::new(self.button)),
713 children: vec![ElementChild::One(Box::new(self.child.into_element()))],
714 }
715 }
716}