1use std::hash::Hash;
8use std::path::PathBuf;
9use std::rc::Rc;
10
11use crabshell_reactive::Memo;
12
13use crate::cx::Cx;
14use crate::input::{Handler, Handlers};
15use crate::keyed::{self, KeyedMount};
16use crate::prop::{IntoProp, Prop};
17use crate::style::Style;
18
19pub trait Widget: 'static {
39 fn into_element(self: Box<Self>) -> Element;
44}
45
46pub struct Element {
49 pub(crate) name: &'static str,
50 pub(crate) style: Option<Prop<Style>>,
51 pub(crate) kind: ElementKind,
52 pub(crate) children: Vec<ElementChild>,
53}
54
55pub(crate) enum ElementKind {
56 Flex(FlexProps),
57 Stack,
58 Spacer,
59 Text(TextProps),
60 Icon(IconProps),
61 Button(ButtonProps),
62}
63
64pub(crate) enum Child {
67 One(Box<dyn Widget>),
68 Keyed(KeyedMount),
69}
70
71pub(crate) enum ElementChild {
73 One(Box<Element>),
74 Keyed(KeyedMount),
75}
76
77fn describe(children: Vec<Child>) -> Vec<ElementChild> {
80 children
81 .into_iter()
82 .map(|child| match child {
83 Child::One(widget) => ElementChild::One(Box::new(widget.into_element())),
84 Child::Keyed(mount) => ElementChild::Keyed(mount),
85 })
86 .collect()
87}
88
89pub(crate) struct FlexProps {
90 pub(crate) column: bool,
91 pub(crate) grow: bool,
94 pub(crate) gap: Option<Prop<u32>>,
95 pub(crate) padding_vertical: Option<Prop<u32>>,
96 pub(crate) padding_horizontal: Option<Prop<u32>>,
97 pub(crate) align: Option<Prop<Align>>,
98 pub(crate) justify: Option<Prop<Justify>>,
99}
100
101pub(crate) struct TextProps {
102 pub(crate) text: Prop<String>,
103 pub(crate) ellipsis: Option<Prop<bool>>,
104 pub(crate) max_lines: Option<Prop<u32>>,
105}
106
107pub(crate) struct IconProps {
108 pub(crate) source: Prop<IconSource>,
109 pub(crate) size: Option<Prop<u32>>,
110}
111
112pub(crate) struct ButtonProps {
113 pub(crate) hover_style: Option<Prop<Style>>,
114 pub(crate) pressed_style: Option<Prop<Style>>,
115 pub(crate) handlers: Handlers,
116}
117
118#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
121pub enum Align {
122 Start,
124 Center,
126 End,
128 #[default]
130 Stretch,
131}
132
133#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
136pub enum Justify {
137 #[default]
139 Start,
140 Center,
142 End,
144 SpaceBetween,
146 SpaceAround,
148 SpaceEvenly,
150}
151
152#[derive(Clone, Debug, PartialEq)]
154pub enum IconSource {
155 Name(String),
158 File(PathBuf),
160 Pixels(IconPixels),
162}
163
164#[derive(Clone, Debug, PartialEq)]
167pub struct IconPixels {
168 pub width: u32,
170 pub height: u32,
172 pub data: Rc<[u8]>,
174}
175
176macro_rules! container_methods {
178 () => {
179 pub fn child(mut self, child: impl Widget) -> Self {
181 self.children.push(Child::One(Box::new(child)));
182 self
183 }
184
185 pub fn children_keyed<T, K, W>(
196 mut self,
197 items: impl FnMut() -> Vec<T> + 'static,
198 key: impl FnMut(&T) -> K + 'static,
199 view: impl FnMut(&Cx, Memo<T>) -> W + 'static,
200 ) -> Self
201 where
202 T: Clone + PartialEq + 'static,
203 K: Eq + Hash + 'static,
204 W: Widget,
205 {
206 self
207 .children
208 .push(Child::Keyed(keyed::mount(items, key, view)));
209 self
210 }
211 };
212}
213
214macro_rules! style_method {
216 () => {
217 pub fn style(mut self, style: impl IntoProp<Style>) -> Self {
219 self.style = Some(style.into_prop());
220 self
221 }
222 };
223}
224
225macro_rules! flex_widget {
227 ($name:ident, $column:literal, $doc:literal) => {
228 #[doc = $doc]
229 pub struct $name {
233 flex: FlexProps,
234 style: Option<Prop<Style>>,
235 children: Vec<Child>,
236 }
237
238 impl $name {
239 pub fn new() -> Self {
241 $name {
242 flex: FlexProps {
243 column: $column,
244 grow: false,
245 gap: None,
246 padding_vertical: None,
247 padding_horizontal: None,
248 align: None,
249 justify: None,
250 },
251 style: None,
252 children: Vec::new(),
253 }
254 }
255
256 pub fn gap(mut self, gap: impl IntoProp<u32>) -> Self {
258 self.flex.gap = Some(gap.into_prop());
259 self
260 }
261
262 pub fn padding(
266 mut self,
267 vertical: impl IntoProp<u32>,
268 horizontal: impl IntoProp<u32>,
269 ) -> Self {
270 self.flex.padding_vertical = Some(vertical.into_prop());
271 self.flex.padding_horizontal = Some(horizontal.into_prop());
272 self
273 }
274
275 pub fn align(mut self, align: impl IntoProp<Align>) -> Self {
277 self.flex.align = Some(align.into_prop());
278 self
279 }
280
281 pub fn justify(mut self, justify: impl IntoProp<Justify>) -> Self {
283 self.flex.justify = Some(justify.into_prop());
284 self
285 }
286
287 container_methods!();
288 style_method!();
289 }
290
291 impl Default for $name {
292 fn default() -> Self {
293 Self::new()
294 }
295 }
296
297 impl Widget for $name {
298 fn into_element(self: Box<Self>) -> Element {
299 Element {
300 name: stringify!($name),
301 style: self.style,
302 kind: ElementKind::Flex(self.flex),
303 children: describe(self.children),
304 }
305 }
306 }
307 };
308}
309
310flex_widget!(
311 Row,
312 false,
313 "Lays its children out left to right, like a CSS flexbox row."
314);
315flex_widget!(
316 Column,
317 true,
318 "Lays its children out top to bottom, like a CSS flexbox column."
319);
320
321pub struct Stack {
324 style: Option<Prop<Style>>,
325 children: Vec<Child>,
326}
327
328impl Stack {
329 pub fn new() -> Stack {
331 Stack {
332 style: None,
333 children: Vec::new(),
334 }
335 }
336
337 container_methods!();
338 style_method!();
339}
340
341impl Default for Stack {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347impl Widget for Stack {
348 fn into_element(self: Box<Self>) -> Element {
349 Element {
350 name: "Stack",
351 style: self.style,
352 kind: ElementKind::Stack,
353 children: describe(self.children),
354 }
355 }
356}
357
358pub struct Spacer;
361
362impl Spacer {
363 pub fn new() -> Spacer {
365 Spacer
366 }
367}
368
369impl Default for Spacer {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375impl Widget for Spacer {
376 fn into_element(self: Box<Self>) -> Element {
377 Element {
378 name: "Spacer",
379 style: None,
380 kind: ElementKind::Spacer,
381 children: Vec::new(),
382 }
383 }
384}
385
386pub struct Text {
392 text: TextProps,
393 style: Option<Prop<Style>>,
394}
395
396impl Text {
397 pub fn new(text: impl IntoProp<String>) -> Text {
399 Text {
400 text: TextProps {
401 text: text.into_prop(),
402 ellipsis: None,
403 max_lines: None,
404 },
405 style: None,
406 }
407 }
408
409 pub fn ellipsis(mut self, ellipsis: impl IntoProp<bool>) -> Text {
412 self.text.ellipsis = Some(ellipsis.into_prop());
413 self
414 }
415
416 pub fn max_lines(mut self, max_lines: impl IntoProp<u32>) -> Text {
420 self.text.max_lines = Some(max_lines.into_prop());
421 self
422 }
423
424 style_method!();
425}
426
427impl Widget for Text {
428 fn into_element(self: Box<Self>) -> Element {
429 Element {
430 name: "Text",
431 style: self.style,
432 kind: ElementKind::Text(self.text),
433 children: Vec::new(),
434 }
435 }
436}
437
438pub struct Icon {
440 icon: IconProps,
441 style: Option<Prop<Style>>,
442}
443
444impl Icon {
445 pub const DEFAULT_SIZE: u32 = 16;
447
448 pub fn new(source: impl IntoProp<IconSource>) -> Icon {
451 Icon {
452 icon: IconProps {
453 source: source.into_prop(),
454 size: None,
455 },
456 style: None,
457 }
458 }
459
460 pub fn size(mut self, size: impl IntoProp<u32>) -> Icon {
462 self.icon.size = Some(size.into_prop());
463 self
464 }
465
466 style_method!();
467}
468
469impl Widget for Icon {
470 fn into_element(self: Box<Self>) -> Element {
471 Element {
472 name: "Icon",
473 style: self.style,
474 kind: ElementKind::Icon(self.icon),
475 children: Vec::new(),
476 }
477 }
478}
479
480pub struct Button {
487 button: ButtonProps,
488 style: Option<Prop<Style>>,
489 child: Box<dyn Widget>,
490}
491
492impl Button {
493 pub fn new(child: impl Widget) -> Button {
495 Button {
496 child: Box::new(child),
497 button: ButtonProps {
498 hover_style: None,
499 pressed_style: None,
500 handlers: Handlers::default(),
501 },
502 style: None,
503 }
504 }
505
506 pub fn hover_style(mut self, style: impl IntoProp<Style>) -> Button {
509 self.button.hover_style = Some(style.into_prop());
510 self
511 }
512
513 pub fn pressed_style(mut self, style: impl IntoProp<Style>) -> Button {
516 self.button.pressed_style = Some(style.into_prop());
517 self
518 }
519
520 pub fn on_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
523 self.button.handlers.click = Some(Rc::new(Handler::new(handler)));
524 self
525 }
526
527 pub fn on_right_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
530 self.button.handlers.right_click = Some(Rc::new(Handler::new(handler)));
531 self
532 }
533
534 pub fn on_middle_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
537 self.button.handlers.middle_click = Some(Rc::new(Handler::new(handler)));
538 self
539 }
540
541 pub fn on_scroll(mut self, handler: impl Fn(&Cx, i32) + 'static) -> Button {
546 self.button.handlers.scroll = Some(Rc::new(Handler::new(handler)));
547 self
548 }
549
550 style_method!();
551}
552
553impl Widget for Button {
554 fn into_element(self: Box<Self>) -> Element {
555 Element {
556 name: "Button",
557 style: self.style,
558 kind: ElementKind::Button(self.button),
559 children: vec![ElementChild::One(Box::new(self.child.into_element()))],
560 }
561 }
562}