commit e3171e8303700d6829880951dbc7184413df371b Author: Jonathan Kelley Date: Wed Jul 28 10:52:38 2021 -0400 wip: first pass diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..1c68f97cc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.inlayHints.enable": true +} diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict new file mode 100644 index 000000000..c30e473fc --- /dev/null +++ b/.vscode/spellright.dict @@ -0,0 +1,2 @@ +esque +Tui diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..19b62bf5b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rink" +version = "0.1.0" +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" +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" } diff --git a/README.md b/README.md new file mode 100644 index 000000000..27a4dbc3f --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Rink: Like "Ink" but for Rust and Dioxus + +The fastest portable TUIs in the west + 🔫🤠🔫 + 🐎🔥🔥🔥 + +Rink lets you build terminal user interfaces in Rust with Dioxus. + +You can use html-esque semantics with stylesheets, inline styles, tree hierarchy, components, etc, but your Tui app is probably not going to work well or look good in the web. It still technically is a limited subset of HTML, so use at your own risk. + +```rust + +static App: FC<()> = |cx| { + cx.render(rsx!{ + div { width: "100%", height: "3px", border_style: "solid", + h1 { "Hello world!" } + p { "This is a paragraph." } + } + }) +} +``` + +an image should go here + + +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. + +## Features + +Rink features: +- Flexbox based layout system +- CSS selectors +- inline css support +- Built-in focusing system +- high-quality keyboard support +- Support for events, hooks, and callbacks +- support for a very limited subset of HTML and CSS primitives + +HTML elements supported: +- div, h1-h6, p, input, textarea, tables, nav + +CSS Supported: +- Flex diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..ad6dc8596 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,270 @@ +use anyhow::Result; +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, +} + +impl RinkDom { + pub fn new(app: FC<()>) -> Self { + Self { + vdom: VirtualDom::new(app), + } + } + + 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!(), + + VNodeKind::Text(te) => { + let span = Span::styled(te.text, TuiStyle::default()); + + let mut m = Modifier::empty(); + + 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, + } + } + + let style = TuiStyle::default().add_modifier(m); + let span = span.styled_graphemes(style); + let cur_block = state.block_stack.last_mut().unwrap(); + + // Paragraph + + // f.render_widget(span); + } + VNodeKind::Element(el) => { + // + let mut new_layout = false; + + // all of our supported styles + + match el.tag_name { + // obviously semantically not really correct + "div" => { + state.layouts.push(Layout::default()); + new_layout = true; + } + + "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) {} +} + +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(); + + let mut render_state = RenderState::new(); + self.render_vnode(frame, root, &mut render_state); + } + + fn event_handler(&self, action: tui_template::crossterm::event::Event) -> Result<()> { + todo!() + } + + fn handle_key(&mut self, key: tui_template::crossterm::event::KeyEvent) { + todo!() + } + + fn tick(&mut self) { + todo!() + } + + fn should_quit(&self) -> bool { + todo!() + } +} +struct RenderState<'a> { + block_stack: Vec>, + + layouts: Vec, + + /// 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(), + } + } +}