Implement new mutations with native core (#630)

* work on seperating tree struture from realdom

* intial update to new mutations

* handle height

* update to taffy 0.2

* add as_varient functions to OwnedAttributeValue

* make get node parent mut optionally return a parent

* work on upward pass

* add more code for parrellel passes

* make resolve passes public

* more work on parallel passes

* fix deadlock and add more tests

* move height into the tree instead of the realdom

* make passes exicute in parallel instead of executing invidual passes in parellel

* fix some warnings

* add up dependant test

* clean up examples

* work on intigrating state with passes

* update to new mutations

* work on implementing macro

* make the macro compile

* more progress on macro

* mark cloned nodes as dirty

* update persistant_iterator utility

* fix mask generation

* update tui with new mutations

* more progress updating tui

* some basic tui examples working

* don't push template nodes onto the stack

* update hover example

* update benchmark

* update more examples

* fix root node layout

* ignore out of bounds renders

* update color picker example

* update all events example

* update remaining examples

* update tests

* tests passing

* move persistant iterator test

* update examples

* fix gaps in layout

* fix formatting

* fix memory leak
This commit is contained in:
Demonthos authored and GitHub committed 2022-12-06 15:38:04 -08:00
1 parent b9aad5e8f4
commit 67a6fa9eb8
38 files changed
+3405 -2417

No files matched your search

+15 -14
View File
@@ -1,6 +1,7 @@
use crate::{node::PreventDefault, Dom};
use crate::{node::PreventDefault, TuiDom};
use dioxus_native_core::{
tree::TreeView,
utils::{ElementProduced, PersistantElementIter},
RealNodeId,
};
@@ -10,7 +11,6 @@ use std::{cmp::Ordering, num::NonZeroU16};
use dioxus_native_core::{
node_ref::{AttributeMask, NodeMask, NodeView},
real_dom::NodeType,
state::NodeDepState,
};
@@ -64,7 +64,8 @@ pub(crate) struct Focus {
pub level: FocusLevel,
}
impl NodeDepState<()> for Focus {
impl NodeDepState for Focus {
type DepState = ();
type Ctx = ();
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
@@ -77,7 +78,7 @@ impl NodeDepState<()> for Focus {
{
if let Some(index) = a
.value
.as_int32()
.as_int()
.or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
{
match index.cmp(&0) {
@@ -126,7 +127,7 @@ pub(crate) struct FocusState {
impl FocusState {
/// Returns true if the focus has changed.
pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool {
pub fn progress(&mut self, rdom: &mut TuiDom, forward: bool) -> bool {
if let Some(last) = self.last_focused_id {
if rdom[last].state.prevent_default == PreventDefault::KeyDown {
return false;
@@ -234,10 +235,10 @@ impl FocusState {
false
}
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
fn remove_children(
to_prune: &mut [&mut Option<RealNodeId>],
rdom: &Dom,
rdom: &TuiDom,
removed: RealNodeId,
) {
for opt in to_prune.iter_mut() {
@@ -247,8 +248,8 @@ impl FocusState {
}
}
}
if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type {
for child in children {
if let Some(children) = &rdom.children_ids(removed) {
for child in *children {
remove_children(to_prune, rdom, *child);
}
}
@@ -258,22 +259,22 @@ impl FocusState {
}
for m in &mutations.edits {
match m {
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
dioxus_core::Mutation::ReplaceWith { id, .. } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
rdom.resolve_maybe_id(*root),
rdom.element_to_node_id(*id),
),
dioxus_core::DomEdit::Remove { root } => remove_children(
dioxus_core::Mutation::Remove { id } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
rdom.resolve_maybe_id(*root),
rdom.element_to_node_id(*id),
),
_ => (),
}
}
}
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: RealNodeId) {
pub(crate) fn set_focus(&mut self, rdom: &mut TuiDom, id: RealNodeId) {
if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false;
}
+97 -82
View File
@@ -2,7 +2,8 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus_core::*;
use dioxus_native_core::RealNodeId;
use dioxus_native_core::tree::TreeView;
use dioxus_native_core::NodeId;
use rustc_hash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
@@ -12,19 +13,25 @@ use dioxus_html::geometry::{
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
use dioxus_html::{event_bubbles, on::*};
use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData};
use std::{
any::Any,
cell::{RefCell, RefMut},
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use taffy::geometry::{Point, Size};
use taffy::{prelude::Layout, Taffy};
use crate::FocusState;
use crate::{Dom, Node};
use crate::{layout_to_screen_space, FocusState};
use crate::{TuiDom, TuiNode};
pub(crate) struct Event {
pub id: ElementId,
pub name: &'static str,
pub data: Rc<dyn Any>,
pub bubbles: bool,
}
// a wrapper around the input state for easier access
// todo: fix loop
@@ -71,12 +78,12 @@ enum EventData {
Keyboard(KeyboardData),
}
impl EventData {
fn into_any(self) -> Arc<dyn Any + Send + Sync> {
fn into_any(self) -> Rc<dyn Any + Send + Sync> {
match self {
Self::Mouse(m) => Arc::new(m),
Self::Wheel(w) => Arc::new(w),
Self::Screen(s) => Arc::new(s),
Self::Keyboard(k) => Arc::new(k),
Self::Mouse(m) => Rc::new(m),
Self::Wheel(w) => Rc::new(w),
Self::Screen(s) => Rc::new(s),
Self::Keyboard(k) => Rc::new(k),
}
}
}
@@ -164,9 +171,9 @@ impl InnerInputState {
fn update(
&mut self,
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut Dom,
dom: &mut TuiDom,
) {
let previous_mouse = self.mouse.clone();
@@ -192,33 +199,33 @@ impl InnerInputState {
if old_focus != self.focus_state.last_focused_id {
// elements with listeners will always have a element id
if let Some(RealNodeId::ElementId(id)) = self.focus_state.last_focused_id {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "focus",
element: Some(id),
data: Arc::new(FocusData {}),
bubbles: event_bubbles("focus"),
});
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "focusin",
element: Some(id),
data: Arc::new(FocusData {}),
bubbles: event_bubbles("focusin"),
});
if let Some(id) = self.focus_state.last_focused_id {
let element = dom.tree.get(id).unwrap();
if let Some(id) = element.node_data.element_id {
resolved_events.push(Event {
name: "focus",
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focus"),
});
resolved_events.push(Event {
name: "focusin",
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focusin"),
});
}
}
if let Some(RealNodeId::ElementId(id)) = old_focus {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "focusout",
element: Some(id),
data: Arc::new(FocusData {}),
bubbles: event_bubbles("focusout"),
});
if let Some(id) = old_focus {
let element = dom.tree.get(id).unwrap();
if let Some(id) = element.node_data.element_id {
resolved_events.push(Event {
name: "focusout",
id,
data: Rc::new(FocusData {}),
bubbles: event_bubbles("focusout"),
});
}
}
}
@@ -230,13 +237,21 @@ impl InnerInputState {
fn resolve_mouse_events(
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<UserEvent>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut Dom,
dom: &mut TuiDom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
let Point { x, y } = layout.location;
let (x, y) = (
layout_to_screen_space(x).round(),
layout_to_screen_space(y).round(),
);
let Size { width, height } = layout.size;
let (width, height) = (
layout_to_screen_space(width).round(),
layout_to_screen_space(height).round(),
);
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
layout_rect.contains(point.cast())
@@ -244,28 +259,29 @@ impl InnerInputState {
fn try_create_event(
name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<RealNodeId>,
resolved_events: &mut Vec<UserEvent>,
node: &Node,
dom: &Dom,
data: Rc<dyn Any>,
will_bubble: &mut FxHashSet<NodeId>,
resolved_events: &mut Vec<Event>,
node: &TuiNode,
dom: &TuiDom,
) {
// only trigger event if the event was not triggered already by a child
let id = node.mounted_id();
let id = node.node_data.node_id;
if will_bubble.insert(id) {
let mut parent = node.node_data.parent;
while let Some(parent_id) = parent {
let mut parent = dom.parent(id);
while let Some(current_parent) = parent {
let parent_id = current_parent.node_data.node_id;
will_bubble.insert(parent_id);
parent = dom[parent_id].node_data.parent;
parent = dom.parent(parent_id);
}
if let Some(id) = node.mounted_id() {
resolved_events.push(Event {
name,
id,
data,
bubbles: event_bubbles(name),
})
}
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(id.as_element_id()),
data,
bubbles: event_bubbles(name),
})
}
}
@@ -326,7 +342,7 @@ impl InnerInputState {
if currently_contains && previously_contained {
try_create_event(
"mousemove",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -350,7 +366,7 @@ impl InnerInputState {
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
Arc::new(mouse_data.clone()),
Rc::new(mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
@@ -373,7 +389,7 @@ impl InnerInputState {
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -393,7 +409,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"mousedown",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -414,7 +430,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"mouseup",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -436,7 +452,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"click",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -459,7 +475,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"contextmenu",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -483,7 +499,7 @@ impl InnerInputState {
if currently_contains {
try_create_event(
"wheel",
Arc::new(w.clone()),
Rc::new(w.clone()),
&mut will_bubble,
resolved_events,
node,
@@ -508,7 +524,7 @@ impl InnerInputState {
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -531,7 +547,7 @@ impl InnerInputState {
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -549,7 +565,7 @@ impl InnerInputState {
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && node.state.focus.level.focusable() {
focus_id = Some(node.mounted_id());
focus_id = Some(node.node_data.node_id);
}
});
if let Some(id) = focus_id {
@@ -564,12 +580,11 @@ impl InnerInputState {
// }
}
fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout {
let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
let mut current = node;
while let Some(parent_id) = current.node_data.parent {
let parent = &dom[parent_id];
while let Some(parent) = dom.parent(current.node_data.node_id) {
current = parent;
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
node_layout.location.x += parent_layout.location.x;
@@ -614,11 +629,11 @@ impl RinkInputHandler {
)
}
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
self.state.borrow_mut().focus_state.prune(mutations, rdom);
}
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> Vec<Event> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
@@ -651,7 +666,7 @@ impl RinkInputHandler {
})
.map(|evt| (evt.0, evt.1.into_any()));
let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
let mut hm: FxHashMap<&'static str, Vec<Rc<dyn Any + Send + Sync>>> = FxHashMap::default();
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
v.push(data);
@@ -663,14 +678,14 @@ impl RinkInputHandler {
for node in dom.get_listening_sorted(event) {
for data in &datas {
if node.state.focused {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: event,
element: Some(node.mounted_id().as_element_id()),
data: data.clone(),
bubbles: event_bubbles(event),
});
if let Some(id) = node.mounted_id() {
resolved_events.push(Event {
name: event,
id,
data: data.clone(),
bubbles: event_bubbles(event),
});
}
}
}
}
+14 -21
View File
@@ -1,15 +1,14 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use dioxus_core::*;
use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node::OwnedAttributeView;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::real_dom::OwnedAttributeView;
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core::RealNodeId;
use dioxus_native_core_macro::sorted_str_slice;
use taffy::prelude::*;
use crate::screen_to_layout_space;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum PossiblyUninitalized<T> {
Uninitalized,
@@ -19,7 +18,7 @@ impl<T> PossiblyUninitalized<T> {
pub fn unwrap(self) -> T {
match self {
Self::Initialized(i) => i,
_ => panic!(),
_ => panic!("uninitalized"),
}
}
pub fn ok(self) -> Option<T> {
@@ -42,8 +41,8 @@ pub(crate) struct TaffyLayout {
}
impl ChildDepState for TaffyLayout {
type Ctx = Rc<RefCell<Taffy>>;
type DepState = Self;
type Ctx = Arc<Mutex<Taffy>>;
type DepState = (Self,);
// use tag to force this to be called when a node is built
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS))
@@ -54,14 +53,14 @@ impl ChildDepState for TaffyLayout {
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
children: impl Iterator<Item = (&'a Self,)>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
let mut changed = false;
let mut taffy = ctx.borrow_mut();
let mut taffy = ctx.lock().expect("poisoned taffy");
let mut style = Style::default();
if let Some(text) = node.text() {
let char_len = text.chars().count();
@@ -69,10 +68,10 @@ impl ChildDepState for TaffyLayout {
style = Style {
size: Size {
// characters are 1 point tall
height: Dimension::Points(1.0),
height: Dimension::Points(screen_to_layout_space(1)),
// text is as long as it is declared
width: Dimension::Points(char_len as f32),
width: Dimension::Points(screen_to_layout_space(char_len as u16)),
},
..Default::default()
};
@@ -81,7 +80,7 @@ impl ChildDepState for TaffyLayout {
taffy.set_style(n, style).unwrap();
}
} else {
self.node = PossiblyUninitalized::Initialized(taffy.new_node(style, &[]).unwrap());
self.node = PossiblyUninitalized::Initialized(taffy.new_leaf(style).unwrap());
changed = true;
}
} else {
@@ -100,15 +99,9 @@ impl ChildDepState for TaffyLayout {
}
}
// the root node fills the entire area
if node.id() == RealNodeId::ElementId(ElementId(0)) {
apply_layout_attributes("width", "100%", &mut style);
apply_layout_attributes("height", "100%", &mut style);
}
// Set all direct nodes as our children
let mut child_layout = vec![];
for l in children {
for (l,) in children {
child_layout.push(l.node.unwrap());
}
@@ -121,7 +114,7 @@ impl ChildDepState for TaffyLayout {
}
} else {
self.node = PossiblyUninitalized::Initialized(
taffy.new_node(style, &child_layout).unwrap(),
taffy.new_with_children(style, &child_layout).unwrap(),
);
changed = true;
}
+62 -53
View File
@@ -1,24 +1,26 @@
use anyhow::Result;
use anymap::AnyMap;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use dioxus_native_core::{real_dom::RealDom, RealNodeId};
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap};
use focus::FocusState;
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use futures_channel::mpsc::unbounded;
use query::Query;
use std::cell::RefCell;
use std::rc::Rc;
use std::{
cell::RefCell,
sync::{Arc, Mutex},
};
use std::{io, time::Duration};
use taffy::Taffy;
pub use taffy::{geometry::Point, prelude::Size};
pub use taffy::{geometry::Point, prelude::*};
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod config;
@@ -36,6 +38,15 @@ pub use config::*;
pub use hooks::*;
pub(crate) use node::*;
// the layout space has a multiplier of 10 to minimize rounding errors
pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
screen as f32 * 10.0
}
pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
layout / 10.0
}
#[derive(Clone)]
pub struct TuiContext {
tx: UnboundedSender<InputEvent>,
@@ -80,10 +91,10 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let cx = dom.base_scope();
let rdom = Rc::new(RefCell::new(RealDom::new()));
let taffy = Rc::new(RefCell::new(Taffy::new()));
cx.provide_root_context(state);
cx.provide_root_context(TuiContext { tx: event_tx_clone });
cx.provide_root_context(Query {
let taffy = Arc::new(Mutex::new(Taffy::new()));
cx.provide_context(state);
cx.provide_context(TuiContext { tx: event_tx_clone });
cx.provide_context(Query {
rdom: rdom.clone(),
stretch: taffy.clone(),
});
@@ -91,8 +102,8 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
{
let mut rdom = rdom.borrow_mut();
let mutations = dom.rebuild();
let to_update = rdom.apply_mutations(vec![mutations]);
let mut any_map = AnyMap::new();
let (to_update, _) = rdom.apply_mutations(mutations);
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(to_update, any_map);
}
@@ -114,8 +125,8 @@ fn render_vdom(
mut event_reciever: UnboundedReceiver<InputEvent>,
handler: RinkInputHandler,
cfg: Config,
rdom: Rc<RefCell<Dom>>,
taffy: Rc<RefCell<Taffy>>,
rdom: Rc<RefCell<TuiDom>>,
taffy: Arc<Mutex<Taffy>>,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> {
tokio::runtime::Builder::new_current_thread()
@@ -133,10 +144,8 @@ fn render_vdom(
terminal.clear().unwrap();
}
let mut to_rerender: rustc_hash::FxHashSet<RealNodeId> =
vec![RealNodeId::ElementId(ElementId(0))]
.into_iter()
.collect();
let mut to_rerender = FxDashSet::default();
to_rerender.insert(NodeId(0));
let mut updated = true;
loop {
@@ -145,43 +154,40 @@ fn render_vdom(
-> wait for changes
-> resolve events
-> lazily update the layout and style based on nodes changed
use simd to compare lines for diffing?
todo: lazy re-rendering
*/
if !to_rerender.is_empty() || updated {
updated = false;
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
let width = dims.width;
let height = dims.height;
let root_node = rdom[ElementId(0)].state.layout.node.unwrap();
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) {
let width = screen_to_layout_space(dims.width);
let height = screen_to_layout_space(dims.height);
let root_node = rdom[NodeId(0)].state.layout.node.unwrap();
taffy
.compute_layout(
root_node,
Size {
width: taffy::prelude::Number::Defined((width - 1) as f32),
height: taffy::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
// the root node fills the entire area
let mut style = *taffy.style(root_node).unwrap();
style.size = Size {
width: Dimension::Points(width),
height: Dimension::Points(height),
};
taffy.set_style(root_node, style).unwrap();
let size = Size {
width: AvailableSpace::Definite(width),
height: AvailableSpace::Definite(height),
};
taffy.compute_layout(root_node, size).unwrap();
}
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
let rdom = rdom.borrow();
let mut taffy = taffy.lock().expect("taffy lock poisoned");
// size is guaranteed to not change when rendering
resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
let root = &rdom[ElementId(0)];
render::render_vnode(
frame,
&taffy.borrow(),
&rdom,
root,
cfg,
Point::zero(),
);
resize(frame.size(), &mut *taffy, &rdom);
let root = &rdom[NodeId(0)];
render::render_vnode(frame, &*taffy, &rdom, root, cfg, Point::ZERO);
})?;
} else {
let rdom = rdom.borrow();
@@ -189,10 +195,10 @@ fn render_vdom(
Rect {
x: 0,
y: 0,
width: 100,
height: 100,
width: 1000,
height: 1000,
},
&mut taffy.borrow_mut(),
&mut taffy.lock().expect("taffy lock poisoned"),
&rdom,
);
}
@@ -234,25 +240,28 @@ fn render_vdom(
{
let evts = {
let mut rdom = rdom.borrow_mut();
handler.get_events(&taffy.borrow(), &mut rdom)
handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom)
};
{
updated |= handler.state().focus_state.clean();
}
for e in evts {
vdom.handle_message(SchedulerMsg::Event(e));
vdom.handle_event(e.name, e.data, e.id, e.bubbles)
}
let mut rdom = rdom.borrow_mut();
let mutations = vdom.work_with_deadline(|| false);
for m in &mutations {
handler.prune(m, &rdom);
}
let mutations = vdom.render_immediate();
handler.prune(&mutations, &rdom);
// updates the dom's nodes
let to_update = rdom.apply_mutations(mutations);
let (to_update, dirty) = rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = AnyMap::new();
let mut any_map = SendAnyMap::new();
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(to_update, any_map);
for (id, mask) in dirty {
if mask.overlaps(&NodeMask::new().with_text()) {
to_rerender.insert(id);
}
}
}
}
+7 -5
View File
@@ -4,12 +4,12 @@ use crate::style_attributes::StyleModifier;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::{sorted_str_slice, State};
pub(crate) type Dom = RealDom<NodeState>;
pub(crate) type Node = dioxus_native_core::real_dom::Node<NodeState>;
pub(crate) type TuiDom = RealDom<NodeState>;
pub(crate) type TuiNode = dioxus_native_core::node::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
pub(crate) struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
#[child_dep_state(layout, Mutex<Stretch>)]
pub layout: TaffyLayout,
#[parent_dep_state(style)]
pub style: StyleModifier,
@@ -45,7 +45,8 @@ impl Default for PreventDefault {
}
}
impl NodeDepState<()> for PreventDefault {
impl NodeDepState for PreventDefault {
type DepState = ();
type Ctx = ();
const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
@@ -53,7 +54,8 @@ impl NodeDepState<()> for PreventDefault {
dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([
"dioxus-prevent-default"
])),
);
)
.with_listeners();
fn reduce(
&mut self,
+27 -13
View File
@@ -1,6 +1,7 @@
use std::{
cell::{Ref, RefCell},
rc::Rc,
sync::{Arc, Mutex, MutexGuard},
};
use dioxus_core::ElementId;
@@ -10,7 +11,7 @@ use taffy::{
Taffy,
};
use crate::Dom;
use crate::{layout_to_screen_space, TuiDom};
/// Allows querying the layout of nodes after rendering. It will only provide a correct value after a node is rendered.
/// Provided as a root context for all tui applictions.
@@ -45,41 +46,54 @@ use crate::Dom;
/// ```
#[derive(Clone)]
pub struct Query {
pub(crate) rdom: Rc<RefCell<Dom>>,
pub(crate) stretch: Rc<RefCell<Taffy>>,
pub(crate) rdom: Rc<RefCell<TuiDom>>,
pub(crate) stretch: Arc<Mutex<Taffy>>,
}
impl Query {
pub fn get(&self, id: ElementId) -> ElementRef {
ElementRef::new(self.rdom.borrow(), self.stretch.borrow(), id)
ElementRef::new(
self.rdom.borrow(),
self.stretch.lock().expect("taffy lock poisoned"),
id,
)
}
}
pub struct ElementRef<'a> {
inner: Ref<'a, Dom>,
stretch: Ref<'a, Taffy>,
inner: Ref<'a, TuiDom>,
stretch: MutexGuard<'a, Taffy>,
id: ElementId,
}
impl<'a> ElementRef<'a> {
fn new(inner: Ref<'a, Dom>, stretch: Ref<'a, Taffy>, id: ElementId) -> Self {
fn new(inner: Ref<'a, TuiDom>, stretch: MutexGuard<'a, Taffy>, id: ElementId) -> Self {
Self { inner, stretch, id }
}
pub fn size(&self) -> Option<Size<u32>> {
self.layout().map(|l| l.size.map(|v| v as u32))
self.layout().map(|l| l.size.map(|v| v.round() as u32))
}
pub fn pos(&self) -> Option<Point<u32>> {
self.layout().map(|l| Point {
x: l.location.x as u32,
y: l.location.y as u32,
x: l.location.x.round() as u32,
y: l.location.y.round() as u32,
})
}
pub fn layout(&self) -> Option<&Layout> {
self.stretch
pub fn layout(&self) -> Option<Layout> {
let layout = self
.stretch
.layout(self.inner[self.id].state.layout.node.ok()?)
.ok()
.ok();
layout.map(|layout| Layout {
order: layout.order,
size: layout.size.map(|v| layout_to_screen_space(v)),
location: Point {
x: layout_to_screen_space(layout.location.x),
y: layout_to_screen_space(layout.location.y),
},
})
}
}
+17 -11
View File
@@ -1,3 +1,4 @@
use dioxus_native_core::tree::TreeView;
use std::io::Stdout;
use taffy::{
geometry::Point,
@@ -7,10 +8,11 @@ use taffy::{
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
use crate::{
layout_to_screen_space,
style::{RinkColor, RinkStyle},
style_attributes::{BorderEdge, BorderStyle},
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
Config, Dom, Node,
Config, TuiDom, TuiNode,
};
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
@@ -18,12 +20,12 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub(crate) fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Taffy,
rdom: &Dom,
node: &Node,
rdom: &TuiDom,
node: &TuiNode,
cfg: Config,
parent_location: Point<f32>,
) {
use dioxus_native_core::real_dom::NodeType;
use dioxus_native_core::node::NodeType;
if let NodeType::Placeholder = &node.node_data.node_type {
return;
@@ -35,8 +37,12 @@ pub(crate) fn render_vnode(
location.x += parent_location.x;
location.y += parent_location.y;
let Point { x, y } = location;
let Size { width, height } = size;
let Point { x: fx, y: fy } = location;
let x = layout_to_screen_space(fx).round() as u16;
let y = layout_to_screen_space(fy).round() as u16;
let Size { width, height } = *size;
let width = layout_to_screen_space(fx + width).round() as u16 + x;
let height = layout_to_screen_space(fy + height).round() as u16 - y;
match &node.node_data.node_type {
NodeType::Text { text } => {
@@ -61,22 +67,22 @@ pub(crate) fn render_vnode(
text,
style: node.state.style.core,
};
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
let area = Rect::new(x, y, width, height);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
frame.render_widget(WidgetWithContext::new(label, cfg), area);
}
}
NodeType::Element { children, .. } => {
let area = Rect::new(x as u16, y as u16, *width as u16, *height as u16);
NodeType::Element { .. } => {
let area = Rect::new(x, y, width, height);
// the renderer will panic if a node is rendered out of range even if the size is zero
if area.width > 0 && area.height > 0 {
frame.render_widget(WidgetWithContext::new(node, cfg), area);
}
for c in children {
for c in rdom.children_ids(node.node_data.node_id).unwrap() {
render_vnode(frame, layout, rdom, &rdom[*c], cfg, location);
}
}
@@ -84,7 +90,7 @@ pub(crate) fn render_vnode(
}
}
impl RinkWidget for &Node {
impl RinkWidget for &TuiNode {
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
use tui::symbols::line::*;
+4 -4
View File
@@ -31,8 +31,8 @@
use dioxus_native_core::{
layout_attributes::parse_value,
node::OwnedAttributeView,
node_ref::{AttributeMask, NodeMask, NodeView},
real_dom::OwnedAttributeView,
state::ParentDepState,
};
use dioxus_native_core_macro::sorted_str_slice;
@@ -48,12 +48,12 @@ pub struct StyleModifier {
impl ParentDepState for StyleModifier {
type Ctx = ();
type DepState = Self;
type DepState = (Self,);
// todo: seperate each attribute into it's own class
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element();
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
fn reduce(&mut self, node: NodeView, parent: Option<(&Self,)>, _: &Self::Ctx) -> bool {
let mut new = StyleModifier::default();
if parent.is_some() {
new.core.fg = None;
@@ -91,7 +91,7 @@ impl ParentDepState for StyleModifier {
}
// keep the text styling from the parent element
if let Some(parent) = parent {
if let Some((parent,)) = parent {
let mut new_style = new.core.merge(parent.core);
new_style.bg = new.core.bg;
new.core = new_style;
+3 -2
View File
@@ -22,8 +22,9 @@ impl<'a> RinkBuffer<'a> {
pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
let area = self.buf.area();
if x < area.x || x > area.width || y < area.y || y > area.height {
panic!("({x}, {y}) is not in {area:?}");
if x < area.x || x >= area.width + area.x || y < area.y || y >= area.height + area.y {
// panic!("({x}, {y}) is not in {area:?}");
return;
}
let mut cell = self.buf.get_mut(x, y);
cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));