wip: add an image

This commit is contained in:
Jonathan Kelley committed 2022-01-01 01:08:31 -05:00
1 parent dc34805ee6
commit 7b29fbbad0
8 files changed
+331 -174

No files matched your search

+10 -4
View File
@@ -12,15 +12,21 @@ You can use html-esque semantics with stylesheets, inline styles, tree hierarchy
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
cx.render(rsx!{ cx.render(rsx!{
div { width: "100%", height: "3px", border_style: "solid", div {
h1 { "Hello world!" } width: "100%",
p { "This is a paragraph." } 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. 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

+59
View File
@@ -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]"
}
}
}
})
}
+22
View File
@@ -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!"
}
})
}
+106 -61
View File
@@ -1,6 +1,6 @@
/* /*
- [ ] pub display: Display, - [ ] pub display: Display,
- [ ] pub position_type: PositionType, - [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [ ] pub direction: Direction, - [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection, - [x] pub flex_direction: FlexDirection,
@@ -9,26 +9,27 @@
- [x] pub flex_shrink: f32, - [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension, - [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_items: AlignItems,
- [x] pub align_self: AlignSelf, - [x] pub align_self: AlignSelf,
- [x] pub align_content: AlignContent, - [x] pub align_content: AlignContent,
- [ ] pub margin: Rect<Dimension>, - [x] pub margin: Rect<Dimension>,
- [ ] pub padding: Rect<Dimension>, - [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent, - [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>, - [ ] pub position: Rect<Dimension>,
- [ ] pub border: Rect<Dimension>, - [ ] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>,
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
- [ ] pub min_size: Size<Dimension>, - [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>, - [ ] pub max_size: Size<Dimension>,
- [ ] pub aspect_ratio: Number, - [ ] pub aspect_ratio: Number,
*/ */
use stretch2::{prelude::*, style::Style}; use stretch2::{prelude::*, style::PositionType, style::Style};
use tui::style::Style as TuiStyle; use tui::style::Style as TuiStyle;
pub struct StyleModifer { pub struct StyleModifer {
@@ -137,7 +138,13 @@ pub fn apply_attributes(
"counter-reset" => {} "counter-reset" => {}
"cursor" => {} "cursor" => {}
"direction" => {} "direction" => {
match value {
"ltr" => style.style.direction = Direction::LTR,
"rtl" => style.style.direction = Direction::RTL,
_ => {}
}
}
"display" => apply_display(name, value, style), "display" => apply_display(name, value, style),
@@ -231,8 +238,20 @@ pub fn apply_attributes(
"perspective" "perspective"
| "perspective-origin" => {} | "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" => {} "pointer-events" => {}
"quotes" => {} "quotes" => {}
"resize" => {} "resize" => {}
"right" => {} "right" => {}
@@ -285,6 +304,29 @@ pub fn apply_attributes(
} }
} }
enum UnitSystem {
Percent(f32),
Point(f32),
}
fn parse_value(value: &str) -> Option<UnitSystem> {
if value.ends_with("px") {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
Some(UnitSystem::Point(px))
} else {
None
}
} else if value.ends_with("%") {
if let Ok(pct) = value.trim_end_matches("%").parse::<f32>() {
Some(UnitSystem::Percent(pct))
} else {
None
}
} else {
None
}
}
fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) { fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
match name { match name {
// todo: add more overflow support to stretch2 // 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) { fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
// // left match parse_value(value) {
// start: stretch::style::Dimension::Points(10f32), Some(UnitSystem::Percent(v)) => match name {
"padding" => {
// // right? let v = Dimension::Percent(v / 100.0);
// end: stretch::style::Dimension::Points(10f32), style.style.padding.top = v;
style.style.padding.bottom = v;
// // top? style.style.padding.start = v;
// // top: stretch::style::Dimension::Points(10f32), style.style.padding.end = v;
// // 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" => style.style.padding.bottom = Dimension::Percent(v / 100.0),
"padding-bottom" => { "padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0),
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() { "padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0),
style.style.padding.bottom = Dimension::Points(px); "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-bottom" => style.style.padding.bottom = Dimension::Points(v),
"padding-left" => { "padding-left" => style.style.padding.start = Dimension::Points(v),
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() { "padding-right" => style.style.padding.end = Dimension::Points(v),
style.style.padding.start = Dimension::Points(px); "padding-top" => style.style.padding.top = Dimension::Points(v),
} _ => {}
} },
"padding-right" => { None => {}
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);
}
}
_ => {}
} }
} }
@@ -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) { pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) {
match name { match parse_value(value) {
"margin" => {} Some(UnitSystem::Percent(v)) => match name {
"margin-bottom" => {} "margin" => {
"margin-left" => {} let v = Dimension::Percent(v / 100.0);
"margin-right" => {} style.style.margin.top = v;
"margin-top" => {} 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 => {}
} }
} }
+6 -2
View File
@@ -7,6 +7,12 @@ use crate::{
TuiNode, 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>( pub fn collect_layout<'a>(
layout: &mut stretch2::Stretch, layout: &mut stretch2::Stretch,
nodes: &mut HashMap<ElementId, TuiNode<'a>>, nodes: &mut HashMap<ElementId, TuiNode<'a>>,
@@ -17,7 +23,6 @@ pub fn collect_layout<'a>(
match node { match node {
VNode::Text(t) => { VNode::Text(t) => {
//
let id = t.id.get().unwrap(); let id = t.id.get().unwrap();
let char_len = t.text.chars().count(); let char_len = t.text.chars().count();
@@ -29,7 +34,6 @@ pub fn collect_layout<'a>(
// text is as long as it is declared // text is as long as it is declared
width: Dimension::Points(char_len as f32), width: Dimension::Points(char_len as f32),
}, },
..Default::default() ..Default::default()
}; };
+93 -100
View File
@@ -1,11 +1,16 @@
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture}, event::{self, DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent},
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use dioxus::core::*; use dioxus::core::*;
use std::{collections::HashMap, io}; use std::{
collections::HashMap,
io,
sync::mpsc,
time::{Duration, Instant},
};
use stretch2::{prelude::Size, Stretch}; use stretch2::{prelude::Size, Stretch};
use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal}; use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal};
@@ -24,21 +29,6 @@ pub struct TuiNode<'a> {
} }
pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { 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 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 backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend).unwrap(); let mut terminal = Terminal::new(backend).unwrap();
let dims = terminal.size().unwrap(); // Setup input handling
let width = dims.width; let (tx, rx) = mpsc::channel();
let height = dims.height; 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));
/* if event::poll(timeout).unwrap() {
Compute the layout given th terminal size if let TermEvent::Key(key) = event::read().unwrap() {
*/ tx.send(InputEvent::UserInput(key)).unwrap();
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| { if last_tick.elapsed() >= tick_rate {
// tx.send(InputEvent::Tick).unwrap();
render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); last_tick = Instant::now();
assert!(nodes.is_empty()); }
})?; }
});
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()?; disable_raw_mode()?;
execute!( execute!(
@@ -88,65 +138,8 @@ pub fn render_vdom(vdom: &VirtualDom) -> Result<()> {
Ok(()) Ok(())
} }
// enum InputEvent { enum InputEvent {
// UserInput(KeyEvent), UserInput(KeyEvent),
// Close, Close,
// Tick, 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;
// }
// };
+35 -7
View File
@@ -14,32 +14,60 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; background-color: black;
align-items: center; /* justify-content: center;
align-items: center; */
/* margin: auto; */ /* margin: auto; */
} }
.smaller { .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; justify-content: center;
align-items: center; align-items: center;
color: violet; flex-direction: column;
/* margin: 20px; */
/* margin: 20px; */
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="smaller">
<div class="superinner">
<h1>Hello World</h1>
<p>This is a test</p>
</div>
</div>
</div>
<!-- <div class="container">
<div class="smaller"> <div class="smaller">
hello world hello world
<div style="color: red;"> <div style="color: green; margin: 40px;">
goodbye goodbye
<div> <div style="color:red;">
asdasdasd asdasdasd
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
</body> </body>
</html> </html>