rebase master

This commit is contained in:
Evan Almloff 2022-03-23 14:18:17 -05:00
parent 499971e9b3
commit 26d92b6e51
14 changed files with 529 additions and 318 deletions

View file

@ -61,6 +61,7 @@ members = [
"packages/fermi", "packages/fermi",
"packages/tui", "packages/tui",
"packages/liveview", "packages/liveview",
"packages/native-core",
] ]
[dev-dependencies] [dev-dependencies]
@ -88,3 +89,6 @@ harness = false
[[bench]] [[bench]]
name = "jsframework" name = "jsframework"
harness = false harness = false
[profile.release]
debug = true

View file

@ -4,11 +4,54 @@ fn main() {
dioxus::tui::launch_cfg( dioxus::tui::launch_cfg(
app, app,
dioxus::tui::Config { dioxus::tui::Config {
rendering_mode: dioxus::tui::RenderingMode::Ansi, rendering_mode: dioxus::tui::RenderingMode::Rgb,
}, },
); );
} }
#[derive(Props, PartialEq)]
struct BoxProps {
x: i32,
y: i32,
hue: f32,
alpha: f32,
}
fn Box(cx: Scope<BoxProps>) -> Element {
let painted = use_state(&cx, || true);
// use_future(&cx, (), move |_| {
// let count = count.to_owned();
// let update = cx.schedule_update();
// async move {
// loop {
// count.with_mut(|i| *i += 1);
// tokio::time::sleep(std::time::Duration::from_millis(800)).await;
// update();
// }
// }
// });
let x = cx.props.x;
let y = cx.props.y;
let hue = cx.props.hue;
let current_painted = painted.get();
let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 };
cx.render(rsx! {
div {
left: "{x}px",
top: "{y}px",
width: "100%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
align_items: "center",
onkeydown: |_| painted.with_mut(|i| *i = !*i),
onmouseenter: |_| painted.with_mut(|i| *i = !*i),
p{" "}
}
})
}
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let steps = 50; let steps = 50;
cx.render(rsx! { cx.render(rsx! {
@ -28,12 +71,11 @@ fn app(cx: Scope) -> Element {
{ {
let alpha = y as f32*100.0/steps as f32; let alpha = y as f32*100.0/steps as f32;
cx.render(rsx! { cx.render(rsx! {
div { Box{
left: "{x}px", x: x,
top: "{y}px", y: y,
width: "10%", alpha: alpha,
height: "100%", hue: hue,
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
} }
}) })
} }

View file

@ -5,15 +5,26 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let alpha = use_state(&cx, || 100);
cx.render(rsx! { cx.render(rsx! {
div { div {
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
width: "100%", width: "100%",
height: "10px", height: "10px",
background_color: "red", background_color: "red",
justify_content: "center", // justify_content: "center",
align_items: "center", // align_items: "center",
p{
color: "rgba(0, 255, 0, {alpha}%)",
"Hello world!" "Hello world!"
} }
p{
"{alpha}"
}
// p{"Hi"}
}
}) })
} }

View file

@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
p { p {
background_color: "black", // background_color: "black",
flex_direction: "column", flex_direction: "column",
justify_content: "center", justify_content: "center",
align_items: "center", align_items: "center",

View file

@ -49,7 +49,7 @@ impl BubbleState {
/// } /// }
/// )).unwrap(); /// )).unwrap();
/// ``` /// ```
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct UserEvent { pub struct UserEvent {
/// The originator of the event trigger if available /// The originator of the event trigger if available
pub scope_id: Option<ScopeId>, pub scope_id: Option<ScopeId>,

View file

@ -29,7 +29,7 @@
- [ ] pub aspect_ratio: Number, - [ ] pub aspect_ratio: Number,
*/ */
use stretch2::{prelude::*, style::PositionType, style::Style}; use stretch2::{prelude::*, style::PositionType};
/// applies the entire html namespace defined in dioxus-html /// applies the entire html namespace defined in dioxus-html
pub fn apply_layout_attributes( pub fn apply_layout_attributes(

View file

@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
pub mod layout_attributes;
/// A tree that can sync with dioxus mutations backed by a hashmap. /// A tree that can sync with dioxus mutations backed by a hashmap.
/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. /// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
@ -10,7 +11,7 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> { pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> {
pub root: usize, pub root: usize,
pub nodes: Vec<Option<TreeNode<US, DS>>>, pub nodes: Vec<Option<TreeNode<US, DS>>>,
pub listeners: HashMap<usize, HashSet<&'static str>>, pub nodes_listening: HashMap<&'static str, HashSet<usize>>,
} }
impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> { impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
@ -29,7 +30,7 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
))); )));
v v
}, },
listeners: HashMap::new(), nodes_listening: HashMap::new(),
} }
} }
@ -128,17 +129,17 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
scope: _, scope: _,
root, root,
} => { } => {
if let Some(v) = self.listeners.get_mut(&(root as usize)) { if let Some(v) = self.nodes_listening.get_mut(event_name) {
v.insert(event_name); v.insert(root as usize);
} else { } else {
let mut hs = HashSet::new(); let mut hs = HashSet::new();
hs.insert(event_name); hs.insert(root as usize);
self.listeners.insert(root as usize, hs); self.nodes_listening.insert(event_name, hs);
} }
} }
RemoveEventListener { root, event } => { RemoveEventListener { root, event } => {
let v = self.listeners.get_mut(&(root as usize)).unwrap(); let v = self.nodes_listening.get_mut(event).unwrap();
v.remove(event); v.remove(&(root as usize));
} }
SetText { SetText {
root, root,
@ -210,10 +211,8 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
to_rerender.insert(id); to_rerender.insert(id);
if let Some(p) = parent { if let Some(p) = parent {
let i = to_bubble.partition_point(|(_, h)| *h < height - 1); let i = to_bubble.partition_point(|(_, h)| *h < height - 1);
// println!("{i}");
// println!("{to_bubble:?}");
// make sure the parent is not already queued // make sure the parent is not already queued
if to_bubble.len() == 0 || to_bubble.get(i).unwrap().0 != p.0 { if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 {
to_bubble.insert(i, (p.0, height - 1)); to_bubble.insert(i, (p.0, height - 1));
} }
} }
@ -307,10 +306,20 @@ impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> { fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> {
self.nodes.get_mut(id).unwrap().as_mut().unwrap() self.nodes.get_mut(id).unwrap().as_mut().unwrap()
} }
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect();
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
listening
} else {
Vec::new()
}
}
} }
/// The node is stored client side and stores render data /// The node is stored client side and stores render data
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> { pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
pub id: ElementId, pub id: ElementId,
pub parent: Option<ElementId>, pub parent: Option<ElementId>,
@ -320,7 +329,7 @@ pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
pub height: u16, pub height: u16,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum TreeNodeType { pub enum TreeNodeType {
Text { Text {
text: String, text: String,
@ -505,7 +514,7 @@ fn test_insert() {
))); )));
v v
}, },
listeners: HashMap::new(), nodes_listening: HashMap::new(),
}; };
println!("{:?}", mutations); println!("{:?}", mutations);
let to_update = tree.apply_mutations(vec![mutations.0]); let to_update = tree.apply_mutations(vec![mutations.0]);

