diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1c68f97cc..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.inlayHints.enable": true -} diff --git a/Cargo.toml b/Cargo.toml index 19b62bf5b..7672c10d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -stretch = "0.3.2" -tui = { version = "0.15.0", features = ["crossterm"] } -crossterm = "0.20.0" +tui = { version = "0.16.0", features = ["crossterm"] } +crossterm = "0.22.1" anyhow = "1.0.42" thiserror = "1.0.24" -dioxus = { path = "../../dioxus", features = ["core"] } -hecs = "0.6.0" -tui-template = { path = "../../Tinkering/tui-builder" } -# tui-template = { git = "https://github.com/jkelleyrtp/tui-builder.git" } +dioxus = { path = "../../dioxus" } +hecs = "0.7.3" +ctrlc = "3.2.1" +bumpalo = { version = "3.8.0", features = ["boxed"] } +stretch2 = { path = "../../Tinkering/stretch2" } +# stretch2 = { git = "https://github.com/elbaro/stretch2.git" } diff --git a/examples/frame.rs b/examples/frame.rs new file mode 100644 index 000000000..3b913b9c9 --- /dev/null +++ b/examples/frame.rs @@ -0,0 +1,85 @@ +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + rink::render_vdom(&dom).unwrap(); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + // justify_content: "center", + // align_items: "center", + // flex_direction: "row", + // background_color: "red", + + p { + background_color: "black", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "hi" + "hi" + "hi" + } + + li { + background_color: "red", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + } + li { + background_color: "blue", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + } + p { + background_color: "yellow", + "asd" + } + p { + background_color: "green", + "asd" + } + p { + background_color: "white", + "asd" + } + p { + background_color: "cyan", + "asd" + } + } + }) +} diff --git a/examples/layouts.rs b/examples/layouts.rs new file mode 100644 index 000000000..e4278c2b1 --- /dev/null +++ b/examples/layouts.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use dioxus::{core::ElementId, prelude::*}; +use rink::TuiNode; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + let mut layout = stretch2::Stretch::new(); + let mut nodes = HashMap::new(); + rink::collect_layout(&mut layout, &mut nodes, &dom, dom.base_scope().root_node()); + + let node = nodes + .remove(&dom.base_scope().root_node().mounted_id()) + .unwrap(); + + layout + .compute_layout(node.layout, stretch2::geometry::Size::undefined()) + .unwrap(); + + for (id, node) in nodes.drain() { + println!("{:?}", layout.layout(node.layout)); + } +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + + div { + "hi" + } + div { + "bi" + "bi" + } + } + }) +} + +// fn print_layout(mut nodes: HashMap, node: &VNode) { +// match node { +// VNode::Text(_) => todo!(), +// VNode::Element(_) => todo!(), +// VNode::Fragment(_) => todo!(), +// VNode::Component(_) => todo!(), +// VNode::Placeholder(_) => todo!(), +// } +// } diff --git a/examples/list.rs b/examples/list.rs new file mode 100644 index 000000000..ebdb68e3c --- /dev/null +++ b/examples/list.rs @@ -0,0 +1,31 @@ +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + rink::render_vdom(&dom).unwrap(); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + border_width: "1px", + + h1 { height: "2px", color: "green", + "that's awesome!" + } + + ul { + flex_direction: "column", + padding_left: "3px", + (0..10).map(|i| rsx!( + "> hello {i}" + )) + } + } + }) +} diff --git a/examples/quadrants.rs b/examples/quadrants.rs new file mode 100644 index 000000000..88138eeb7 --- /dev/null +++ b/examples/quadrants.rs @@ -0,0 +1,63 @@ +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + rink::render_vdom(&dom).unwrap(); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + + div { + width: "100%", + height: "50%", + flex_direction: "row", + div { + border_width: "1px", + width: "50%", + height: "100%", + background_color: "red", + justify_content: "center", + align_items: "center", + "[A]" + } + div { + width: "50%", + height: "100%", + background_color: "black", + justify_content: "center", + align_items: "center", + "[B]" + } + } + + div { + width: "100%", + height: "50%", + flex_direction: "row", + div { + width: "50%", + height: "100%", + background_color: "green", + justify_content: "center", + align_items: "center", + "[C]" + } + div { + width: "50%", + height: "100%", + background_color: "blue", + justify_content: "center", + align_items: "center", + "[D]" + } + } + } + }) +} diff --git a/examples/strecher.rs b/examples/strecher.rs new file mode 100644 index 000000000..b1fa93050 --- /dev/null +++ b/examples/strecher.rs @@ -0,0 +1,34 @@ +use stretch2::prelude::*; + +fn main() -> Result<(), Error> { + let mut stretch = Stretch::new(); + + let child = stretch.new_node( + Style { + size: Size { + width: Dimension::Percent(0.5), + height: Dimension::Auto, + }, + ..Default::default() + }, + &[], + )?; + + let node = stretch.new_node( + Style { + size: Size { + width: Dimension::Points(100.0), + height: Dimension::Points(100.0), + }, + justify_content: JustifyContent::Center, + ..Default::default() + }, + &[child], + )?; + + stretch.compute_layout(node, Size::undefined())?; + println!("node: {:#?}", stretch.layout(node)?); + println!("child: {:#?}", stretch.layout(child)?); + + Ok(()) +} diff --git a/examples/text.rs b/examples/text.rs new file mode 100644 index 000000000..3b913b9c9 --- /dev/null +++ b/examples/text.rs @@ -0,0 +1,85 @@ +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + rink::render_vdom(&dom).unwrap(); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + // justify_content: "center", + // align_items: "center", + // flex_direction: "row", + // background_color: "red", + + p { + background_color: "black", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "hi" + "hi" + "hi" + } + + li { + background_color: "red", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + "bib" + } + li { + background_color: "blue", + flex_direction: "column", + justify_content: "center", + align_items: "center", + // height: "10%", + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + "zib" + } + p { + background_color: "yellow", + "asd" + } + p { + background_color: "green", + "asd" + } + p { + background_color: "white", + "asd" + } + p { + background_color: "cyan", + "asd" + } + } + }) +} diff --git a/src/attributes.rs b/src/attributes.rs new file mode 100644 index 000000000..fb267fa9c --- /dev/null +++ b/src/attributes.rs @@ -0,0 +1,610 @@ +/* +- [ ] pub display: Display, +- [ ] pub position_type: PositionType, +- [ ] pub direction: Direction, + +- [x] pub flex_direction: FlexDirection, +- [x] pub flex_wrap: FlexWrap, +- [x] pub flex_grow: f32, +- [x] pub flex_shrink: f32, +- [x] pub flex_basis: Dimension, + +- [ ] pub overflow: Overflow, + +- [x] pub align_items: AlignItems, +- [x] pub align_self: AlignSelf, +- [x] pub align_content: AlignContent, + +- [ ] pub margin: Rect, +- [ ] pub padding: Rect, + +- [x] pub justify_content: JustifyContent, +- [ ] pub position: Rect, +- [ ] pub border: Rect, +- [ ] pub size: Size, + +- [ ] pub min_size: Size, +- [ ] pub max_size: Size, +- [ ] pub aspect_ratio: Number, +*/ + +use stretch2::{prelude::*, style::Style}; +use tui::style::Style as TuiStyle; + +pub struct StyleModifer { + pub style: Style, + pub tui_style: TuiStyle, +} + +enum TuiModifier { + Text, +} + +/// applies the entire html namespace defined in dioxus-html +pub fn apply_attributes( + // + name: &str, + value: &str, + style: &mut StyleModifer, +) { + match name { + "align-content" + | "align-items" + | "align-self" => apply_align(name, value, style), + + "animation" + | "animation-delay" + | "animation-direction" + | "animation-duration" + | "animation-fill-mode" + | "animation-iteration-count" + | "animation-name" + | "animation-play-state" + | "animation-timing-function" => apply_animation(name, value, style), + + "backface-visibility" => {} + + "background" + | "background-attachment" + | "background-clip" + | "background-color" + | "background-image" + | "background-origin" + | "background-position" + | "background-repeat" + | "background-size" => apply_background(name, value, style), + + "border" + | "border-bottom" + | "border-bottom-color" + | "border-bottom-left-radius" + | "border-bottom-right-radius" + | "border-bottom-style" + | "border-bottom-width" + | "border-collapse" + | "border-color" + | "border-image" + | "border-image-outset" + | "border-image-repeat" + | "border-image-slice" + | "border-image-source" + | "border-image-width" + | "border-left" + | "border-left-color" + | "border-left-style" + | "border-left-width" + | "border-radius" + | "border-right" + | "border-right-color" + | "border-right-style" + | "border-right-width" + | "border-spacing" + | "border-style" + | "border-top" + | "border-top-color" + | "border-top-left-radius" + | "border-top-right-radius" + | "border-top-style" + | "border-top-width" + | "border-width" => apply_border(name, value, style), + + "bottom" => {} + "box-shadow" => {} + "box-sizing" => {} + "caption-side" => {} + "clear" => {} + "clip" => {} + + "color" => { + // text color + } + + "column-count" + | "column-fill" + | "column-gap" + | "column-rule" + | "column-rule-color" + | "column-rule-style" + | "column-rule-width" + | "column-span" + // add column-width + | "column-width" => apply_column(name, value, style), + + "columns" => {} + + "content" => {} + "counter-increment" => {} + "counter-reset" => {} + + "cursor" => {} + "direction" => {} + + "display" => apply_display(name, value, style), + + "empty-cells" => {} + + "flex" + | "flex-basis" + | "flex-direction" + | "flex-flow" + | "flex-grow" + | "flex-shrink" + | "flex-wrap" => apply_flex(name, value, style), + + "float" => {} + + "font" + | "font-family" + | "font-size" + | "font-size-adjust" + | "font-stretch" + | "font-style" + | "font-variant" + | "font-weight" => apply_font(name, value, style), + + "height" => { + if value.ends_with("%") { + if let Ok(pct) = value.trim_end_matches("%").parse::() { + style.style.size.height = Dimension::Percent(pct / 100.0); + } + } else if value.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.size.height = Dimension::Points(px); + } + } + } + "justify-content" => { + use JustifyContent::*; + style.style.justify_content = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "space-between" => SpaceBetween, + "space-around" => SpaceAround, + "space-evenly" => SpaceEvenly, + _ => FlexStart, + }; + } + "left" => {} + "letter-spacing" => {} + "line-height" => {} + + "list-style" + | "list-style-image" + | "list-style-position" + | "list-style-type" => {} + + "margin" + | "margin-bottom" + | "margin-left" + | "margin-right" + | "margin-top" => apply_margin(name, value, style), + + "max-height" => {} + "max-width" => {} + "min-height" => {} + "min-width" => {} + + "opacity" => {} + "order" => {} + "outline" => {} + + "outline-color" + | "outline-offset" + | "outline-style" + | "outline-width" => {} + + "overflow" + | "overflow-x" + | "overflow-y" => apply_overflow(name, value, style), + + "padding" + | "padding-bottom" + | "padding-left" + | "padding-right" + | "padding-top" => apply_padding(name, value, style), + + "page-break-after" + | "page-break-before" + | "page-break-inside" => {} + + "perspective" + | "perspective-origin" => {} + + "position" => {} + "pointer-events" => {} + "quotes" => {} + "resize" => {} + "right" => {} + "tab-size" => {} + "table-layout" => {} + + "text-align" + | "text-align-last" + | "text-decoration" + | "text-decoration-color" + | "text-decoration-line" + | "text-decoration-style" + | "text-indent" + | "text-justify" + | "text-overflow" + | "text-shadow" + | "text-transform" => apply_text(name, value, style), + + "top" => {} + + "transform" + | "transform-origin" + | "transform-style" => apply_transform(name, value, style), + + "transition" + | "transition-delay" + | "transition-duration" + | "transition-property" + | "transition-timing-function" => apply_transition(name, value, style), + + "vertical-align" => {} + "visibility" => {} + "white-space" => {} + "width" => { + if value.ends_with("%") { + if let Ok(pct) = value.trim_end_matches("%").parse::() { + style.style.size.width = Dimension::Percent(pct / 100.0); + } + } else if value.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.size.width = Dimension::Points(px); + } + } + } + "word-break" => {} + "word-spacing" => {} + "word-wrap" => {} + "z-index" => {} + _ => {} + } +} + +fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) { + match name { + // todo: add more overflow support to stretch2 + "overflow" | "overflow-x" | "overflow-y" => { + style.style.overflow = match value { + "auto" => Overflow::Visible, + "hidden" => Overflow::Hidden, + "scroll" => Overflow::Scroll, + "visible" => Overflow::Visible, + _ => Overflow::Visible, + }; + } + _ => {} + } +} + +fn apply_display(name: &str, value: &str, style: &mut StyleModifer) { + use stretch2::style::Display; + style.style.display = match value { + "flex" => Display::Flex, + "block" => Display::None, + _ => Display::Flex, + } + + // TODO: there are way more variants + // stretch needs to be updated to handle them + // + // "block" => Display::Block, + // "inline" => Display::Inline, + // "inline-block" => Display::InlineBlock, + // "inline-table" => Display::InlineTable, + // "list-item" => Display::ListItem, + // "run-in" => Display::RunIn, + // "table" => Display::Table, + // "table-caption" => Display::TableCaption, + // "table-cell" => Display::TableCell, + // "table-column" => Display::TableColumn, + // "table-column-group" => Display::TableColumnGroup, + // "table-footer-group" => Display::TableFooterGroup, + // "table-header-group" => Display::TableHeaderGroup, + // "table-row" => Display::TableRow, + // "table-row-group" => Display::TableRowGroup, + // "none" => Display::None, + // _ => Display::Inline, +} + +fn apply_background(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "background-color" => { + use tui::style::Color; + match value { + "red" => style.tui_style.bg.replace(Color::Red), + "green" => style.tui_style.bg.replace(Color::Green), + "blue" => style.tui_style.bg.replace(Color::Blue), + "yellow" => style.tui_style.bg.replace(Color::Yellow), + "cyan" => style.tui_style.bg.replace(Color::Cyan), + "magenta" => style.tui_style.bg.replace(Color::Magenta), + "white" => style.tui_style.bg.replace(Color::White), + "black" => style.tui_style.bg.replace(Color::Black), + _ => None, + }; + } + "background" => {} + "background-attachment" => {} + "background-clip" => {} + "background-image" => {} + "background-origin" => {} + "background-position" => {} + "background-repeat" => {} + "background-size" => {} + _ => {} + } +} + +fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "border" => {} + "border-bottom" => {} + "border-bottom-color" => {} + "border-bottom-left-radius" => {} + "border-bottom-right-radius" => {} + "border-bottom-style" => {} + "border-bottom-width" => {} + "border-collapse" => {} + "border-color" => {} + "border-image" => {} + "border-image-outset" => {} + "border-image-repeat" => {} + "border-image-slice" => {} + "border-image-source" => {} + "border-image-width" => {} + "border-left" => {} + "border-left-color" => {} + "border-left-style" => {} + "border-left-width" => {} + "border-radius" => {} + "border-right" => {} + "border-right-color" => {} + "border-right-style" => {} + "border-right-width" => {} + "border-spacing" => {} + "border-style" => {} + "border-top" => {} + "border-top-color" => {} + "border-top-left-radius" => {} + "border-top-right-radius" => {} + "border-top-style" => {} + "border-top-width" => {} + "border-width" => { + if let Ok(px) = value.trim_end_matches("px").parse::() { + // tuistyle = px; + } + } + _ => {} + } +} + +fn apply_animation(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "animation" => {} + "animation-delay" => {} + "animation-direction =>{}" => {} + "animation-duration" => {} + "animation-fill-mode" => {} + "animation-itera =>{}tion-count" => {} + "animation-name" => {} + "animation-play-state" => {} + "animation-timing-function" => {} + _ => {} + } +} + +fn apply_column(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "column-count" => {} + "column-fill" => {} + "column-gap" => {} + "column-rule" => {} + "column-rule-color" => {} + "column-rule-style" => {} + "column-rule-width" => {} + "column-span" => {} + "column-width" => {} + _ => {} + } +} + +fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) { + // - [x] pub flex_direction: FlexDirection, + // - [x] pub flex_wrap: FlexWrap, + // - [x] pub flex_grow: f32, + // - [x] pub flex_shrink: f32, + // - [x] pub flex_basis: Dimension, + + match name { + "flex" => {} + "flex-direction" => { + use FlexDirection::*; + style.style.flex_direction = match value { + "row" => Row, + "row-reverse" => RowReverse, + "column" => Column, + "column-reverse" => ColumnReverse, + _ => Row, + }; + } + "flex-basis" => { + if value.ends_with("%") { + if let Ok(pct) = value.trim_end_matches("%").parse::() { + style.style.flex_basis = Dimension::Percent(pct / 100.0); + } + } else if value.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.flex_basis = Dimension::Points(px); + } + } + } + "flex-flow" => {} + "flex-grow" => { + if let Ok(val) = value.parse::() { + style.style.flex_grow = val; + } + } + "flex-shrink" => { + if let Ok(px) = value.parse::() { + style.style.flex_shrink = px; + } + } + "flex-wrap" => { + use FlexWrap::*; + style.style.flex_wrap = match value { + "nowrap" => NoWrap, + "wrap" => Wrap, + "wrap-reverse" => WrapReverse, + _ => NoWrap, + }; + } + _ => {} + } +} + +fn apply_font(name: &str, value: &str, style: &mut StyleModifer) { + todo!() +} + +fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) { + // // left + // start: stretch::style::Dimension::Points(10f32), + + // // right? + // end: stretch::style::Dimension::Points(10f32), + + // // top? + // // top: stretch::style::Dimension::Points(10f32), + + // // bottom? + // // bottom: stretch::style::Dimension::Points(10f32), + + match name { + "padding" => { + if name.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.padding.bottom = Dimension::Points(px); + style.style.padding.top = Dimension::Points(px); + style.style.padding.start = Dimension::Points(px); + style.style.padding.end = Dimension::Points(px); + } + } else if name.ends_with("%") { + if let Ok(pct) = value.trim_end_matches("%").parse::() { + // + } + } + } + "padding-bottom" => { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.padding.bottom = Dimension::Points(px); + } + } + "padding-left" => { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.padding.start = Dimension::Points(px); + } + } + "padding-right" => { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.padding.end = Dimension::Points(px); + } + } + "padding-top" => { + if let Ok(px) = value.trim_end_matches("px").parse::() { + style.style.padding.top = Dimension::Points(px); + } + } + _ => {} + } +} + +fn apply_text(name: &str, value: &str, style: &mut StyleModifer) { + todo!() +} + +fn apply_transform(name: &str, value: &str, style: &mut StyleModifer) { + todo!() +} + +fn apply_transition(name: &str, value: &str, style: &mut StyleModifer) { + todo!() +} + +fn apply_align(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "align-items" => { + use AlignItems::*; + style.style.align_items = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "baseline" => Baseline, + "stretch" => Stretch, + _ => FlexStart, + }; + } + "align-content" => { + use AlignContent::*; + style.style.align_content = match value { + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "space-between" => SpaceBetween, + "space-around" => SpaceAround, + _ => FlexStart, + }; + } + "align-self" => { + use AlignSelf::*; + style.style.align_self = match value { + "auto" => Auto, + "flex-start" => FlexStart, + "flex-end" => FlexEnd, + "center" => Center, + "baseline" => Baseline, + "stretch" => Stretch, + _ => Auto, + }; + } + _ => {} + } +} + +pub fn apply_size(name: &str, value: &str, style: &mut StyleModifer) { + // +} + +pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) { + match name { + "margin" => {} + "margin-bottom" => {} + "margin-left" => {} + "margin-right" => {} + "margin-top" => {} + _ => {} + } +} diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 000000000..c7b01046a --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,88 @@ +use dioxus::core::*; +use std::collections::HashMap; +use tui::style::Style as TuiStyle; + +use crate::{ + attributes::{apply_attributes, StyleModifer}, + TuiNode, +}; + +pub fn collect_layout<'a>( + layout: &mut stretch2::Stretch, + nodes: &mut HashMap>, + vdom: &'a VirtualDom, + node: &'a VNode<'a>, +) { + use stretch2::prelude::*; + + match node { + VNode::Text(t) => { + // + let id = t.id.get().unwrap(); + let char_len = t.text.chars().count(); + + let style = Style { + size: Size { + // characters are 1 point tall + height: Dimension::Points(1.0), + + // text is as long as it is declared + width: Dimension::Points(char_len as f32), + }, + + ..Default::default() + }; + + nodes.insert( + id, + TuiNode { + node, + block_style: tui::style::Style::default(), + layout: layout.new_node(style, &[]).unwrap(), + }, + ); + } + VNode::Element(el) => { + // gather up all the styles from the attribute list + let mut modifier = StyleModifer { + style: Style::default(), + tui_style: TuiStyle::default(), + }; + + for &Attribute { name, value, .. } in el.attributes { + apply_attributes(name, value, &mut modifier); + } + + // Layout the children + for child in el.children { + collect_layout(layout, nodes, vdom, child); + } + + // Set all direct nodes as our children + let mut child_layout = vec![]; + for el in el.children { + let ite = ElementIdIterator::new(vdom, el); + for node in ite { + child_layout.push(nodes[&node.mounted_id()].layout) + } + } + + nodes.insert( + node.mounted_id(), + TuiNode { + node, + block_style: modifier.tui_style, + layout: layout.new_node(modifier.style, &child_layout).unwrap(), + }, + ); + } + VNode::Fragment(el) => { + // + for child in el.children { + collect_layout(layout, nodes, vdom, child); + } + } + VNode::Component(_) => todo!(), + VNode::Placeholder(_) => todo!(), + }; +} diff --git a/src/lib.rs b/src/lib.rs index ad6dc8596..4a606318f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,270 +1,152 @@ use anyhow::Result; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; use dioxus::core::*; -use tui::backend::CrosstermBackend; -use tui::layout::{Layout, Rect}; -use tui::style::{Modifier, Style as TuiStyle}; -use tui::text::{Span, Spans}; -use tui::widgets::{Block, Paragraph}; -use tui_template::tuiapp::CrosstermFrame; -struct RinkDom { - vdom: VirtualDom, +use std::{collections::HashMap, io}; +use stretch2::{prelude::Size, Stretch}; +use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal}; + +mod attributes; +mod layout; +mod render; + +pub use attributes::*; +pub use layout::*; +pub use render::*; + +pub struct TuiNode<'a> { + pub layout: stretch2::node::Node, + pub block_style: TuiStyle, + pub node: &'a VNode<'a>, } -impl RinkDom { - pub fn new(app: FC<()>) -> Self { - Self { - vdom: VirtualDom::new(app), - } - } +pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { + /* + -> collect all the nodes with their layout + -> solve their layout + -> render the nodes in the right place with tui/crosstream + -> while rendering, apply styling - fn render_vnode<'a>( - &self, - f: &mut CrosstermFrame, - node: &'a VNode<'a>, - state: &mut RenderState<'a>, - ) -> Rect { - match &node.kind { - VNodeKind::Fragment(_) => todo!(), - VNodeKind::Component(_) => todo!(), - VNodeKind::Suspended { node } => todo!(), + use simd to compare lines for diffing? - VNodeKind::Text(te) => { - let span = Span::styled(te.text, TuiStyle::default()); + */ + let mut layout = Stretch::new(); + let mut nodes = HashMap::new(); - let mut m = Modifier::empty(); + let root_node = vdom.base_scope().root_node(); + layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); - for style in &state.current_styles { - match style { - Styles::Bold => m = m | Modifier::BOLD, - Styles::Italic => m = m | Modifier::ITALIC, - Styles::Strikethrough => m = m | Modifier::CROSSED_OUT, - Styles::Emphasis => m = m | Modifier::ITALIC, - Styles::Underline => m = m | Modifier::UNDERLINED, - } - } + /* + Get the terminal to calcualte the layout from + */ + enable_raw_mode().unwrap(); + ctrlc::set_handler(move || { + disable_raw_mode().unwrap(); + }) + .expect("Error setting Ctrl-C handler"); + let mut stdout = std::io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); + let backend = CrosstermBackend::new(io::stdout()); + let mut terminal = Terminal::new(backend).unwrap(); - let style = TuiStyle::default().add_modifier(m); - let span = span.styled_graphemes(style); - let cur_block = state.block_stack.last_mut().unwrap(); + let dims = terminal.size().unwrap(); + let width = dims.width; + let height = dims.height; - // Paragraph + /* + Compute the layout given th terminal size + */ + let node_id = root_node.try_mounted_id().unwrap(); + let root_layout = nodes[&node_id].layout; + layout.compute_layout( + root_layout, + Size { + width: stretch2::prelude::Number::Defined(width as f32), + height: stretch2::prelude::Number::Defined(height as f32), + }, + )?; - // f.render_widget(span); - } - VNodeKind::Element(el) => { - // - let mut new_layout = false; + terminal.draw(|frame| { + // + render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); + assert!(nodes.is_empty()); + })?; - // all of our supported styles + std::thread::sleep(std::time::Duration::from_millis(5000)); - match el.tag_name { - // obviously semantically not really correct - "div" => { - state.layouts.push(Layout::default()); - new_layout = true; - } + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; - "title" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => { - let mut block = state.block_stack.pop().unwrap(); - let children = el.children; - - if let (1, Some(VNodeKind::Text(te))) = - (children.len(), children.get(0).map(|f| &f.kind)) - { - block = block.title(vec![Span::from(te.text)]); - } - - state.block_stack.push(block); - } - - "span" | "header" => {} - - "footer" => {} - - "p" => { - state.layouts.push(Layout::default()); - new_layout = true; - } - - // elements that style for whatever reason - "em" => state.current_styles.push(Styles::Emphasis), - "i" => state.current_styles.push(Styles::Italic), - "b" => state.current_styles.push(Styles::Bold), - "u" => state.current_styles.push(Styles::Underline), - "strike" => state.current_styles.push(Styles::Strikethrough), - - "li" => {} - "ul" => {} - "ol" => {} - "code" => {} - "hr" => {} - - // Tables - "table" => {} - "tr" => {} - "th" => {} - "td" => {} - - // Inputs - "input" => {} - "label" => {} - - _ => {} - } - - let cur_layout = state.layouts.last_mut().unwrap(); - let cur_block = state.block_stack.last_mut().unwrap(); - let mut cur_style = TuiStyle::default(); - - for attr in el.attributes { - if attr.namespace == Some("style") { - match attr.name { - "width" => {} - "height" => {} - - "background" => { - // - // cur_style.bg - // cur_block.style() - } - "background-color" => {} - - "border" => {} - "border-bottom" => {} - "border-bottom-color" => {} - "border-bottom-style" => {} - "border-bottom-width" => {} - "border-color" => {} - "border-left" => {} - "border-left-color" => {} - "border-left-style" => {} - "border-left-width" => {} - "border-right" => {} - "border-right-color" => {} - "border-right-style" => {} - "border-right-width" => {} - "border-style" => {} - "border-top" => {} - "border-top-color" => {} - "border-top-style" => {} - "border-top-width" => {} - "border-width" => {} - - "clear" => {} - "clip" => {} - "color" => {} - "cursor" => {} - "display" => {} - "filter" => {} - "float" => {} - "font" => {} - "font-family" => {} - "font-size" => {} - "font-variant" => {} - "font-weight" => {} - - "left" => {} - "letter-spacing" => {} - "line-height" => {} - "list-style" => {} - "list-style-image" => {} - "list-style-position" => {} - "list-style-type" => {} - "margin" => {} - "margin-bottom" => {} - "margin-left" => {} - "margin-right" => {} - "margin-top" => {} - "overflow" => {} - "padding" => {} - "padding-bottom" => {} - "padding-left" => {} - "padding-right" => {} - "padding-top" => {} - "position" => {} - "stroke-dasharray" => {} - "stroke-dashoffset" => {} - "text-align" => {} - "text-decoration" => {} - "text-indent" => {} - "text-transform" => {} - "top" => {} - "vertical-align" => {} - "visibility" => {} - "z-index" => {} - - "page-break-after" - | "page-break-before" - | "background-position" - | "background-attachment" - | "background-image" - | "background-repeat" - | _ => {} - } - } - } - - for child in el.children {} - } - } - Rect::new(0, 0, 0, 0) - } - - fn render_text(&self, f: &mut CrosstermFrame, node: &VNode) {} - - fn render_fragment(&self, f: &mut CrosstermFrame) {} + Ok(()) } -impl<'a> tui_template::tuiapp::TuiApp for RinkDom { - fn render(&mut self, frame: &mut CrosstermFrame) { - let base_scope = self.vdom.base_scope(); - let root = base_scope.root(); +// enum InputEvent { +// UserInput(KeyEvent), +// Close, +// Tick, +// } - let mut render_state = RenderState::new(); - self.render_vnode(frame, root, &mut render_state); - } +// // Setup input handling +// let (tx, rx) = mpsc::channel(); +// let tick_rate = Duration::from_millis(100); +// thread::spawn(move || { +// let mut last_tick = Instant::now(); +// loop { +// // poll for tick rate duration, if no events, sent tick event. +// let timeout = tick_rate +// .checked_sub(last_tick.elapsed()) +// .unwrap_or_else(|| Duration::from_secs(0)); - fn event_handler(&self, action: tui_template::crossterm::event::Event) -> Result<()> { - todo!() - } +// if event::poll(timeout).unwrap() { +// if let TermEvent::Key(key) = event::read().unwrap() { +// tx.send(InputEvent::UserInput(key)).unwrap(); +// } +// } - fn handle_key(&mut self, key: tui_template::crossterm::event::KeyEvent) { - todo!() - } +// // if last_tick.elapsed() >= tick_rate { +// // tx.send(InputEvent::Tick).unwrap(); +// // last_tick = Instant::now(); +// // } +// } +// }); - fn tick(&mut self) { - todo!() - } +// terminal.clear()?; - fn should_quit(&self) -> bool { - todo!() - } -} -struct RenderState<'a> { - block_stack: Vec>, +// // loop { +// terminal.draw(|frame| { +// // draw the frame +// let size = frame.size(); +// let root_node = vdom.base_scope().root_node(); +// let block = render_vnode(frame, vdom, root_node); - layouts: Vec, +// frame.render_widget(block, size); +// })?; - /// All the current styles applied through the "style" tag - current_styles: Vec, -} - -// I don't think we can do any of these? -enum Styles { - Bold, - Italic, - Strikethrough, - Emphasis, - Underline, -} - -impl<'a> RenderState<'a> { - fn new() -> Self { - let block_stack = Vec::new(); - Self { - block_stack, - current_styles: Vec::new(), - layouts: Vec::new(), - } - } -} +// // // terminal.draw(|f| ui::draw(f, &mut app))?; +// match rx.recv()? { +// InputEvent::UserInput(event) => match event.code { +// KeyCode::Char('q') => { +// disable_raw_mode()?; +// execute!( +// terminal.backend_mut(), +// LeaveAlternateScreen, +// DisableMouseCapture +// )?; +// terminal.show_cursor()?; +// // break; +// } +// _ => {} // handle event +// }, +// InputEvent::Tick => {} // tick +// InputEvent::Close => { +// // break; +// } +// }; diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 000000000..ab671bb09 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,97 @@ +use anyhow::Result; +use crossterm::{ + event, + event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use dioxus::core::*; +use std::{ + collections::HashMap, + io::{self, Stdout}, + sync::mpsc, + thread, + time::{Duration, Instant}, +}; +use stretch2::{ + geometry::Point, + prelude::{Layout, Size}, + style::Style as StretchStyle, + Stretch, +}; +use tui::{ + backend::CrosstermBackend, + buffer::Buffer, + layout::Rect, + style::Style as TuiStyle, + text::Span, + widgets::{canvas::Label, Block, BorderType, Borders, Widget}, + Terminal, +}; + +use crate::TuiNode; + +pub fn render_vnode<'a>( + frame: &mut tui::Frame>, + layout: &Stretch, + layouts: &mut HashMap>, + vdom: &'a VirtualDom, + node: &'a VNode<'a>, +) { + match node { + VNode::Fragment(f) => { + for child in f.children { + render_vnode(frame, layout, layouts, vdom, child); + } + return; + } + VNode::Component(_) => todo!(), + + VNode::Placeholder(_) | VNode::Element(_) | VNode::Text(_) => {} + } + + let id = node.try_mounted_id().unwrap(); + let node = layouts.remove(&id).unwrap(); + + let Layout { location, size, .. } = layout.layout(node.layout).unwrap(); + + let Point { x, y } = location; + let Size { width, height } = size; + + match node.node { + VNode::Text(t) => { + #[derive(Default)] + struct Label<'a> { + text: &'a str, + } + + impl<'a> Widget for Label<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + buf.set_string(area.left(), area.top(), self.text, TuiStyle::default()); + } + } + + // let s = Span::raw(t.text); + + // Block::default(). + + let label = Label { text: t.text }; + let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); + + frame.render_widget(label, area); + } + VNode::Element(el) => { + let block = Block::default().style(node.block_style); + let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); + + frame.render_widget(block, area); + + for el in el.children { + render_vnode(frame, layout, layouts, vdom, el); + } + } + VNode::Fragment(_) => todo!(), + VNode::Component(_) => todo!(), + VNode::Placeholder(_) => todo!(), + } +} diff --git a/test.html b/test.html new file mode 100644 index 000000000..48c9541d9 --- /dev/null +++ b/test.html @@ -0,0 +1,45 @@ + + + + + Test + + + + +
+
+ hello world +
+ goodbye +
+ asdasdasd +
+
+
+
+ + + \ No newline at end of file diff --git a/tests/margin.rs b/tests/margin.rs new file mode 100644 index 000000000..0ad4720c4 --- /dev/null +++ b/tests/margin.rs @@ -0,0 +1,102 @@ +use stretch2 as stretch; + +#[test] +fn margin_and_flex_row() { + let mut stretch = stretch::Stretch::new(); + let node0 = stretch + .new_node( + stretch::style::Style { + flex_grow: 1f32, + margin: stretch::geometry::Rect { + start: stretch::style::Dimension::Points(10f32), + end: stretch::style::Dimension::Points(10f32), + ..Default::default() + }, + ..Default::default() + }, + &[], + ) + .unwrap(); + let node = stretch + .new_node( + stretch::style::Style { + size: stretch::geometry::Size { + width: stretch::style::Dimension::Points(100f32), + height: stretch::style::Dimension::Points(100f32), + ..Default::default() + }, + ..Default::default() + }, + &[node0], + ) + .unwrap(); + stretch + .compute_layout(node, stretch::geometry::Size::undefined()) + .unwrap(); + assert_eq!(stretch.layout(node).unwrap().size.width, 100f32); + assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); + assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); + assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); + assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32); + assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); + assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32); + assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); +} + +#[test] +fn margin_and_flex_row2() { + let mut stretch = stretch::Stretch::new(); + let node0 = stretch + .new_node( + stretch::style::Style { + flex_grow: 1f32, + margin: stretch::geometry::Rect { + // left + start: stretch::style::Dimension::Points(10f32), + + // right? + end: stretch::style::Dimension::Points(10f32), + + // top? + // top: stretch::style::Dimension::Points(10f32), + + // bottom? + // bottom: stretch::style::Dimension::Points(10f32), + ..Default::default() + }, + ..Default::default() + }, + &[], + ) + .unwrap(); + + let node = stretch + .new_node( + stretch::style::Style { + size: stretch::geometry::Size { + width: stretch::style::Dimension::Points(100f32), + height: stretch::style::Dimension::Points(100f32), + ..Default::default() + }, + ..Default::default() + }, + &[node0], + ) + .unwrap(); + + stretch + .compute_layout(node, stretch::geometry::Size::undefined()) + .unwrap(); + + assert_eq!(stretch.layout(node).unwrap().size.width, 100f32); + assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); + assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); + assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); + + dbg!(stretch.layout(node0)); + + // assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32); + // assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); + // assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32); + // assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); +}