wip: add an image

This commit is contained in:
Jonathan Kelley 2022-01-01 01:08:31 -05:00
parent dc34805ee6
commit 7b29fbbad0
8 changed files with 331 additions and 174 deletions

View file

@ -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.

BIN
examples/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

59
examples/margin.rs Normal file
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
examples/readme.rs Normal file
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!"
}
})
}

View file

@ -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<Dimension>,
- [ ] pub padding: Rect<Dimension>,
- [x] pub margin: Rect<Dimension>,
- [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: 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 max_size: Size<Dimension>,
- [ ] 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<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) {
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::<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>() {
//
}
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::<f32>() {
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::<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);
}
}
_ => {}
"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 => {}
}
}

View file

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

View file

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

View file

@ -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; */
}
</style>
</head>
<body>
<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">
hello world
<div style="color: red;">
<div style="color: green; margin: 40px;">
goodbye
<div>
<div style="color:red;">
asdasdasd
</div>
</div>
</div>
</div>
</div> -->
</body>
</html>