mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
wip: first pass
This commit is contained in:
commit
e3171e8303
6 changed files with 337 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
}
|
2
.vscode/spellright.dict
vendored
Normal file
2
.vscode/spellright.dict
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
esque
|
||||
Tui
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -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" }
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -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
|
270
src/lib.rs
Normal file
270
src/lib.rs
Normal file
|
@ -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<Block<'a>>,
|
||||
|
||||
layouts: Vec<Layout>,
|
||||
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue