feat: wire up stretch into place

This commit is contained in:
Jonathan Kelley 2021-12-31 23:53:37 -05:00
parent e3171e8303
commit dc34805ee6
14 changed files with 1430 additions and 257 deletions

View file

@ -1,3 +0,0 @@
{
"rust-analyzer.inlayHints.enable": true
}

View file

@ -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" }

85
examples/frame.rs Normal file
View file

@ -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"
}
}
})
}

53
examples/layouts.rs Normal file
View file

@ -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<ElementId, TuiNode>, node: &VNode) {
// match node {
// VNode::Text(_) => todo!(),
// VNode::Element(_) => todo!(),
// VNode::Fragment(_) => todo!(),
// VNode::Component(_) => todo!(),
// VNode::Placeholder(_) => todo!(),
// }
// }

31
examples/list.rs Normal file
View file

@ -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}"
))
}
}
})
}

63
examples/quadrants.rs Normal file
View file

@ -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]"
}
}
}
})
}

34
examples/strecher.rs Normal file
View file

@ -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(())
}

85
examples/text.rs Normal file
View file

@ -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"
}
}
})
}

610
src/attributes.rs Normal file
View file

@ -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<Dimension>,
- [ ] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>,
- [ ] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>,
- [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>,
- [ ] 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::<f32>() {
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::<f32>() {
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::<f32>() {
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::<f32>() {
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::<f32>() {
// 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::<f32>() {
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::<f32>() {
style.style.flex_basis = Dimension::Points(px);
}
}
}
"flex-flow" => {}
"flex-grow" => {
if let Ok(val) = value.parse::<f32>() {
style.style.flex_grow = val;
}
}
"flex-shrink" => {
if let Ok(px) = value.parse::<f32>() {
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::<f32>() {
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::<f32>() {
//
}
}
}
"padding-bottom" => {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
style.style.padding.bottom = Dimension::Points(px);
}
}
"padding-left" => {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
style.style.padding.start = Dimension::Points(px);
}
}
"padding-right" => {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
style.style.padding.end = Dimension::Points(px);
}
}
"padding-top" => {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
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" => {}
_ => {}
}
}

88
src/layout.rs Normal file
View file

@ -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<ElementId, TuiNode<'a>>,
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!(),
};
}

View file

@ -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<Block<'a>>,
// // 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<Layout>,
// frame.render_widget(block, size);
// })?;
/// All the current styles applied through the "style" tag
current_styles: Vec<Styles>,
}
// 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;
// }
// };

97
src/render.rs Normal file
View file

@ -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<CrosstermBackend<Stdout>>,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
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!(),
}
}

45
test.html Normal file
View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style>
html,
body {
height: 100%;
}
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
/* margin: auto; */
}
.smaller {
height: 50%;
justify-content: center;
align-items: center;
color: violet;
}
</style>
</head>
<body>
<div class="container">
<div class="smaller">
hello world
<div style="color: red;">
goodbye
<div>
asdasdasd
</div>
</div>
</div>
</div>
</body>
</html>

102
tests/margin.rs Normal file
View file

@ -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);
}