From 395558232e2b1e2a2e5985adb723f12e3bd76f78 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 12 Jan 2022 09:40:36 -0500 Subject: [PATCH] updates: move to stable versions --- Cargo.toml | 7 +-- examples/frame.rs | 5 +- examples/keys.rs | 59 ++++++++++++++++++++++++ examples/list.rs | 5 +- examples/margin.rs | 15 ++---- examples/quadrants.rs | 5 +- examples/readme.rs | 5 +- examples/task.rs | 5 +- examples/text.rs | 5 +- examples/ui.rsx | 5 ++ src/hooks.rs | 104 ++++++++++++++++++++++++++++++++++++++++++ src/layout.rs | 23 ++++++++-- src/lib.rs | 55 ++++++++++++---------- 13 files changed, 233 insertions(+), 65 deletions(-) create mode 100644 examples/keys.rs create mode 100644 examples/ui.rsx create mode 100644 src/hooks.rs diff --git a/Cargo.toml b/Cargo.toml index 2b67f00c3..1d2084f1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ tui = { version = "0.16.0", features = ["crossterm"] } crossterm = "0.22.1" anyhow = "1.0.42" thiserror = "1.0.24" -dioxus = { path = "../../dioxus" } +# dioxus = { path = "../../dioxus" } +dioxus = { version = "0.1.7" } hecs = "0.7.3" ctrlc = "3.2.1" bumpalo = { version = "3.8.0", features = ["boxed"] } -stretch2 = { path = "../../Tinkering/stretch2" } +# stretch2 = { path = "../../Tinkering/stretch2" } tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" -# stretch2 = { git = "https://github.com/elbaro/stretch2.git" } +stretch2 = { git = "https://github.com/DioxusLabs/stretch" } diff --git a/examples/frame.rs b/examples/frame.rs index 20bbbfb36..db49cd733 100644 --- a/examples/frame.rs +++ b/examples/frame.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/keys.rs b/examples/keys.rs new file mode 100644 index 000000000..58236a4d5 --- /dev/null +++ b/examples/keys.rs @@ -0,0 +1,59 @@ +use std::cell::RefCell; + +use crossterm::event::KeyEvent; +use dioxus::prelude::*; +use rink::InputHandler; + +fn main() { + rink::launch(app); +} + +fn app(cx: Scope) -> Element { + let count = use_state(&cx, || 0); + + cx.render(rsx! { + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + "Hello world!", + + // todo: enabling this will panic + // rink::InputHandler { + // onkeydown: move |evt: KeyEvent| { + // use crossterm::event::KeyCode::*; + // match evt.code { + // Left => count += 1, + // Right => count -= 1, + // Up => count += 10, + // Down => count -= 10, + // _ => {}, + // } + // }, + // onmousedown: move |evt| {}, + // onresize: move |dims| { + // println!("{:?}", dims); + // }, + // } + } + }) +} + +fn app2<'a>(cx: Scope<'a>) -> Element<'a> { + let mut count = use_state(&cx, || 0); + + cx.render(rsx! { + div { + width: "100%", + height: "10px", + background_color: "red", + justify_content: "center", + align_items: "center", + oninput: move |_| count += 1, + "Hello world!", + h1 {}, + } + }) +} diff --git a/examples/list.rs b/examples/list.rs index 354f3f008..ff219dcc6 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/margin.rs b/examples/margin.rs index 853721027..edc2b659f 100644 --- a/examples/margin.rs +++ b/examples/margin.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { @@ -16,44 +13,38 @@ fn app(cx: Scope) -> Element { background_color: "black", // margin_right: "10px", - - div { width: "70%", height: "70%", - // margin_left: "4px", background_color: "green", + // margin_left: "4px", div { width: "100%", height: "100%", - margin_top: "2px", margin_bottom: "2px", margin_left: "2px", margin_right: "2px", - // flex_shrink: "0", + 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/quadrants.rs b/examples/quadrants.rs index 4e9642b6e..db0b92f19 100644 --- a/examples/quadrants.rs +++ b/examples/quadrants.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/readme.rs b/examples/readme.rs index 7f68e884c..95a306686 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/task.rs b/examples/task.rs index f8a8fe111..a9b757612 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/text.rs b/examples/text.rs index 20bbbfb36..db49cd733 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -1,10 +1,7 @@ use dioxus::prelude::*; fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - rink::render_vdom(&mut dom).unwrap(); + rink::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/ui.rsx b/examples/ui.rsx new file mode 100644 index 000000000..962dbc3f1 --- /dev/null +++ b/examples/ui.rsx @@ -0,0 +1,5 @@ +div { + div { + + } +} diff --git a/src/hooks.rs b/src/hooks.rs new file mode 100644 index 000000000..1400acd62 --- /dev/null +++ b/src/hooks.rs @@ -0,0 +1,104 @@ +use anyhow::Result; +use crossterm::{ + event::{ + DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent, MouseEvent, + }, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use dioxus::{core::exports::futures_channel::mpsc::unbounded, prelude::Props}; +use dioxus::{core::*, prelude::*}; +use futures::{channel::mpsc::UnboundedReceiver, future::Either, pin_mut, StreamExt}; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, + io, + rc::Rc, + time::{Duration, Instant}, +}; +use stretch2::{prelude::Size, Stretch}; +use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal}; + +pub struct RinkContext { + last_event: RefCell>, + receiver: Rc>>>, +} + +impl RinkContext { + pub fn new(receiver: UnboundedReceiver) -> Self { + Self { + last_event: RefCell::new(None), + receiver: Rc::new(Cell::new(Some(receiver))), + } + } + pub fn subscribe_to_events(&self, scope: ScopeId) { + // + } +} + +#[derive(Props)] +pub struct AppHandlerProps<'a> { + // #[props(setter(strip_option), default)] + onkeydown: EventHandler<'a, KeyEvent>, + + // #[props(setter(strip_option), default)] + onmousedown: EventHandler<'a, MouseEvent>, + + #[props(setter(strip_option), default)] + onresize: Option>, +} + +/// This component lets you handle input events +/// +/// Once attached to the DOM, it will listen for input events from the terminal +/// +/// +pub fn InputHandler<'a>(cx: Scope<'a, AppHandlerProps<'a>>) -> Element { + let rcx = cx.use_hook(|_| { + let rcx = cx + .consume_context::() + .unwrap_or_else(|| panic!("Rink InputHandlers can only be used in Rink apps!")); + + // our component will only re-render if new events are received ... or if the parent is updated + // todo: if update was not caused by a new event, we should not re-render + // perhaps add some tracking to context? + rcx.subscribe_to_events(cx.scope_id()); + + let mut rec = rcx.receiver.take().unwrap(); + let updater = cx.schedule_update(); + let rc2 = rcx.clone(); + cx.push_future(async move { + while let Some(evt) = rec.next().await { + rc2.last_event.borrow_mut().replace(evt); + println!("{:?}", evt); + updater(); + } + // + }); + + rcx + }); + + if let Some(evet) = rcx.last_event.borrow().as_ref() { + match evet { + TermEvent::Key(key) => { + cx.props.onkeydown.call(key.clone()); + // let mut handler = cx.props.keydown.borrow_mut(); + // handler(*key); + // if let Some(handler) = cx.props.onkeydown { + // handler(*key); + // } + } + TermEvent::Mouse(mouse) => { + cx.props.onmousedown.call(mouse.clone()); + } + TermEvent::Resize(x, y) => { + // if let Some(handler) = cx.props.onresize { + // handler((*x, *y)); + // } + } + } + } + + None +} diff --git a/src/layout.rs b/src/layout.rs index d5443c6c1..94f750990 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -67,7 +67,17 @@ pub fn collect_layout<'a>( for el in el.children { let ite = ElementIdIterator::new(vdom, el); for node in ite { - child_layout.push(nodes[&node.mounted_id()].layout) + match node { + VNode::Element(_) | VNode::Text(_) => { + // + child_layout.push(nodes[&node.mounted_id()].layout) + } + VNode::Placeholder(_) => {} + VNode::Fragment(_) => todo!(), + VNode::Component(_) => todo!(), + } + + // child_layout.push(nodes[&node.mounted_id()].layout) } } @@ -86,7 +96,14 @@ pub fn collect_layout<'a>( collect_layout(layout, nodes, vdom, child); } } - VNode::Component(_) => todo!(), - VNode::Placeholder(_) => todo!(), + VNode::Component(sc) => { + // + let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap(); + let root = scope.root_node(); + collect_layout(layout, nodes, vdom, root); + } + VNode::Placeholder(_) => { + // + } }; } diff --git a/src/lib.rs b/src/lib.rs index 6b737538d..801d5e068 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,35 +4,52 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use dioxus::core::exports::futures_channel::mpsc::unbounded; -use dioxus::core::*; -use futures::{future::Either, pin_mut, StreamExt}; +use dioxus::{core::exports::futures_channel::mpsc::unbounded, prelude::Props}; +use dioxus::{core::*, prelude::*}; +use futures::{ + channel::mpsc::{UnboundedReceiver, UnboundedSender}, + future::Either, + pin_mut, StreamExt, +}; use std::{ + cell::Cell, collections::HashMap, io, + rc::Rc, time::{Duration, Instant}, }; use stretch2::{prelude::Size, Stretch}; use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal}; mod attributes; +mod hooks; mod layout; mod render; pub use attributes::*; +pub use hooks::*; pub use layout::*; pub use render::*; +pub fn launch(app: Component<()>) { + let mut dom = VirtualDom::new(app); + let (tx, rx) = unbounded(); + + dom.base_scope().provide_context(RinkContext::new(rx)); + dom.rebuild(); + + render_vdom(&mut dom, tx).unwrap(); +} + pub struct TuiNode<'a> { pub layout: stretch2::node::Node, pub block_style: TuiStyle, pub node: &'a VNode<'a>, } -pub fn render_vdom(vdom: &mut VirtualDom) -> Result<()> { +pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender) -> Result<()> { // Setup input handling let (tx, mut rx) = unbounded(); - std::thread::spawn(move || { let tick_rate = Duration::from_millis(100); let mut last_tick = Instant::now(); @@ -117,35 +134,27 @@ pub fn render_vdom(vdom: &mut VirtualDom) -> Result<()> { match select(wait, rx.next()).await { Either::Left((a, b)) => { - // println!("work"); + // } Either::Right((evt, o)) => { - // println!("event"); - match evt.unwrap() { + // + match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { match key.code { - KeyCode::Char('q') => { - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; - break; - } + KeyCode::Char('q') => break, _ => {} // handle event } } - TermEvent::Mouse(_) => {} - TermEvent::Resize(_, _) => {} + TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {} }, InputEvent::Tick => {} // tick - InputEvent::Close => { - break; - } + InputEvent::Close => break, }; + + if let InputEvent::UserInput(evt) = evt.unwrap() { + ctx.unbounded_send(evt).unwrap(); + } } } }