View file

@ -4,12 +4,12 @@ use crossterm::event::{
use dioxus_core::*; use dioxus_core::*;
use dioxus_html::{on::*, KeyCode}; use dioxus_html::{on::*, KeyCode};
use dioxus_native_core::{Tree, TreeNodeType}; use dioxus_native_core::{Tree, TreeNode};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{ use std::{
any::Any, any::Any,
cell::RefCell, cell::RefCell,
collections::HashSet, collections::{HashMap, HashSet},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -163,12 +163,35 @@ impl InnerInputState {
fn update<'a>( fn update<'a>(
&mut self, &mut self,
dom: &'a VirtualDom,
evts: &mut Vec<EventCore>, evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>, resolved_events: &mut Vec<UserEvent>,
layout: &Stretch, layout: &Stretch,
layouts: &mut Tree<RinkLayout, StyleModifier>, tree: &mut Tree<RinkLayout, StyleModifier>,
node: &'a VNode<'a>, ) {
let previous_mouse = self
.mouse
.as_ref()
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
self.wheel = None;
for e in evts.iter_mut() {
self.apply_event(e);
}
self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree);
// for s in &self.subscribers {
// s();
// }
}
fn resolve_mouse_events(
&self,
previous_mouse: Option<(MouseData, Vec<u16>)>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
tree: &mut Tree<RinkLayout, StyleModifier>,
) { ) {
struct Data<'b> { struct Data<'b> {
new_pos: (i32, i32), new_pos: (i32, i32),
@ -187,136 +210,31 @@ impl InnerInputState {
&& layout.location.y as i32 + layout.size.height as i32 >= point.1 && layout.location.y as i32 + layout.size.height as i32 >= point.1
} }
fn get_mouse_events<'c, 'd>( fn try_create_event(
dom: &'c VirtualDom, name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut HashSet<ElementId>,
resolved_events: &mut Vec<UserEvent>, resolved_events: &mut Vec<UserEvent>,
layout: &Stretch, node: &TreeNode<RinkLayout, StyleModifier>,
layouts: &Tree<RinkLayout, StyleModifier>, tree: &Tree<RinkLayout, StyleModifier>,
node: &'c VNode<'c>, ) {
data: &'d Data<'d>,
) -> HashSet<&'static str> {
match node {
VNode::Fragment(f) => {
let mut union = HashSet::new();
for child in f.children {
union = union
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
child,
data,
))
.copied()
.collect();
}
return union;
}
VNode::Component(vcomp) => {
let idx = vcomp.scope.get().unwrap();
let new_node = dom.get_scope(idx).unwrap().root_node();
return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
}
VNode::Placeholder(_) => return HashSet::new(),
VNode::Element(_) | VNode::Text(_) => {}
}
let id = node.try_mounted_id().unwrap();
let node = layouts.get(id.0);
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
match &node.node_type {
TreeNodeType::Element { children, .. } => {
let mut events = HashSet::new();
if previously_contained || currently_contains {
for c in children {
events = events
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
dom.get_element(*c).unwrap(),
data,
))
.copied()
.collect();
}
}
let mut try_create_event = |name| {
// only trigger event if the event was not triggered already by a child // only trigger event if the event was not triggered already by a child
if events.insert(name) { if will_bubble.insert(node.id) {
let mut parent = node.parent;
while let Some(parent_id) = parent {
will_bubble.insert(parent_id);
parent = tree.get(parent_id.0).parent;
}
resolved_events.push(UserEvent { resolved_events.push(UserEvent {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
name, name,
element: Some(node.id), element: Some(node.id),
data: Arc::new(clone_mouse_data(data.mouse_data)), data: data,
})
}
};
if currently_contains {
if !previously_contained {
try_create_event("mouseenter");
try_create_event("mouseover");
}
if data.clicked {
try_create_event("mousedown");
}
if data.released {
try_create_event("mouseup");
match data.mouse_data.button {
0 => try_create_event("click"),
2 => try_create_event("contextmenu"),
_ => (),
}
}
if let Some(w) = data.wheel_data {
if data.wheel_delta != 0.0 {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "wheel",
element: Some(node.id),
data: Arc::new(clone_wheel_data(w)),
}) })
} }
} }
} else if previously_contained {
try_create_event("mouseleave");
try_create_event("mouseout");
}
events
}
TreeNodeType::Text { .. } => HashSet::new(),
_ => todo!(),
}
}
let previous_mouse = self
.mouse
.as_ref()
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
// println!("{previous_mouse:?}");
self.wheel = None;
for e in evts.iter_mut() {
self.apply_event(e);
}
// resolve hover events
if let Some(mouse) = &self.mouse { if let Some(mouse) = &self.mouse {
let new_pos = (mouse.0.screen_x, mouse.0.screen_y); let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
let old_pos = previous_mouse let old_pos = previous_mouse
@ -338,12 +256,245 @@ impl InnerInputState {
mouse_data, mouse_data,
wheel_data, wheel_data,
}; };
get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
{
// mousemove
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mousemove") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if previously_contained {
try_create_event(
"mousemove",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
} }
// for s in &self.subscribers { {
// s(); // mouseenter
// } let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mouseenter") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if !previously_contained {
try_create_event(
"mouseenter",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// mouseover
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mouseover") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if !previously_contained {
try_create_event(
"mouseover",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// mousedown
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mousedown") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if data.clicked {
try_create_event(
"mousedown",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// mouseup
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mouseup") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if data.released {
try_create_event(
"mouseup",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// click
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("click") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if data.released && data.mouse_data.button == 0 {
try_create_event(
"click",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// contextmenu
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("contextmenu") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if data.released && data.mouse_data.button == 2 {
try_create_event(
"contextmenu",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
{
// wheel
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("wheel") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains {
if let Some(w) = data.wheel_data {
if data.wheel_delta != 0.0 {
try_create_event(
"wheel",
Arc::new(clone_wheel_data(w)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
}
{
// mouseleave
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mouseleave") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
{
// mouseout
let mut will_bubble = HashSet::new();
for node in tree.get_listening_sorted("mouseout") {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(clone_mouse_data(data.mouse_data)),
&mut will_bubble,
resolved_events,
node,
tree,
);
}
}
}
}
} }
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) { // fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
@ -364,7 +515,7 @@ impl RinkInputHandler {
cx: &ScopeState, cx: &ScopeState,
) -> (Self, Rc<RefCell<InnerInputState>>) { ) -> (Self, Rc<RefCell<InnerInputState>>) {
let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events); let queued_events2 = Rc::downgrade(&queued_events);
cx.push_future(async move { cx.push_future(async move {
while let Some(evt) = receiver.next().await { while let Some(evt) = receiver.next().await {
@ -391,68 +542,63 @@ impl RinkInputHandler {
pub fn get_events<'a>( pub fn get_events<'a>(
&self, &self,
dom: &'a VirtualDom,
layout: &Stretch, layout: &Stretch,
layouts: &mut Tree<RinkLayout, StyleModifier>, tree: &mut Tree<RinkLayout, StyleModifier>,
node: &'a VNode<'a>,
) -> Vec<UserEvent> { ) -> Vec<UserEvent> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
tree,
);
let events = self
.queued_events
.replace(Vec::new())
.into_iter()
// these events were added in the update stage
.filter(|e| {
![
"mouseenter",
"mouseover",
"mouseleave",
"mouseout",
"mousedown",
"mouseup",
"mousemove",
"drag",
"wheel",
"click",
"contextmenu",
]
.contains(&e.0)
})
.map(|evt| (evt.0, evt.1.into_any()));
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
fn inner( let mut hm: HashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = HashMap::new();
queue: &[(&'static str, Arc<dyn Any + Send + Sync>)], for (event, data) in events {
resolved: &mut Vec<UserEvent>, if let Some(v) = hm.get_mut(event) {
node: &VNode, v.push(data);
) { } else {
match node { hm.insert(event, vec![data]);
VNode::Fragment(frag) => {
for c in frag.children {
inner(queue, resolved, c);
} }
} }
VNode::Element(el) => { for (event, datas) in hm {
for l in el.listeners { for node in tree.get_listening_sorted(event) {
for (name, data) in queue.iter() { for data in &datas {
if *name == l.event { resolved_events.push(UserEvent {
if let Some(id) = el.id.get() {
resolved.push(UserEvent {
scope_id: None, scope_id: None,
priority: EventPriority::Medium, priority: EventPriority::Medium,
name: *name, name: event,
element: Some(id), element: Some(node.id),
data: data.clone(), data: data.clone(),
}); });
} }
} }
} }
}
for c in el.children {
inner(queue, resolved, c);
}
}
_ => (),
}
}
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
dom,
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
layouts,
node,
);
let events: Vec<_> = self
.queued_events
.replace(Vec::new())
.into_iter()
// these events were added in the update stage
.filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
.map(|e| (e.0, e.1.into_any()))
.collect();
inner(&events, &mut resolved_events, node);
resolved_events resolved_events
} }

View file

@ -2,7 +2,7 @@ use dioxus_core::*;
use dioxus_native_core::BubbledUpState; use dioxus_native_core::BubbledUpState;
use stretch2::prelude::*; use stretch2::prelude::*;
use crate::layout_attributes::apply_layout_attributes; use dioxus_native_core::layout_attributes::apply_layout_attributes;
/* /*
The layout system uses the lineheight as one point. The layout system uses the lineheight as one point.
@ -42,7 +42,6 @@ impl BubbledUpState for RinkLayout {
if let Some(n) = self.node { if let Some(n) = self.node {
if self.style != style { if self.style != style {
panic!("new style: {style:?}");
stretch.set_style(n, style).unwrap(); stretch.set_style(n, style).unwrap();
} }
} else { } else {
@ -74,11 +73,9 @@ impl BubbledUpState for RinkLayout {
if let Some(n) = self.node { if let Some(n) = self.node {
if &stretch.children(n).unwrap() != &child_layout { if &stretch.children(n).unwrap() != &child_layout {
panic!("new children: {child_layout:?}");
stretch.set_children(n, &child_layout).unwrap(); stretch.set_children(n, &child_layout).unwrap();
} }
if self.style != style { if self.style != style {
panic!("new style: {style:?}");
stretch.set_style(n, style).unwrap(); stretch.set_style(n, style).unwrap();
} }
} else { } else {

View file

@ -1,3 +1,6 @@
// notes:
// mouse events binary search was broken for absolutely positioned elements
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers}, event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
@ -6,23 +9,19 @@ use crossterm::{
}; };
use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*; use dioxus_core::*;
use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData};
use dioxus_native_core::Tree; use dioxus_native_core::Tree;
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
use std::{ use std::collections::HashSet;
io, use std::{io, time::Duration};
time::{Duration, Instant}, use stretch2::{prelude::Size, Stretch};
};
use stretch2::{
prelude::{Layout, Size},
Stretch,
};
use style_attributes::StyleModifier; use style_attributes::StyleModifier;
use tokio::time::Instant;
use tui::{backend::CrosstermBackend, Terminal}; use tui::{backend::CrosstermBackend, Terminal};
mod config; mod config;
mod hooks; mod hooks;
mod layout; mod layout;
mod layout_attributes;
mod render; mod render;
mod style; mod style;
mod style_attributes; mod style_attributes;
@ -31,7 +30,6 @@ mod widget;
pub use config::*; pub use config::*;
pub use hooks::*; pub use hooks::*;
pub use layout::*; pub use layout::*;
pub use layout_attributes::*;
pub use render::*; pub use render::*;
pub fn launch(app: Component<()>) { pub fn launch(app: Component<()>) {
@ -104,6 +102,12 @@ pub fn render_vdom(
let mut terminal = Terminal::new(backend).unwrap(); let mut terminal = Terminal::new(backend).unwrap();
terminal.clear().unwrap(); terminal.clear().unwrap();
let mut to_rerender: HashSet<usize> = tree
.nodes
.iter()
.filter_map(|n| n.as_ref())
.map(|n| n.id.0)
.collect();
loop { loop {
/* /*
@ -115,20 +119,14 @@ pub fn render_vdom(
use simd to compare lines for diffing? use simd to compare lines for diffing?
todo: lazy re-rendering
todo: reuse the layout and node objects.
our work_with_deadline method can tell us which nodes are dirty.
*/ */
/* if !to_rerender.is_empty() {
Compute the layout given the terminal size
*/
let mut events = Vec::new();
terminal.draw(|frame| { terminal.draw(|frame| {
// size is guaranteed to not change when rendering // size is guaranteed to not change when rendering
let dims = frame.size(); let dims = frame.size();
println!("{dims:?}"); // println!("{dims:?}");
let width = dims.width; let width = dims.width;
let height = dims.height; let height = dims.height;
let root_id = tree.root; let root_id = tree.root;
@ -142,27 +140,30 @@ pub fn render_vdom(
}, },
) )
.unwrap(); .unwrap();
let root = tree.get(tree.root);
render::render_vnode(frame, &stretch, &tree, &root, cfg);
})?;
}
// resolve events before rendering // resolve events before rendering
events = handler.get_events( // todo: events do not trigger update?
vdom, for e in handler.get_events(&stretch, &mut tree) {
&stretch, let tname = if e.data.is::<PointerData>() {
&mut tree, "PointerData"
vdom.base_scope().root_node(), } else if e.data.is::<WheelData>() {
); "WheelData"
for n in &tree.nodes { } else if e.data.is::<MouseData>() {
if let Some(node) = n { "MouseData"
let Layout { location, size, .. } = } else if e.data.is::<KeyboardData>() {
stretch.layout(node.up_state.node.unwrap()).unwrap(); "KeyboardData"
println!("{node:#?}"); } else if e.data.is::<TouchData>() {
println!("\t{location:?}: {size:?}"); "TouchData"
} } else if e.data.is::<(u16, u16)>() {
} "(u16, u16)"
let root = tree.get(tree.root); } else {
// render::render_vnode(frame, &stretch, &tree, &root, cfg); panic!()
})?; };
// println!("{tname}: {e:?}");
for e in events {
vdom.handle_message(SchedulerMsg::Event(e)); vdom.handle_message(SchedulerMsg::Event(e));
} }
@ -199,8 +200,10 @@ pub fn render_vdom(
} }
let mutations = vdom.work_with_deadline(|| false); let mutations = vdom.work_with_deadline(|| false);
// updates the tree's nodes
let to_update = tree.apply_mutations(mutations); let to_update = tree.apply_mutations(mutations);
let _to_rerender = tree // update the style and layout
to_rerender = tree
.update_state(&vdom, to_update, &mut stretch, &mut ()) .update_state(&vdom, to_update, &mut stretch, &mut ())
.unwrap(); .unwrap();
} }
@ -220,7 +223,6 @@ pub fn render_vdom(
enum InputEvent { enum InputEvent {
UserInput(TermEvent), UserInput(TermEvent),
Tick, Tick,
#[allow(dead_code)] #[allow(dead_code)]
Close, Close,
} }

View file

@ -1,4 +1,4 @@
use dioxus_native_core::{Tree, TreeNode}; use dioxus_native_core::{layout_attributes::UnitSystem, Tree, TreeNode};
use std::io::Stdout; use std::io::Stdout;
use stretch2::{ use stretch2::{
geometry::Point, geometry::Point,
@ -11,7 +11,7 @@ use crate::{
style::{RinkColor, RinkStyle}, style::{RinkColor, RinkStyle},
style_attributes::{BorderEdge, BorderStyle}, style_attributes::{BorderEdge, BorderStyle},
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext}, widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
Config, RinkLayout, StyleModifier, UnitSystem, Config, RinkLayout, StyleModifier,
}; };
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
@ -48,7 +48,7 @@ pub fn render_vnode<'a>(
// println!("{:?}", self.style); // println!("{:?}", self.style);
new_cell.set_style(self.style); new_cell.set_style(self.style);
new_cell.symbol = c.to_string(); new_cell.symbol = c.to_string();
buf.set(area.left() + i as u16, area.top(), &new_cell); buf.set(area.left() + i as u16, area.top(), new_cell);
} }
} }
} }
@ -150,7 +150,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
buf.set( buf.set(
(current[0] + pos[0] as i32) as u16, (current[0] + pos[0] as i32) as u16,
(current[1] + pos[1] as i32) as u16, (current[1] + pos[1] as i32) as u16,
&new_cell, new_cell,
); );
} }
@ -267,7 +267,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
if let Some(c) = self.down_state.style.bg { if let Some(c) = self.down_state.style.bg {
new_cell.bg = c; new_cell.bg = c;
} }
buf.set(x, y, &new_cell); buf.set(x, y, new_cell);
} }
} }
@ -295,7 +295,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
} }
for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) { for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
new_cell.symbol = symbols.horizontal.to_string(); new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.top(), &new_cell); buf.set(x, area.top(), new_cell.clone());
} }
draw_arc( draw_arc(
[area.right() - radius[0] - 1, area.top() + radius[1]], [area.right() - radius[0] - 1, area.top() + radius[1]],
@ -330,7 +330,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
} }
for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) { for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
new_cell.symbol = symbols.vertical.to_string(); new_cell.symbol = symbols.vertical.to_string();
buf.set(area.right() - 1, y, &new_cell); buf.set(area.right() - 1, y, new_cell.clone());
} }
draw_arc( draw_arc(
[area.right() - radius[0] - 1, area.bottom() - radius[1] - 1], [area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
@ -365,7 +365,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
} }
for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) { for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
new_cell.symbol = symbols.horizontal.to_string(); new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.bottom() - 1, &new_cell); buf.set(x, area.bottom() - 1, new_cell.clone());
} }
draw_arc( draw_arc(
[area.left() + radius[0], area.bottom() - radius[1] - 1], [area.left() + radius[0], area.bottom() - radius[1] - 1],
@ -400,7 +400,7 @@ impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
} }
for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) { for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
new_cell.symbol = symbols.vertical.to_string(); new_cell.symbol = symbols.vertical.to_string();
buf.set(area.left(), y, &new_cell); buf.set(area.left(), y, new_cell.clone());
} }
draw_arc( draw_arc(
[area.left() + radius[0], area.top() + radius[1]], [area.left() + radius[0], area.top() + radius[1]],

View file

@ -30,14 +30,13 @@
*/ */
use dioxus_core::{Attribute, VNode}; use dioxus_core::{Attribute, VNode};
use dioxus_native_core::PushedDownState; use dioxus_native_core::{
layout_attributes::{parse_value, UnitSystem},
use crate::{ PushedDownState,
parse_value,
style::{RinkColor, RinkStyle},
UnitSystem,
}; };
use crate::style::{RinkColor, RinkStyle};
#[derive(Default, Clone, PartialEq, Debug)] #[derive(Default, Clone, PartialEq, Debug)]
pub struct StyleModifier { pub struct StyleModifier {
pub style: RinkStyle, pub style: RinkStyle,

View file

@ -20,7 +20,7 @@ impl<'a> RinkBuffer<'a> {
Self { buf, cfg } Self { buf, cfg }
} }
pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) { pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
let area = self.buf.area(); let area = self.buf.area();
if x < area.x || x > area.width || y < area.y || y > area.height { if x < area.x || x > area.width || y < area.y || y > area.height {
panic!("({x}, {y}) is not in {area:?}"); panic!("({x}, {y}) is not in {area:?}");
@ -34,7 +34,7 @@ impl<'a> RinkBuffer<'a> {
} }
} else { } else {
cell.modifier = new.modifier; cell.modifier = new.modifier;
cell.symbol = new.symbol.clone(); cell.symbol = new.symbol;
cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg)); cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
} }
} }

