Merge branch 'master' of https://github.com/DioxusLabs/dioxus into rusty-events

 Conflicts:
	packages/html/src/events.rs
	packages/tui/src/hooks.rs
This commit is contained in:
Reinis Mazeiks committed 2022-06-28 21:09:20 +03:00
commit f3fcec2bdf
87 files changed
+6230 -868

No files matched your search

+282
View File
@@ -0,0 +1,282 @@
use crate::{node::PreventDefault, Dom};
use dioxus_core::ElementId;
use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
use dioxus_native_core_macro::sorted_str_slice;
use std::{cmp::Ordering, num::NonZeroU16};
use dioxus_native_core::{
node_ref::{AttributeMask, NodeMask, NodeView},
real_dom::NodeType,
state::NodeDepState,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum FocusLevel {
Unfocusable,
Focusable,
Ordered(std::num::NonZeroU16),
}
impl FocusLevel {
pub fn focusable(&self) -> bool {
match self {
FocusLevel::Unfocusable => false,
FocusLevel::Focusable => true,
FocusLevel::Ordered(_) => true,
}
}
}
impl PartialOrd for FocusLevel {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(FocusLevel::Unfocusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Equal),
(FocusLevel::Unfocusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
(FocusLevel::Unfocusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Less),
(FocusLevel::Focusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
(FocusLevel::Focusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Equal),
(FocusLevel::Focusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Greater),
(FocusLevel::Ordered(_), FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
(FocusLevel::Ordered(_), FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
(FocusLevel::Ordered(a), FocusLevel::Ordered(b)) => a.partial_cmp(b),
}
}
}
impl Ord for FocusLevel {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl Default for FocusLevel {
fn default() -> Self {
FocusLevel::Unfocusable
}
}
#[derive(Clone, PartialEq, Debug, Default)]
pub(crate) struct Focus {
pub level: FocusLevel,
}
impl NodeDepState for Focus {
type Ctx = ();
type DepState = ();
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
let new = Focus {
level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
if let Some(index) = a
.value
.as_int32()
.or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
{
match index.cmp(&0) {
Ordering::Less => FocusLevel::Unfocusable,
Ordering::Equal => FocusLevel::Focusable,
Ordering::Greater => {
FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap())
}
}
} else {
FocusLevel::Unfocusable
}
} else if node
.listeners()
.iter()
.any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
{
FocusLevel::Focusable
} else {
FocusLevel::Unfocusable
},
};
if *self != new {
*self = new;
true
} else {
false
}
}
}
const FOCUS_EVENTS: &[&str] = &sorted_str_slice!(["keydown", "keypress", "keyup"]);
const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
#[derive(Default)]
pub(crate) struct FocusState {
pub(crate) focus_iter: PersistantElementIter,
pub(crate) last_focused_id: Option<ElementId>,
pub(crate) focus_level: FocusLevel,
pub(crate) dirty: bool,
}
impl FocusState {
/// Returns true if the focus has changed.
pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool {
if let Some(last) = self.last_focused_id {
if rdom[last].state.prevent_default == PreventDefault::KeyDown {
return false;
}
}
// the id that started focused to track when a loop has happened
let mut loop_marker_id = self.last_focused_id;
let focus_level = &mut self.focus_level;
let mut next_focus = None;
loop {
let new = if forward {
self.focus_iter.next(rdom)
} else {
self.focus_iter.prev(rdom)
};
let new_id = new.id();
if let ElementProduced::Looped(_) = new {
let mut closest_level = None;
if forward {
// find the closest focusable element after the current level
rdom.traverse_depth_first(|n| {
let node_level = n.state.focus.level;
if node_level != *focus_level
&& node_level.focusable()
&& node_level > *focus_level
{
if let Some(level) = &mut closest_level {
if node_level < *level {
*level = node_level;
}
} else {
closest_level = Some(node_level);
}
}
});
} else {
// find the closest focusable element before the current level
rdom.traverse_depth_first(|n| {
let node_level = n.state.focus.level;
if node_level != *focus_level
&& node_level.focusable()
&& node_level < *focus_level
{
if let Some(level) = &mut closest_level {
if node_level > *level {
*level = node_level;
}
} else {
closest_level = Some(node_level);
}
}
});
}
// extend the loop_marker_id to allow for another pass
loop_marker_id = None;
if let Some(level) = closest_level {
*focus_level = level;
} else if forward {
*focus_level = FocusLevel::Unfocusable;
} else {
*focus_level = FocusLevel::Focusable;
}
}
// once we have looked at all the elements exit the loop
if let Some(last) = loop_marker_id {
if new_id == last {
break;
}
} else {
loop_marker_id = Some(new_id);
}
let current_level = rdom[new_id].state.focus.level;
let after_previous_focused = if forward {
current_level >= *focus_level
} else {
current_level <= *focus_level
};
if after_previous_focused && current_level.focusable() && current_level == *focus_level
{
next_focus = Some(new_id);
break;
}
}
if let Some(id) = next_focus {
rdom[id].state.focused = true;
if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false;
}
// reset the position to the currently focused element
while self.focus_iter.next(rdom).id() != id {}
self.dirty = true;
return true;
}
false
}
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
fn remove_children(
to_prune: &mut [&mut Option<ElementId>],
rdom: &Dom,
removed: ElementId,
) {
for opt in to_prune.iter_mut() {
if let Some(id) = opt {
if *id == removed {
**opt = None;
}
}
}
if let NodeType::Element { children, .. } = &rdom[removed].node_type {
for child in children {
remove_children(to_prune, rdom, *child);
}
}
}
if self.focus_iter.prune(mutations, rdom) {
self.dirty = true;
}
for m in &mutations.edits {
match m {
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
ElementId(*root as usize),
),
dioxus_core::DomEdit::Remove { root } => remove_children(
&mut [&mut self.last_focused_id],
rdom,
ElementId(*root as usize),
),
_ => (),
}
}
}
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) {
if let Some(old) = self.last_focused_id.replace(id) {
rdom[old].state.focused = false;
}
let state = &mut rdom[id].state;
state.focused = true;
self.focus_level = state.focus.level;
// reset the position to the currently focused element
while self.focus_iter.next(rdom).id() != id {}
self.dirty = true;
}
pub(crate) fn clean(&mut self) -> bool {
let old = self.dirty;
self.dirty = false;
old
}
}
+140 -55
View File
@@ -11,17 +11,18 @@ 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::on::*;
use dioxus_html::{event_bubbles, on::*, KeyCode};
use std::{
any::Any,
cell::RefCell,
cell::{RefCell, RefMut},
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use stretch2::geometry::{Point, Size};
use stretch2::{prelude::Layout, Stretch};
use taffy::geometry::{Point, Size};
use taffy::{prelude::Layout, Taffy};
use crate::FocusState;
use crate::{Dom, Node};
// a wrapper around the input state for easier access
@@ -38,22 +39,22 @@ use crate::{Dom, Node};
// pub fn mouse(&self) -> Option<MouseData> {
// let data = (**self.0).borrow();
// data.mouse.as_ref().map(|m| m.clone())
// mouse.as_ref().map(|m| m.clone())
// }
// pub fn wheel(&self) -> Option<WheelData> {
// let data = (**self.0).borrow();
// data.wheel.as_ref().map(|w| w.clone())
// wheel.as_ref().map(|w| w.clone())
// }
// pub fn screen(&self) -> Option<(u16, u16)> {
// let data = (**self.0).borrow();
// data.screen.as_ref().map(|m| m.clone())
// screen.as_ref().map(|m| m.clone())
// }
// pub fn last_key_pressed(&self) -> Option<KeyboardData> {
// let data = (**self.0).borrow();
// data.last_key_pressed
// last_key_pressed
// .as_ref()
// .map(|k| &k.0.clone())
// }
@@ -86,6 +87,7 @@ pub struct InnerInputState {
wheel: Option<WheelData>,
last_key_pressed: Option<(KeyboardData, Instant)>,
screen: Option<(u16, u16)>,
pub(crate) focus_state: FocusState,
// subscribers: Vec<Rc<dyn Fn() + 'static>>,
}
@@ -97,6 +99,7 @@ impl InnerInputState {
last_key_pressed: None,
screen: None,
// subscribers: Vec::new(),
focus_state: FocusState::default(),
}
}
@@ -157,31 +160,72 @@ impl InnerInputState {
fn update(
&mut self,
evts: &mut [EventCore],
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layout: &Taffy,
dom: &mut Dom,
) {
let previous_mouse = self.mouse.clone();
self.wheel = None;
let old_focus = self.focus_state.last_focused_id;
evts.retain(|e| match &e.1 {
EventData::Keyboard(k) => match k.key_code {
KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key),
_ => true,
},
_ => true,
});
for e in evts.iter_mut() {
self.apply_event(e);
}
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
if old_focus != self.focus_state.last_focused_id {
if let Some(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) = 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"),
});
}
}
// for s in &self.subscribers {
// s();
// }
}
fn resolve_mouse_events(
&self,
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layout: &Taffy,
dom: &mut Dom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
@@ -213,6 +257,7 @@ impl InnerInputState {
name,
element: Some(node.id),
data,
bubbles: event_bubbles(name),
})
}
}
@@ -265,16 +310,16 @@ impl InnerInputState {
if old_pos != Some(new_pos) {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousemove") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && previously_contained {
try_create_event(
"mousemove",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -289,11 +334,11 @@ impl InnerInputState {
// mouseenter
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseenter") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
@@ -312,16 +357,16 @@ impl InnerInputState {
// mouseover
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseover") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -335,13 +380,13 @@ impl InnerInputState {
if was_pressed {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousedown") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"mousedown",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -356,13 +401,13 @@ impl InnerInputState {
if was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseup") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"mouseup",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -378,13 +423,13 @@ impl InnerInputState {
if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("click") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"click",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -401,13 +446,13 @@ impl InnerInputState {
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("contextmenu") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"contextmenu",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -424,9 +469,9 @@ impl InnerInputState {
if was_scrolled {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("wheel") {
let node_layout =
layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
@@ -447,16 +492,16 @@ impl InnerInputState {
// mouseleave
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseleave") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -470,16 +515,16 @@ impl InnerInputState {
// mouseout
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseout") {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, new_pos);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
Arc::new(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
@@ -488,6 +533,22 @@ impl InnerInputState {
}
}
}
// update focus
if was_released {
let mut focus_id = None;
dom.traverse_depth_first(|node| {
let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && node.state.focus.level.focusable() {
focus_id = Some(node.id);
}
});
if let Some(id) = focus_id {
self.focus_state.set_focus(dom, id);
}
}
}
}
@@ -496,6 +557,20 @@ impl InnerInputState {
// }
}
fn get_abs_layout(node: &Node, dom: &Dom, 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.parent {
let parent = &dom[parent_id];
current = parent;
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
node_layout.location.x += parent_layout.location.x;
node_layout.location.y += parent_layout.location.y;
}
node_layout
}
pub struct RinkInputHandler {
state: Rc<RefCell<InnerInputState>>,
queued_events: Rc<RefCell<Vec<EventCore>>>,
@@ -532,7 +607,11 @@ impl RinkInputHandler {
)
}
pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
self.state.borrow_mut().focus_state.prune(mutations, rdom);
}
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut Dom) -> Vec<UserEvent> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
@@ -565,7 +644,6 @@ impl RinkInputHandler {
})
.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
let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
@@ -577,19 +655,26 @@ impl RinkInputHandler {
for (event, datas) in hm {
for node in dom.get_listening_sorted(event) {
for data in &datas {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: event,
element: Some(node.id),
data: data.clone(),
});
if node.state.focused {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: event,
element: Some(node.id),
data: data.clone(),
bubbles: event_bubbles(event),
});
}
}
}
}
resolved_events
}
pub(crate) fn state(&self) -> RefMut<InnerInputState> {
self.state.borrow_mut()
}
}
// translate crossterm events into dioxus events
+11 -12
View File
@@ -6,7 +6,7 @@ use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
use dioxus_native_core::state::ChildDepState;
use dioxus_native_core_macro::sorted_str_slice;
use stretch2::prelude::*;
use taffy::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum PossiblyUninitalized<T> {
@@ -28,13 +28,13 @@ impl<T> Default for PossiblyUninitalized<T> {
}
#[derive(Clone, PartialEq, Default, Debug)]
pub(crate) struct StretchLayout {
pub(crate) struct TaffyLayout {
pub style: Style,
pub node: PossiblyUninitalized<Node>,
}
impl ChildDepState for StretchLayout {
type Ctx = Rc<RefCell<Stretch>>;
impl ChildDepState for TaffyLayout {
type Ctx = Rc<RefCell<Taffy>>;
type DepState = Self;
// use tag to force this to be called when a node is built
const NODE_MASK: NodeMask =
@@ -53,7 +53,7 @@ impl ChildDepState for StretchLayout {
Self::DepState: 'a,
{
let mut changed = false;
let mut stretch = ctx.borrow_mut();
let mut taffy = ctx.borrow_mut();
let mut style = Style::default();
if let Some(text) = node.text() {
let char_len = text.chars().count();
@@ -70,11 +70,10 @@ impl ChildDepState for StretchLayout {
};
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
taffy.set_style(n, style).unwrap();
}
} else {
self.node =
PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap());
self.node = PossiblyUninitalized::Initialized(taffy.new_node(style, &[]).unwrap());
changed = true;
}
} else {
@@ -100,14 +99,14 @@ impl ChildDepState for StretchLayout {
if let PossiblyUninitalized::Initialized(n) = self.node {
if self.style != style {
stretch.set_style(n, style).unwrap();
taffy.set_style(n, style).unwrap();
}
if stretch.children(n).unwrap() != child_layout {
stretch.set_children(n, &child_layout).unwrap();
if taffy.children(n).unwrap() != child_layout {
taffy.set_children(n, &child_layout).unwrap();
}
} else {
self.node = PossiblyUninitalized::Initialized(
stretch.new_node(style, &child_layout).unwrap(),
taffy.new_node(style, &child_layout).unwrap(),
);
changed = true;
}
+45 -37
View File
@@ -7,23 +7,23 @@ use crossterm::{
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use dioxus_native_core::{real_dom::RealDom, state::*};
use dioxus_native_core_macro::State;
use dioxus_native_core::real_dom::RealDom;
use focus::FocusState;
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use layout::StretchLayout;
use std::cell::RefCell;
use std::rc::Rc;
use std::{io, time::Duration};
use stretch2::{prelude::Size, Stretch};
use style_attributes::StyleModifier;
use taffy::{geometry::Point, prelude::Size, Taffy};
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod config;
mod focus;
mod hooks;
mod layout;
mod node;
mod render;
mod style;
mod style_attributes;
@@ -31,18 +31,7 @@ mod widget;
pub use config::*;
pub use hooks::*;
type Dom = RealDom<NodeState>;
type Node = dioxus_native_core::real_dom::Node<NodeState>;
#[derive(Debug, Clone, State, Default)]
struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
layout: StretchLayout,
// depends on attributes, the C component of it's parent and a u8 context
#[parent_dep_state(style)]
style: StyleModifier,
}
pub(crate) use node::*;
#[derive(Clone)]
pub struct TuiContext {
@@ -52,6 +41,12 @@ impl TuiContext {
pub fn quit(&self) {
self.tx.unbounded_send(InputEvent::Close).unwrap();
}
pub fn inject_event(&self, event: crossterm::event::Event) {
self.tx
.unbounded_send(InputEvent::UserInput(event))
.unwrap();
}
}
pub fn launch(app: Component<()>) {
@@ -71,7 +66,6 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let tick_rate = Duration::from_millis(1000);
loop {
if crossterm::event::poll(tick_rate).unwrap() {
// if crossterm::event::poll(timeout).unwrap() {
let evt = crossterm::event::read().unwrap();
if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
break;
@@ -88,9 +82,9 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let mut rdom: Dom = RealDom::new();
let mutations = dom.rebuild();
let to_update = rdom.apply_mutations(vec![mutations]);
let stretch = Rc::new(RefCell::new(Stretch::new()));
let taffy = Rc::new(RefCell::new(Taffy::new()));
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
render_vdom(
@@ -99,7 +93,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
handler,
cfg,
rdom,
stretch,
taffy,
register_event,
)
.unwrap();
@@ -111,7 +105,7 @@ fn render_vdom(
handler: RinkInputHandler,
cfg: Config,
mut rdom: Dom,
stretch: Rc<RefCell<Stretch>>,
taffy: Rc<RefCell<Taffy>>,
mut register_event: impl FnMut(crossterm::event::Event),
) -> Result<()> {
tokio::runtime::Builder::new_current_thread()
@@ -130,7 +124,7 @@ fn render_vdom(
}
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
let mut resized = true;
let mut updated = true;
loop {
/*
@@ -144,19 +138,19 @@ fn render_vdom(
todo: lazy re-rendering
*/
if !to_rerender.is_empty() || resized {
resized = false;
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
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[0].state.layout.node.unwrap();
stretch
taffy
.compute_layout(
root_node,
Size {
width: stretch2::prelude::Number::Defined((width - 1) as f32),
height: stretch2::prelude::Number::Defined((height - 1) as f32),
width: taffy::prelude::Number::Defined((width - 1) as f32),
height: taffy::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
@@ -164,19 +158,26 @@ fn render_vdom(
if let Some(terminal) = &mut terminal {
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
let root = &rdom[0];
render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
render::render_vnode(
frame,
&taffy.borrow(),
&rdom,
root,
cfg,
Point::zero(),
);
})?;
} else {
resize(
Rect {
x: 0,
y: 0,
width: 300,
height: 300,
width: 100,
height: 100,
},
&mut stretch.borrow_mut(),
&mut taffy.borrow_mut(),
&rdom,
);
}
@@ -202,7 +203,7 @@ fn render_vdom(
break;
}
}
TermEvent::Resize(_, _) => resized = true,
TermEvent::Resize(_, _) => updated = true,
TermEvent::Mouse(_) => {}
},
InputEvent::Close => break,
@@ -216,16 +217,22 @@ fn render_vdom(
}
{
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
let evts = handler.get_events(&taffy.borrow(), &mut rdom);
{
updated |= handler.state().focus_state.clean();
}
for e in evts {
vdom.handle_message(SchedulerMsg::Event(e));
}
let mutations = vdom.work_with_deadline(|| false);
for m in &mutations {
handler.prune(m, &rdom);
}
// updates the dom's nodes
let to_update = rdom.apply_mutations(mutations);
// update the style and layout
let mut any_map = AnyMap::new();
any_map.insert(stretch.clone());
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
}
}
@@ -244,6 +251,7 @@ fn render_vdom(
})
}
#[derive(Debug)]
enum InputEvent {
UserInput(TermEvent),
Close,
+94
View File
@@ -0,0 +1,94 @@
use crate::focus::Focus;
use crate::layout::TaffyLayout;
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>;
#[derive(Debug, Clone, State, Default)]
pub(crate) struct NodeState {
#[child_dep_state(layout, RefCell<Stretch>)]
pub layout: TaffyLayout,
#[parent_dep_state(style)]
pub style: StyleModifier,
#[node_dep_state()]
pub prevent_default: PreventDefault,
#[node_dep_state()]
pub focus: Focus,
pub focused: bool,
}
#[derive(PartialEq, Debug, Clone)]
pub(crate) enum PreventDefault {
Focus,
KeyPress,
KeyRelease,
KeyDown,
KeyUp,
MouseDown,
Click,
MouseEnter,
MouseLeave,
MouseOut,
Unknown,
MouseOver,
ContextMenu,
Wheel,
MouseUp,
}
impl Default for PreventDefault {
fn default() -> Self {
PreventDefault::Unknown
}
}
impl NodeDepState for PreventDefault {
type Ctx = ();
type DepState = ();
const NODE_MASK: dioxus_native_core::node_ref::NodeMask =
dioxus_native_core::node_ref::NodeMask::new_with_attrs(
dioxus_native_core::node_ref::AttributeMask::Static(&sorted_str_slice!([
"dioxus-prevent-default"
])),
);
fn reduce(
&mut self,
node: dioxus_native_core::node_ref::NodeView,
_sibling: &Self::DepState,
_ctx: &Self::Ctx,
) -> bool {
let new = match node
.attributes()
.find(|a| a.name == "dioxus-prevent-default")
.and_then(|a| a.value.as_text())
{
Some("onfocus") => PreventDefault::Focus,
Some("onkeypress") => PreventDefault::KeyPress,
Some("onkeyrelease") => PreventDefault::KeyRelease,
Some("onkeydown") => PreventDefault::KeyDown,
Some("onkeyup") => PreventDefault::KeyUp,
Some("onclick") => PreventDefault::Click,
Some("onmousedown") => PreventDefault::MouseDown,
Some("onmouseup") => PreventDefault::MouseUp,
Some("onmouseenter") => PreventDefault::MouseEnter,
Some("onmouseover") => PreventDefault::MouseOver,
Some("onmouseleave") => PreventDefault::MouseLeave,
Some("onmouseout") => PreventDefault::MouseOut,
Some("onwheel") => PreventDefault::Wheel,
Some("oncontextmenu") => PreventDefault::ContextMenu,
_ => return false,
};
if new == *self {
false
} else {
*self = new;
true
}
}
}
+17 -8
View File
@@ -1,11 +1,11 @@
use dioxus_native_core::layout_attributes::UnitSystem;
use std::io::Stdout;
use stretch2::{
use taffy::{
geometry::Point,
prelude::{Layout, Size},
Stretch,
Taffy,
};
use tui::{backend::CrosstermBackend, layout::Rect};
use tui::{backend::CrosstermBackend, layout::Rect, style::Color};
use crate::{
style::{RinkColor, RinkStyle},
@@ -18,10 +18,11 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub(crate) fn render_vnode(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Stretch,
layout: &Taffy,
rdom: &Dom,
node: &Node,
cfg: Config,
parent_location: Point<f32>,
) {
use dioxus_native_core::real_dom::NodeType;
@@ -29,7 +30,11 @@ pub(crate) fn render_vnode(
return;
}
let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
let Layout {
mut location, size, ..
} = layout.layout(node.state.layout.node.unwrap()).unwrap();
location.x += parent_location.x;
location.y += parent_location.y;
let Point { x, y } = location;
let Size { width, height } = size;
@@ -57,7 +62,7 @@ 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 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
if area.width > 0 && area.height > 0 {
@@ -65,7 +70,7 @@ pub(crate) fn render_vnode(
}
}
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
if area.width > 0 && area.height > 0 {
@@ -73,7 +78,7 @@ pub(crate) fn render_vnode(
}
for c in children {
render_vnode(frame, layout, rdom, &rdom[c.0], cfg);
render_vnode(frame, layout, rdom, &rdom[c.0], cfg, location);
}
}
NodeType::Placeholder => unreachable!(),
@@ -267,6 +272,10 @@ impl RinkWidget for &Node {
if let Some(c) = self.state.style.core.bg {
new_cell.bg = c;
}
if self.state.focused {
new_cell.bg.alpha = 100;
new_cell.bg.color = new_cell.bg.blend(Color::White);
}
buf.set(x, y, new_cell);
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [x] pub position_type: PositionType, --> kinda, taffy doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
@@ -9,7 +9,7 @@
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub overflow: Overflow, ---> kinda implemented... taffy doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
+1 -1
View File
@@ -57,7 +57,7 @@ impl<T: RinkWidget> WidgetWithContext<T> {
impl<T: RinkWidget> Widget for WidgetWithContext<T> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.widget.render(area, RinkBuffer::new(buf, self.config))
self.widget.render(area, RinkBuffer::new(buf, self.config));
}
}