bugfixes, docs, and pass clippy

This commit is contained in:
Evan Almloff 2022-04-02 16:46:46 -05:00
parent 5b25500c0b
commit 32b2e3a135
11 changed files with 350 additions and 306 deletions

View file

@ -9,13 +9,12 @@ TUI support is currently quite experimental. Even the project name will change.
## Getting Set up ## Getting Set up
To tinker with TUI support, start by making a new package and adding our TUI package from git. To tinker with TUI support, start by making a new package and adding our TUI feature.
```shell ```shell
$ cargo new --bin demo $ cargo new --bin demo
$ cd demo $ cd demo
$ cargo add dioxus $ cargo add dioxus --features tui
$ cargo add rink --git https://github.com/DioxusLabs/rink.git
``` ```
@ -27,10 +26,7 @@ Then, edit your `main.rs` with the basic template.
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
let mut dom = VirtualDom::new(app); dioxus::tui::launch(app);
dom.rebuild();
rink::render_vdom(&mut dom).unwrap();
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
@ -54,7 +50,44 @@ To run our app:
$ cargo run $ cargo run
``` ```
Press "q" to close the app (yes, this is hardcoded, we are working on handlers to expose this in your code.) Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a Configeration to disable the default quit and use the root TuiContext to quit on your own.
```rust
// main
use dioxus::events::{KeyCode, KeyboardEvent};
use dioxus::prelude::*;
use dioxus::tui::TuiContext;
fn main() {
dioxus::tui::launch_cfg(
app,
dioxus::tui::Config {
ctrl_c_quit: false,
// Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI
rendering_mode: dioxus::tui::RenderingMode::Rgb,
},
);
}
fn app(cx: Scope) -> Element {
let tui_ctx: TuiContext = cx.consume_context().unwrap();
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code {
tui_ctx.quit();
},
"Hello world!"
}
})
}
```
## Notes ## Notes

View file

@ -5,6 +5,7 @@ fn main() {
app, app,
dioxus::tui::Config { dioxus::tui::Config {
rendering_mode: dioxus::tui::RenderingMode::Ansi, rendering_mode: dioxus::tui::RenderingMode::Ansi,
..Default::default()
}, },
); );
} }

View file

@ -255,11 +255,11 @@ pub enum UnitSystem {
Point(f32), Point(f32),
} }
impl Into<Dimension> for UnitSystem { impl From<UnitSystem> for Dimension {
fn into(self) -> Dimension { fn from(other: UnitSystem) -> Dimension {
match self { match other {
Self::Percent(v) => Dimension::Percent(v), UnitSystem::Percent(v) => Dimension::Percent(v),
Self::Point(v) => Dimension::Points(v), UnitSystem::Point(v) => Dimension::Points(v),
} }
} }
} }

View file