View file

@ -1,3 +1,4 @@
use stretch::style::Dimension;
use stretch2 as stretch; use stretch2 as stretch;
#[test] #[test]
@ -6,14 +7,7 @@ fn relayout() {
let node1 = stretch let node1 = stretch
.new_node( .new_node(
stretch::style::Style { stretch::style::Style {
position: stretch::geometry::Point { size: stretch::geometry::Size { width: Dimension::Points(8f32), height: Dimension::Points(80f32) },
x: stretch::style::Dimension::Points(10f32),
y: stretch::style::Dimension::Points(10f32),
},
size: stretch::geometry::Size {
width: stretch::style::Dimension::Points(10f32),
height: stretch::style::Dimension::Points(10f32),
},
..Default::default() ..Default::default()
}, },
&[], &[],
@ -22,10 +16,9 @@ fn relayout() {
let node0 = stretch let node0 = stretch
.new_node( .new_node(
stretch::style::Style { stretch::style::Style {
size: stretch::geometry::Size { align_self: stretch::prelude::AlignSelf::Center,
width: stretch::style::Dimension::Percent(1f32), size: stretch::geometry::Size { width: Dimension::Auto, height: Dimension::Auto },
height: stretch::style::Dimension::Percent(1f32), // size: stretch::geometry::Size { width: Dimension::Percent(1.0), height: Dimension::Percent(1.0) },
},
..Default::default() ..Default::default()
}, },
&[node1], &[node1],
@ -34,30 +27,38 @@ fn relayout() {
let node = stretch let node = stretch
.new_node( .new_node(
stretch::style::Style { stretch::style::Style {
size: stretch::geometry::Size { size: stretch::geometry::Size { width: Dimension::Percent(1f32), height: Dimension::Percent(1f32) },
width: stretch::style::Dimension::Points(100f32),
height: stretch::style::Dimension::Points(100f32),
},
..Default::default() ..Default::default()
}, },
&[node0], &[node0],
) )
.unwrap(); .unwrap();
for _ in 0..10 { println!("0:");
stretch stretch
.compute_layout(node, stretch::geometry::Size::undefined()) .compute_layout(
node,
stretch::geometry::Size {
width: stretch::prelude::Number::Defined(100f32),
height: stretch::prelude::Number::Defined(100f32),
},
)
.unwrap(); .unwrap();
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32); let initial = stretch.layout(node).unwrap().location;
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); let initial0 = stretch.layout(node0).unwrap().location;
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); let initial1 = stretch.layout(node1).unwrap().location;
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); for i in 1..10 {
assert_eq!(stretch.layout(node1).unwrap().size.width, 10f32); println!("\n\n{i}:");
assert_eq!(stretch.layout(node1).unwrap().size.height, 10f32); stretch
assert_eq!(stretch.layout(node1).unwrap().location.x, 0f32); .compute_layout(
assert_eq!(stretch.layout(node1).unwrap().location.y, 0f32); node,
assert_eq!(stretch.layout(node0).unwrap().size.width, 100f32); stretch::geometry::Size {
assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); width: stretch::prelude::Number::Defined(100f32),
assert_eq!(stretch.layout(node0).unwrap().location.x, 0f32); height: stretch::prelude::Number::Defined(100f32),
assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); },
)
.unwrap();
assert_eq!(stretch.layout(node).unwrap().location, initial);
assert_eq!(stretch.layout(node0).unwrap().location, initial0);
assert_eq!(stretch.layout(node1).unwrap().location, initial1);
} }
} }