use std::hash::Hash;
use std::path::PathBuf;
use std::rc::Rc;
use std::time::Duration;
use crabshell_reactive::{Memo, Signal};
use crate::cx::Cx;
use crate::input::{Handler, Handlers};
use crate::keyboard::KeyEvent;
use crate::keyed::{self, KeyedList, KeyedMount};
use crate::prop::{IntoProp, Prop};
use crate::screencopy::ScreencopyProps;
use crate::scroll::ScrollProps;
use crate::style::Style;
use crate::text_input::InputProps;
pub trait Widget: 'static {
fn into_element(self: Box<Self>) -> Element;
}
pub struct Element {
pub(crate) name: &'static str,
pub(crate) style: Option<Prop<Style>>,
pub(crate) kind: ElementKind,
pub(crate) children: Vec<ElementChild>,
}
pub(crate) enum ElementKind {
Flex(FlexProps),
Stack,
Spacer,
Text(TextProps),
Icon(IconProps),
Image(ImageProps),
Capture(ScreencopyProps),
Button(Box<ButtonProps>),
Scroll(ScrollProps),
Input(Box<InputProps>),
}
pub(crate) enum Child {
One(Box<dyn Widget>),
Keyed(KeyedList),
}
pub(crate) enum ElementChild {
One(Box<Element>),
Keyed(KeyedMount),
}
fn describe(children: Vec<Child>) -> Vec<ElementChild> {
children
.into_iter()
.map(|child| match child {
Child::One(widget) => ElementChild::One(Box::new(widget.into_element())),
Child::Keyed(list) => ElementChild::Keyed(list.into_mount()),
})
.collect()
}
pub(crate) struct FlexProps {
pub(crate) column: bool,
pub(crate) grow: bool,
pub(crate) gap: Option<Prop<u32>>,
pub(crate) padding_vertical: Option<Prop<u32>>,
pub(crate) padding_horizontal: Option<Prop<u32>>,
pub(crate) align: Option<Prop<Align>>,
pub(crate) justify: Option<Prop<Justify>>,
}
pub(crate) struct TextProps {
pub(crate) text: Prop<String>,
pub(crate) ellipsis: Option<Prop<bool>>,
pub(crate) max_lines: Option<Prop<u32>>,
}
pub(crate) struct IconProps {
pub(crate) source: Prop<IconSource>,
pub(crate) size: Option<Prop<u32>>,
}
pub(crate) struct ImageProps {
pub(crate) source: Prop<PathBuf>,
pub(crate) width: Option<Prop<u32>>,
pub(crate) height: Option<Prop<u32>>,
pub(crate) fit: Option<Prop<Fit>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Fit {
#[default]
Fill,
Contain,
Cover,
}
pub(crate) struct ButtonProps {
pub(crate) hover_style: Option<Prop<Style>>,
pub(crate) pressed_style: Option<Prop<Style>>,
pub(crate) focus_style: Option<Prop<Style>>,
pub(crate) hovered: Option<Signal<bool>>,
pub(crate) handlers: Handlers,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Align {
Start,
Center,
End,
#[default]
Stretch,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Justify {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[derive(Clone, Debug, PartialEq)]
pub enum IconSource {
Name(String),
File(PathBuf),
Pixels(IconPixels),
}
#[derive(Clone, Debug, PartialEq)]
pub struct IconPixels {
pub width: u32,
pub height: u32,
pub data: Rc<[u8]>,
}
macro_rules! container_methods {
() => {
pub fn child(mut self, child: impl Widget) -> Self {
self.children.push(Child::One(Box::new(child)));
self
}
pub fn children_keyed<T, K, W>(
mut self,
items: impl FnMut() -> Vec<T> + 'static,
key: impl FnMut(&T) -> K + 'static,
view: impl FnMut(&Cx, Memo<T>) -> W + 'static,
) -> Self
where
T: Clone + PartialEq + 'static,
K: Eq + Hash + 'static,
W: Widget,
{
self
.children
.push(Child::Keyed(keyed::mount(items, key, view)));
self
}
pub fn exit(mut self, exit: impl IntoProp<Duration>) -> Self {
let last = self
.children
.iter_mut()
.rev()
.find_map(|child| match child {
Child::Keyed(list) => Some(list),
Child::One(_) => None,
});
match last {
Some(list) => list.exit = Some(exit.into_prop()),
None => tracing::warn!("`exit` was called before any `children_keyed`; it does nothing"),
}
self
}
};
}
macro_rules! style_method {
() => {
pub fn style(mut self, style: impl IntoProp<Style>) -> Self {
self.style = Some(style.into_prop());
self
}
};
}
macro_rules! flex_widget {
($name:ident, $column:literal, $doc:literal) => {
#[doc = $doc]
pub struct $name {
flex: FlexProps,
style: Option<Prop<Style>>,
children: Vec<Child>,
}
impl $name {
pub fn new() -> Self {
$name {
flex: FlexProps {
column: $column,
grow: false,
gap: None,
padding_vertical: None,
padding_horizontal: None,
align: None,
justify: None,
},
style: None,
children: Vec::new(),
}
}
pub fn gap(mut self, gap: impl IntoProp<u32>) -> Self {
self.flex.gap = Some(gap.into_prop());
self
}
pub fn padding(
mut self,
vertical: impl IntoProp<u32>,
horizontal: impl IntoProp<u32>,
) -> Self {
self.flex.padding_vertical = Some(vertical.into_prop());
self.flex.padding_horizontal = Some(horizontal.into_prop());
self
}
pub fn align(mut self, align: impl IntoProp<Align>) -> Self {
self.flex.align = Some(align.into_prop());
self
}
pub fn justify(mut self, justify: impl IntoProp<Justify>) -> Self {
self.flex.justify = Some(justify.into_prop());
self
}
container_methods!();
style_method!();
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
impl Widget for $name {
fn into_element(self: Box<Self>) -> Element {
Element {
name: stringify!($name),
style: self.style,
kind: ElementKind::Flex(self.flex),
children: describe(self.children),
}
}
}
};
}
flex_widget!(
Row,
false,
"Lays its children out left to right, like a CSS flexbox row."
);
flex_widget!(
Column,
true,
"Lays its children out top to bottom, like a CSS flexbox column."
);
pub struct Stack {
style: Option<Prop<Style>>,
children: Vec<Child>,
}
impl Stack {
pub fn new() -> Stack {
Stack {
style: None,
children: Vec::new(),
}
}
container_methods!();
style_method!();
}
impl Default for Stack {
fn default() -> Self {
Self::new()
}
}
impl Widget for Stack {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Stack",
style: self.style,
kind: ElementKind::Stack,
children: describe(self.children),
}
}
}
pub struct Spacer;
impl Spacer {
pub fn new() -> Spacer {
Spacer
}
}
impl Default for Spacer {
fn default() -> Self {
Self::new()
}
}
impl Widget for Spacer {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Spacer",
style: None,
kind: ElementKind::Spacer,
children: Vec::new(),
}
}
}
pub struct Text {
text: TextProps,
style: Option<Prop<Style>>,
}
impl Text {
pub fn new(text: impl IntoProp<String>) -> Text {
Text {
text: TextProps {
text: text.into_prop(),
ellipsis: None,
max_lines: None,
},
style: None,
}
}
pub fn ellipsis(mut self, ellipsis: impl IntoProp<bool>) -> Text {
self.text.ellipsis = Some(ellipsis.into_prop());
self
}
pub fn max_lines(mut self, max_lines: impl IntoProp<u32>) -> Text {
self.text.max_lines = Some(max_lines.into_prop());
self
}
style_method!();
}
impl Widget for Text {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Text",
style: self.style,
kind: ElementKind::Text(self.text),
children: Vec::new(),
}
}
}
pub struct Icon {
icon: IconProps,
style: Option<Prop<Style>>,
}
impl Icon {
pub const DEFAULT_SIZE: u32 = 16;
pub fn new(source: impl IntoProp<IconSource>) -> Icon {
Icon {
icon: IconProps {
source: source.into_prop(),
size: None,
},
style: None,
}
}
pub fn size(mut self, size: impl IntoProp<u32>) -> Icon {
self.icon.size = Some(size.into_prop());
self
}
style_method!();
}
pub struct Image {
image: ImageProps,
style: Option<Prop<Style>>,
}
impl Image {
pub const DEFAULT_SIZE: u32 = 64;
pub fn new(source: impl IntoProp<PathBuf>) -> Image {
Image {
image: ImageProps {
source: source.into_prop(),
width: None,
height: None,
fit: None,
},
style: None,
}
}
pub fn width(mut self, width: impl IntoProp<u32>) -> Image {
self.image.width = Some(width.into_prop());
self
}
pub fn height(mut self, height: impl IntoProp<u32>) -> Image {
self.image.height = Some(height.into_prop());
self
}
pub fn fit(mut self, fit: impl IntoProp<Fit>) -> Image {
self.image.fit = Some(fit.into_prop());
self
}
style_method!();
}
impl Widget for Image {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Image",
style: self.style,
kind: ElementKind::Image(self.image),
children: Vec::new(),
}
}
}
impl Widget for Icon {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Icon",
style: self.style,
kind: ElementKind::Icon(self.icon),
children: Vec::new(),
}
}
}
pub struct Button {
button: ButtonProps,
style: Option<Prop<Style>>,
child: Box<dyn Widget>,
}
impl Button {
pub fn new(child: impl Widget) -> Button {
Button {
child: Box::new(child),
button: ButtonProps {
hover_style: None,
pressed_style: None,
focus_style: None,
hovered: None,
handlers: Handlers::default(),
},
style: None,
}
}
pub fn hover_style(mut self, style: impl IntoProp<Style>) -> Button {
self.button.hover_style = Some(style.into_prop());
self
}
pub fn hovered(mut self, hovered: Signal<bool>) -> Button {
self.button.hovered = Some(hovered);
self
}
pub fn pressed_style(mut self, style: impl IntoProp<Style>) -> Button {
self.button.pressed_style = Some(style.into_prop());
self
}
pub fn focus_style(mut self, style: impl IntoProp<Style>) -> Button {
self.button.focus_style = Some(style.into_prop());
self
}
pub fn on_key(mut self, handler: impl Fn(&Cx, &KeyEvent) -> bool + 'static) -> Button {
self.button.handlers.key = Some(Rc::new(Handler::new(handler)));
self
}
pub fn on_press(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
self.button.handlers.press = Some(Rc::new(Handler::new(handler)));
self
}
pub fn on_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
self.button.handlers.click = Some(Rc::new(Handler::new(handler)));
self
}
pub fn on_right_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
self.button.handlers.right_click = Some(Rc::new(Handler::new(handler)));
self
}
pub fn on_middle_click(mut self, handler: impl Fn(&Cx) + 'static) -> Button {
self.button.handlers.middle_click = Some(Rc::new(Handler::new(handler)));
self
}
pub fn on_scroll(mut self, handler: impl Fn(&Cx, i32) + 'static) -> Button {
self.button.handlers.scroll = Some(Rc::new(Handler::new(handler)));
self
}
style_method!();
}
impl Widget for Button {
fn into_element(self: Box<Self>) -> Element {
Element {
name: "Button",
style: self.style,
kind: ElementKind::Button(Box::new(self.button)),
children: vec![ElementChild::One(Box::new(self.child.into_element()))],
}
}
}