@ -12,25 +12,30 @@ use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
#[derive(Debug)] #[derive(Debug)]
pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> { pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> {
root: usize, root: usize,
nodes: Vec<Option<TreeNode<US, DS>>>, nodes: Vec<Option<Node<US, DS>>>,
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>, nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
node_stack: smallvec::SmallVec<[usize; 10]>, node_stack: smallvec::SmallVec<[usize; 10]>,
} }
impl<US: BubbledUpState, DS: PushedDownState> Default for RealDom<US, DS> {
fn default() -> Self {
Self::new()
}
}
impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> { impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
pub fn new() -> RealDom<US, DS> { pub fn new() -> RealDom<US, DS> {
RealDom { RealDom {
root: 0, root: 0,
nodes: { nodes: {
let mut v = Vec::new(); let v = vec![Some(Node::new(
v.push(Some(TreeNode::new(
0, 0,
TreeNodeType::Element { NodeType::Element {
tag: "Root".to_string(), tag: "Root".to_string(),
namespace: Some("Root"), namespace: Some("Root"),
children: Vec::new(), children: Vec::new(),
}, },
))); ))];
v v
}, },
nodes_listening: FxHashMap::default(), nodes_listening: FxHashMap::default(),
@ -47,7 +52,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
match e { match e {
PushRoot { root } => self.node_stack.push(root as usize), PushRoot { root } => self.node_stack.push(root as usize),
AppendChildren { many } => { AppendChildren { many } => {
let target = if self.node_stack.len() >= many as usize + 1 { let target = if self.node_stack.len() > many as usize {
*self *self
.node_stack .node_stack
.get(self.node_stack.len() - (many as usize + 1)) .get(self.node_stack.len() - (many as usize + 1))
@ -96,9 +101,9 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
self.remove(root as usize).unwrap(); self.remove(root as usize).unwrap();
} }
CreateTextNode { root, text } => { CreateTextNode { root, text } => {
let n = TreeNode::new( let n = Node::new(
root, root,
TreeNodeType::Text { NodeType::Text {
text: text.to_string(), text: text.to_string(),
}, },
); );
@ -106,9 +111,9 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
self.node_stack.push(root as usize) self.node_stack.push(root as usize)
} }
CreateElement { root, tag } => { CreateElement { root, tag } => {
let n = TreeNode::new( let n = Node::new(
root, root,
TreeNodeType::Element { NodeType::Element {
tag: tag.to_string(), tag: tag.to_string(),
namespace: None, namespace: None,
children: Vec::new(), children: Vec::new(),
@ -118,9 +123,9 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
self.node_stack.push(root as usize) self.node_stack.push(root as usize)
} }
CreateElementNs { root, tag, ns } => { CreateElementNs { root, tag, ns } => {
let n = TreeNode::new( let n = Node::new(
root, root,
TreeNodeType::Element { NodeType::Element {
tag: tag.to_string(), tag: tag.to_string(),
namespace: Some(ns), namespace: Some(ns),
children: Vec::new(), children: Vec::new(),
@ -130,7 +135,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
self.node_stack.push(root as usize) self.node_stack.push(root as usize)
} }
CreatePlaceholder { root } => { CreatePlaceholder { root } => {
let n = TreeNode::new(root, TreeNodeType::Placeholder); let n = Node::new(root, NodeType::Placeholder);
self.insert(n); self.insert(n);
self.node_stack.push(root as usize) self.node_stack.push(root as usize)
} }
@ -159,7 +164,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
let target = &mut self[root as usize]; let target = &mut self[root as usize];
nodes_updated.push(root as usize); nodes_updated.push(root as usize);
match &mut target.node_type { match &mut target.node_type {
TreeNodeType::Text { text } => { NodeType::Text { text } => {
*text = new_text.to_string(); *text = new_text.to_string();
} }
_ => unreachable!(), _ => unreachable!(),
@ -204,13 +209,13 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
let node_type = &node.node_type; let node_type = &node.node_type;
let up_state = &mut node.up_state; let up_state = &mut node.up_state;
let children = match node_type { let children = match node_type {
TreeNodeType::Element { children, .. } => Some(children), NodeType::Element { children, .. } => Some(children),
_ => None, _ => None,
}; };
// todo: reduce cloning state // todo: reduce cloning state
let old = up_state.clone(); let old = up_state.clone();
let mut new = up_state.clone(); let mut new = up_state.clone();
let parent = node.parent.clone(); let parent = node.parent;
new.reduce( new.reduce(
children children
.unwrap_or(&Vec::new()) .unwrap_or(&Vec::new())
@ -254,19 +259,16 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
if new != old { if new != old {
to_rerender.insert(id); to_rerender.insert(id);
let node = &mut self[id as usize]; let node = &mut self[id as usize];
match &node.node_type { if let NodeType::Element { children, .. } = &node.node_type {
TreeNodeType::Element { children, .. } => { for c in children {
for c in children { let i = to_push.partition_point(|(other_id, h)| {
let i = to_push.partition_point(|(other_id, h)| { *h < height + 1 || (*h == height + 1 && *other_id < c.0)
*h < height + 1 || (*h == height + 1 && *other_id < c.0) });
}); if i >= to_push.len() || to_push[i].0 != c.0 {
if i >= to_push.len() || to_push[i].0 != c.0 { to_push.insert(i, (c.0, height + 1));
to_push.insert(i, (c.0, height + 1));
}
} }
} }
_ => (), }
};
node.down_state = new; node.down_state = new;
} }
} }
@ -287,31 +289,25 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
fn increase_height(&mut self, id: usize, amount: u16) { fn increase_height(&mut self, id: usize, amount: u16) {
let n = &mut self[id]; let n = &mut self[id];
n.height += amount; n.height += amount;
match &n.node_type { if let NodeType::Element { children, .. } = &n.node_type {
TreeNodeType::Element { children, .. } => { for c in children.clone() {
for c in children.clone() { self.increase_height(c.0, amount);
self.increase_height(c.0, amount);
}
} }
_ => (),
} }
} }
// remove a node and it's children from the tree. // remove a node and it's children from the tree.
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> { fn remove(&mut self, id: usize) -> Option<Node<US, DS>> {
// We do not need to remove the node from the parent's children list for children. // We do not need to remove the node from the parent's children list for children.
fn inner<US: BubbledUpState, DS: PushedDownState>( fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &mut RealDom<US, DS>, tree: &mut RealDom<US, DS>,
id: usize, id: usize,
) -> Option<TreeNode<US, DS>> { ) -> Option<Node<US, DS>> {
let mut node = tree.nodes[id as usize].take()?; let mut node = tree.nodes[id as usize].take()?;
match &mut node.node_type { if let NodeType::Element { children, .. } = &mut node.node_type {
TreeNodeType::Element { children, .. } => { for c in children {
for c in children { inner(tree, c.0)?;
inner(tree, c.0)?;
}
} }
_ => (),
} }
Some(node) Some(node)
} }
@ -320,18 +316,15 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
let parent = &mut self[parent]; let parent = &mut self[parent];
parent.remove_child(ElementId(id)); parent.remove_child(ElementId(id));
} }
match &mut node.node_type { if let NodeType::Element { children, .. } = &mut node.node_type {
TreeNodeType::Element { children, .. } => { for c in children {
for c in children { inner(self, c.0)?;
inner(self, c.0)?;
}
} }
_ => (),
} }
Some(node) Some(node)
} }
fn insert(&mut self, node: TreeNode<US, DS>) { fn insert(&mut self, node: Node<US, DS>) {
let current_len = self.nodes.len(); let current_len = self.nodes.len();
let id = node.id.0; let id = node.id.0;
if current_len - 1 < node.id.0 { if current_len - 1 < node.id.0 {
@ -341,15 +334,15 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
self.nodes[id] = Some(node); self.nodes[id] = Some(node);
} }
pub fn get(&self, id: usize) -> Option<&TreeNode<US, DS>> { pub fn get(&self, id: usize) -> Option<&Node<US, DS>> {
self.nodes.get(id)?.as_ref() self.nodes.get(id)?.as_ref()
} }
pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode<US, DS>> { pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<US, DS>> {
self.nodes.get_mut(id)?.as_mut() self.nodes.get_mut(id)?.as_mut()
} }
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> { pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<US, DS>> {
if let Some(nodes) = self.nodes_listening.get(event) { if let Some(nodes) = self.nodes_listening.get(event) {
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect(); let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
@ -369,7 +362,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
if let Some(id) = e.id.get() { if let Some(id) = e.id.get() {
let tree_node = &self[id]; let tree_node = &self[id];
match &tree_node.node_type { match &tree_node.node_type {
TreeNodeType::Element { NodeType::Element {
tag, tag,
namespace, namespace,
children, children,
@ -398,7 +391,7 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
if let Some(id) = t.id.get() { if let Some(id) = t.id.get() {
let tree_node = &self[id]; let tree_node = &self[id];
match &tree_node.node_type { match &tree_node.node_type {
TreeNodeType::Text { text } => t.text == text, NodeType::Text { text } => t.text == text,
_ => false, _ => false,
} }
} else { } else {
@ -420,36 +413,53 @@ impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
} }
/// Call a function for each node in the tree, depth first. /// Call a function for each node in the tree, depth first.
pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode<US, DS>)) { pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<US, DS>)) {
fn inner<US: BubbledUpState, DS: PushedDownState>( fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &RealDom<US, DS>, tree: &RealDom<US, DS>,
id: ElementId, id: ElementId,
f: &mut impl FnMut(&TreeNode<US, DS>), f: &mut impl FnMut(&Node<US, DS>),
) { ) {
let node = &tree[id]; let node = &tree[id];
f(node); f(node);
match &node.node_type { if let NodeType::Element { children, .. } = &node.node_type {
TreeNodeType::Element { children, .. } => { for c in children {
for c in children { inner(tree, *c, f);
inner(tree, *c, f);
}
} }
_ => (),
} }
} }
match &self[self.root].node_type { if let NodeType::Element { children, .. } = &self[self.root].node_type {
TreeNodeType::Element { children, .. } => { for c in children {
for c in children { inner(self, *c, &mut f);
inner(self, *c, &mut f); }
}
}
/// Call a function for each node in the tree, depth first.
pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<US, DS>)) {
fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &mut RealDom<US, DS>,
id: ElementId,
f: &mut impl FnMut(&mut Node<US, DS>),
) {
let node = &mut tree[id];
f(node);
if let NodeType::Element { children, .. } = &mut node.node_type {
for c in children.clone() {
inner(tree, c, f);
} }
} }
_ => (), }
let root = self.root;
if let NodeType::Element { children, .. } = &mut self[root].node_type {
for c in children.clone() {
inner(self, c, &mut f);
}
} }
} }
} }
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> { impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
type Output = TreeNode<US, DS>; type Output = Node<US, DS>;
fn index(&self, idx: usize) -> &Self::Output { fn index(&self, idx: usize) -> &Self::Output {
self.get(idx).expect("Node does not exist") self.get(idx).expect("Node does not exist")
@ -457,7 +467,7 @@ impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
} }
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> { impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> {
type Output = TreeNode<US, DS>; type Output = Node<US, DS>;
fn index(&self, idx: ElementId) -> &Self::Output { fn index(&self, idx: ElementId) -> &Self::Output {
&self[idx.0] &self[idx.0]
@ -477,7 +487,7 @@ impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`TreeNode::element`]. /// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`TreeNode::element`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> { pub struct Node<US: BubbledUpState, DS: PushedDownState> {
/// The id of the node this node was created from. /// The id of the node this node was created from.
pub id: ElementId, pub id: ElementId,
/// The parent id of the node. /// The parent id of the node.
@ -487,13 +497,13 @@ pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
/// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent. /// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent.
pub down_state: DS, pub down_state: DS,
/// Additional inforation specific to the node type /// Additional inforation specific to the node type
pub node_type: TreeNodeType, pub node_type: NodeType,
/// The number of parents before the root node. The root node has height 1. /// The number of parents before the root node. The root node has height 1.
pub height: u16, pub height: u16,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TreeNodeType { pub enum NodeType {
Text { Text {
text: String, text: String,
}, },
@ -505,9 +515,9 @@ pub enum TreeNodeType {
Placeholder, Placeholder,
} }
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> { impl<US: BubbledUpState, DS: PushedDownState> Node<US, DS> {
fn new(id: u64, node_type: TreeNodeType) -> Self { fn new(id: u64, node_type: NodeType) -> Self {
TreeNode { Node {
id: ElementId(id as usize), id: ElementId(id as usize),
parent: None, parent: None,
node_type, node_type,
@ -523,20 +533,14 @@ impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
} }
fn add_child(&mut self, child: ElementId) { fn add_child(&mut self, child: ElementId) {
match &mut self.node_type { if let NodeType::Element { children, .. } = &mut self.node_type {
TreeNodeType::Element { children, .. } => { children.push(child);
children.push(child);
}
_ => (),
} }
} }
fn remove_child(&mut self, child: ElementId) { fn remove_child(&mut self, child: ElementId) {
match &mut self.node_type { if let NodeType::Element { children, .. } = &mut self.node_type {
TreeNodeType::Element { children, .. } => { children.retain(|c| c != &child);
children.retain(|c| c != &child);
}
_ => (),
} }
} }

View file

@ -1,6 +1,18 @@
#[derive(Default, Clone, Copy)] #[derive(Clone, Copy)]
pub struct Config { pub struct Config {
pub rendering_mode: RenderingMode, pub rendering_mode: RenderingMode,
/// Should the terminal quit when the user presses `ctrl+c`?
/// To handle quiting on your own, use the [crate::TuiContext] root context.
pub ctrl_c_quit: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
rendering_mode: Default::default(),
ctrl_c_quit: true,
}
}
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View file

@ -5,8 +5,7 @@ use dioxus_core::*;
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use dioxus_html::{on::*, KeyCode}; use dioxus_html::{on::*, KeyCode};
use dioxus_native_core::real_dom::{RealDom, TreeNode}; use dioxus_native_core::real_dom::{Node, RealDom};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{ use std::{
any::Any, any::Any,
cell::RefCell, cell::RefCell,
@ -162,7 +161,7 @@ impl InnerInputState {
} }
} }
fn update<'a>( fn update(
&mut self, &mut self,
evts: &mut Vec<EventCore>, evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>, resolved_events: &mut Vec<UserEvent>,
@ -176,9 +175,11 @@ impl InnerInputState {
self.wheel = None; self.wheel = None;
println!("update {evts:?}");
for e in evts.iter_mut() { for e in evts.iter_mut() {
self.apply_event(e); self.apply_event(e);
} }
println!("->update {evts:?}");
self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree); self.resolve_mouse_events(previous_mouse, resolved_events, layout, tree);
@ -216,7 +217,7 @@ impl InnerInputState {
data: Arc<dyn Any + Send + Sync>, data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<ElementId>, will_bubble: &mut FxHashSet<ElementId>,
resolved_events: &mut Vec<UserEvent>, resolved_events: &mut Vec<UserEvent>,
node: &TreeNode<StretchLayout, StyleModifier>, node: &Node<StretchLayout, StyleModifier>,
tree: &RealDom<StretchLayout, StyleModifier>, tree: &RealDom<StretchLayout, StyleModifier>,
) { ) {
// 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
@ -231,7 +232,7 @@ impl InnerInputState {
priority: EventPriority::Medium, priority: EventPriority::Medium,
name, name,
element: Some(node.id), element: Some(node.id),
data: data, data,
}) })
} }
} }
@ -269,17 +270,15 @@ impl InnerInputState {
.is_some(); .is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && previously_contained {
if previously_contained { try_create_event(
try_create_event( "mousemove",
"mousemove", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -295,17 +294,15 @@ impl InnerInputState {
.is_some(); .is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && !previously_contained {
if !previously_contained { try_create_event(
try_create_event( "mouseenter",
"mouseenter", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -321,17 +318,15 @@ impl InnerInputState {
.is_some(); .is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && !previously_contained {
if !previously_contained { try_create_event(
try_create_event( "mouseover",
"mouseover", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -343,17 +338,15 @@ impl InnerInputState {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && data.clicked {
if data.clicked { try_create_event(
try_create_event( "mousedown",
"mousedown", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -365,17 +358,15 @@ impl InnerInputState {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && data.released {
if data.released { try_create_event(
try_create_event( "mouseup",
"mouseup", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -387,17 +378,15 @@ impl InnerInputState {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && data.released && data.mouse_data.button == 0 {
if data.released && data.mouse_data.button == 0 { try_create_event(
try_create_event( "click",
"click", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -409,17 +398,15 @@ impl InnerInputState {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if currently_contains && data.released && data.mouse_data.button == 2 {
if data.released && data.mouse_data.button == 2 { try_create_event(
try_create_event( "contextmenu",
"contextmenu", Arc::new(clone_mouse_data(data.mouse_data)),
Arc::new(clone_mouse_data(data.mouse_data)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -431,18 +418,16 @@ impl InnerInputState {
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, data.new_pos); let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains { if let Some(w) = data.wheel_data {
if let Some(w) = data.wheel_data { if currently_contains && data.wheel_delta != 0.0 {
if data.wheel_delta != 0.0 { try_create_event(
try_create_event( "wheel",
"wheel", Arc::new(clone_wheel_data(w)),
Arc::new(clone_wheel_data(w)), &mut will_bubble,
&mut will_bubble, resolved_events,
resolved_events, node,
node, tree,
tree, );
);
}
} }
} }
} }
@ -511,24 +496,22 @@ pub struct RinkInputHandler {
impl RinkInputHandler { impl RinkInputHandler {
/// global context that handles events /// global context that handles events
/// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
pub fn new( pub fn new() -> (
mut receiver: UnboundedReceiver<TermEvent>, Self,
cx: &ScopeState, Rc<RefCell<InnerInputState>>,
) -> (Self, Rc<RefCell<InnerInputState>>) { impl FnMut(crossterm::event::Event),
) {
let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::downgrade(&queued_events); let queued_events2 = Rc::downgrade(&queued_events);
cx.push_future(async move { let regester_event = move |evt: crossterm::event::Event| {
while let Some(evt) = receiver.next().await { if let Some(evt) = get_event(evt) {
if let Some(evt) = get_event(evt) { if let Some(v) = queued_events2.upgrade() {
if let Some(v) = queued_events2.upgrade() { println!("queued event: {:?}", evt);
(*v).borrow_mut().push(evt); (*v).borrow_mut().push(evt);
} else {
break;
}
} }
} }
}); };
let state = Rc::new(RefCell::new(InnerInputState::new())); let state = Rc::new(RefCell::new(InnerInputState::new()));
@ -538,14 +521,16 @@ impl RinkInputHandler {
queued_events, queued_events,
}, },
state, state,
regester_event,
) )
} }
pub fn get_events<'a>( pub fn get_events(
&self, &self,
layout: &Stretch, layout: &Stretch,
tree: &mut RealDom<StretchLayout, StyleModifier>, tree: &mut RealDom<StretchLayout, StyleModifier>,
) -> Vec<UserEvent> { ) -> Vec<UserEvent> {
println!("get_events");
let mut resolved_events = Vec::new(); let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update( (*self.state).borrow_mut().update(
@ -799,7 +784,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
Some(EventData::Keyboard(KeyboardData { Some(EventData::Keyboard(KeyboardData {
char_code: code.raw_code(), char_code: code.raw_code(),
key: key_str.to_string(), key: key_str,
key_code: code, key_code: code,
alt_key: event.modifiers.contains(KeyModifiers::ALT), alt_key: event.modifiers.contains(KeyModifiers::ALT),
ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),

View file

@ -65,7 +65,7 @@ impl BubbledUpState for StretchLayout {
} }
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 {
stretch.set_children(n, &child_layout).unwrap(); stretch.set_children(n, &child_layout).unwrap();
} }
if self.style != style { if self.style != style {

View file

@ -1,6 +1,3 @@
// 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},
@ -48,11 +45,8 @@ pub fn launch(app: Component<()>) {
pub fn launch_cfg(app: Component<()>, cfg: Config) { pub fn launch_cfg(app: Component<()>, cfg: Config) {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
let (tx, rx) = unbounded();
let cx = dom.base_scope(); let (handler, state, register_event) = RinkInputHandler::new();
let (handler, state) = RinkInputHandler::new(rx, cx);
// Setup input handling // Setup input handling
let (event_tx, event_rx) = unbounded(); let (event_tx, event_rx) = unbounded();
@ -70,6 +64,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
} }
}); });
let cx = dom.base_scope();
cx.provide_root_context(state); cx.provide_root_context(state);
cx.provide_root_context(TuiContext { tx: event_tx_clone }); cx.provide_root_context(TuiContext { tx: event_tx_clone });
@ -81,17 +76,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
.update_state(&dom, to_update, &mut stretch, &mut ()) .update_state(&dom, to_update, &mut stretch, &mut ())
.unwrap(); .unwrap();
render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap(); render_vdom(
&mut dom,
event_rx,
handler,
cfg,
tree,
stretch,
register_event,
)
.unwrap();
} }
fn render_vdom( fn render_vdom(
vdom: &mut VirtualDom, vdom: &mut VirtualDom,
crossterm_event_sender: UnboundedSender<TermEvent>,
mut event_reciever: UnboundedReceiver<InputEvent>, mut event_reciever: UnboundedReceiver<InputEvent>,
handler: RinkInputHandler, handler: RinkInputHandler,
cfg: Config, cfg: Config,
mut tree: RealDom<StretchLayout, StyleModifier>, mut tree: RealDom<StretchLayout, StyleModifier>,
mut stretch: Stretch, mut stretch: Stretch,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> { ) -> Result<()> {
tokio::runtime::Builder::new_current_thread() tokio::runtime::Builder::new_current_thread()
.enable_all() .enable_all()
@ -105,7 +109,7 @@ fn render_vdom(
terminal.clear().unwrap(); terminal.clear().unwrap();
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect(); let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
let mut redraw = true; let mut resized = true;
loop { loop {
/* /*
@ -119,8 +123,8 @@ fn render_vdom(
todo: lazy re-rendering todo: lazy re-rendering
*/ */
if !to_rerender.is_empty() || redraw { if !to_rerender.is_empty() || resized {
redraw = false; resized = false;
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();
@ -138,8 +142,8 @@ fn render_vdom(
}, },
) )
.unwrap(); .unwrap();
let root = &tree[tree.root_id()]; // let root = &tree[tree.root_id()];
render::render_vnode(frame, &stretch, &tree, &root, cfg); // render::render_vnode(frame, &stretch, &tree, &root, cfg);
})?; })?;
} }
@ -158,35 +162,44 @@ fn render_vdom(
TermEvent::Key(key) => { TermEvent::Key(key) => {
if matches!(key.code, KeyCode::Char('C' | 'c')) if matches!(key.code, KeyCode::Char('C' | 'c'))
&& key.modifiers.contains(KeyModifiers::CONTROL) && key.modifiers.contains(KeyModifiers::CONTROL)
&& cfg.ctrl_c_quit
{ {
break; break;
} }
} }
TermEvent::Resize(_, _) => redraw = true, TermEvent::Resize(_, _) => resized = true,
TermEvent::Mouse(_) => {} TermEvent::Mouse(_) => {}
}, },
InputEvent::Close => break, InputEvent::Close => break,
}; };
if let InputEvent::UserInput(evt) = evt.unwrap() { if let InputEvent::UserInput(evt) = evt.unwrap() {
crossterm_event_sender.unbounded_send(evt).unwrap(); register_event(evt);
} }
} }
} }
} }
// resolve events before rendering {
for e in handler.get_events(&stretch, &mut tree) { // resolve events before rendering
vdom.handle_message(SchedulerMsg::Event(e)); let evts = handler.get_events(&stretch, &mut tree);
println!("evts: {:?}", evts);
for e in evts {
vdom.handle_message(SchedulerMsg::Event(e));
}
let mutations = vdom.work_with_deadline(|| false);
// updates the tree's nodes
let to_update = tree.apply_mutations(mutations);
// update the style and layout
to_rerender.extend(
tree.update_state(vdom, to_update, &mut stretch, &mut ())
.unwrap()
.iter(),
)
} }
vdom.process_all_messages(); println!();
let mutations = vdom.work_with_deadline(|| false); println!();
// updates the tree's nodes println!();
let to_update = tree.apply_mutations(mutations);
// update the style and layout
to_rerender = tree
.update_state(&vdom, to_update, &mut stretch, &mut ())
.unwrap();
} }
disable_raw_mode()?; disable_raw_mode()?;
@ -203,6 +216,5 @@ fn render_vdom(
enum InputEvent { enum InputEvent {
UserInput(TermEvent), UserInput(TermEvent),
#[allow(dead_code)]
Close, Close,
} }

View file

@ -1,7 +1,7 @@
use crate::layout::StretchLayout; use crate::layout::StretchLayout;
use dioxus_native_core::{ use dioxus_native_core::{
layout_attributes::UnitSystem, layout_attributes::UnitSystem,
real_dom::{RealDom, TreeNode}, real_dom::{Node, RealDom},
}; };
use std::io::Stdout; use std::io::Stdout;
use stretch2::{ use stretch2::{
@ -20,18 +20,17 @@ use crate::{
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub fn render_vnode<'a>( pub fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>, frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Stretch, layout: &Stretch,
tree: &RealDom<StretchLayout, StyleModifier>, tree: &RealDom<StretchLayout, StyleModifier>,
node: &TreeNode<StretchLayout, StyleModifier>, node: &Node<StretchLayout, StyleModifier>,
cfg: Config, cfg: Config,
) { ) {
use dioxus_native_core::real_dom::TreeNodeType; use dioxus_native_core::real_dom::NodeType;
match &node.node_type { if let NodeType::Placeholder = &node.node_type {
TreeNodeType::Placeholder => return, return;
_ => (),
} }
let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap(); let Layout { location, size, .. } = layout.layout(node.up_state.node.unwrap()).unwrap();
@ -40,7 +39,7 @@ pub fn render_vnode<'a>(
let Size { width, height } = size; let Size { width, height } = size;
match &node.node_type { match &node.node_type {
TreeNodeType::Text { text } => { NodeType::Text { text } => {
#[derive(Default)] #[derive(Default)]
struct Label<'a> { struct Label<'a> {
text: &'a str, text: &'a str,
@ -59,7 +58,7 @@ pub fn render_vnode<'a>(
} }
let label = Label { let label = Label {
text: &text, text,
style: node.down_state.style, style: node.down_state.style,
}; };
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
@ -69,7 +68,7 @@ pub fn render_vnode<'a>(
frame.render_widget(WidgetWithContext::new(label, cfg), area); frame.render_widget(WidgetWithContext::new(label, cfg), area);
} }
} }
TreeNodeType::Element { children, .. } => { NodeType::Element { children, .. } => {
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
// the renderer will panic if a node is rendered out of range even if the size is zero // the renderer will panic if a node is rendered out of range even if the size is zero
@ -81,11 +80,11 @@ pub fn render_vnode<'a>(
render_vnode(frame, layout, tree, &tree[c.0], cfg); render_vnode(frame, layout, tree, &tree[c.0], cfg);
} }
} }
TreeNodeType::Placeholder => unreachable!(), NodeType::Placeholder => unreachable!(),
} }
} }
impl RinkWidget for &TreeNode<StretchLayout, StyleModifier> { impl RinkWidget for &Node<StretchLayout, StyleModifier> {
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
use tui::symbols::line::*; use tui::symbols::line::*;
@ -249,8 +248,8 @@ impl RinkWidget for &TreeNode<StretchLayout, StyleModifier> {
fn get_radius(border: &BorderEdge, area: Rect) -> f32 { fn get_radius(border: &BorderEdge, area: Rect) -> f32 {
match border.style { match border.style {
BorderStyle::HIDDEN => 0.0, BorderStyle::Hidden => 0.0,
BorderStyle::NONE => 0.0, BorderStyle::None => 0.0,
_ => match border.radius { _ => match border.radius {
UnitSystem::Percent(p) => p * area.width as f32 / 100.0, UnitSystem::Percent(p) => p * area.width as f32 / 100.0,
UnitSystem::Point(p) => p, UnitSystem::Point(p) => p,

View file

@ -51,33 +51,28 @@ impl PushedDownState for StyleModifier {
if parent.is_some() { if parent.is_some() {
self.style.fg = None; self.style.fg = None;
} }
match vnode { if let VNode::Element(el) = vnode {
VNode::Element(el) => { // handle text modifier elements
// handle text modifier elements if el.namespace.is_none() {
if el.namespace.is_none() { match el.tag {
match el.tag { "b" => apply_style_attributes("font-weight", "bold", self),
"b" => apply_style_attributes("font-weight", "bold", self), "strong" => apply_style_attributes("font-weight", "bold", self),
"strong" => apply_style_attributes("font-weight", "bold", self), "u" => apply_style_attributes("text-decoration", "underline", self),
"u" => apply_style_attributes("text-decoration", "underline", self), "ins" => apply_style_attributes("text-decoration", "underline", self),
"ins" => apply_style_attributes("text-decoration", "underline", self), "del" => apply_style_attributes("text-decoration", "line-through", self),
"del" => apply_style_attributes("text-decoration", "line-through", self), "i" => apply_style_attributes("font-style", "italic", self),
"i" => apply_style_attributes("font-style", "italic", self), "em" => apply_style_attributes("font-style", "italic", self),
"em" => apply_style_attributes("font-style", "italic", self), "mark" => {
"mark" => apply_style_attributes( apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self)
"background-color",
"rgba(241, 231, 64, 50%)",
self,
),
_ => (),
} }
} _ => (),
// gather up all the styles from the attribute list
for &Attribute { name, value, .. } in el.attributes {
apply_style_attributes(name, value, self);
} }
} }
_ => (),
// gather up all the styles from the attribute list
for &Attribute { name, value, .. } in el.attributes {
apply_style_attributes(name, value, self);
}
} }
// keep the text styling from the parent element // keep the text styling from the parent element
@ -125,7 +120,7 @@ impl Default for BorderEdge {
fn default() -> Self { fn default() -> Self {
Self { Self {
color: None, color: None,
style: BorderStyle::NONE, style: BorderStyle::None,
width: UnitSystem::Point(0.0), width: UnitSystem::Point(0.0),
radius: UnitSystem::Point(0.0), radius: UnitSystem::Point(0.0),
} }
@ -134,16 +129,16 @@ impl Default for BorderEdge {
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum BorderStyle { pub enum BorderStyle {
DOTTED, Dotted,
DASHED, Dashed,
SOLID, Solid,
DOUBLE, Double,
GROOVE, Groove,
RIDGE, Ridge,
INSET, Inset,
OUTSET, Outset,
HIDDEN, Hidden,
NONE, None,
} }
impl BorderStyle { impl BorderStyle {
@ -160,16 +155,16 @@ impl BorderStyle {
..NORMAL ..NORMAL
}; };
match self { match self {
BorderStyle::DOTTED => Some(DOTTED), BorderStyle::Dotted => Some(DOTTED),
BorderStyle::DASHED => Some(DASHED), BorderStyle::Dashed => Some(DASHED),
BorderStyle::SOLID => Some(NORMAL), BorderStyle::Solid => Some(NORMAL),
BorderStyle::DOUBLE => Some(DOUBLE), BorderStyle::Double => Some(DOUBLE),
BorderStyle::GROOVE => Some(NORMAL), BorderStyle::Groove => Some(NORMAL),
BorderStyle::RIDGE => Some(NORMAL), BorderStyle::Ridge => Some(NORMAL),
BorderStyle::INSET => Some(NORMAL), BorderStyle::Inset => Some(NORMAL),
BorderStyle::OUTSET => Some(NORMAL), BorderStyle::Outset => Some(NORMAL),
BorderStyle::HIDDEN => None, BorderStyle::Hidden => None,
BorderStyle::NONE => None, BorderStyle::None => None,
} }
} }
} }
@ -334,16 +329,16 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifier) {
fn apply_border(name: &str, value: &str, style: &mut StyleModifier) { fn apply_border(name: &str, value: &str, style: &mut StyleModifier) {
fn parse_border_style(v: &str) -> BorderStyle { fn parse_border_style(v: &str) -> BorderStyle {
match v { match v {
"dotted" => BorderStyle::DOTTED, "dotted" => BorderStyle::Dotted,
"dashed" => BorderStyle::DASHED, "dashed" => BorderStyle::Dashed,
"solid" => BorderStyle::SOLID, "solid" => BorderStyle::Solid,
"double" => BorderStyle::DOUBLE, "double" => BorderStyle::Double,
"groove" => BorderStyle::GROOVE, "groove" => BorderStyle::Groove,
"ridge" => BorderStyle::RIDGE, "ridge" => BorderStyle::Ridge,
"inset" => BorderStyle::INSET, "inset" => BorderStyle::Inset,
"outset" => BorderStyle::OUTSET, "outset" => BorderStyle::Outset,
"none" => BorderStyle::NONE, "none" => BorderStyle::None,
"hidden" => BorderStyle::HIDDEN, "hidden" => BorderStyle::Hidden,
_ => todo!(), _ => todo!(),
} }
} }
@ -505,7 +500,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifier) {
.zip(style.modifier.borders.slice().iter_mut()) .zip(style.modifier.borders.slice().iter_mut())
{ {
if let Some(w) = parse_value(v) { if let Some(w) = parse_value(v) {
width.width = w.into(); width.width = w;
} }
} }
} }

View file

@ -23,6 +23,9 @@ pub use dioxus_desktop as desktop;
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
pub use dioxus_tui as tui; pub use dioxus_tui as tui;
#[cfg(feature = "native-core")]
pub use dioxus_native_core as native_core;
#[cfg(feature = "fermi")] #[cfg(feature = "fermi")]
pub use fermi; pub use fermi;