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:
commit
f3fcec2bdf
87 files changed
+6230
-868
No files matched your search
@@ -23,7 +23,12 @@ crossterm = "0.23.0"
|
||||
anyhow = "1.0.42"
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
futures = "0.3.19"
|
||||
stretch2 = "0.4.2"
|
||||
taffy = "0.1.0"
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
|
||||
dioxus-hooks = { path = "../hooks", version = "^0.2.1" }
|
||||
tokio = { version = "1" }
|
||||
@@ -85,11 +85,10 @@ Rink features:
|
||||
- [x] Flexbox based layout system
|
||||
- [ ] CSS selectors
|
||||
- [x] inline CSS support
|
||||
- [ ] Built-in focusing system
|
||||
- [x] Built-in focusing system
|
||||
- [ ] high-quality keyboard support
|
||||
- [ ] Support for events, hooks, and callbacks
|
||||
* [ ] Html tags<sup>1</sup>
|
||||
|
||||
<sup>1</sup> Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality.
|
||||
|
||||
* [ ] Support for events, hooks, and callbacks<sup>1</sup>
|
||||
* [ ] Html tags<sup>2</sup>
|
||||
|
||||
<sup>1</sup> Basic keyboard and mouse events are implemented.
|
||||
<sup>2</sup> Currently, HTML tags don't translate into any meaning inside of rink. So an `input` won't really mean anything nor does it have any additional functionality.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
|
||||
use dioxus_core::VNode;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_hooks::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_tui::TuiContext;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// The tui renderer will look for any event that has occured or any future that has resolved in a loop.
|
||||
/// It will resolve at most one event per loop.
|
||||
/// This future will resolve after a certain number of polls. If the number of polls is greater than the number of events triggered, and the event has not been recieved there is an issue with the event system.
|
||||
struct PollN(usize);
|
||||
impl PollN {
|
||||
fn new(n: usize) -> Self {
|
||||
PollN(n)
|
||||
}
|
||||
}
|
||||
impl Future for PollN {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.0 == 0 {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.0 -= 1;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_down() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
// focus the element
|
||||
tui_ctx.inject_event(Event::Key(KeyEvent {
|
||||
code: KeyCode::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onkeydown: move |evt| {
|
||||
assert_eq!(evt.data.key_code, dioxus_html::KeyCode::A);
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_down() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(2).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 0,
|
||||
row: 0,
|
||||
kind: crossterm::event::MouseEventKind::Down(MouseButton::Left),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onmousedown: move |evt| {
|
||||
assert!(evt.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary));
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_up() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 0,
|
||||
row: 0,
|
||||
kind: crossterm::event::MouseEventKind::Down(MouseButton::Left),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 0,
|
||||
row: 0,
|
||||
kind: crossterm::event::MouseEventKind::Up(MouseButton::Left),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onmouseup: move |_| {
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_enter() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 100,
|
||||
row: 100,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 0,
|
||||
row: 0,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
onmouseenter: move |_| {
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_exit() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 0,
|
||||
row: 0,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 100,
|
||||
row: 100,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
onmouseenter: move |_| {
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_move() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 40,
|
||||
row: 40,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 60,
|
||||
row: 60,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onmousemove: move |_|{
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wheel() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::Moved,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::ScrollDown,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onwheel: move |evt| {
|
||||
assert!(evt.data.delta_y > 0.0);
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn click() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::Down(MouseButton::Left),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::Up(MouseButton::Left),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
onclick: move |_|{
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_menu() {
|
||||
dioxus_tui::launch_cfg(app, dioxus_tui::Config::new().with_headless());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let render_count = use_state(&cx, || 0);
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
let render_count_handle = render_count.clone();
|
||||
cx.spawn(async move {
|
||||
PollN::new(3).await;
|
||||
render_count_handle.modify(|x| *x + 1);
|
||||
});
|
||||
if *render_count.get() > 2 {
|
||||
panic!("Event was not received");
|
||||
}
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::Down(MouseButton::Right),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
tui_ctx.inject_event(Event::Mouse(MouseEvent {
|
||||
column: 50,
|
||||
row: 50,
|
||||
kind: crossterm::event::MouseEventKind::Up(MouseButton::Right),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
oncontextmenu: move |_|{
|
||||
tui_ctx.quit();
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
use stretch2 as stretch;
|
||||
|
||||
#[test]
|
||||
fn margin_and_flex_row() {
|
||||
let mut stretch = stretch::Stretch::new();
|
||||
let node0 = stretch
|
||||
let mut taffy = taffy::Taffy::new();
|
||||
let node0 = taffy
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
taffy::style::Style {
|
||||
flex_grow: 1f32,
|
||||
margin: stretch::geometry::Rect {
|
||||
start: stretch::style::Dimension::Points(10f32),
|
||||
end: stretch::style::Dimension::Points(10f32),
|
||||
margin: taffy::geometry::Rect {
|
||||
start: taffy::style::Dimension::Points(10f32),
|
||||
end: taffy::style::Dimension::Points(10f32),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -17,50 +15,50 @@ fn margin_and_flex_row() {
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let node = stretch
|
||||
let node = taffy
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
size: stretch::geometry::Size {
|
||||
width: stretch::style::Dimension::Points(100f32),
|
||||
height: stretch::style::Dimension::Points(100f32),
|
||||
taffy::style::Style {
|
||||
size: taffy::geometry::Size {
|
||||
width: taffy::style::Dimension::Points(100f32),
|
||||
height: taffy::style::Dimension::Points(100f32),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
&[node0],
|
||||
)
|
||||
.unwrap();
|
||||
stretch
|
||||
.compute_layout(node, stretch::geometry::Size::undefined())
|
||||
taffy
|
||||
.compute_layout(node, taffy::geometry::Size::undefined())
|
||||
.unwrap();
|
||||
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().size.width, 100f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().size.height, 100f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().location.x, 0f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().location.y, 0f32);
|
||||
assert_eq!(taffy.layout(node0).unwrap().size.width, 80f32);
|
||||
assert_eq!(taffy.layout(node0).unwrap().size.height, 100f32);
|
||||
assert_eq!(taffy.layout(node0).unwrap().location.x, 10f32);
|
||||
assert_eq!(taffy.layout(node0).unwrap().location.y, 0f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn margin_and_flex_row2() {
|
||||
let mut stretch = stretch::Stretch::new();
|
||||
let node0 = stretch
|
||||
let mut taffy = taffy::Taffy::new();
|
||||
let node0 = taffy
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
taffy::style::Style {
|
||||
flex_grow: 1f32,
|
||||
margin: stretch::geometry::Rect {
|
||||
margin: taffy::geometry::Rect {
|
||||
// left
|
||||
start: stretch::style::Dimension::Points(10f32),
|
||||
start: taffy::style::Dimension::Points(10f32),
|
||||
|
||||
// right?
|
||||
end: stretch::style::Dimension::Points(10f32),
|
||||
end: taffy::style::Dimension::Points(10f32),
|
||||
|
||||
// top?
|
||||
// top: stretch::style::Dimension::Points(10f32),
|
||||
// top: taffy::style::Dimension::Points(10f32),
|
||||
|
||||
// bottom?
|
||||
// bottom: stretch::style::Dimension::Points(10f32),
|
||||
// bottom: taffy::style::Dimension::Points(10f32),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -69,12 +67,12 @@ fn margin_and_flex_row2() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let node = stretch
|
||||
let node = taffy
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
size: stretch::geometry::Size {
|
||||
width: stretch::style::Dimension::Points(100f32),
|
||||
height: stretch::style::Dimension::Points(100f32),
|
||||
taffy::style::Style {
|
||||
size: taffy::geometry::Size {
|
||||
width: taffy::style::Dimension::Points(100f32),
|
||||
height: taffy::style::Dimension::Points(100f32),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
@@ -82,12 +80,12 @@ fn margin_and_flex_row2() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
stretch
|
||||
.compute_layout(node, stretch::geometry::Size::undefined())
|
||||
taffy
|
||||
.compute_layout(node, taffy::geometry::Size::undefined())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().size.width, 100f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().size.height, 100f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().location.x, 0f32);
|
||||
assert_eq!(taffy.layout(node).unwrap().location.y, 0f32);
|
||||
}
|
||||
Reference in new issue
Block a user