diff --git a/README.md b/README.md index 27a4dbc3f..9ea2cbc8c 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,21 @@ You can use html-esque semantics with stylesheets, inline styles, tree hierarchy static App: FC<()> = |cx| { cx.render(rsx!{ - div { width: "100%", height: "3px", border_style: "solid", - h1 { "Hello world!" } - p { "This is a paragraph." } + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + + + "Hello world!" } }) } ``` -an image should go here +![demo app](examples/example.png) Rink is basically a port of [Ink]() but for Rust and Dioxus. Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. diff --git a/examples/example.png b/examples/example.png new file mode 100644 index 000000000..ba45e6e99 Binary files /dev/null and b/examples/example.png differ diff --git a/examples/margin.rs b/examples/margin.rs new file mode 100644 index 000000000..db08ff802 --- /dev/null +++ b/examples/margin.rs @@ -0,0 +1,59 @@ +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", + background_color: "black", + // margin_right: "10px", + + + + div { + width: "70%", + height: "70%", + // margin_left: "4px", + background_color: "green", + + div { + width: "100%", + height: "100%", + + + margin_top: "2px", + margin_bottom: "2px", + margin_left: "2px", + margin_right: "2px", + // flex_shrink: "0", + + background_color: "red", + justify_content: "center", + align_items: "center", + flex_direction: "column", + + + // padding_top: "2px", + // padding_bottom: "2px", + // padding_left: "4px", + // padding_right: "4px", + + + "[A]" + "[A]" + "[A]" + "[A]" + } + } + + } + }) +} diff --git a/examples/readme.rs b/examples/readme.rs new file mode 100644 index 000000000..2edb2ac98 --- /dev/null +++ b/examples/readme.rs @@ -0,0 +1,22 @@ +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: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + + "Hello world!" + } + }) +} diff --git a/src/attributes.rs b/src/attributes.rs index fb267fa9c..637d9e9d3 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -1,6 +1,6 @@ /* - [ ] pub display: Display, -- [ ] pub position_type: PositionType, +- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything - [ ] pub direction: Direction, - [x] pub flex_direction: FlexDirection, @@ -9,26 +9,27 @@ - [x] pub flex_shrink: f32, - [x] pub flex_basis: Dimension, -- [ ] pub overflow: Overflow, +- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow - [x] pub align_items: AlignItems, - [x] pub align_self: AlignSelf, - [x] pub align_content: AlignContent, -- [ ] pub margin: Rect, -- [ ] pub padding: Rect, +- [x] pub margin: Rect, +- [x] pub padding: Rect, - [x] pub justify_content: JustifyContent, - [ ] pub position: Rect, - [ ] pub border: Rect, -- [ ] pub size: Size, +- [ ] pub size: Size, ----> ??? seems to only be relevant for input? - [ ] pub min_size: Size, - [ ] pub max_size: Size, + - [ ] pub aspect_ratio: Number, */ -use stretch2::{prelude::*, style::Style}; +use stretch2::{prelude::*, style::PositionType, style::Style}; use tui::style::Style as TuiStyle; pub struct StyleModifer { @@ -137,7 +138,13 @@ pub fn apply_attributes( "counter-reset" => {} "cursor" => {} - "direction" => {} + "direction" => { + match value { + "ltr" => style.style.direction = Direction::LTR, + "rtl" => style.style.direction = Direction::RTL, + _ => {} + } + } "display" => apply_display(name, value, style), @@ -231,8 +238,20 @@ pub fn apply_attributes( "perspective" | "perspective-origin" => {} - "position" => {} + "position" => { + match value { + "static" => {} + "relative" => style.style.position_type = PositionType::Relative, + "fixed" => {} + "absolute" => style.style.position_type = PositionType::Absolute, + "sticky" => {} + _ => {} + } + + } + "pointer-events" => {} + "quotes" => {} "resize" => {} "right" => {} @@ -285,6 +304,29 @@ pub fn apply_attributes( } } +enum UnitSystem { + Percent(f32), + Point(f32), +} + +fn parse_value(value: &str) -> Option { + if value.ends_with("px") { + if let Ok(px) = value.trim_end_matches("px").parse::() { + Some(UnitSystem::Point(px)) + } else { + None + } + } else if value.ends_with("%") { + if let Ok(pct) = value.trim_end_matches("%").parse::() { + Some(UnitSystem::Percent(pct)) + } else { + None + } + } else { + None + } +} + fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) { match name { // todo: add more overflow support to stretch2 @@ -491,54 +533,35 @@ fn apply_font(name: &str, value: &str, style: &mut StyleModifer) { } 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::() { - // - } + match parse_value(value) { + Some(UnitSystem::Percent(v)) => match name { + "padding" => { + let v = Dimension::Percent(v / 100.0); + style.style.padding.top = v; + style.style.padding.bottom = v; + style.style.padding.start = v; + style.style.padding.end = v; } - } - "padding-bottom" => { - if let Ok(px) = value.trim_end_matches("px").parse::() { - style.style.padding.bottom = Dimension::Points(px); + "padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0), + "padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0), + "padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0), + "padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0), + _ => {} + }, + Some(UnitSystem::Point(v)) => match name { + "padding" => { + style.style.padding.top = Dimension::Points(v); + style.style.padding.bottom = Dimension::Points(v); + style.style.padding.start = Dimension::Points(v); + style.style.padding.end = Dimension::Points(v); } - } - "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); - } - } - _ => {} + "padding-bottom" => style.style.padding.bottom = Dimension::Points(v), + "padding-left" => style.style.padding.start = Dimension::Points(v), + "padding-right" => style.style.padding.end = Dimension::Points(v), + "padding-top" => style.style.padding.top = Dimension::Points(v), + _ => {} + }, + None => {} } } @@ -599,12 +622,34 @@ 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" => {} - _ => {} + match parse_value(value) { + Some(UnitSystem::Percent(v)) => match name { + "margin" => { + let v = Dimension::Percent(v / 100.0); + style.style.margin.top = v; + style.style.margin.bottom = v; + style.style.margin.start = v; + style.style.margin.end = v; + } + "margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0), + "margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0), + "margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0), + "margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0), + _ => {} + }, + Some(UnitSystem::Point(v)) => match name { + "margin" => { + style.style.margin.top = Dimension::Points(v); + style.style.margin.bottom = Dimension::Points(v); + style.style.margin.start = Dimension::Points(v); + style.style.margin.end = Dimension::Points(v); + } + "margin-top" => style.style.margin.top = Dimension::Points(v), + "margin-bottom" => style.style.margin.bottom = Dimension::Points(v), + "margin-left" => style.style.margin.start = Dimension::Points(v), + "margin-right" => style.style.margin.end = Dimension::Points(v), + _ => {} + }, + None => {} } } diff --git a/src/layout.rs b/src/layout.rs index c7b01046a..d5443c6c1 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -7,6 +7,12 @@ use crate::{ TuiNode, }; +/* +The layout system uses the lineheight as one point. + +stretch uses fractional points, so we can rasterize if we need too, but not with characters +this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger +*/ pub fn collect_layout<'a>( layout: &mut stretch2::Stretch, nodes: &mut HashMap>, @@ -17,7 +23,6 @@ pub fn collect_layout<'a>( match node { VNode::Text(t) => { - // let id = t.id.get().unwrap(); let char_len = t.text.chars().count(); @@ -29,7 +34,6 @@ pub fn collect_layout<'a>( // text is as long as it is declared width: Dimension::Points(char_len as f32), }, - ..Default::default() }; diff --git a/src/lib.rs b/src/lib.rs index 4a606318f..9acb3d487 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,16 @@ use anyhow::Result; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture}, + event::{self, 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}; +use std::{ + collections::HashMap, + io, + sync::mpsc, + time::{Duration, Instant}, +}; use stretch2::{prelude::Size, Stretch}; use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal}; @@ -24,21 +29,6 @@ pub struct TuiNode<'a> { } 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 - - use simd to compare lines for diffing? - - */ - let mut layout = Stretch::new(); - let mut nodes = HashMap::new(); - - let root_node = vdom.base_scope().root_node(); - layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); - /* Get the terminal to calcualte the layout from */ @@ -52,30 +42,90 @@ pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend).unwrap(); - let dims = terminal.size().unwrap(); - let width = dims.width; - let height = dims.height; + // Setup input handling + let (tx, rx) = mpsc::channel(); + std::thread::spawn(move || { + let tick_rate = Duration::from_millis(100); + 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)); - /* - 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), - }, - )?; + if event::poll(timeout).unwrap() { + if let TermEvent::Key(key) = event::read().unwrap() { + tx.send(InputEvent::UserInput(key)).unwrap(); + } + } - terminal.draw(|frame| { - // - render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); - assert!(nodes.is_empty()); - })?; + if last_tick.elapsed() >= tick_rate { + tx.send(InputEvent::Tick).unwrap(); + last_tick = Instant::now(); + } + } + }); - std::thread::sleep(std::time::Duration::from_millis(5000)); + terminal.clear().unwrap(); + + loop { + let dims = terminal.size().unwrap(); + let width = dims.width; + let height = dims.height; + + /* + -> collect all the nodes with their layout + -> solve their layout + -> render the nodes in the right place with tui/crosstream + -> while rendering, apply styling + + use simd to compare lines for diffing? + + */ + let mut layout = Stretch::new(); + let mut nodes = HashMap::new(); + + let root_node = vdom.base_scope().root_node(); + layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); + /* + 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), + }, + )?; + + terminal.draw(|frame| { + // + render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); + assert!(nodes.is_empty()); + })?; + + 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; + } + }; + } disable_raw_mode()?; execute!( @@ -88,65 +138,8 @@ pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { Ok(()) } -// enum InputEvent { -// UserInput(KeyEvent), -// Close, -// Tick, -// } - -// // 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)); - -// if event::poll(timeout).unwrap() { -// if let TermEvent::Key(key) = event::read().unwrap() { -// tx.send(InputEvent::UserInput(key)).unwrap(); -// } -// } - -// // if last_tick.elapsed() >= tick_rate { -// // tx.send(InputEvent::Tick).unwrap(); -// // last_tick = Instant::now(); -// // } -// } -// }); - -// terminal.clear()?; - -// // 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); - -// frame.render_widget(block, size); -// })?; - -// // // 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; -// } -// }; +enum InputEvent { + UserInput(KeyEvent), + Close, + Tick, +} diff --git a/test.html b/test.html index 48c9541d9..5298c723e 100644 --- a/test.html +++ b/test.html @@ -14,32 +14,60 @@ height: 100%; display: flex; flex-direction: column; - justify-content: center; - align-items: center; + background-color: black; + /* justify-content: center; + align-items: center; */ /* margin: auto; */ } .smaller { - height: 50%; + height: 70%; + width: 70%; + background-color: green; + /* justify-content: center; */ + /* align-items: center; */ + } + + .superinner { + height: 100%; + width: 100%; + /* display: flex; */ + /* */ + margin-top: 20px; + margin-bottom: 20px; + margin-left: 20px; + margin-right: 20px; + /* */ + background-color: red; justify-content: center; align-items: center; - color: violet; + flex-direction: column; + /* margin: 20px; */ + /* margin: 20px; */ }
+
+
+

Hello World

+

This is a test

+
+
+
+ \ No newline at end of file