Merge remote-tracking branch 'origin/master' into jk/rsx-refactor
This commit is contained in:
commit
f9713b2fe9
83 files changed
+6905
-2326
No files matched your search
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-core-macro"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2021"
|
||||
description = "Core macro for Dioxus Virtual DOM"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-core"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
use std::fmt::Formatter;
|
||||
|
||||
/// Possible values for an attribute
|
||||
// trying to keep values at 3 bytes
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serialize", serde(untagged))]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AttributeValue<'a> {
|
||||
Text(&'a str),
|
||||
Float32(f32),
|
||||
Float64(f64),
|
||||
Int32(i32),
|
||||
Int64(i64),
|
||||
Uint32(u32),
|
||||
Uint64(u64),
|
||||
Bool(bool),
|
||||
|
||||
Vec3Float(f32, f32, f32),
|
||||
Vec3Int(i32, i32, i32),
|
||||
Vec3Uint(u32, u32, u32),
|
||||
|
||||
Vec4Float(f32, f32, f32, f32),
|
||||
Vec4Int(i32, i32, i32, i32),
|
||||
Vec4Uint(u32, u32, u32, u32),
|
||||
|
||||
Bytes(&'a [u8]),
|
||||
Any(ArbitraryAttributeValue<'a>),
|
||||
}
|
||||
|
||||
// todo
|
||||
#[allow(missing_docs)]
|
||||
impl<'a> AttributeValue<'a> {
|
||||
pub fn is_truthy(&self) -> bool {
|
||||
match self {
|
||||
AttributeValue::Text(t) => *t == "true",
|
||||
AttributeValue::Bool(t) => *t,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_falsy(&self) -> bool {
|
||||
match self {
|
||||
AttributeValue::Text(t) => *t == "false",
|
||||
AttributeValue::Bool(t) => !(*t),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for AttributeValue<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AttributeValue::Text(a) => write!(f, "{}", a),
|
||||
AttributeValue::Float32(a) => write!(f, "{}", a),
|
||||
AttributeValue::Float64(a) => write!(f, "{}", a),
|
||||
AttributeValue::Int32(a) => write!(f, "{}", a),
|
||||
AttributeValue::Int64(a) => write!(f, "{}", a),
|
||||
AttributeValue::Uint32(a) => write!(f, "{}", a),
|
||||
AttributeValue::Uint64(a) => write!(f, "{}", a),
|
||||
AttributeValue::Bool(a) => write!(f, "{}", a),
|
||||
AttributeValue::Vec3Float(_, _, _) => todo!(),
|
||||
AttributeValue::Vec3Int(_, _, _) => todo!(),
|
||||
AttributeValue::Vec3Uint(_, _, _) => todo!(),
|
||||
AttributeValue::Vec4Float(_, _, _, _) => todo!(),
|
||||
AttributeValue::Vec4Int(_, _, _, _) => todo!(),
|
||||
AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
|
||||
AttributeValue::Bytes(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ArbitraryAttributeValue<'a> {
|
||||
pub value: &'a dyn std::any::Any,
|
||||
pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
|
||||
}
|
||||
|
||||
impl PartialEq for ArbitraryAttributeValue<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.cmp)(self.value, other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ArbitraryAttributeValue").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
|
||||
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
panic!("ArbitraryAttributeValue should not be serialized")
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
|
||||
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
panic!("ArbitraryAttributeValue is not deserializable!")
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
|
||||
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
panic!("ArbitraryAttributeValue is not deserializable!")
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
#[allow(missing_docs)]
|
||||
impl<'a> AttributeValue<'a> {
|
||||
pub fn as_text(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
AttributeValue::Text(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float32(&self) -> Option<f32> {
|
||||
match self {
|
||||
AttributeValue::Float32(f) => Some(*f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float64(&self) -> Option<f64> {
|
||||
match self {
|
||||
AttributeValue::Float64(f) => Some(*f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int32(&self) -> Option<i32> {
|
||||
match self {
|
||||
AttributeValue::Int32(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int64(&self) -> Option<i64> {
|
||||
match self {
|
||||
AttributeValue::Int64(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_uint32(&self) -> Option<u32> {
|
||||
match self {
|
||||
AttributeValue::Uint32(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_uint64(&self) -> Option<u64> {
|
||||
match self {
|
||||
AttributeValue::Uint64(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
AttributeValue::Bool(b) => Some(*b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
|
||||
match self {
|
||||
AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
|
||||
match self {
|
||||
AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
|
||||
match self {
|
||||
AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
|
||||
match self {
|
||||
AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
|
||||
match self {
|
||||
AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
|
||||
match self {
|
||||
AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Option<&[u8]> {
|
||||
match self {
|
||||
AttributeValue::Bytes(b) => Some(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
|
||||
match self {
|
||||
AttributeValue::Any(a) => Some(a),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_attribute_value_size() {
|
||||
// assert_eq!(std::mem::size_of::<AttributeValue<'_>>(), 24);
|
||||
// }
|
||||
@@ -49,7 +49,7 @@ impl BubbleState {
|
||||
/// }
|
||||
/// )).unwrap();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserEvent {
|
||||
/// The originator of the event trigger if available
|
||||
pub scope_id: Option<ScopeId>,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub(crate) mod arbitrary_value;
|
||||
pub(crate) mod diff;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod lazynodes;
|
||||
@@ -13,6 +14,7 @@ pub(crate) mod util;
|
||||
pub(crate) mod virtual_dom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::arbitrary_value::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
@@ -71,10 +73,10 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
AnyEvent, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator,
|
||||
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
|
||||
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, VComponent,
|
||||
VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
|
||||
AnyEvent, Attribute, AttributeValue, Component, DioxusElement, DomEdit, Element, ElementId,
|
||||
ElementIdIterator, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations,
|
||||
NodeFactory, Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent,
|
||||
VComponent, VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
|
||||
@@ -167,8 +167,9 @@ pub enum DomEdit<'bump> {
|
||||
field: &'static str,
|
||||
|
||||
/// The value of the attribute.
|
||||
value: &'bump str,
|
||||
value: AttributeValue<'bump>,
|
||||
|
||||
// value: &'bump str,
|
||||
/// The (optional) namespace of the attribute.
|
||||
/// For instance, "style" is in the "style" namespace.
|
||||
ns: Option<&'bump str>,
|
||||
@@ -286,7 +287,7 @@ impl<'a> Mutations<'a> {
|
||||
self.edits.push(SetText { text, root });
|
||||
}
|
||||
|
||||
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) {
|
||||
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
|
||||
let Attribute {
|
||||
name,
|
||||
value,
|
||||
@@ -296,7 +297,7 @@ impl<'a> Mutations<'a> {
|
||||
|
||||
self.edits.push(SetAttribute {
|
||||
field: name,
|
||||
value,
|
||||
value: value.clone(),
|
||||
ns: *namespace,
|
||||
root,
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! cheap and *very* fast to construct - building a full tree should be quick.
|
||||
|
||||
use crate::{
|
||||
innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
|
||||
innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
|
||||
lazynodes::LazyNodes,
|
||||
AnyEvent, Component,
|
||||
};
|
||||
@@ -339,7 +339,7 @@ pub struct Attribute<'a> {
|
||||
pub name: &'static str,
|
||||
|
||||
/// The value of the attribute.
|
||||
pub value: &'a str,
|
||||
pub value: AttributeValue<'a>,
|
||||
|
||||
/// An indication if this attribute can be ignored during diffing
|
||||
///
|
||||
@@ -610,6 +610,24 @@ impl<'a> NodeFactory<'a> {
|
||||
is_volatile: bool,
|
||||
) -> Attribute<'a> {
|
||||
let (value, is_static) = self.raw_text(val);
|
||||
Attribute {
|
||||
name,
|
||||
value: AttributeValue::Text(value),
|
||||
is_static,
|
||||
namespace,
|
||||
is_volatile,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Attribute`] using non-arguments
|
||||
pub fn custom_attr(
|
||||
&self,
|
||||
name: &'static str,
|
||||
value: AttributeValue<'a>,
|
||||
namespace: Option<&'static str>,
|
||||
is_volatile: bool,
|
||||
is_static: bool,
|
||||
) -> Attribute<'a> {
|
||||
Attribute {
|
||||
name,
|
||||
value,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-desktop"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||
@@ -12,15 +12,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
|
||||
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
wry = { version = "0.13.1" }
|
||||
wry = { version = "0.16.0" }
|
||||
futures-channel = "0.3.21"
|
||||
tokio = { version = "1.16.1", features = [
|
||||
"sync",
|
||||
@@ -28,7 +28,7 @@ tokio = { version = "1.16.1", features = [
|
||||
"rt",
|
||||
"time",
|
||||
], optional = true, default-features = false }
|
||||
webbrowser = "0.6.0"
|
||||
webbrowser = "0.7.1"
|
||||
mime_guess = "2.0.3"
|
||||
dunce = "1.0.2"
|
||||
|
||||
@@ -38,13 +38,9 @@ core-foundation = "0.9.3"
|
||||
[features]
|
||||
default = ["tokio_runtime"]
|
||||
tokio_runtime = ["tokio"]
|
||||
|
||||
devtool = ["wry/devtool"]
|
||||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
|
||||
tray = ["wry/tray"]
|
||||
ayatana = ["wry/ayatana"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -115,6 +115,16 @@ impl DesktopContext {
|
||||
let _ = self.proxy.send_event(SetDecorations(decoration));
|
||||
}
|
||||
|
||||
/// set window zoom level
|
||||
pub fn set_zoom_level(&self, scale_factor: f64) {
|
||||
let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
|
||||
}
|
||||
|
||||
/// launch print modal
|
||||
pub fn print(&self) {
|
||||
let _ = self.proxy.send_event(Print);
|
||||
}
|
||||
|
||||
/// opens DevTool window
|
||||
pub fn devtool(&self) {
|
||||
let _ = self.proxy.send_event(DevTool);
|
||||
@@ -148,6 +158,9 @@ pub enum UserWindowEvent {
|
||||
SetTitle(String),
|
||||
SetDecorations(bool),
|
||||
|
||||
SetZoomLevel(f64),
|
||||
|
||||
Print,
|
||||
DevTool,
|
||||
|
||||
Eval(String),
|
||||
@@ -191,11 +204,22 @@ pub(super) fn handler(
|
||||
SetTitle(content) => window.set_title(&content),
|
||||
SetDecorations(state) => window.set_decorations(state),
|
||||
|
||||
DevTool => webview.devtool(),
|
||||
SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
|
||||
|
||||
Eval(code) => webview
|
||||
.evaluate_script(code.as_str())
|
||||
.expect("eval shouldn't panic"),
|
||||
Print => {
|
||||
if let Err(e) = webview.print() {
|
||||
// we can't panic this error.
|
||||
log::warn!("Open print modal failed: {e}");
|
||||
}
|
||||
}
|
||||
DevTool => webview.open_devtools(),
|
||||
|
||||
Eval(code) => {
|
||||
if let Err(e) = webview.evaluate_script(code.as_str()) {
|
||||
// we can't panic this error.
|
||||
log::warn!("Eval script error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
||||
)
|
||||
} else {
|
||||
// in debug, we are okay with the reload menu showing and dev tool
|
||||
webview = webview.with_dev_tool(true);
|
||||
webview = webview.with_devtools(true);
|
||||
}
|
||||
|
||||
desktop.webviews.insert(window_id, webview.build().unwrap());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fermi"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Global state management for Dioxus"
|
||||
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
im-rc = { version = "15.0.0", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
|
||||
|
||||
@@ -16,15 +16,35 @@ use std::{
|
||||
pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
|
||||
let root = use_atom_root(cx);
|
||||
|
||||
cx.use_hook(|_| {
|
||||
&cx.use_hook(|_| {
|
||||
root.initialize(atom);
|
||||
UseAtomRef {
|
||||
ptr: atom.unique_id(),
|
||||
root: root.clone(),
|
||||
scope_id: cx.scope_id(),
|
||||
value: root.register(atom, cx.scope_id()),
|
||||
}
|
||||
(
|
||||
UseAtomRef {
|
||||
ptr: atom.unique_id(),
|
||||
root: root.clone(),
|
||||
scope_id: cx.scope_id(),
|
||||
value: root.register(atom, cx.scope_id()),
|
||||
},
|
||||
AtomRefSubscription {
|
||||
ptr: atom.unique_id(),
|
||||
root: root.clone(),
|
||||
scope_id: cx.scope_id(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.0
|
||||
}
|
||||
|
||||
pub struct AtomRefSubscription {
|
||||
ptr: AtomId,
|
||||
root: Rc<AtomRoot>,
|
||||
scope_id: ScopeId,
|
||||
}
|
||||
|
||||
impl Drop for AtomRefSubscription {
|
||||
fn drop(&mut self) {
|
||||
self.root.unsubscribe(self.ptr, self.scope_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UseAtomRef<T> {
|
||||
@@ -34,6 +54,17 @@ pub struct UseAtomRef<T> {
|
||||
scope_id: ScopeId,
|
||||
}
|
||||
|
||||
impl<T> Clone for UseAtomRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
ptr: self.ptr,
|
||||
value: self.value.clone(),
|
||||
root: self.root.clone(),
|
||||
scope_id: self.scope_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> UseAtomRef<T> {
|
||||
pub fn read(&self) -> Ref<T> {
|
||||
self.value.borrow()
|
||||
@@ -45,7 +76,6 @@ impl<T: 'static> UseAtomRef<T> {
|
||||
}
|
||||
|
||||
pub fn write_silent(&self) -> RefMut<T> {
|
||||
self.root.force_update(self.ptr);
|
||||
self.value.borrow_mut()
|
||||
}
|
||||
|
||||
@@ -54,9 +84,3 @@ impl<T: 'static> UseAtomRef<T> {
|
||||
self.root.set(self.ptr, new);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for UseAtomRef<T> {
|
||||
fn drop(&mut self) {
|
||||
self.root.unsubscribe(self.ptr, self.scope_id)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-hooks"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||
@@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
|
||||
futures-channel = "0.3.21"
|
||||
log = { version = "0.4" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
|
||||
@@ -16,5 +16,8 @@ pub use usecoroutine::*;
|
||||
mod usefuture;
|
||||
pub use usefuture::*;
|
||||
|
||||
mod useeffect;
|
||||
pub use useeffect::*;
|
||||
|
||||
// mod usesuspense;
|
||||
// pub use usesuspense::*;
|
||||
@@ -24,7 +24,7 @@ use std::{
|
||||
/// val.write().insert(1, "hello".to_string());
|
||||
/// ```
|
||||
///
|
||||
/// You can avoid this defualt behavior with `write_silent`
|
||||
/// You can avoid this default behavior with `write_silent`
|
||||
///
|
||||
/// ```
|
||||
/// // with `write_silent`, the component will not be re-rendered
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-html"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
@@ -11,10 +11,34 @@ documentation = "https://docs.rs/dioxus"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { version = "0.2.79", optional = true }
|
||||
euclid = "0.22.7"
|
||||
enumset = "1.0.11"
|
||||
keyboard-types = "0.6.2"
|
||||
|
||||
[dependencies.web-sys]
|
||||
optional = true
|
||||
version = "0.3.56"
|
||||
features = [
|
||||
"TouchEvent",
|
||||
"MouseEvent",
|
||||
"InputEvent",
|
||||
"ClipboardEvent",
|
||||
"KeyboardEvent",
|
||||
"TouchEvent",
|
||||
"WheelEvent",
|
||||
"AnimationEvent",
|
||||
"TransitionEvent",
|
||||
"PointerEvent",
|
||||
"FocusEvent",
|
||||
"CompositionEvent",
|
||||
"ClipboardEvent",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serialize = ["serde", "serde_repr"]
|
||||
wasm-bind = ["web-sys", "wasm-bindgen"]
|
||||
+190
-15
@@ -3,6 +3,13 @@ use dioxus_core::exports::bumpalo;
|
||||
use dioxus_core::*;
|
||||
|
||||
pub mod on {
|
||||
//! Input events and associated data
|
||||
|
||||
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use crate::input_data::{
|
||||
decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet,
|
||||
};
|
||||
use keyboard_types::Modifiers;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
@@ -398,21 +405,21 @@ pub mod on {
|
||||
|
||||
pub type ClipboardEvent = UiEvent<ClipboardData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClipboardData {
|
||||
// DOMDataTransfer clipboardData
|
||||
}
|
||||
|
||||
pub type CompositionEvent = UiEvent<CompositionData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompositionData {
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
pub type KeyboardEvent = UiEvent<KeyboardData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyboardData {
|
||||
pub char_code: u32,
|
||||
|
||||
@@ -481,12 +488,12 @@ pub mod on {
|
||||
|
||||
pub type FocusEvent = UiEvent<FocusData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FocusData {/* DOMEventInner: Send + SyncTarget relatedTarget */}
|
||||
|
||||
pub type FormEvent = UiEvent<FormData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FormData {
|
||||
pub value: String,
|
||||
pub values: HashMap<String, String>,
|
||||
@@ -495,26 +502,194 @@ pub mod on {
|
||||
|
||||
pub type MouseEvent = UiEvent<MouseData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
/// Data associated with a mouse event
|
||||
///
|
||||
/// Do not use the deprecated fields; they may change or become private in the future.
|
||||
pub struct MouseData {
|
||||
/// True if the alt key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub alt_key: bool,
|
||||
/// The button number that was pressed (if applicable) when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use trigger_button() instead")]
|
||||
pub button: i16,
|
||||
/// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered.
|
||||
///
|
||||
/// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4).
|
||||
///
|
||||
/// - 1: Primary button (usually the left button)
|
||||
/// - 2: Secondary button (usually the right button)
|
||||
/// - 4: Auxiliary button (usually the mouse wheel button or middle button)
|
||||
/// - 8: 4th button (typically the "Browser Back" button)
|
||||
/// - 16 : 5th button (typically the "Browser Forward" button)
|
||||
#[deprecated(since = "0.3.0", note = "use held_buttons() instead")]
|
||||
pub buttons: u16,
|
||||
/// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally.
|
||||
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
|
||||
pub client_x: i32,
|
||||
/// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically.
|
||||
#[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
|
||||
pub client_y: i32,
|
||||
/// True if the control key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub ctrl_key: bool,
|
||||
/// True if the meta key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub meta_key: bool,
|
||||
/// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
|
||||
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
|
||||
pub offset_x: i32,
|
||||
/// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
|
||||
#[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
|
||||
pub offset_y: i32,
|
||||
/// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible.
|
||||
///
|
||||
/// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300.
|
||||
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
|
||||
pub page_x: i32,
|
||||
/// The Y (vertical) coordinate in pixels of the event relative to the whole document.
|
||||
///
|
||||
/// See `page_x`.
|
||||
#[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
|
||||
pub page_y: i32,
|
||||
/// The X coordinate of the mouse pointer in global (screen) coordinates.
|
||||
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
|
||||
pub screen_x: i32,
|
||||
/// The Y coordinate of the mouse pointer in global (screen) coordinates.
|
||||
#[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
|
||||
pub screen_y: i32,
|
||||
/// True if the shift key was down when the mouse event was fired.
|
||||
#[deprecated(since = "0.3.0", note = "use modifiers() instead")]
|
||||
pub shift_key: bool,
|
||||
// fn get_modifier_state(&self, key_code: &str) -> bool;
|
||||
}
|
||||
|
||||
impl MouseData {
|
||||
/// Construct MouseData with the specified properties
|
||||
///
|
||||
/// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part.
|
||||
pub fn new(
|
||||
coordinates: Coordinates,
|
||||
trigger_button: Option<MouseButton>,
|
||||
held_buttons: MouseButtonSet,
|
||||
modifiers: Modifiers,
|
||||
) -> Self {
|
||||
let alt_key = modifiers.contains(Modifiers::ALT);
|
||||
let ctrl_key = modifiers.contains(Modifiers::CONTROL);
|
||||
let meta_key = modifiers.contains(Modifiers::META);
|
||||
let shift_key = modifiers.contains(Modifiers::SHIFT);
|
||||
|
||||
let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into();
|
||||
let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into();
|
||||
let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into();
|
||||
let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into();
|
||||
|
||||
#[allow(deprecated)]
|
||||
Self {
|
||||
alt_key,
|
||||
ctrl_key,
|
||||
meta_key,
|
||||
shift_key,
|
||||
|
||||
button: trigger_button.map_or(0, |b| b.into_web_code()),
|
||||
buttons: encode_mouse_button_set(held_buttons),
|
||||
|
||||
client_x,
|
||||
client_y,
|
||||
offset_x,
|
||||
offset_y,
|
||||
page_x,
|
||||
page_y,
|
||||
screen_x,
|
||||
screen_y,
|
||||
}
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
|
||||
pub fn client_coordinates(&self) -> ClientPoint {
|
||||
#[allow(deprecated)]
|
||||
ClientPoint::new(self.client_x.into(), self.client_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the padding edge of the target element
|
||||
///
|
||||
/// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
|
||||
pub fn element_coordinates(&self) -> ElementPoint {
|
||||
#[allow(deprecated)]
|
||||
ElementPoint::new(self.offset_x.into(), self.offset_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible.
|
||||
///
|
||||
/// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
|
||||
pub fn page_coordinates(&self) -> PagePoint {
|
||||
#[allow(deprecated)]
|
||||
PagePoint::new(self.page_x.into(), self.page_y.into())
|
||||
}
|
||||
|
||||
/// The event's coordinates relative to the entire screen. This takes into account the window's offset.
|
||||
pub fn screen_coordinates(&self) -> ScreenPoint {
|
||||
#[allow(deprecated)]
|
||||
ScreenPoint::new(self.screen_x.into(), self.screen_y.into())
|
||||
}
|
||||
|
||||
pub fn coordinates(&self) -> Coordinates {
|
||||
Coordinates::new(
|
||||
self.screen_coordinates(),
|
||||
self.client_coordinates(),
|
||||
self.element_coordinates(),
|
||||
self.page_coordinates(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The set of modifier keys which were pressed when the event occurred
|
||||
pub fn modifiers(&self) -> Modifiers {
|
||||
let mut modifiers = Modifiers::empty();
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
if self.alt_key {
|
||||
modifiers.insert(Modifiers::ALT);
|
||||
}
|
||||
if self.ctrl_key {
|
||||
modifiers.insert(Modifiers::CONTROL);
|
||||
}
|
||||
if self.meta_key {
|
||||
modifiers.insert(Modifiers::META);
|
||||
}
|
||||
if self.shift_key {
|
||||
modifiers.insert(Modifiers::SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
modifiers
|
||||
}
|
||||
|
||||
/// The set of mouse buttons which were held when the event occurred.
|
||||
pub fn held_buttons(&self) -> MouseButtonSet {
|
||||
#[allow(deprecated)]
|
||||
decode_mouse_button_set(self.buttons)
|
||||
}
|
||||
|
||||
/// The mouse button that triggered the event
|
||||
///
|
||||
// todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here
|
||||
/// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed.
|
||||
pub fn trigger_button(&self) -> Option<MouseButton> {
|
||||
#[allow(deprecated)]
|
||||
Some(MouseButton::from_web_code(self.button))
|
||||
}
|
||||
}
|
||||
|
||||
pub type PointerEvent = UiEvent<PointerData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PointerData {
|
||||
// Mouse only
|
||||
pub alt_key: bool,
|
||||
@@ -544,12 +719,12 @@ pub mod on {
|
||||
|
||||
pub type SelectionEvent = UiEvent<SelectionData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelectionData {}
|
||||
|
||||
pub type TouchEvent = UiEvent<TouchData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TouchData {
|
||||
pub alt_key: bool,
|
||||
pub ctrl_key: bool,
|
||||
@@ -563,7 +738,7 @@ pub mod on {
|
||||
|
||||
pub type WheelEvent = UiEvent<WheelData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WheelData {
|
||||
pub delta_mode: u32,
|
||||
pub delta_x: f64,
|
||||
@@ -573,19 +748,19 @@ pub mod on {
|
||||
|
||||
pub type MediaEvent = UiEvent<MediaData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaData {}
|
||||
|
||||
pub type ImageEvent = UiEvent<ImageData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageData {
|
||||
pub load_error: bool,
|
||||
}
|
||||
|
||||
pub type AnimationEvent = UiEvent<AnimationData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnimationData {
|
||||
pub animation_name: String,
|
||||
pub pseudo_element: String,
|
||||
@@ -594,7 +769,7 @@ pub mod on {
|
||||
|
||||
pub type TransitionEvent = UiEvent<TransitionData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TransitionData {
|
||||
pub property_name: String,
|
||||
pub pseudo_element: String,
|
||||
@@ -603,7 +778,7 @@ pub mod on {
|
||||
|
||||
pub type ToggleEvent = UiEvent<ToggleData>;
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToggleData {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Geometry primitives for representing e.g. mouse events
|
||||
|
||||
/// A re-export of euclid, which we use for geometry primitives
|
||||
pub use euclid;
|
||||
|
||||
use euclid::*;
|
||||
|
||||
/// Coordinate space relative to the screen
|
||||
pub struct ScreenSpace;
|
||||
/// A point in ScreenSpace
|
||||
pub type ScreenPoint = Point2D<f64, ScreenSpace>;
|
||||
|
||||
/// Coordinate space relative to the viewport
|
||||
pub struct ClientSpace;
|
||||
/// A point in ClientSpace
|
||||
pub type ClientPoint = Point2D<f64, ClientSpace>;
|
||||
|
||||
/// Coordinate space relative to an element
|
||||
pub struct ElementSpace;
|
||||
/// A point in ElementSpace
|
||||
pub type ElementPoint = Point2D<f64, ElementSpace>;
|
||||
|
||||
/// Coordinate space relative to the page
|
||||
pub struct PageSpace;
|
||||
/// A point in PageSpace
|
||||
pub type PagePoint = Point2D<f64, PageSpace>;
|
||||
|
||||
/// Coordinates of a point in the app's interface
|
||||
pub struct Coordinates {
|
||||
screen: ScreenPoint,
|
||||
client: ClientPoint,
|
||||
element: ElementPoint,
|
||||
page: PagePoint,
|
||||
}
|
||||
|
||||
impl Coordinates {
|
||||
/// Construct new coordinates with the specified screen-, client-, element- and page-relative points
|
||||
pub fn new(
|
||||
screen: ScreenPoint,
|
||||
client: ClientPoint,
|
||||
element: ElementPoint,
|
||||
page: PagePoint,
|
||||
) -> Self {
|
||||
Self {
|
||||
screen,
|
||||
client,
|
||||
element,
|
||||
page,
|
||||
}
|
||||
}
|
||||
/// Coordinates relative to the entire screen. This takes into account the window's offset.
|
||||
pub fn screen(&self) -> ScreenPoint {
|
||||
self.screen
|
||||
}
|
||||
/// Coordinates relative to the application's viewport (as opposed to the coordinate within the page).
|
||||
///
|
||||
/// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
|
||||
pub fn client(&self) -> ClientPoint {
|
||||
self.client
|
||||
}
|
||||
/// Coordinates relative to the padding edge of the target element
|
||||
///
|
||||
/// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
|
||||
pub fn element(&self) -> ElementPoint {
|
||||
self.element
|
||||
}
|
||||
/// Coordinates relative to the entire document. This includes any portion of the document not currently visible.
|
||||
///
|
||||
/// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
|
||||
pub fn page(&self) -> PagePoint {
|
||||
self.page
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//! Data structures representing user input, such as modifier keys and mouse buttons
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
|
||||
/// A re-export of keyboard_types
|
||||
pub use keyboard_types;
|
||||
|
||||
/// A mouse button type (such as Primary/Secondary)
|
||||
// note: EnumSetType also derives Copy and Clone for some reason
|
||||
#[derive(EnumSetType, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum MouseButton {
|
||||
/// Primary button (typically the left button)
|
||||
Primary,
|
||||
/// Secondary button (typically the right button)
|
||||
Secondary,
|
||||
/// Auxiliary button (typically the middle button)
|
||||
Auxiliary,
|
||||
/// Fourth button (typically the "Browser Back" button)
|
||||
Fourth,
|
||||
/// Fifth button (typically the "Browser Forward" button)
|
||||
Fifth,
|
||||
/// A button with an unknown code
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
/// Constructs a MouseButton for the specified button code
|
||||
///
|
||||
/// E.g. 0 => Primary; 1 => Auxiliary
|
||||
///
|
||||
/// Unknown codes get mapped to MouseButton::Unknown.
|
||||
pub fn from_web_code(code: i16) -> Self {
|
||||
match code {
|
||||
0 => MouseButton::Primary,
|
||||
// not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
1 => MouseButton::Auxiliary,
|
||||
2 => MouseButton::Secondary,
|
||||
3 => MouseButton::Fourth,
|
||||
4 => MouseButton::Fifth,
|
||||
_ => MouseButton::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts MouseButton into the corresponding button code
|
||||
///
|
||||
/// MouseButton::Unknown will get mapped to -1
|
||||
pub fn into_web_code(self) -> i16 {
|
||||
match self {
|
||||
MouseButton::Primary => 0,
|
||||
// not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
MouseButton::Auxiliary => 1,
|
||||
MouseButton::Secondary => 2,
|
||||
MouseButton::Fourth => 3,
|
||||
MouseButton::Fifth => 4,
|
||||
MouseButton::Unknown => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of mouse buttons
|
||||
pub type MouseButtonSet = EnumSet<MouseButton>;
|
||||
|
||||
pub fn decode_mouse_button_set(code: u16) -> MouseButtonSet {
|
||||
let mut set = EnumSet::empty();
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
if code & 0b1 != 0 {
|
||||
set |= MouseButton::Primary;
|
||||
}
|
||||
if code & 0b10 != 0 {
|
||||
set |= MouseButton::Secondary;
|
||||
}
|
||||
if code & 0b100 != 0 {
|
||||
set |= MouseButton::Auxiliary;
|
||||
}
|
||||
if code & 0b1000 != 0 {
|
||||
set |= MouseButton::Fourth;
|
||||
}
|
||||
if code & 0b10000 != 0 {
|
||||
set |= MouseButton::Fifth;
|
||||
}
|
||||
if code & (!0b11111) != 0 {
|
||||
set |= MouseButton::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
|
||||
pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {
|
||||
let mut code = 0;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
||||
{
|
||||
if set.contains(MouseButton::Primary) {
|
||||
code |= 0b1;
|
||||
}
|
||||
if set.contains(MouseButton::Secondary) {
|
||||
code |= 0b10;
|
||||
}
|
||||
if set.contains(MouseButton::Auxiliary) {
|
||||
code |= 0b100;
|
||||
}
|
||||
if set.contains(MouseButton::Fourth) {
|
||||
code |= 0b1000;
|
||||
}
|
||||
if set.contains(MouseButton::Fifth) {
|
||||
code |= 0b10000;
|
||||
}
|
||||
if set.contains(MouseButton::Unknown) {
|
||||
code |= 0b100000;
|
||||
}
|
||||
}
|
||||
|
||||
code
|
||||
}
|
||||
@@ -15,7 +15,11 @@
|
||||
|
||||
mod elements;
|
||||
mod events;
|
||||
pub mod geometry;
|
||||
mod global_attributes;
|
||||
pub mod input_data;
|
||||
#[cfg(feature = "wasm-bind")]
|
||||
mod web_sys_bind;
|
||||
|
||||
pub use elements::*;
|
||||
pub use events::*;
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use crate::input_data::{decode_mouse_button_set, MouseButton};
|
||||
use crate::on::{
|
||||
AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
|
||||
TransitionData, WheelData,
|
||||
};
|
||||
use crate::KeyCode;
|
||||
use keyboard_types::Modifiers;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
|
||||
TransitionEvent, WheelEvent,
|
||||
};
|
||||
|
||||
macro_rules! uncheck_convert {
|
||||
($t:ty, $d:ty) => {
|
||||
impl From<Event> for $d {
|
||||
#[inline]
|
||||
fn from(e: Event) -> Self {
|
||||
let e: $t = e.unchecked_into();
|
||||
Self::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Event> for $d {
|
||||
#[inline]
|
||||
fn from(e: &Event) -> Self {
|
||||
let e: &$t = e.unchecked_ref();
|
||||
Self::from(e)
|
||||
}
|
||||
}
|
||||
};
|
||||
($($t:ty => $d:ty),+ $(,)?) => {
|
||||
$(uncheck_convert!($t, $d);)+
|
||||
};
|
||||
}
|
||||
|
||||
uncheck_convert![
|
||||
CompositionEvent => CompositionData,
|
||||
KeyboardEvent => KeyboardData,
|
||||
MouseEvent => MouseData,
|
||||
TouchEvent => TouchData,
|
||||
PointerEvent => PointerData,
|
||||
WheelEvent => WheelData,
|
||||
AnimationEvent => AnimationData,
|
||||
TransitionEvent => TransitionData,
|
||||
];
|
||||
|
||||
impl From<&CompositionEvent> for CompositionData {
|
||||
fn from(e: &CompositionEvent) -> Self {
|
||||
Self {
|
||||
data: e.data().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KeyboardEvent> for KeyboardData {
|
||||
fn from(e: &KeyboardEvent) -> Self {
|
||||
Self {
|
||||
alt_key: e.alt_key(),
|
||||
char_code: e.char_code(),
|
||||
key: e.key(),
|
||||
key_code: KeyCode::from_raw_code(e.key_code() as u8),
|
||||
ctrl_key: e.ctrl_key(),
|
||||
locale: "not implemented".to_string(),
|
||||
location: e.location() as usize,
|
||||
meta_key: e.meta_key(),
|
||||
repeat: e.repeat(),
|
||||
shift_key: e.shift_key(),
|
||||
which: e.which() as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MouseEvent> for MouseData {
|
||||
fn from(e: &MouseEvent) -> Self {
|
||||
let mut modifiers = Modifiers::empty();
|
||||
|
||||
if e.alt_key() {
|
||||
modifiers.insert(Modifiers::ALT);
|
||||
}
|
||||
if e.ctrl_key() {
|
||||
modifiers.insert(Modifiers::CONTROL);
|
||||
}
|
||||
if e.meta_key() {
|
||||
modifiers.insert(Modifiers::META);
|
||||
}
|
||||
if e.shift_key() {
|
||||
modifiers.insert(Modifiers::SHIFT);
|
||||
}
|
||||
|
||||
MouseData::new(
|
||||
Coordinates::new(
|
||||
ScreenPoint::new(e.screen_x().into(), e.screen_y().into()),
|
||||
ClientPoint::new(e.client_x().into(), e.client_y().into()),
|
||||
ElementPoint::new(e.offset_x().into(), e.offset_y().into()),
|
||||
PagePoint::new(e.page_x().into(), e.page_y().into()),
|
||||
),
|
||||
Some(MouseButton::from_web_code(e.button().into())),
|
||||
decode_mouse_button_set(e.buttons()),
|
||||
modifiers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TouchEvent> for TouchData {
|
||||
fn from(e: &TouchEvent) -> Self {
|
||||
Self {
|
||||
alt_key: e.alt_key(),
|
||||
ctrl_key: e.ctrl_key(),
|
||||
meta_key: e.meta_key(),
|
||||
shift_key: e.shift_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PointerEvent> for PointerData {
|
||||
fn from(e: &PointerEvent) -> Self {
|
||||
Self {
|
||||
alt_key: e.alt_key(),
|
||||
button: e.button(),
|
||||
buttons: e.buttons(),
|
||||
client_x: e.client_x(),
|
||||
client_y: e.client_y(),
|
||||
ctrl_key: e.ctrl_key(),
|
||||
meta_key: e.meta_key(),
|
||||
page_x: e.page_x(),
|
||||
page_y: e.page_y(),
|
||||
screen_x: e.screen_x(),
|
||||
screen_y: e.screen_y(),
|
||||
shift_key: e.shift_key(),
|
||||
pointer_id: e.pointer_id(),
|
||||
width: e.width(),
|
||||
height: e.height(),
|
||||
pressure: e.pressure(),
|
||||
tangential_pressure: e.tangential_pressure(),
|
||||
tilt_x: e.tilt_x(),
|
||||
tilt_y: e.tilt_y(),
|
||||
twist: e.twist(),
|
||||
pointer_type: e.pointer_type(),
|
||||
is_primary: e.is_primary(),
|
||||
// get_modifier_state: evt.get_modifier_state(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WheelEvent> for WheelData {
|
||||
fn from(e: &WheelEvent) -> Self {
|
||||
Self {
|
||||
delta_x: e.delta_x(),
|
||||
delta_y: e.delta_y(),
|
||||
delta_z: e.delta_z(),
|
||||
delta_mode: e.delta_mode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AnimationEvent> for AnimationData {
|
||||
fn from(e: &AnimationEvent) -> Self {
|
||||
Self {
|
||||
elapsed_time: e.elapsed_time(),
|
||||
animation_name: e.animation_name(),
|
||||
pseudo_element: e.pseudo_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TransitionEvent> for TransitionData {
|
||||
fn from(e: &TransitionEvent) -> Self {
|
||||
Self {
|
||||
elapsed_time: e.elapsed_time(),
|
||||
property_name: e.property_name(),
|
||||
pseudo_element: e.pseudo_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mod events;
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-interpreter-js"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Kelley"]
|
||||
description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
|
||||
@@ -409,6 +409,8 @@ export function serialize_event(event) {
|
||||
clientY,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX,
|
||||
pageY,
|
||||
screenX,
|
||||
@@ -423,6 +425,8 @@ export function serialize_event(event) {
|
||||
client_y: clientY,
|
||||
ctrl_key: ctrlKey,
|
||||
meta_key: metaKey,
|
||||
offset_x: offsetX,
|
||||
offset_y: offsetY,
|
||||
page_x: pageX,
|
||||
page_y: pageY,
|
||||
screen_x: screenX,
|
||||
|
||||
@@ -6,6 +6,9 @@ repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
description = "Build server-side apps with Dioxus"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -23,8 +26,8 @@ serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
tokio-util = { version = "0.7.0", features = ["full"] }
|
||||
|
||||
dioxus-html = { path = "../html", features = ["serialize"] }
|
||||
dioxus-core = { path = "../core", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" }
|
||||
|
||||
|
||||
# warp
|
||||
@@ -42,4 +45,4 @@ axum = { version = "0.5.1", features = ["ws"] }
|
||||
tower = "0.4.12"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = []
|
||||
@@ -9,21 +9,33 @@ use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_util::task::LocalPoolHandle;
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
impl crate::Liveview {
|
||||
pub async fn upgrade(&self, ws: WebSocket, app: fn(Scope) -> Element) {
|
||||
connect(ws, self.pool.clone(), app).await;
|
||||
connect(ws, self.pool.clone(), app, ()).await;
|
||||
}
|
||||
pub async fn upgrade_with_props<T>(&self, ws: WebSocket, app: fn(Scope<T>) -> Element, props: T)
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
connect(ws, self.pool.clone(), app, props).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(socket: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
|
||||
pub async fn connect<T>(
|
||||
socket: WebSocket,
|
||||
pool: LocalPoolHandle,
|
||||
app: fn(Scope<T>) -> Element,
|
||||
props: T,
|
||||
) where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
let (mut user_ws_tx, mut user_ws_rx) = socket.split();
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
let (edits_tx, edits_rx) = mpsc::unbounded_channel();
|
||||
let mut edits_rx = UnboundedReceiverStream::new(edits_rx);
|
||||
let mut event_rx = UnboundedReceiverStream::new(event_rx);
|
||||
let vdom_fut = pool.clone().spawn_pinned(move || async move {
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let mut vdom = VirtualDom::new_with_props(app, props);
|
||||
let edits = vdom.rebuild();
|
||||
let serialized = serde_json::to_string(&edits.edits).unwrap();
|
||||
edits_tx.send(serialized).unwrap();
|
||||
|
||||
@@ -6,14 +6,30 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_util::task::LocalPoolHandle;
|
||||
use warp::ws::{Message, WebSocket};
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
impl crate::Liveview {
|
||||
pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) {
|
||||
connect(ws, self.pool.clone(), app).await;
|
||||
connect(ws, self.pool.clone(), app, ()).await;
|
||||
}
|
||||
pub async fn upgrade_with_props<T>(
|
||||
&self,
|
||||
ws: warp::ws::WebSocket,
|
||||
app: fn(Scope<T>) -> Element,
|
||||
props: T,
|
||||
) where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
connect(ws, self.pool.clone(), app, props).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
|
||||
pub async fn connect<T>(
|
||||
ws: WebSocket,
|
||||
pool: LocalPoolHandle,
|
||||
app: fn(Scope<T>) -> Element,
|
||||
props: T,
|
||||
) where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
// Use a counter to assign a new unique ID for this user.
|
||||
|
||||
// Split the socket into a sender and receive of messages.
|
||||
@@ -26,7 +42,7 @@ pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Ele
|
||||
let mut event_rx = UnboundedReceiverStream::new(event_rx);
|
||||
|
||||
let vdom_fut = pool.spawn_pinned(move || async move {
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let mut vdom = VirtualDom::new_with_props(app, props);
|
||||
|
||||
let edits = vdom.rebuild();
|
||||
|
||||
|
||||
@@ -418,6 +418,8 @@ function serialize_event(event) {
|
||||
clientY,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX,
|
||||
pageY,
|
||||
screenX,
|
||||
@@ -432,6 +434,8 @@ function serialize_event(event) {
|
||||
client_y: clientY,
|
||||
ctrl_key: ctrlKey,
|
||||
meta_key: metaKey,
|
||||
offset_x: offsetX,
|
||||
offset_y: offsetY,
|
||||
page_x: pageX,
|
||||
page_y: pageY,
|
||||
screen_x: screenX,
|
||||
|
||||
@@ -12,7 +12,7 @@ license = "MIT/Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-desktop = { path = "../desktop", version = "^0.2.0" }
|
||||
dioxus-desktop = { path = "../desktop", version = "^0.2.3" }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "dioxus-native-core-macro"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
description = "Build natively rendered apps with Dioxus"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
syn = { version = "1.0.11", features = ["extra-traits"] }
|
||||
quote = "1.0"
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
|
||||
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
||||
@@ -0,0 +1,703 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
mod sorted_slice;
|
||||
|
||||
use dioxus_native_core::state::MemberId;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::format_ident;
|
||||
use quote::{quote, ToTokens, __private::Span};
|
||||
use sorted_slice::StrSlice;
|
||||
use syn::{
|
||||
self,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
|
||||
};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
|
||||
let slice: StrSlice = parse_macro_input!(input as StrSlice);
|
||||
let strings = slice.map.values();
|
||||
quote!([#(#strings, )*]).into()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
enum DepKind {
|
||||
Node,
|
||||
Child,
|
||||
Parent,
|
||||
}
|
||||
|
||||
#[proc_macro_derive(
|
||||
State,
|
||||
attributes(node_dep_state, child_dep_state, parent_dep_state, state)
|
||||
)]
|
||||
pub fn state_macro_derive(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse(input).unwrap();
|
||||
impl_derive_macro(&ast)
|
||||
}
|
||||
|
||||
fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let type_name = &ast.ident;
|
||||
let fields: Vec<_> = match &ast.data {
|
||||
syn::Data::Struct(data) => match &data.fields {
|
||||
syn::Fields::Named(e) => &e.named,
|
||||
syn::Fields::Unnamed(_) => todo!("unnamed fields"),
|
||||
syn::Fields::Unit => todo!("unit structs"),
|
||||
}
|
||||
.iter()
|
||||
.collect(),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let strct = Struct::new(type_name.clone(), &fields);
|
||||
match StateStruct::parse(&fields, &strct) {
|
||||
Ok(state_strct) => {
|
||||
let node_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::Node)
|
||||
.map(|f| f.reduce_self());
|
||||
let child_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::Child)
|
||||
.map(|f| f.reduce_self());
|
||||
let parent_dep_state_fields = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|f| f.dep_kind == DepKind::Parent)
|
||||
.map(|f| f.reduce_self());
|
||||
|
||||
let node_iter = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|m| m.dep_kind == DepKind::Node);
|
||||
let node_ids = node_iter.clone().map(|m| m.member_id.0);
|
||||
let node_ids_clone = node_ids.clone();
|
||||
let node_types = node_iter.map(|f| &f.mem.ty);
|
||||
|
||||
let child_iter = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|m| m.dep_kind == DepKind::Child);
|
||||
let child_ids = child_iter.clone().map(|m| m.member_id.0);
|
||||
let child_ids_clone = child_ids.clone();
|
||||
let child_types = child_iter.map(|f| &f.mem.ty);
|
||||
|
||||
let parent_iter = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.filter(|m| m.dep_kind == DepKind::Parent);
|
||||
let parent_ids = parent_iter.clone().map(|m| m.member_id.0);
|
||||
let parent_ids_clone = parent_ids.clone();
|
||||
let parent_types = parent_iter.map(|f| &f.mem.ty);
|
||||
|
||||
let type_name_str = type_name.to_string();
|
||||
|
||||
let child_states = &state_strct.child_states;
|
||||
|
||||
let member_size = state_strct.state_members.len();
|
||||
|
||||
let child_state_ty = child_states.iter().map(|m| &m.ty);
|
||||
let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect();
|
||||
let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| {
|
||||
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
|
||||
let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
|
||||
if i == 0 {
|
||||
quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1;
|
||||
const #ident: usize = #member_size + #ty::SIZE;)
|
||||
} else {
|
||||
let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
|
||||
quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1;
|
||||
const #ident: usize = #prev_ident + #ty::SIZE;)
|
||||
}
|
||||
});
|
||||
let sum_idents: Vec<_> = std::iter::once(quote!(#member_size))
|
||||
.chain((0..child_states.len()).map(|i| {
|
||||
let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
|
||||
quote!(#ident)
|
||||
}))
|
||||
.collect();
|
||||
|
||||
let child_state_ranges: Vec<_> = (0..child_state_ty.len())
|
||||
.map(|i| {
|
||||
let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
|
||||
let previous = if i == 0 {
|
||||
quote!(#member_size)
|
||||
} else {
|
||||
let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
|
||||
quote!(#ident)
|
||||
};
|
||||
quote!(#previous..=#current)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let gen = quote! {
|
||||
#(
|
||||
#sum_const_declarations
|
||||
)*
|
||||
impl State for #type_name{
|
||||
const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*;
|
||||
|
||||
fn update_node_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: dioxus_native_core::state::MemberId,
|
||||
node: &'a dioxus_core::VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
ctx: &anymap::AnyMap,
|
||||
) -> Option<dioxus_native_core::state::NodeStatesChanged>{
|
||||
use dioxus_native_core::state::NodeDepState as _;
|
||||
use dioxus_native_core::state::State as _;
|
||||
match ty.0{
|
||||
#(
|
||||
#node_ids => #node_dep_state_fields,
|
||||
)*
|
||||
#(
|
||||
#child_state_ranges => {
|
||||
self.#child_state_idents.update_node_dep_state(
|
||||
ty - #sum_idents,
|
||||
node,
|
||||
vdom,
|
||||
ctx,
|
||||
).map(|mut changed|{
|
||||
for id in &mut changed.node_dep{
|
||||
*id += #sum_idents;
|
||||
}
|
||||
changed
|
||||
})
|
||||
}
|
||||
)*
|
||||
_ => panic!("{:?} not in {}", ty, #type_name_str),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_parent_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: dioxus_native_core::state::MemberId,
|
||||
node: &'a dioxus_core::VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
parent: Option<&Self>,
|
||||
ctx: &anymap::AnyMap,
|
||||
) -> Option<dioxus_native_core::state::ParentStatesChanged>{
|
||||
use dioxus_native_core::state::ParentDepState as _;
|
||||
match ty.0{
|
||||
#(
|
||||
#parent_ids => #parent_dep_state_fields,
|
||||
)*
|
||||
#(
|
||||
#child_state_ranges => {
|
||||
self.#child_state_idents.update_parent_dep_state(
|
||||
ty - #sum_idents,
|
||||
node,
|
||||
vdom,
|
||||
parent.map(|p| &p.#child_state_idents),
|
||||
ctx,
|
||||
).map(|mut changed|{
|
||||
for id in &mut changed.node_dep{
|
||||
*id += #sum_idents;
|
||||
}
|
||||
for id in &mut changed.parent_dep{
|
||||
*id += #sum_idents;
|
||||
}
|
||||
changed
|
||||
})
|
||||
}
|
||||
)*
|
||||
_ => panic!("{:?} not in {}", ty, #type_name_str),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_child_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: dioxus_native_core::state::MemberId,
|
||||
node: &'a dioxus_core::VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
children: &Vec<&Self>,
|
||||
ctx: &anymap::AnyMap,
|
||||
) -> Option<dioxus_native_core::state::ChildStatesChanged>{
|
||||
use dioxus_native_core::state::ChildDepState as _;
|
||||
match ty.0{
|
||||
#(
|
||||
#child_ids => #child_dep_state_fields,
|
||||
)*
|
||||
#(
|
||||
#child_state_ranges => {
|
||||
self.#child_state_idents.update_child_dep_state(
|
||||
ty - #sum_idents,
|
||||
node,
|
||||
vdom,
|
||||
&children.iter().map(|p| &p.#child_state_idents).collect(),
|
||||
ctx,
|
||||
).map(|mut changed|{
|
||||
for id in &mut changed.node_dep{
|
||||
*id += #sum_idents;
|
||||
}
|
||||
for id in &mut changed.child_dep{
|
||||
*id += #sum_idents;
|
||||
}
|
||||
changed
|
||||
})
|
||||
}
|
||||
)*
|
||||
_ => panic!("{:?} not in {}", ty, #type_name_str),
|
||||
}
|
||||
}
|
||||
|
||||
fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #child_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone));
|
||||
})*
|
||||
#(
|
||||
dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents));
|
||||
)*
|
||||
dep_types
|
||||
}
|
||||
|
||||
fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #parent_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
|
||||
})*
|
||||
#(
|
||||
dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents));
|
||||
)*
|
||||
dep_types
|
||||
}
|
||||
|
||||
fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
|
||||
let mut dep_types = Vec::new();
|
||||
#(if #node_types::NODE_MASK.overlaps(mask) {
|
||||
dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone));
|
||||
})*
|
||||
#(
|
||||
dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents));
|
||||
)*
|
||||
dep_types
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
Err(e) => e.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct Struct {
|
||||
name: Ident,
|
||||
members: Vec<Member>,
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
fn new(name: Ident, fields: &[&Field]) -> Self {
|
||||
let members = fields.iter().filter_map(|f| Member::parse(f)).collect();
|
||||
Self { name, members }
|
||||
}
|
||||
}
|
||||
|
||||
struct StateStruct<'a> {
|
||||
state_members: Vec<StateMember<'a>>,
|
||||
child_states: Vec<&'a Member>,
|
||||
}
|
||||
|
||||
impl<'a> StateStruct<'a> {
|
||||
fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
|
||||
let mut parse_err = Ok(());
|
||||
let state_members = strct
|
||||
.members
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.filter_map(|(m, f)| match StateMember::parse(f, m, strct) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
parse_err = Err(err);
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let child_states = strct
|
||||
.members
|
||||
.iter()
|
||||
.zip(fields.iter())
|
||||
.filter(|(_, f)| {
|
||||
f.attrs.iter().any(|a| {
|
||||
a.path
|
||||
.get_ident()
|
||||
.filter(|i| i.to_string().as_str() == "state")
|
||||
.is_some()
|
||||
})
|
||||
})
|
||||
.map(|(m, _)| m);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DepNode<'a> {
|
||||
state_mem: StateMember<'a>,
|
||||
depandants: Vec<DepNode<'a>>,
|
||||
}
|
||||
impl<'a> DepNode<'a> {
|
||||
fn new(state_mem: StateMember<'a>) -> Self {
|
||||
Self {
|
||||
state_mem,
|
||||
depandants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// flattens the node in pre order
|
||||
fn flatten(self) -> Vec<StateMember<'a>> {
|
||||
let DepNode {
|
||||
state_mem,
|
||||
depandants,
|
||||
} = self;
|
||||
let mut flat = vec![state_mem];
|
||||
for d in depandants {
|
||||
flat.append(&mut d.flatten());
|
||||
}
|
||||
flat
|
||||
}
|
||||
|
||||
fn set_ids(&mut self, current_id: &mut usize) {
|
||||
self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id);
|
||||
// if the node depends on itself, we need to add the dependency seperately
|
||||
if let Some(dep) = self.state_mem.dep_mem {
|
||||
if dep == self.state_mem.mem {
|
||||
self.state_mem
|
||||
.dependants
|
||||
.push((MemberId(*current_id), self.state_mem.dep_kind.clone()));
|
||||
}
|
||||
}
|
||||
*current_id += 1;
|
||||
for d in &mut self.depandants {
|
||||
self.state_mem
|
||||
.dependants
|
||||
.push((MemberId(*current_id), d.state_mem.dep_kind.clone()));
|
||||
d.set_ids(current_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_member(&self, member: &Member) -> bool {
|
||||
if self.state_mem.mem == member {
|
||||
true
|
||||
} else {
|
||||
self.depandants.iter().any(|d| d.contains_member(member))
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are any mixed child/parent dependancies
|
||||
fn check(&self) -> Option<Error> {
|
||||
self.kind().err()
|
||||
}
|
||||
|
||||
fn kind(&self) -> Result<&DepKind> {
|
||||
fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> {
|
||||
match (dk1, dk2) {
|
||||
(DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => {
|
||||
Err(Error::new(
|
||||
Span::call_site(),
|
||||
"There is a ChildDepState that depends on a ParentDepState",
|
||||
))
|
||||
}
|
||||
// node dep state takes the lowest priority
|
||||
(DepKind::Node, important) | (important, DepKind::Node) => Ok(important),
|
||||
// they are the same
|
||||
(fst, _) => Ok(fst),
|
||||
}
|
||||
}
|
||||
reduce_kind(
|
||||
self.depandants
|
||||
.iter()
|
||||
.try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?,
|
||||
&self.state_mem.dep_kind,
|
||||
)
|
||||
}
|
||||
|
||||
fn insert_dependant(&mut self, other: DepNode<'a>) -> bool {
|
||||
let dep = other.state_mem.dep_mem.unwrap();
|
||||
if self.contains_member(dep) {
|
||||
if self.state_mem.mem == dep {
|
||||
self.depandants.push(other);
|
||||
true
|
||||
} else {
|
||||
self.depandants
|
||||
.iter_mut()
|
||||
.find(|d| d.contains_member(dep))
|
||||
.unwrap()
|
||||
.insert_dependant(other)
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// members need to be sorted so that members are updated after the members they depend on
|
||||
let mut roots: Vec<DepNode> = vec![];
|
||||
for m in state_members {
|
||||
if let Some(dep) = m.dep_mem {
|
||||
let root_depends_on = roots
|
||||
.iter()
|
||||
.filter_map(|m| m.state_mem.dep_mem)
|
||||
.any(|d| m.mem == d);
|
||||
|
||||
if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) {
|
||||
let new = DepNode::new(m);
|
||||
if root_depends_on {
|
||||
return Err(Error::new(
|
||||
new.state_mem.mem.ident.span(),
|
||||
format!("{} has a circular dependancy", new.state_mem.mem.ident),
|
||||
));
|
||||
}
|
||||
// return Err(Error::new(new.state_mem.mem.ident.span(), "stuff"));
|
||||
r.insert_dependant(new);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let mut new = DepNode::new(m);
|
||||
let mut i = 0;
|
||||
while i < roots.len() {
|
||||
if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) {
|
||||
let child = roots.remove(i);
|
||||
new.insert_dependant(child);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
roots.push(new);
|
||||
}
|
||||
parse_err?;
|
||||
let mut current_id = 0;
|
||||
for r in &mut roots {
|
||||
r.set_ids(&mut current_id);
|
||||
}
|
||||
if let Some(err) = roots.iter().find_map(DepNode::check) {
|
||||
Err(err)
|
||||
} else {
|
||||
let state_members: Vec<_> = roots
|
||||
.into_iter()
|
||||
.flat_map(|r| r.flatten().into_iter())
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
state_members,
|
||||
child_states: child_states.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Dependancy {
|
||||
ctx_ty: Option<Type>,
|
||||
dep: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Dependancy {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let dep = input
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|i: &Ident| format!("{}", i) != "NONE");
|
||||
let comma: Option<Token![,]> = input.parse().ok();
|
||||
let ctx_ty = input.parse().ok();
|
||||
Ok(Self {
|
||||
ctx_ty: comma.and(ctx_ty),
|
||||
dep,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Member {
|
||||
ty: Type,
|
||||
ident: Ident,
|
||||
}
|
||||
|
||||
impl Member {
|
||||
fn parse(field: &Field) -> Option<Self> {
|
||||
Some(Self {
|
||||
ty: field.ty.clone(),
|
||||
ident: field.ident.as_ref()?.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StateMember<'a> {
|
||||
mem: &'a Member,
|
||||
dep_kind: DepKind,
|
||||
dep_mem: Option<&'a Member>,
|
||||
ctx_ty: Option<Type>,
|
||||
dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>,
|
||||
// This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in.
|
||||
member_id: dioxus_native_core::state::MemberId,
|
||||
}
|
||||
|
||||
impl<'a> StateMember<'a> {
|
||||
fn parse(
|
||||
field: &Field,
|
||||
mem: &'a Member,
|
||||
parent: &'a Struct,
|
||||
) -> Result<Option<StateMember<'a>>> {
|
||||
let mut err = Ok(());
|
||||
let member = field.attrs.iter().find_map(|a| {
|
||||
let dep_kind = a
|
||||
.path
|
||||
.get_ident()
|
||||
.and_then(|i| match i.to_string().as_str() {
|
||||
"node_dep_state" => Some(DepKind::Node),
|
||||
"child_dep_state" => Some(DepKind::Child),
|
||||
"parent_dep_state" => Some(DepKind::Parent),
|
||||
_ => None,
|
||||
})?;
|
||||
match a.parse_args::<Dependancy>() {
|
||||
Ok(dependancy) => {
|
||||
let dep_mem = if let Some(name) = &dependancy.dep {
|
||||
if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
|
||||
Some(found)
|
||||
} else {
|
||||
err = Err(Error::new(
|
||||
name.span(),
|
||||
format!("{} not found in {}", name, parent.name),
|
||||
));
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Self {
|
||||
mem,
|
||||
dep_kind,
|
||||
dep_mem,
|
||||
ctx_ty: dependancy.ctx_ty,
|
||||
dependants: Vec::new(),
|
||||
member_id: dioxus_native_core::state::MemberId(0),
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
err = Err(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
err?;
|
||||
Ok(member)
|
||||
}
|
||||
|
||||
fn reduce_self(&self) -> quote::__private::TokenStream {
|
||||
let ident = &self.mem.ident;
|
||||
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||
if ctx_ty == &parse_quote!(()) {
|
||||
quote! {&()}
|
||||
} else {
|
||||
let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
|
||||
quote! {ctx.get().expect(#msg)}
|
||||
}
|
||||
} else {
|
||||
quote! {&()}
|
||||
};
|
||||
let states_changed = {
|
||||
let child_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::Child)
|
||||
.map(|(id, _)| id.0);
|
||||
let parent_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::Parent)
|
||||
.map(|(id, _)| id.0);
|
||||
let node_dep = self
|
||||
.dependants
|
||||
.iter()
|
||||
.filter(|(_, kind)| kind == &DepKind::Node)
|
||||
.map(|(id, _)| id.0);
|
||||
match self.dep_kind {
|
||||
DepKind::Node => {
|
||||
quote! {
|
||||
dioxus_native_core::state::NodeStatesChanged{
|
||||
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
DepKind::Child => {
|
||||
quote! {
|
||||
dioxus_native_core::state::ChildStatesChanged{
|
||||
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
DepKind::Parent => {
|
||||
quote! {
|
||||
dioxus_native_core::state::ParentStatesChanged{
|
||||
node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
|
||||
parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ty = &self.mem.ty;
|
||||
let node_view =
|
||||
quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom));
|
||||
if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) {
|
||||
match self.dep_kind {
|
||||
DepKind::Node => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::Child => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::Parent => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.dep_kind {
|
||||
DepKind::Node => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, &(), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::Child => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
DepKind::Parent => {
|
||||
quote!({
|
||||
if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
|
||||
Some(#states_changed)
|
||||
} else{
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use syn::{
|
||||
self, bracketed,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
LitStr, Token,
|
||||
};
|
||||
pub struct StrSlice {
|
||||
pub map: BTreeMap<String, LitStr>,
|
||||
}
|
||||
|
||||
impl Parse for StrSlice {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let content;
|
||||
bracketed!(content in input);
|
||||
let mut map = BTreeMap::new();
|
||||
while let Ok(s) = content.parse::<LitStr>() {
|
||||
map.insert(s.value(), s);
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
Ok(StrSlice { map })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use dioxus_core::VNode;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::state::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Empty {}
|
||||
|
||||
#[test]
|
||||
fn remove_node() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let child_div = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
let child_div_el = VNode::Element(&child_div);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_el],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 2);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
assert_eq!(dom[2].height, 2);
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let mutations = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
);
|
||||
dom.apply_mutations(vec![mutations.1]);
|
||||
|
||||
let new_root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_node() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let mutations = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
rsx! {
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
},
|
||||
);
|
||||
dom.apply_mutations(vec![mutations.1]);
|
||||
|
||||
let child_div = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
let child_div_el = VNode::Element(&child_div);
|
||||
let new_root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_el],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 2);
|
||||
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
assert_eq!(dom[2].height, 2);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use dioxus_core::VNode;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::state::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(Default, Clone, State)]
|
||||
struct Empty {}
|
||||
|
||||
#[test]
|
||||
fn initial_build_simple() {
|
||||
use std::cell::Cell;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_build_with_children() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{
|
||||
div{
|
||||
"hello"
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
"hello world"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let first_text = VText {
|
||||
id: Cell::new(Some(ElementId(3))),
|
||||
text: "hello",
|
||||
is_static: true,
|
||||
};
|
||||
let first_text_node = VNode::Text(&first_text);
|
||||
let child_text = VText {
|
||||
id: Cell::new(Some(ElementId(5))),
|
||||
text: "world",
|
||||
is_static: true,
|
||||
};
|
||||
let child_text_node = VNode::Text(&child_text);
|
||||
let child_p_el = VElement {
|
||||
id: Cell::new(Some(ElementId(4))),
|
||||
key: None,
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(2))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_text_node],
|
||||
};
|
||||
let child_p_node = VNode::Element(&child_p_el);
|
||||
let second_text = VText {
|
||||
id: Cell::new(Some(ElementId(6))),
|
||||
text: "hello world",
|
||||
is_static: true,
|
||||
};
|
||||
let second_text_node = VNode::Text(&second_text);
|
||||
let child_div_el = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[first_text_node, child_p_node, second_text_node],
|
||||
};
|
||||
let child_div_node = VNode::Element(&child_div_el);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_node],
|
||||
};
|
||||
assert_eq!(dom.size(), 6);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[1].height, 1);
|
||||
assert_eq!(dom[2].height, 2);
|
||||
assert_eq!(dom[3].height, 3);
|
||||
assert_eq!(dom[4].height, 3);
|
||||
assert_eq!(dom[5].height, 4);
|
||||
assert_eq!(dom[6].height, 3);
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::AttributeValue;
|
||||
use dioxus_core::VNode;
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
|
||||
use dioxus_native_core_macro::State;
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterStatePart1 {
|
||||
#[child_dep_state(child_counter)]
|
||||
child_counter: ChildDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterStatePart2 {
|
||||
#[parent_dep_state(parent_counter)]
|
||||
parent_counter: ParentDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterStatePart3 {
|
||||
#[node_dep_state()]
|
||||
node_counter: NodeDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct CallCounterState {
|
||||
#[child_dep_state(child_counter)]
|
||||
child_counter: ChildDepCallCounter,
|
||||
#[state]
|
||||
part2: CallCounterStatePart2,
|
||||
#[parent_dep_state(parent_counter)]
|
||||
parent_counter: ParentDepCallCounter,
|
||||
#[state]
|
||||
part1: CallCounterStatePart1,
|
||||
#[state]
|
||||
part3: CallCounterStatePart3,
|
||||
#[node_dep_state()]
|
||||
node_counter: NodeDepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ChildDepCallCounter(u32);
|
||||
impl ChildDepState for ChildDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct ParentDepCallCounter(u32);
|
||||
impl ParentDepState for ParentDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(
|
||||
&mut self,
|
||||
_node: NodeView,
|
||||
_parent: Option<&Self::DepState>,
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NodeDepCallCounter(u32);
|
||||
impl NodeDepState for NodeDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
|
||||
impl ChildDepState for BubbledUpStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = &'a Self::DepState>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = BubbledUpStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
children.into_iter().map(|c| Box::new(c.clone())).collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
|
||||
impl ParentDepState for PushedDownStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = Self;
|
||||
const NODE_MASK: NodeMask = NodeMask::new().with_tag();
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = PushedDownStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
parent.map(|c| Box::new(c.clone())),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct NodeStateTester(Option<String>, Vec<(String, String)>);
|
||||
impl NodeDepState for NodeStateTester {
|
||||
type Ctx = u32;
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
|
||||
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool {
|
||||
assert_eq!(*ctx, 42);
|
||||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
node.attributes()
|
||||
.map(|a| (a.name.to_string(), a.value.to_string()))
|
||||
.collect(),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(State, Clone, Default, Debug)]
|
||||
struct StateTester {
|
||||
#[child_dep_state(bubbled, u32)]
|
||||
bubbled: BubbledUpStateTester,
|
||||
#[parent_dep_state(pushed, u32)]
|
||||
pushed: PushedDownStateTester,
|
||||
#[node_dep_state(NONE, u32)]
|
||||
node: NodeStateTester,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_initial() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
p{}
|
||||
h1{}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
p{
|
||||
color: "red"
|
||||
}
|
||||
h1{}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<StateTester> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let mut ctx = AnyMap::new();
|
||||
ctx.insert(42u32);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
|
||||
|
||||
let root_div = &dom[1];
|
||||
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.bubbled.1,
|
||||
vec![
|
||||
Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
|
||||
Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
|
||||
]
|
||||
);
|
||||
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
);
|
||||
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.node.1, vec![]);
|
||||
|
||||
let child_p = &dom[2];
|
||||
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
|
||||
assert_eq!(child_p.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||
assert_eq!(
|
||||
child_p.state.node.1,
|
||||
vec![("color".to_string(), "red".to_string())]
|
||||
);
|
||||
|
||||
let child_h1 = &dom[3];
|
||||
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
|
||||
assert_eq!(
|
||||
child_h1.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.node.1, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_initally_called_minimally() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.state.part1.child_counter.0, 1);
|
||||
assert_eq!(n.state.child_counter.0, 1);
|
||||
assert_eq!(n.state.part2.parent_counter.0, 1);
|
||||
assert_eq!(n.state.parent_counter.0, 1);
|
||||
assert_eq!(n.state.part3.node_counter.0, 1);
|
||||
assert_eq!(n.state.node_counter.0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_parent_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
div{
|
||||
div{
|
||||
p{}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 1,
|
||||
field: "width",
|
||||
value: AttributeValue::Text("99%"),
|
||||
ns: Some("style"),
|
||||
}],
|
||||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.state.part2.parent_counter.0, 2);
|
||||
assert_eq!(n.state.parent_counter.0, 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_reduce_child_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
div{
|
||||
div{
|
||||
p{
|
||||
width: "100%",
|
||||
}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
div{
|
||||
div{
|
||||
p{
|
||||
width: "100%",
|
||||
}
|
||||
}
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
div{
|
||||
h1{}
|
||||
}
|
||||
p{
|
||||
"world"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 4,
|
||||
field: "width",
|
||||
value: AttributeValue::Text("99%"),
|
||||
ns: Some("style"),
|
||||
}],
|
||||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
println!("{:?}", n);
|
||||
assert_eq!(
|
||||
n.state.part1.child_counter.0,
|
||||
if n.id.0 > 4 { 1 } else { 2 }
|
||||
);
|
||||
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, State)]
|
||||
struct UnorderedDependanciesState {
|
||||
#[node_dep_state(c)]
|
||||
b: BDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
c: CDepCallCounter,
|
||||
#[node_dep_state(b)]
|
||||
a: ADepCallCounter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct ADepCallCounter(usize, BDepCallCounter);
|
||||
impl NodeDepState for ADepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = BDepCallCounter;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct BDepCallCounter(usize, CDepCallCounter);
|
||||
impl NodeDepState for BDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = CDepCallCounter;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
self.1 = sibling.clone();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
struct CDepCallCounter(usize);
|
||||
impl NodeDepState for CDepCallCounter {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependancies_order_independant() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {
|
||||
width: "100%",
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
p{
|
||||
"hello"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
|
||||
let c = CDepCallCounter(1);
|
||||
let b = BDepCallCounter(1, c.clone());
|
||||
let a = ADepCallCounter(1, b.clone());
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(&n.state.a, &a);
|
||||
assert_eq!(&n.state.b, &b);
|
||||
assert_eq!(&n.state.c, &c);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, State)]
|
||||
struct DependanciesStateTest {
|
||||
#[node_dep_state(c)]
|
||||
b: BDepCallCounter,
|
||||
#[node_dep_state()]
|
||||
c: CDepCallCounter,
|
||||
#[node_dep_state(b)]
|
||||
a: ADepCallCounter,
|
||||
#[state]
|
||||
child: UnorderedDependanciesState,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "dioxus-native-core"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
description = "Build natively rendered apps with Dioxus"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
|
||||
|
||||
stretch2 = "0.4.2"
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
- [ ] pub display: Display,
|
||||
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
|
||||
- [ ] pub direction: Direction,
|
||||
|
||||
- [x] pub flex_direction: FlexDirection,
|
||||
- [x] pub flex_wrap: FlexWrap,
|
||||
- [x] pub flex_grow: f32,
|
||||
- [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 align_items: AlignItems,
|
||||
- [x] pub align_self: AlignSelf,
|
||||
- [x] pub align_content: AlignContent,
|
||||
|
||||
- [x] pub margin: Rect<Dimension>,
|
||||
- [x] pub padding: Rect<Dimension>,
|
||||
|
||||
- [x] pub justify_content: JustifyContent,
|
||||
- [ ] pub position: Rect<Dimension>,
|
||||
- [x] pub border: Rect<Dimension>,
|
||||
|
||||
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
|
||||
- [ ] pub min_size: Size<Dimension>,
|
||||
- [ ] pub max_size: Size<Dimension>,
|
||||
|
||||
- [ ] pub aspect_ratio: Number,
|
||||
*/
|
||||
|
||||
use stretch2::{prelude::*, style::PositionType};
|
||||
|
||||
/// applies the entire html namespace defined in dioxus-html
|
||||
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"align-content"
|
||||
| "align-items"
|
||||
| "align-self" => apply_align(name, value, style),
|
||||
|
||||
"animation"
|
||||
| "animation-delay"
|
||||
| "animation-direction"
|
||||
| "animation-duration"
|
||||
| "animation-fill-mode"
|
||||
| "animation-iteration-count"
|
||||
| "animation-name"
|
||||
| "animation-play-state"
|
||||
| "animation-timing-function" => apply_animation(name, value, style),
|
||||
|
||||
"backface-visibility" => {}
|
||||
|
||||
"border"
|
||||
| "border-bottom"
|
||||
| "border-bottom-color"
|
||||
| "border-bottom-left-radius"
|
||||
| "border-bottom-right-radius"
|
||||
| "border-bottom-style"
|
||||
| "border-bottom-width"
|
||||
| "border-collapse"
|
||||
| "border-color"
|
||||
| "border-image"
|
||||
| "border-image-outset"
|
||||
| "border-image-repeat"
|
||||
| "border-image-slice"
|
||||
| "border-image-source"
|
||||
| "border-image-width"
|
||||
| "border-left"
|
||||
| "border-left-color"
|
||||
| "border-left-style"
|
||||
| "border-left-width"
|
||||
| "border-radius"
|
||||
| "border-right"
|
||||
| "border-right-color"
|
||||
| "border-right-style"
|
||||
| "border-right-width"
|
||||
| "border-spacing"
|
||||
| "border-style"
|
||||
| "border-top"
|
||||
| "border-top-color"
|
||||
| "border-top-left-radius"
|
||||
| "border-top-right-radius"
|
||||
| "border-top-style"
|
||||
| "border-top-width"
|
||||
| "border-width" => apply_border(name, value, style),
|
||||
|
||||
"bottom" => {}
|
||||
"box-shadow" => {}
|
||||
"box-sizing" => {}
|
||||
"caption-side" => {}
|
||||
"clear" => {}
|
||||
"clip" => {}
|
||||
|
||||
"column-count"
|
||||
| "column-fill"
|
||||
| "column-gap"
|
||||
| "column-rule"
|
||||
| "column-rule-color"
|
||||
| "column-rule-style"
|
||||
| "column-rule-width"
|
||||
| "column-span"
|
||||
// add column-width
|
||||
| "column-width" => apply_column(name, value, style),
|
||||
|
||||
"columns" => {}
|
||||
|
||||
"content" => {}
|
||||
"counter-increment" => {}
|
||||
"counter-reset" => {}
|
||||
|
||||
"cursor" => {}
|
||||
"direction" => {
|
||||
match value {
|
||||
"ltr" => style.direction = Direction::LTR,
|
||||
"rtl" => style.direction = Direction::RTL,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
"display" => apply_display(name, value, style),
|
||||
|
||||
"empty-cells" => {}
|
||||
|
||||
"flex"
|
||||
| "flex-basis"
|
||||
| "flex-direction"
|
||||
| "flex-flow"
|
||||
| "flex-grow"
|
||||
| "flex-shrink"
|
||||
| "flex-wrap" => apply_flex(name, value, style),
|
||||
|
||||
"float" => {}
|
||||
|
||||
"height" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.size.height = match v {
|
||||
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
|
||||
UnitSystem::Point(v)=> Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"justify-content" => {
|
||||
use JustifyContent::*;
|
||||
style.justify_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
"space-evenly" => SpaceEvenly,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"left" => {}
|
||||
"letter-spacing" => {}
|
||||
"line-height" => {}
|
||||
|
||||
"list-style"
|
||||
| "list-style-image"
|
||||
| "list-style-position"
|
||||
| "list-style-type" => {}
|
||||
|
||||
"margin"
|
||||
| "margin-bottom"
|
||||
| "margin-left"
|
||||
| "margin-right"
|
||||
| "margin-top" => apply_margin(name, value, style),
|
||||
|
||||
"max-height" => {}
|
||||
"max-width" => {}
|
||||
"min-height" => {}
|
||||
"min-width" => {}
|
||||
|
||||
"opacity" => {}
|
||||
"order" => {}
|
||||
"outline" => {}
|
||||
|
||||
"outline-color"
|
||||
| "outline-offset"
|
||||
| "outline-style"
|
||||
| "outline-width" => {}
|
||||
|
||||
"overflow"
|
||||
| "overflow-x"
|
||||
| "overflow-y" => apply_overflow(name, value, style),
|
||||
|
||||
"padding"
|
||||
| "padding-bottom"
|
||||
| "padding-left"
|
||||
| "padding-right"
|
||||
| "padding-top" => apply_padding(name, value, style),
|
||||
|
||||
"page-break-after"
|
||||
| "page-break-before"
|
||||
| "page-break-inside" => {}
|
||||
|
||||
"perspective"
|
||||
| "perspective-origin" => {}
|
||||
|
||||
"position" => {
|
||||
match value {
|
||||
"static" => {}
|
||||
"relative" => style.position_type = PositionType::Relative,
|
||||
"fixed" => {}
|
||||
"absolute" => style.position_type = PositionType::Absolute,
|
||||
"sticky" => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"pointer-events" => {}
|
||||
|
||||
"quotes" => {}
|
||||
"resize" => {}
|
||||
"right" => {}
|
||||
"tab-size" => {}
|
||||
"table-layout" => {}
|
||||
|
||||
"top" => {}
|
||||
|
||||
"transform"
|
||||
| "transform-origin"
|
||||
| "transform-style" => apply_transform(name, value, style),
|
||||
|
||||
"transition"
|
||||
| "transition-delay"
|
||||
| "transition-duration"
|
||||
| "transition-property"
|
||||
| "transition-timing-function" => apply_transition(name, value, style),
|
||||
|
||||
"vertical-align" => {}
|
||||
"visibility" => {}
|
||||
"white-space" => {}
|
||||
"width" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.size.width = match v {
|
||||
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
|
||||
UnitSystem::Point(v)=> Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"word-break" => {}
|
||||
"word-spacing" => {}
|
||||
"word-wrap" => {}
|
||||
"z-index" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// a relative or absolute size
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum UnitSystem {
|
||||
Percent(f32),
|
||||
Point(f32),
|
||||
}
|
||||
|
||||
impl From<UnitSystem> for Dimension {
|
||||
fn from(other: UnitSystem) -> Dimension {
|
||||
match other {
|
||||
UnitSystem::Percent(v) => Dimension::Percent(v),
|
||||
UnitSystem::Point(v) => Dimension::Points(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// parse relative or absolute value
|
||||
pub fn parse_value(value: &str) -> Option<UnitSystem> {
|
||||
if value.ends_with("px") {
|
||||
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
|
||||
Some(UnitSystem::Point(px))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if value.ends_with('%') {
|
||||
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
|
||||
Some(UnitSystem::Percent(pct))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_overflow(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
// todo: add more overflow support to stretch2
|
||||
"overflow" | "overflow-x" | "overflow-y" => {
|
||||
style.overflow = match value {
|
||||
"auto" => Overflow::Visible,
|
||||
"hidden" => Overflow::Hidden,
|
||||
"scroll" => Overflow::Scroll,
|
||||
"visible" => Overflow::Visible,
|
||||
_ => Overflow::Visible,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_display(_name: &str, value: &str, style: &mut Style) {
|
||||
style.display = match value {
|
||||
"flex" => Display::Flex,
|
||||
"block" => Display::None,
|
||||
_ => Display::Flex,
|
||||
}
|
||||
|
||||
// TODO: there are way more variants
|
||||
// stretch needs to be updated to handle them
|
||||
//
|
||||
// "block" => Display::Block,
|
||||
// "inline" => Display::Inline,
|
||||
// "inline-block" => Display::InlineBlock,
|
||||
// "inline-table" => Display::InlineTable,
|
||||
// "list-item" => Display::ListItem,
|
||||
// "run-in" => Display::RunIn,
|
||||
// "table" => Display::Table,
|
||||
// "table-caption" => Display::TableCaption,
|
||||
// "table-cell" => Display::TableCell,
|
||||
// "table-column" => Display::TableColumn,
|
||||
// "table-column-group" => Display::TableColumnGroup,
|
||||
// "table-footer-group" => Display::TableFooterGroup,
|
||||
// "table-header-group" => Display::TableHeaderGroup,
|
||||
// "table-row" => Display::TableRow,
|
||||
// "table-row-group" => Display::TableRowGroup,
|
||||
// "none" => Display::None,
|
||||
// _ => Display::Inline,
|
||||
}
|
||||
|
||||
fn apply_border(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"border" => {}
|
||||
"border-bottom" => {}
|
||||
"border-bottom-color" => {}
|
||||
"border-bottom-left-radius" => {}
|
||||
"border-bottom-right-radius" => {}
|
||||
"border-bottom-style" => {
|
||||
if style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.bottom = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.bottom = v.into();
|
||||
}
|
||||
}
|
||||
"border-collapse" => {}
|
||||
"border-color" => {}
|
||||
"border-image" => {}
|
||||
"border-image-outset" => {}
|
||||
"border-image-repeat" => {}
|
||||
"border-image-slice" => {}
|
||||
"border-image-source" => {}
|
||||
"border-image-width" => {}
|
||||
"border-left" => {}
|
||||
"border-left-color" => {}
|
||||
"border-left-style" => {
|
||||
if style.border.start == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.start = v;
|
||||
}
|
||||
}
|
||||
"border-left-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.start = v.into();
|
||||
}
|
||||
}
|
||||
"border-radius" => {}
|
||||
"border-right" => {}
|
||||
"border-right-color" => {}
|
||||
"border-right-style" => {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.end = v;
|
||||
}
|
||||
"border-right-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.end = v.into();
|
||||
}
|
||||
}
|
||||
"border-spacing" => {}
|
||||
"border-style" => {
|
||||
if style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.top = v;
|
||||
}
|
||||
if style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.bottom = v;
|
||||
}
|
||||
if style.border.start == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.start = v;
|
||||
}
|
||||
if style.border.end == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.end = v;
|
||||
}
|
||||
}
|
||||
"border-top" => {}
|
||||
"border-top-color" => {}
|
||||
"border-top-left-radius" => {}
|
||||
"border-top-right-radius" => {}
|
||||
"border-top-style" => {
|
||||
if style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.top = v;
|
||||
}
|
||||
}
|
||||
"border-top-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.top = v.into();
|
||||
}
|
||||
}
|
||||
"border-width" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(w) = parse_value(values[0]) {
|
||||
style.border.top = w.into();
|
||||
style.border.bottom = w.into();
|
||||
style.border.start = w.into();
|
||||
style.border.end = w.into();
|
||||
}
|
||||
} else {
|
||||
let border_widths = [
|
||||
&mut style.border.top,
|
||||
&mut style.border.bottom,
|
||||
&mut style.border.start,
|
||||
&mut style.border.end,
|
||||
];
|
||||
for (v, width) in values.into_iter().zip(border_widths) {
|
||||
if let Some(w) = parse_value(v) {
|
||||
*width = w.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_animation(name: &str, _value: &str, _style: &mut Style) {
|
||||
match name {
|
||||
"animation" => {}
|
||||
"animation-delay" => {}
|
||||
"animation-direction =>{}" => {}
|
||||
"animation-duration" => {}
|
||||
"animation-fill-mode" => {}
|
||||
"animation-itera =>{}tion-count" => {}
|
||||
"animation-name" => {}
|
||||
"animation-play-state" => {}
|
||||
"animation-timing-function" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_column(name: &str, _value: &str, _style: &mut Style) {
|
||||
match name {
|
||||
"column-count" => {}
|
||||
"column-fill" => {}
|
||||
"column-gap" => {}
|
||||
"column-rule" => {}
|
||||
"column-rule-color" => {}
|
||||
"column-rule-style" => {}
|
||||
"column-rule-width" => {}
|
||||
"column-span" => {}
|
||||
"column-width" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_flex(name: &str, value: &str, style: &mut Style) {
|
||||
// - [x] pub flex_direction: FlexDirection,
|
||||
// - [x] pub flex_wrap: FlexWrap,
|
||||
// - [x] pub flex_grow: f32,
|
||||
// - [x] pub flex_shrink: f32,
|
||||
// - [x] pub flex_basis: Dimension,
|
||||
|
||||
match name {
|
||||
"flex" => {}
|
||||
"flex-direction" => {
|
||||
use FlexDirection::*;
|
||||
style.flex_direction = match value {
|
||||
"row" => Row,
|
||||
"row-reverse" => RowReverse,
|
||||
"column" => Column,
|
||||
"column-reverse" => ColumnReverse,
|
||||
_ => Row,
|
||||
};
|
||||
}
|
||||
"flex-basis" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.flex_basis = match v {
|
||||
UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
|
||||
UnitSystem::Point(v) => Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"flex-flow" => {}
|
||||
"flex-grow" => {
|
||||
if let Ok(val) = value.parse::<f32>() {
|
||||
style.flex_grow = val;
|
||||
}
|
||||
}
|
||||
"flex-shrink" => {
|
||||
if let Ok(px) = value.parse::<f32>() {
|
||||
style.flex_shrink = px;
|
||||
}
|
||||
}
|
||||
"flex-wrap" => {
|
||||
use FlexWrap::*;
|
||||
style.flex_wrap = match value {
|
||||
"nowrap" => NoWrap,
|
||||
"wrap" => Wrap,
|
||||
"wrap-reverse" => WrapReverse,
|
||||
_ => NoWrap,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_padding(name: &str, value: &str, style: &mut Style) {
|
||||
match parse_value(value) {
|
||||
Some(UnitSystem::Percent(v)) => match name {
|
||||
"padding" => {
|
||||
let v = Dimension::Percent(v / 100.0);
|
||||
style.padding.top = v;
|
||||
style.padding.bottom = v;
|
||||
style.padding.start = v;
|
||||
style.padding.end = v;
|
||||
}
|
||||
"padding-bottom" => style.padding.bottom = Dimension::Percent(v / 100.0),
|
||||
"padding-left" => style.padding.start = Dimension::Percent(v / 100.0),
|
||||
"padding-right" => style.padding.end = Dimension::Percent(v / 100.0),
|
||||
"padding-top" => style.padding.top = Dimension::Percent(v / 100.0),
|
||||
_ => {}
|
||||
},
|
||||
Some(UnitSystem::Point(v)) => match name {
|
||||
"padding" => {
|
||||
style.padding.top = Dimension::Points(v);
|
||||
style.padding.bottom = Dimension::Points(v);
|
||||
style.padding.start = Dimension::Points(v);
|
||||
style.padding.end = Dimension::Points(v);
|
||||
}
|
||||
"padding-bottom" => style.padding.bottom = Dimension::Points(v),
|
||||
"padding-left" => style.padding.start = Dimension::Points(v),
|
||||
"padding-right" => style.padding.end = Dimension::Points(v),
|
||||
"padding-top" => style.padding.top = Dimension::Points(v),
|
||||
_ => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_transform(_name: &str, _value: &str, _style: &mut Style) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_transition(_name: &str, _value: &str, _style: &mut Style) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_align(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"align-items" => {
|
||||
use AlignItems::*;
|
||||
style.align_items = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-content" => {
|
||||
use AlignContent::*;
|
||||
style.align_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-self" => {
|
||||
use AlignSelf::*;
|
||||
style.align_self = match value {
|
||||
"auto" => Auto,
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => Auto,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_margin(name: &str, value: &str, style: &mut Style) {
|
||||
match parse_value(value) {
|
||||
Some(UnitSystem::Percent(v)) => match name {
|
||||
"margin" => {
|
||||
let v = Dimension::Percent(v / 100.0);
|
||||
style.margin.top = v;
|
||||
style.margin.bottom = v;
|
||||
style.margin.start = v;
|
||||
style.margin.end = v;
|
||||
}
|
||||
"margin-top" => style.margin.top = Dimension::Percent(v / 100.0),
|
||||
"margin-bottom" => style.margin.bottom = Dimension::Percent(v / 100.0),
|
||||
"margin-left" => style.margin.start = Dimension::Percent(v / 100.0),
|
||||
"margin-right" => style.margin.end = Dimension::Percent(v / 100.0),
|
||||
_ => {}
|
||||
},
|
||||
Some(UnitSystem::Point(v)) => match name {
|
||||
"margin" => {
|
||||
style.margin.top = Dimension::Points(v);
|
||||
style.margin.bottom = Dimension::Points(v);
|
||||
style.margin.start = Dimension::Points(v);
|
||||
style.margin.end = Dimension::Points(v);
|
||||
}
|
||||
"margin-top" => style.margin.top = Dimension::Points(v),
|
||||
"margin-bottom" => style.margin.bottom = Dimension::Points(v),
|
||||
"margin-left" => style.margin.start = Dimension::Points(v),
|
||||
"margin-right" => style.margin.end = Dimension::Points(v),
|
||||
_ => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod layout_attributes;
|
||||
pub mod node_ref;
|
||||
pub mod real_dom;
|
||||
pub mod state;
|
||||
@@ -0,0 +1,249 @@
|
||||
use dioxus_core::*;
|
||||
|
||||
use crate::state::union_ordered_iter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VNode<'a>,
|
||||
mask: NodeMask,
|
||||
}
|
||||
impl<'a> NodeView<'a> {
|
||||
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
|
||||
if let VNode::Component(sc) = vnode {
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
vnode = scope.root_node();
|
||||
}
|
||||
Self {
|
||||
inner: vnode,
|
||||
mask: view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ElementId {
|
||||
self.inner.mounted_id()
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
|
||||
}
|
||||
|
||||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then(|| self.el().and_then(|el| el.namespace))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||
self.el()
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|a| self.mask.attritutes.contains_attribute(a.name))
|
||||
}
|
||||
|
||||
pub fn text(&self) -> Option<&str> {
|
||||
self.mask
|
||||
.text
|
||||
.then(|| self.txt().map(|txt| txt.text))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn listeners(&self) -> &'a [Listener<'a>] {
|
||||
self.el().map(|el| el.listeners).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn el(&self) -> Option<&'a VElement<'a>> {
|
||||
if let VNode::Element(el) = &self.inner {
|
||||
Some(el)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn txt(&self) -> Option<&'a VText<'a>> {
|
||||
if let VNode::Text(txt) = &self.inner {
|
||||
Some(txt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum AttributeMask {
|
||||
All,
|
||||
Dynamic(Vec<&'static str>),
|
||||
Static(&'static [&'static str]),
|
||||
}
|
||||
|
||||
impl AttributeMask {
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
|
||||
AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single(new: &'static str) -> Self {
|
||||
Self::Dynamic(vec![new])
|
||||
}
|
||||
|
||||
pub fn verify(&self) {
|
||||
match &self {
|
||||
AttributeMask::Static(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
AttributeMask::Dynamic(attrs) => debug_assert!(
|
||||
attrs.windows(2).all(|w| w[0] < w[1]),
|
||||
"attritutes must be increasing"
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
let new = match (self, other) {
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
(AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
|
||||
union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
|
||||
),
|
||||
_ => AttributeMask::All,
|
||||
};
|
||||
new.verify();
|
||||
new
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
fn overlaps_iter(
|
||||
self_iter: impl Iterator<Item = &'static str>,
|
||||
mut other_iter: impl Iterator<Item = &'static str>,
|
||||
) -> bool {
|
||||
if let Some(mut other_attr) = other_iter.next() {
|
||||
for self_attr in self_iter {
|
||||
while other_attr < self_attr {
|
||||
if let Some(attr) = other_iter.next() {
|
||||
other_attr = attr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if other_attr == self_attr {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
match (self, other) {
|
||||
(AttributeMask::All, AttributeMask::All) => true,
|
||||
(AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
|
||||
(AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
|
||||
(AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
|
||||
(AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
|
||||
overlaps_iter(v1.iter().copied(), v2.iter().copied())
|
||||
}
|
||||
(AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
|
||||
overlaps_iter(v.iter().copied(), s.iter().copied())
|
||||
}
|
||||
(AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
|
||||
overlaps_iter(s1.iter().copied(), s2.iter().copied())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttributeMask {
|
||||
fn default() -> Self {
|
||||
AttributeMask::Static(&[])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Debug)]
|
||||
pub struct NodeMask {
|
||||
// must be sorted
|
||||
attritutes: AttributeMask,
|
||||
tag: bool,
|
||||
namespace: bool,
|
||||
text: bool,
|
||||
listeners: bool,
|
||||
}
|
||||
|
||||
impl NodeMask {
|
||||
pub const NONE: Self = Self::new();
|
||||
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
|
||||
.with_text()
|
||||
.with_element();
|
||||
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
(self.tag && other.tag)
|
||||
|| (self.namespace && other.namespace)
|
||||
|| self.attritutes.overlaps(&other.attritutes)
|
||||
|| (self.text && other.text)
|
||||
|| (self.listeners && other.listeners)
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
attritutes: self.attritutes.union(&other.attritutes),
|
||||
tag: self.tag | other.tag,
|
||||
namespace: self.namespace | other.namespace,
|
||||
text: self.text | other.text,
|
||||
listeners: self.listeners | other.listeners,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
|
||||
Self {
|
||||
attritutes,
|
||||
tag: false,
|
||||
namespace: false,
|
||||
text: false,
|
||||
listeners: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self::new_with_attrs(AttributeMask::NONE)
|
||||
}
|
||||
|
||||
pub const fn with_tag(mut self) -> Self {
|
||||
self.tag = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_namespace(mut self) -> Self {
|
||||
self.namespace = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_element(self) -> Self {
|
||||
self.with_namespace().with_tag()
|
||||
}
|
||||
|
||||
pub const fn with_text(mut self) -> Self {
|
||||
self.text = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_listeners(mut self) -> Self {
|
||||
self.listeners = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,774 @@
|
||||
use anymap::AnyMap;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
||||
|
||||
use crate::state::{union_ordered_iter, State};
|
||||
use crate::{
|
||||
node_ref::{AttributeMask, NodeMask},
|
||||
state::MemberId,
|
||||
};
|
||||
|
||||
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
||||
/// The render state passes from parent to children and or accumulates state from children to parents.
|
||||
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
|
||||
#[derive(Debug)]
|
||||
pub struct RealDom<S: State> {
|
||||
root: usize,
|
||||
nodes: Vec<Option<Node<S>>>,
|
||||
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
|
||||
node_stack: smallvec::SmallVec<[usize; 10]>,
|
||||
}
|
||||
|
||||
impl<S: State> Default for RealDom<S> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> RealDom<S> {
|
||||
pub fn new() -> RealDom<S> {
|
||||
RealDom {
|
||||
root: 0,
|
||||
nodes: {
|
||||
let v = vec![Some(Node::new(
|
||||
0,
|
||||
NodeType::Element {
|
||||
tag: "Root".to_string(),
|
||||
namespace: Some("Root"),
|
||||
children: Vec::new(),
|
||||
},
|
||||
))];
|
||||
v
|
||||
},
|
||||
nodes_listening: FxHashMap::default(),
|
||||
node_stack: smallvec::SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
|
||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
|
||||
let mut nodes_updated = Vec::new();
|
||||
for mutations in mutations_vec {
|
||||
for e in mutations.edits {
|
||||
use dioxus_core::DomEdit::*;
|
||||
match e {
|
||||
PushRoot { root } => self.node_stack.push(root as usize),
|
||||
AppendChildren { many } => {
|
||||
let target = if self.node_stack.len() > many as usize {
|
||||
*self
|
||||
.node_stack
|
||||
.get(self.node_stack.len() - (many as usize + 1))
|
||||
.unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let drained: Vec<_> = self
|
||||
.node_stack
|
||||
.drain(self.node_stack.len() - many as usize..)
|
||||
.collect();
|
||||
for ns in drained {
|
||||
self.link_child(ns, target).unwrap();
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
}
|
||||
}
|
||||
ReplaceWith { root, m } => {
|
||||
let root = self.remove(root as usize).unwrap();
|
||||
let target = root.parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
InsertAfter { root, n } => {
|
||||
let target = self[root as usize].parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
InsertBefore { root, n } => {
|
||||
let target = self[root as usize].parent.unwrap().0;
|
||||
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||
for ns in drained {
|
||||
nodes_updated.push((ns, NodeMask::ALL));
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
Remove { root } => {
|
||||
if let Some(parent) = self[root as usize].parent {
|
||||
nodes_updated.push((parent.0, NodeMask::NONE));
|
||||
}
|
||||
self.remove(root as usize).unwrap();
|
||||
}
|
||||
CreateTextNode { root, text } => {
|
||||
let n = Node::new(
|
||||
root,
|
||||
NodeType::Text {
|
||||
text: text.to_string(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
self.node_stack.push(root as usize)
|
||||
}
|
||||
CreateElement { root, tag } => {
|
||||
let n = Node::new(
|
||||
root,
|
||||
NodeType::Element {
|
||||
tag: tag.to_string(),
|
||||
namespace: None,
|
||||
children: Vec::new(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
self.node_stack.push(root as usize)
|
||||
}
|
||||
CreateElementNs { root, tag, ns } => {
|
||||
let n = Node::new(
|
||||
root,
|
||||
NodeType::Element {
|
||||
tag: tag.to_string(),
|
||||
namespace: Some(ns),
|
||||
children: Vec::new(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
self.node_stack.push(root as usize)
|
||||
}
|
||||
CreatePlaceholder { root } => {
|
||||
let n = Node::new(root, NodeType::Placeholder);
|
||||
self.insert(n);
|
||||
self.node_stack.push(root as usize)
|
||||
}
|
||||
|
||||
NewEventListener {
|
||||
event_name,
|
||||
scope: _,
|
||||
root,
|
||||
} => {
|
||||
nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
|
||||
if let Some(v) = self.nodes_listening.get_mut(event_name) {
|
||||
v.insert(root as usize);
|
||||
} else {
|
||||
let mut hs = FxHashSet::default();
|
||||
hs.insert(root as usize);
|
||||
self.nodes_listening.insert(event_name, hs);
|
||||
}
|
||||
}
|
||||
RemoveEventListener { root, event } => {
|
||||
nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
|
||||
let v = self.nodes_listening.get_mut(event).unwrap();
|
||||
v.remove(&(root as usize));
|
||||
}
|
||||
SetText {
|
||||
root,
|
||||
text: new_text,
|
||||
} => {
|
||||
let target = &mut self[root as usize];
|
||||
nodes_updated.push((root as usize, NodeMask::new().with_text()));
|
||||
match &mut target.node_type {
|
||||
NodeType::Text { text } => {
|
||||
*text = new_text.to_string();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
SetAttribute { root, field, .. } => {
|
||||
nodes_updated.push((
|
||||
root as usize,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(field)),
|
||||
));
|
||||
}
|
||||
RemoveAttribute {
|
||||
root, name: field, ..
|
||||
} => {
|
||||
nodes_updated.push((
|
||||
root as usize,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(field)),
|
||||
));
|
||||
}
|
||||
PopRoot {} => {
|
||||
self.node_stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes_updated
|
||||
}
|
||||
|
||||
/// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
vdom: &VirtualDom,
|
||||
nodes_updated: Vec<(usize, NodeMask)>,
|
||||
ctx: AnyMap,
|
||||
) -> Option<FxHashSet<usize>> {
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
enum StatesToCheck {
|
||||
All,
|
||||
Some(Vec<MemberId>),
|
||||
}
|
||||
impl StatesToCheck {
|
||||
fn union(&self, other: &Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter(
|
||||
s.iter().copied(),
|
||||
o.iter().copied(),
|
||||
s.len() + o.len(),
|
||||
)),
|
||||
_ => Self::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct NodeRef {
|
||||
id: usize,
|
||||
height: u16,
|
||||
node_mask: NodeMask,
|
||||
to_check: StatesToCheck,
|
||||
}
|
||||
impl NodeRef {
|
||||
fn union_with(&mut self, other: &Self) {
|
||||
self.node_mask = self.node_mask.union(&other.node_mask);
|
||||
self.to_check = self.to_check.union(&other.to_check);
|
||||
}
|
||||
}
|
||||
impl Eq for NodeRef {}
|
||||
impl PartialEq for NodeRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.height == other.height
|
||||
}
|
||||
}
|
||||
impl Ord for NodeRef {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
impl PartialOrd for NodeRef {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
// Sort nodes first by height, then if the height is the same id.
|
||||
// The order of the id does not matter it just helps with binary search later
|
||||
Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_rerender = FxHashSet::default();
|
||||
to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
|
||||
let mut nodes_updated: Vec<_> = nodes_updated
|
||||
.into_iter()
|
||||
.map(|(id, mask)| NodeRef {
|
||||
id,
|
||||
height: self[id].height,
|
||||
node_mask: mask,
|
||||
to_check: StatesToCheck::All,
|
||||
})
|
||||
.collect();
|
||||
nodes_updated.sort();
|
||||
|
||||
// Combine mutations that affect the same node.
|
||||
let mut last_node: Option<NodeRef> = None;
|
||||
let mut new_nodes_updated = VecDeque::new();
|
||||
for current in nodes_updated.into_iter() {
|
||||
if let Some(node) = &mut last_node {
|
||||
if *node == current {
|
||||
node.union_with(¤t);
|
||||
} else {
|
||||
new_nodes_updated.push_back(last_node.replace(current).unwrap());
|
||||
}
|
||||
} else {
|
||||
last_node = Some(current);
|
||||
}
|
||||
}
|
||||
if let Some(node) = last_node {
|
||||
new_nodes_updated.push_back(node);
|
||||
}
|
||||
let nodes_updated = new_nodes_updated;
|
||||
|
||||
// update the state that only depends on nodes. The order does not matter.
|
||||
for node_ref in &nodes_updated {
|
||||
let mut changed = false;
|
||||
let node = &mut self[node_ref.id];
|
||||
let mut ids = match &node_ref.to_check {
|
||||
StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask),
|
||||
// this should only be triggered from the current node, so all members will need to be checked
|
||||
StatesToCheck::Some(_) => unreachable!(),
|
||||
};
|
||||
let mut i = 0;
|
||||
while i < ids.len() {
|
||||
let id = ids[i];
|
||||
let node = &mut self[node_ref.id];
|
||||
let vnode = node.element(vdom);
|
||||
if let Some(members_effected) =
|
||||
node.state.update_node_dep_state(id, vnode, vdom, &ctx)
|
||||
{
|
||||
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
|
||||
for m in &members_effected.node_dep {
|
||||
if let Err(idx) = ids.binary_search(m) {
|
||||
ids.insert(idx, *m);
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if changed {
|
||||
to_rerender.insert(node_ref.id);
|
||||
}
|
||||
}
|
||||
|
||||
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
||||
let mut to_bubble = nodes_updated.clone();
|
||||
while let Some(node_ref) = to_bubble.pop_back() {
|
||||
let NodeRef {
|
||||
id,
|
||||
height,
|
||||
node_mask,
|
||||
to_check,
|
||||
} = node_ref;
|
||||
let (node, children) = self.get_node_children_mut(id).unwrap();
|
||||
let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
|
||||
let mut ids = match to_check {
|
||||
StatesToCheck::All => node.state.child_dep_types(&node_mask),
|
||||
StatesToCheck::Some(ids) => ids,
|
||||
};
|
||||
let mut changed = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < ids.len() {
|
||||
let id = ids[i];
|
||||
let vnode = node.element(vdom);
|
||||
if let Some(members_effected) =
|
||||
node.state
|
||||
.update_child_dep_state(id, vnode, vdom, &children_state, &ctx)
|
||||
{
|
||||
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
|
||||
for m in members_effected.node_dep {
|
||||
if let Err(idx) = ids.binary_search(&m) {
|
||||
ids.insert(idx, m);
|
||||
}
|
||||
}
|
||||
for m in members_effected.child_dep {
|
||||
changed.push(m);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if let Some(parent_id) = node.parent {
|
||||
if !changed.is_empty() {
|
||||
to_rerender.insert(id);
|
||||
let i = to_bubble.partition_point(
|
||||
|NodeRef {
|
||||
id: other_id,
|
||||
height: h,
|
||||
..
|
||||
}| {
|
||||
*h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
|
||||
},
|
||||
);
|
||||
// make sure the parent is not already queued
|
||||
if i < to_bubble.len() && to_bubble[i].id == parent_id.0 {
|
||||
to_bubble[i].to_check =
|
||||
to_bubble[i].to_check.union(&StatesToCheck::Some(changed));
|
||||
} else {
|
||||
to_bubble.insert(
|
||||
i,
|
||||
NodeRef {
|
||||
id: parent_id.0,
|
||||
height: height - 1,
|
||||
node_mask: NodeMask::NONE,
|
||||
to_check: StatesToCheck::Some(changed),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
||||
let mut to_push = nodes_updated;
|
||||
while let Some(node_ref) = to_push.pop_front() {
|
||||
let NodeRef {
|
||||
id,
|
||||
height,
|
||||
node_mask,
|
||||
to_check,
|
||||
} = node_ref;
|
||||
let node = &self[id];
|
||||
let mut ids = match to_check {
|
||||
StatesToCheck::All => node.state.parent_dep_types(&node_mask),
|
||||
StatesToCheck::Some(ids) => ids,
|
||||
};
|
||||
let mut changed = Vec::new();
|
||||
let (node, parent) = self.get_node_parent_mut(id).unwrap();
|
||||
let mut i = 0;
|
||||
while i < ids.len() {
|
||||
let id = ids[i];
|
||||
let vnode = node.element(vdom);
|
||||
let parent = parent.as_deref();
|
||||
let state = &mut node.state;
|
||||
if let Some(members_effected) =
|
||||
state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx)
|
||||
{
|
||||
debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
|
||||
for m in members_effected.node_dep {
|
||||
if let Err(idx) = ids.binary_search(&m) {
|
||||
ids.insert(idx, m);
|
||||
}
|
||||
}
|
||||
for m in members_effected.parent_dep {
|
||||
changed.push(m);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
to_rerender.insert(id);
|
||||
if !changed.is_empty() {
|
||||
let node = &self[id];
|
||||
if let NodeType::Element { children, .. } = &node.node_type {
|
||||
for c in children {
|
||||
let i = to_push.partition_point(
|
||||
|NodeRef {
|
||||
id: other_id,
|
||||
height: h,
|
||||
..
|
||||
}| {
|
||||
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
|
||||
},
|
||||
);
|
||||
if i < to_push.len() && to_push[i].id == c.0 {
|
||||
to_push[i].to_check = to_push[i]
|
||||
.to_check
|
||||
.union(&StatesToCheck::Some(changed.clone()));
|
||||
} else {
|
||||
to_push.insert(
|
||||
i,
|
||||
NodeRef {
|
||||
id: c.0,
|
||||
height: height + 1,
|
||||
node_mask: NodeMask::NONE,
|
||||
to_check: StatesToCheck::Some(changed.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(to_rerender)
|
||||
}
|
||||
|
||||
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
|
||||
debug_assert_ne!(child_id, parent_id);
|
||||
let parent = &mut self[parent_id];
|
||||
parent.add_child(ElementId(child_id));
|
||||
let parent_height = parent.height + 1;
|
||||
self[child_id].set_parent(ElementId(parent_id));
|
||||
self.increase_height(child_id, parent_height);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn increase_height(&mut self, id: usize, amount: u16) {
|
||||
let n = &mut self[id];
|
||||
n.height += amount;
|
||||
if let NodeType::Element { children, .. } = &n.node_type {
|
||||
for c in children.clone() {
|
||||
self.increase_height(c.0, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove a node and it's children from the dom.
|
||||
fn remove(&mut self, id: usize) -> Option<Node<S>> {
|
||||
// We do not need to remove the node from the parent's children list for children.
|
||||
fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> {
|
||||
let mut node = dom.nodes[id as usize].take()?;
|
||||
if let NodeType::Element { children, .. } = &mut node.node_type {
|
||||
for c in children {
|
||||
inner(dom, c.0)?;
|
||||
}
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
let mut node = self.nodes[id as usize].take()?;
|
||||
if let Some(parent) = node.parent {
|
||||
let parent = &mut self[parent];
|
||||
parent.remove_child(ElementId(id));
|
||||
}
|
||||
if let NodeType::Element { children, .. } = &mut node.node_type {
|
||||
for c in children {
|
||||
inner(self, c.0)?;
|
||||
}
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn insert(&mut self, node: Node<S>) {
|
||||
let current_len = self.nodes.len();
|
||||
let id = node.id.0;
|
||||
if current_len - 1 < node.id.0 {
|
||||
// self.nodes.reserve(1 + id - current_len);
|
||||
self.nodes.extend((0..1 + id - current_len).map(|_| None));
|
||||
}
|
||||
self.nodes[id] = Some(node);
|
||||
}
|
||||
|
||||
pub fn get(&self, id: usize) -> Option<&Node<S>> {
|
||||
self.nodes.get(id)?.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
|
||||
self.nodes.get_mut(id)?.as_mut()
|
||||
}
|
||||
|
||||
// this is safe because no node will have itself as a child
|
||||
pub fn get_node_children_mut(
|
||||
&mut self,
|
||||
id: usize,
|
||||
) -> Option<(&mut Node<S>, Vec<&mut Node<S>>)> {
|
||||
let ptr = self.nodes.as_mut_ptr();
|
||||
unsafe {
|
||||
if id >= self.nodes.len() {
|
||||
None
|
||||
} else {
|
||||
let node = &mut *ptr.add(id);
|
||||
if let Some(node) = node.as_mut() {
|
||||
let children = match &node.node_type {
|
||||
NodeType::Element { children, .. } => children
|
||||
.iter()
|
||||
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
Some((node, children))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is safe because no node will have itself as a parent
|
||||
pub fn get_node_parent_mut(
|
||||
&mut self,
|
||||
id: usize,
|
||||
) -> Option<(&mut Node<S>, Option<&mut Node<S>>)> {
|
||||
let ptr = self.nodes.as_mut_ptr();
|
||||
unsafe {
|
||||
let node = &mut *ptr.add(id);
|
||||
if id >= self.nodes.len() {
|
||||
None
|
||||
} else if let Some(node) = node.as_mut() {
|
||||
let parent = node
|
||||
.parent
|
||||
.map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
|
||||
Some((node, parent))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
|
||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
|
||||
listening
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the dom contains a node and its children.
|
||||
pub fn contains_node(&self, node: &VNode) -> bool {
|
||||
match node {
|
||||
VNode::Component(_) => {
|
||||
todo!()
|
||||
}
|
||||
VNode::Element(e) => {
|
||||
if let Some(id) = e.id.get() {
|
||||
let dom_node = &self[id];
|
||||
match &dom_node.node_type {
|
||||
NodeType::Element {
|
||||
tag,
|
||||
namespace,
|
||||
children,
|
||||
} => {
|
||||
tag == e.tag
|
||||
&& namespace == &e.namespace
|
||||
&& children.iter().copied().collect::<FxHashSet<_>>()
|
||||
== e.children
|
||||
.iter()
|
||||
.map(|c| c.mounted_id())
|
||||
.collect::<FxHashSet<_>>()
|
||||
&& e.children.iter().all(|c| {
|
||||
self.contains_node(c)
|
||||
&& self[c.mounted_id()].parent == e.id.get()
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)),
|
||||
VNode::Placeholder(_) => true,
|
||||
VNode::Text(t) => {
|
||||
if let Some(id) = t.id.get() {
|
||||
let dom_node = &self[id];
|
||||
match &dom_node.node_type {
|
||||
NodeType::Text { text } => t.text == text,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of nodes in the dom.
|
||||
pub fn size(&self) -> usize {
|
||||
// The dom has a root node, ignore it.
|
||||
self.nodes.iter().filter(|n| n.is_some()).count() - 1
|
||||
}
|
||||
|
||||
/// Returns the id of the root node.
|
||||
pub fn root_id(&self) -> usize {
|
||||
self.root
|
||||
}
|
||||
|
||||
/// Call a function for each node in the dom, depth first.
|
||||
pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
|
||||
fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
|
||||
let node = &dom[id];
|
||||
f(node);
|
||||
if let NodeType::Element { children, .. } = &node.node_type {
|
||||
for c in children {
|
||||
inner(dom, *c, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let NodeType::Element { children, .. } = &self[self.root].node_type {
|
||||
for c in children {
|
||||
inner(self, *c, &mut f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a function for each node in the dom, depth first.
|
||||
pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
|
||||
fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
|
||||
let node = &mut dom[id];
|
||||
f(node);
|
||||
if let NodeType::Element { children, .. } = &mut node.node_type {
|
||||
for c in children.clone() {
|
||||
inner(dom, c, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
let root = self.root;
|
||||
if let NodeType::Element { children, .. } = &mut self[root].node_type {
|
||||
for c in children.clone() {
|
||||
inner(self, c, &mut f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> Index<usize> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
|
||||
fn index(&self, idx: usize) -> &Self::Output {
|
||||
self.get(idx).expect("Node does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> Index<ElementId> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
|
||||
fn index(&self, idx: ElementId) -> &Self::Output {
|
||||
&self[idx.0]
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> IndexMut<usize> for RealDom<S> {
|
||||
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
|
||||
self.get_mut(idx).expect("Node does not exist")
|
||||
}
|
||||
}
|
||||
impl<S: State> IndexMut<ElementId> for RealDom<S> {
|
||||
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
|
||||
&mut self[idx.0]
|
||||
}
|
||||
}
|
||||
|
||||
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node<S: State> {
|
||||
/// The id of the node this node was created from.
|
||||
pub id: ElementId,
|
||||
/// The parent id of the node.
|
||||
pub parent: Option<ElementId>,
|
||||
/// State of the node.
|
||||
pub state: S,
|
||||
/// Additional inforation specific to the node type
|
||||
pub node_type: NodeType,
|
||||
/// The number of parents before the root node. The root node has height 1.
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NodeType {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Element {
|
||||
tag: String,
|
||||
namespace: Option<&'static str>,
|
||||
children: Vec<ElementId>,
|
||||
},
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
impl<S: State> Node<S> {
|
||||
fn new(id: u64, node_type: NodeType) -> Self {
|
||||
Node {
|
||||
id: ElementId(id as usize),
|
||||
parent: None,
|
||||
node_type,
|
||||
state: S::default(),
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the element that this node refrences.
|
||||
pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
|
||||
vdom.get_element(self.id).unwrap()
|
||||
}
|
||||
|
||||
fn add_child(&mut self, child: ElementId) {
|
||||
if let NodeType::Element { children, .. } = &mut self.node_type {
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_child(&mut self, child: ElementId) {
|
||||
if let NodeType::Element { children, .. } = &mut self.node_type {
|
||||
children.retain(|c| c != &child);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parent(&mut self, parent: ElementId) {
|
||||
self.parent = Some(parent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Debug,
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::VNode;
|
||||
|
||||
use crate::node_ref::{NodeMask, NodeView};
|
||||
|
||||
pub(crate) fn union_ordered_iter<T: Ord + Debug>(
|
||||
s_iter: impl Iterator<Item = T>,
|
||||
o_iter: impl Iterator<Item = T>,
|
||||
new_len_guess: usize,
|
||||
) -> Vec<T> {
|
||||
let mut s_peekable = s_iter.peekable();
|
||||
let mut o_peekable = o_iter.peekable();
|
||||
let mut v = Vec::with_capacity(new_len_guess);
|
||||
while let Some(s_i) = s_peekable.peek() {
|
||||
while let Some(o_i) = o_peekable.peek() {
|
||||
match o_i.cmp(s_i) {
|
||||
Ordering::Greater => {
|
||||
break;
|
||||
}
|
||||
Ordering::Less => {
|
||||
v.push(o_peekable.next().unwrap());
|
||||
}
|
||||
Ordering::Equal => {
|
||||
o_peekable.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
v.push(s_peekable.next().unwrap());
|
||||
}
|
||||
for o_i in o_peekable {
|
||||
v.push(o_i);
|
||||
}
|
||||
for w in v.windows(2) {
|
||||
debug_assert!(w[1] > w[0]);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a node's size could be derived from the size of children.
|
||||
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
|
||||
/// Called at most once per update.
|
||||
pub trait ChildDepState {
|
||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
/// This must be either a [ChildDepState] or [NodeDepState]
|
||||
type DepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = &'a Self::DepState>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a;
|
||||
}
|
||||
|
||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||
/// Called at most once per update.
|
||||
pub trait ParentDepState {
|
||||
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||
/// This is sometimes nessisary for lifetime purposes.
|
||||
type Ctx;
|
||||
/// This must be either a [ParentDepState] or [NodeDepState]
|
||||
type DepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
|
||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||
/// Called at most once per update.
|
||||
pub trait NodeDepState {
|
||||
type Ctx;
|
||||
type DepState: NodeDepState;
|
||||
const NODE_MASK: NodeMask = NodeMask::NONE;
|
||||
fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChildStatesChanged {
|
||||
pub node_dep: Vec<MemberId>,
|
||||
pub child_dep: Vec<MemberId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParentStatesChanged {
|
||||
pub node_dep: Vec<MemberId>,
|
||||
pub parent_dep: Vec<MemberId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeStatesChanged {
|
||||
pub node_dep: Vec<MemberId>,
|
||||
}
|
||||
|
||||
pub trait State: Default + Clone {
|
||||
const SIZE: usize;
|
||||
|
||||
fn update_node_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
ctx: &AnyMap,
|
||||
) -> Option<NodeStatesChanged>;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
fn child_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
|
||||
fn update_parent_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
parent: Option<&Self>,
|
||||
ctx: &AnyMap,
|
||||
) -> Option<ParentStatesChanged>;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
fn parent_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
|
||||
fn update_child_dep_state<'a>(
|
||||
&'a mut self,
|
||||
ty: MemberId,
|
||||
node: &'a VNode<'a>,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
children: &Vec<&Self>,
|
||||
ctx: &AnyMap,
|
||||
) -> Option<ChildStatesChanged>;
|
||||
/// This must be a valid resolution order. (no nodes updated before a state they rely on)
|
||||
fn node_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
|
||||
}
|
||||
|
||||
// Todo: once GATs land we can model multable dependencies
|
||||
impl ChildDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MemberId(pub usize);
|
||||
|
||||
impl Sub<usize> for MemberId {
|
||||
type Output = MemberId;
|
||||
fn sub(self, rhs: usize) -> Self::Output {
|
||||
MemberId(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for MemberId {
|
||||
type Output = MemberId;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
MemberId(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<usize> for MemberId {
|
||||
fn sub_assign(&mut self, rhs: usize) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<usize> for MemberId {
|
||||
fn add_assign(&mut self, rhs: usize) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-router"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
edition = "2018"
|
||||
description = "Cross-platform router for Dioxus apps"
|
||||
license = "MIT/Apache-2.0"
|
||||
@@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0", default-features = false }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0", default-features = false }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", default-features = false }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", default-features = false }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
|
||||
futures-channel = "0.3.21"
|
||||
url = { version = "2.2.2", default-features = false }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-ssr"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus render-to-string"
|
||||
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> {
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
match attr.namespace {
|
||||
None => match attr.name {
|
||||
"dangerous_inner_html" => inner_html = Some(attr.value),
|
||||
"dangerous_inner_html" => {
|
||||
inner_html = Some(attr.value.as_text().unwrap())
|
||||
}
|
||||
"allowfullscreen"
|
||||
| "allowpaymentrequest"
|
||||
| "async"
|
||||
@@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> {
|
||||
| "reversed"
|
||||
| "selected"
|
||||
| "truespeed" => {
|
||||
if attr.value != "false" {
|
||||
if attr.value.is_truthy() {
|
||||
write!(f, " {}=\"{}\"", attr.name, attr.value)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-tui"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
authors = ["Jonathan Kelley, @dementhos"]
|
||||
edition = "2021"
|
||||
description = "TUI-based renderer for Dioxus"
|
||||
@@ -13,13 +13,17 @@ license = "MIT/Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
|
||||
|
||||
tui = "0.17.0"
|
||||
crossterm = "0.23.0"
|
||||
anyhow = "1.0.42"
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
futures = "0.3.19"
|
||||
stretch2 = "0.4.1"
|
||||
|
||||
stretch2 = "0.4.2"
|
||||
smallvec = "1.6"
|
||||
fxhash = "0.2"
|
||||
anymap = "0.12.1"
|
||||
@@ -1,968 +0,0 @@
|
||||
/*
|
||||
- [ ] pub display: Display,
|
||||
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
|
||||
- [ ] pub direction: Direction,
|
||||
|
||||
- [x] pub flex_direction: FlexDirection,
|
||||
- [x] pub flex_wrap: FlexWrap,
|
||||
- [x] pub flex_grow: f32,
|
||||
- [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 align_items: AlignItems,
|
||||
- [x] pub align_self: AlignSelf,
|
||||
- [x] pub align_content: AlignContent,
|
||||
|
||||
- [x] pub margin: Rect<Dimension>,
|
||||
- [x] pub padding: Rect<Dimension>,
|
||||
|
||||
- [x] pub justify_content: JustifyContent,
|
||||
- [ ] pub position: Rect<Dimension>,
|
||||
- [ ] pub border: Rect<Dimension>,
|
||||
|
||||
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
|
||||
- [ ] pub min_size: Size<Dimension>,
|
||||
- [ ] pub max_size: Size<Dimension>,
|
||||
|
||||
- [ ] pub aspect_ratio: Number,
|
||||
*/
|
||||
|
||||
use stretch2::{prelude::*, style::PositionType, style::Style};
|
||||
|
||||
use crate::style::{RinkColor, RinkStyle};
|
||||
|
||||
pub struct StyleModifer {
|
||||
pub style: Style,
|
||||
pub tui_style: RinkStyle,
|
||||
pub tui_modifier: TuiModifier,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TuiModifier {
|
||||
pub borders: Borders,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Borders {
|
||||
pub top: BorderEdge,
|
||||
pub right: BorderEdge,
|
||||
pub bottom: BorderEdge,
|
||||
pub left: BorderEdge,
|
||||
}
|
||||
|
||||
impl Borders {
|
||||
fn slice(&mut self) -> [&mut BorderEdge; 4] {
|
||||
[
|
||||
&mut self.top,
|
||||
&mut self.right,
|
||||
&mut self.bottom,
|
||||
&mut self.left,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BorderEdge {
|
||||
pub color: Option<RinkColor>,
|
||||
pub style: BorderStyle,
|
||||
pub width: UnitSystem,
|
||||
pub radius: UnitSystem,
|
||||
}
|
||||
|
||||
impl Default for BorderEdge {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: None,
|
||||
style: BorderStyle::NONE,
|
||||
width: UnitSystem::Point(0.0),
|
||||
radius: UnitSystem::Point(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum BorderStyle {
|
||||
DOTTED,
|
||||
DASHED,
|
||||
SOLID,
|
||||
DOUBLE,
|
||||
GROOVE,
|
||||
RIDGE,
|
||||
INSET,
|
||||
OUTSET,
|
||||
HIDDEN,
|
||||
NONE,
|
||||
}
|
||||
|
||||
impl BorderStyle {
|
||||
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
|
||||
use tui::symbols::line::*;
|
||||
const DASHED: Set = Set {
|
||||
horizontal: "╌",
|
||||
vertical: "╎",
|
||||
..NORMAL
|
||||
};
|
||||
const DOTTED: Set = Set {
|
||||
horizontal: "┈",
|
||||
vertical: "┊",
|
||||
..NORMAL
|
||||
};
|
||||
match self {
|
||||
BorderStyle::DOTTED => Some(DOTTED),
|
||||
BorderStyle::DASHED => Some(DASHED),
|
||||
BorderStyle::SOLID => Some(NORMAL),
|
||||
BorderStyle::DOUBLE => Some(DOUBLE),
|
||||
BorderStyle::GROOVE => Some(NORMAL),
|
||||
BorderStyle::RIDGE => Some(NORMAL),
|
||||
BorderStyle::INSET => Some(NORMAL),
|
||||
BorderStyle::OUTSET => Some(NORMAL),
|
||||
BorderStyle::HIDDEN => None,
|
||||
BorderStyle::NONE => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// applies the entire html namespace defined in dioxus-html
|
||||
pub fn apply_attributes(
|
||||
//
|
||||
name: &str,
|
||||
value: &str,
|
||||
style: &mut StyleModifer,
|
||||
) {
|
||||
match name {
|
||||
"align-content"
|
||||
| "align-items"
|
||||
| "align-self" => apply_align(name, value, style),
|
||||
|
||||
"animation"
|
||||
| "animation-delay"
|
||||
| "animation-direction"
|
||||
| "animation-duration"
|
||||
| "animation-fill-mode"
|
||||
| "animation-iteration-count"
|
||||
| "animation-name"
|
||||
| "animation-play-state"
|
||||
| "animation-timing-function" => apply_animation(name, value, style),
|
||||
|
||||
"backface-visibility" => {}
|
||||
|
||||
"background"
|
||||
| "background-attachment"
|
||||
| "background-clip"
|
||||
| "background-color"
|
||||
| "background-image"
|
||||
| "background-origin"
|
||||
| "background-position"
|
||||
| "background-repeat"
|
||||
| "background-size" => apply_background(name, value, style),
|
||||
|
||||
"border"
|
||||
| "border-bottom"
|
||||
| "border-bottom-color"
|
||||
| "border-bottom-left-radius"
|
||||
| "border-bottom-right-radius"
|
||||
| "border-bottom-style"
|
||||
| "border-bottom-width"
|
||||
| "border-collapse"
|
||||
| "border-color"
|
||||
| "border-image"
|
||||
| "border-image-outset"
|
||||
| "border-image-repeat"
|
||||
| "border-image-slice"
|
||||
| "border-image-source"
|
||||
| "border-image-width"
|
||||
| "border-left"
|
||||
| "border-left-color"
|
||||
| "border-left-style"
|
||||
| "border-left-width"
|
||||
| "border-radius"
|
||||
| "border-right"
|
||||
| "border-right-color"
|
||||
| "border-right-style"
|
||||
| "border-right-width"
|
||||
| "border-spacing"
|
||||
| "border-style"
|
||||
| "border-top"
|
||||
| "border-top-color"
|
||||
| "border-top-left-radius"
|
||||
| "border-top-right-radius"
|
||||
| "border-top-style"
|
||||
| "border-top-width"
|
||||
| "border-width" => apply_border(name, value, style),
|
||||
|
||||
"bottom" => {}
|
||||
"box-shadow" => {}
|
||||
"box-sizing" => {}
|
||||
"caption-side" => {}
|
||||
"clear" => {}
|
||||
"clip" => {}
|
||||
|
||||
"color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_style.fg.replace(c);
|
||||
}
|
||||
}
|
||||
|
||||
"column-count"
|
||||
| "column-fill"
|
||||
| "column-gap"
|
||||
| "column-rule"
|
||||
| "column-rule-color"
|
||||
| "column-rule-style"
|
||||
| "column-rule-width"
|
||||
| "column-span"
|
||||
// add column-width
|
||||
| "column-width" => apply_column(name, value, style),
|
||||
|
||||
"columns" => {}
|
||||
|
||||
"content" => {}
|
||||
"counter-increment" => {}
|
||||
"counter-reset" => {}
|
||||
|
||||
"cursor" => {}
|
||||
"direction" => {
|
||||
match value {
|
||||
"ltr" => style.style.direction = Direction::LTR,
|
||||
"rtl" => style.style.direction = Direction::RTL,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
"display" => apply_display(name, value, style),
|
||||
|
||||
"empty-cells" => {}
|
||||
|
||||
"flex"
|
||||
| "flex-basis"
|
||||
| "flex-direction"
|
||||
| "flex-flow"
|
||||
| "flex-grow"
|
||||
| "flex-shrink"
|
||||
| "flex-wrap" => apply_flex(name, value, style),
|
||||
|
||||
"float" => {}
|
||||
|
||||
"font"
|
||||
| "font-family"
|
||||
| "font-size"
|
||||
| "font-size-adjust"
|
||||
| "font-stretch"
|
||||
| "font-style"
|
||||
| "font-variant"
|
||||
| "font-weight" => apply_font(name, value, style),
|
||||
|
||||
"height" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.style.size.height = match v {
|
||||
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
|
||||
UnitSystem::Point(v)=> Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"justify-content" => {
|
||||
use JustifyContent::*;
|
||||
style.style.justify_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
"space-evenly" => SpaceEvenly,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"left" => {}
|
||||
"letter-spacing" => {}
|
||||
"line-height" => {}
|
||||
|
||||
"list-style"
|
||||
| "list-style-image"
|
||||
| "list-style-position"
|
||||
| "list-style-type" => {}
|
||||
|
||||
"margin"
|
||||
| "margin-bottom"
|
||||
| "margin-left"
|
||||
| "margin-right"
|
||||
| "margin-top" => apply_margin(name, value, style),
|
||||
|
||||
"max-height" => {}
|
||||
"max-width" => {}
|
||||
"min-height" => {}
|
||||
"min-width" => {}
|
||||
|
||||
"opacity" => {}
|
||||
"order" => {}
|
||||
"outline" => {}
|
||||
|
||||
"outline-color"
|
||||
| "outline-offset"
|
||||
| "outline-style"
|
||||
| "outline-width" => {}
|
||||
|
||||
"overflow"
|
||||
| "overflow-x"
|
||||
| "overflow-y" => apply_overflow(name, value, style),
|
||||
|
||||
"padding"
|
||||
| "padding-bottom"
|
||||
| "padding-left"
|
||||
| "padding-right"
|
||||
| "padding-top" => apply_padding(name, value, style),
|
||||
|
||||
"page-break-after"
|
||||
| "page-break-before"
|
||||
| "page-break-inside" => {}
|
||||
|
||||
"perspective"
|
||||
| "perspective-origin" => {}
|
||||
|
||||
"position" => {
|
||||
match value {
|
||||
"static" => {}
|
||||
"relative" => style.style.position_type = PositionType::Relative,
|
||||
"fixed" => {}
|
||||
"absolute" => style.style.position_type = PositionType::Absolute,
|
||||
"sticky" => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"pointer-events" => {}
|
||||
|
||||
"quotes" => {}
|
||||
"resize" => {}
|
||||
"right" => {}
|
||||
"tab-size" => {}
|
||||
"table-layout" => {}
|
||||
|
||||
"text-align"
|
||||
| "text-align-last"
|
||||
| "text-decoration"
|
||||
| "text-decoration-color"
|
||||
| "text-decoration-line"
|
||||
| "text-decoration-style"
|
||||
| "text-indent"
|
||||
| "text-justify"
|
||||
| "text-overflow"
|
||||
| "text-shadow"
|
||||
| "text-transform" => apply_text(name, value, style),
|
||||
|
||||
"top" => {}
|
||||
|
||||
"transform"
|
||||
| "transform-origin"
|
||||
| "transform-style" => apply_transform(name, value, style),
|
||||
|
||||
"transition"
|
||||
| "transition-delay"
|
||||
| "transition-duration"
|
||||
| "transition-property"
|
||||
| "transition-timing-function" => apply_transition(name, value, style),
|
||||
|
||||
"vertical-align" => {}
|
||||
"visibility" => {}
|
||||
"white-space" => {}
|
||||
"width" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.style.size.width = match v {
|
||||
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
|
||||
UnitSystem::Point(v)=> Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"word-break" => {}
|
||||
"word-spacing" => {}
|
||||
"word-wrap" => {}
|
||||
"z-index" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum UnitSystem {
|
||||
Percent(f32),
|
||||
Point(f32),
|
||||
}
|
||||
|
||||
impl Into<Dimension> for UnitSystem {
|
||||
fn into(self) -> Dimension {
|
||||
match self {
|
||||
Self::Percent(v) => Dimension::Percent(v),
|
||||
Self::Point(v) => Dimension::Points(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(value: &str) -> Option<UnitSystem> {
|
||||
if value.ends_with("px") {
|
||||
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
|
||||
Some(UnitSystem::Point(px))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if value.ends_with('%') {
|
||||
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
|
||||
Some(UnitSystem::Percent(pct))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match name {
|
||||
// todo: add more overflow support to stretch2
|
||||
"overflow" | "overflow-x" | "overflow-y" => {
|
||||
style.style.overflow = match value {
|
||||
"auto" => Overflow::Visible,
|
||||
"hidden" => Overflow::Hidden,
|
||||
"scroll" => Overflow::Scroll,
|
||||
"visible" => Overflow::Visible,
|
||||
_ => Overflow::Visible,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
|
||||
style.style.display = match value {
|
||||
"flex" => Display::Flex,
|
||||
"block" => Display::None,
|
||||
_ => Display::Flex,
|
||||
}
|
||||
|
||||
// TODO: there are way more variants
|
||||
// stretch needs to be updated to handle them
|
||||
//
|
||||
// "block" => Display::Block,
|
||||
// "inline" => Display::Inline,
|
||||
// "inline-block" => Display::InlineBlock,
|
||||
// "inline-table" => Display::InlineTable,
|
||||
// "list-item" => Display::ListItem,
|
||||
// "run-in" => Display::RunIn,
|
||||
// "table" => Display::Table,
|
||||
// "table-caption" => Display::TableCaption,
|
||||
// "table-cell" => Display::TableCell,
|
||||
// "table-column" => Display::TableColumn,
|
||||
// "table-column-group" => Display::TableColumnGroup,
|
||||
// "table-footer-group" => Display::TableFooterGroup,
|
||||
// "table-header-group" => Display::TableHeaderGroup,
|
||||
// "table-row" => Display::TableRow,
|
||||
// "table-row-group" => Display::TableRowGroup,
|
||||
// "none" => Display::None,
|
||||
// _ => Display::Inline,
|
||||
}
|
||||
|
||||
fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match name {
|
||||
"background-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_style.bg.replace(c);
|
||||
}
|
||||
}
|
||||
"background" => {}
|
||||
"background-attachment" => {}
|
||||
"background-clip" => {}
|
||||
"background-image" => {}
|
||||
"background-origin" => {}
|
||||
"background-position" => {}
|
||||
"background-repeat" => {}
|
||||
"background-size" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
fn parse_border_style(v: &str) -> BorderStyle {
|
||||
match v {
|
||||
"dotted" => BorderStyle::DOTTED,
|
||||
"dashed" => BorderStyle::DASHED,
|
||||
"solid" => BorderStyle::SOLID,
|
||||
"double" => BorderStyle::DOUBLE,
|
||||
"groove" => BorderStyle::GROOVE,
|
||||
"ridge" => BorderStyle::RIDGE,
|
||||
"inset" => BorderStyle::INSET,
|
||||
"outset" => BorderStyle::OUTSET,
|
||||
"none" => BorderStyle::NONE,
|
||||
"hidden" => BorderStyle::HIDDEN,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
match name {
|
||||
"border" => {}
|
||||
"border-bottom" => {}
|
||||
"border-bottom-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.borders.bottom.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-bottom-left-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.left.radius = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-right-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.right.radius = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-style" => {
|
||||
if style.style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.bottom = v;
|
||||
}
|
||||
style.tui_modifier.borders.bottom.style = parse_border_style(value)
|
||||
}
|
||||
"border-bottom-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.bottom.width = v;
|
||||
style.style.border.bottom = v.into();
|
||||
}
|
||||
}
|
||||
"border-collapse" => {}
|
||||
"border-color" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Ok(c) = values[0].parse() {
|
||||
style
|
||||
.tui_modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.color = Some(c));
|
||||
}
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.tui_modifier.borders.slice().iter_mut())
|
||||
{
|
||||
if let Ok(c) = v.parse() {
|
||||
b.color = Some(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-image" => {}
|
||||
"border-image-outset" => {}
|
||||
"border-image-repeat" => {}
|
||||
"border-image-slice" => {}
|
||||
"border-image-source" => {}
|
||||
"border-image-width" => {}
|
||||
"border-left" => {}
|
||||
"border-left-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.borders.left.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-left-style" => {
|
||||
if style.style.border.start == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.start = v;
|
||||
}
|
||||
style.tui_modifier.borders.left.style = parse_border_style(value)
|
||||
}
|
||||
"border-left-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.left.width = v;
|
||||
style.style.border.start = v.into();
|
||||
}
|
||||
}
|
||||
"border-radius" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(r) = parse_value(values[0]) {
|
||||
style
|
||||
.tui_modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.radius = r);
|
||||
}
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.tui_modifier.borders.slice().iter_mut())
|
||||
{
|
||||
if let Some(r) = parse_value(v) {
|
||||
b.radius = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-right" => {}
|
||||
"border-right-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.borders.right.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-right-style" => {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.end = v;
|
||||
style.tui_modifier.borders.right.style = parse_border_style(value)
|
||||
}
|
||||
"border-right-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.right.width = v;
|
||||
}
|
||||
}
|
||||
"border-spacing" => {}
|
||||
"border-style" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if style.style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.top = v;
|
||||
}
|
||||
if style.style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.bottom = v;
|
||||
}
|
||||
if style.style.border.start == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.start = v;
|
||||
}
|
||||
if style.style.border.end == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.end = v;
|
||||
}
|
||||
if values.len() == 1 {
|
||||
let border_style = parse_border_style(values[0]);
|
||||
style
|
||||
.tui_modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.style = border_style);
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.tui_modifier.borders.slice().iter_mut())
|
||||
{
|
||||
b.style = parse_border_style(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-top" => {}
|
||||
"border-top-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.borders.top.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-top-left-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.left.radius = v;
|
||||
}
|
||||
}
|
||||
"border-top-right-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.tui_modifier.borders.right.radius = v;
|
||||
}
|
||||
}
|
||||
"border-top-style" => {
|
||||
if style.style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.style.border.top = v;
|
||||
}
|
||||
style.tui_modifier.borders.top.style = parse_border_style(value)
|
||||
}
|
||||
"border-top-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.style.border.top = v.into();
|
||||
style.tui_modifier.borders.top.width = v;
|
||||
}
|
||||
}
|
||||
"border-width" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(w) = parse_value(values[0]) {
|
||||
style.style.border.top = w.into();
|
||||
style.style.border.bottom = w.into();
|
||||
style.style.border.start = w.into();
|
||||
style.style.border.end = w.into();
|
||||
style
|
||||
.tui_modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.width = w);
|
||||
}
|
||||
} else {
|
||||
let border_widths = [
|
||||
&mut style.style.border.top,
|
||||
&mut style.style.border.bottom,
|
||||
&mut style.style.border.start,
|
||||
&mut style.style.border.end,
|
||||
];
|
||||
for ((v, b), width) in values
|
||||
.into_iter()
|
||||
.zip(style.tui_modifier.borders.slice().iter_mut())
|
||||
.zip(border_widths)
|
||||
{
|
||||
if let Some(w) = parse_value(v) {
|
||||
*width = w.into();
|
||||
b.width = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) {
|
||||
match name {
|
||||
"animation" => {}
|
||||
"animation-delay" => {}
|
||||
"animation-direction =>{}" => {}
|
||||
"animation-duration" => {}
|
||||
"animation-fill-mode" => {}
|
||||
"animation-itera =>{}tion-count" => {}
|
||||
"animation-name" => {}
|
||||
"animation-play-state" => {}
|
||||
"animation-timing-function" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) {
|
||||
match name {
|
||||
"column-count" => {}
|
||||
"column-fill" => {}
|
||||
"column-gap" => {}
|
||||
"column-rule" => {}
|
||||
"column-rule-color" => {}
|
||||
"column-rule-style" => {}
|
||||
"column-rule-width" => {}
|
||||
"column-span" => {}
|
||||
"column-width" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
// - [x] pub flex_direction: FlexDirection,
|
||||
// - [x] pub flex_wrap: FlexWrap,
|
||||
// - [x] pub flex_grow: f32,
|
||||
// - [x] pub flex_shrink: f32,
|
||||
// - [x] pub flex_basis: Dimension,
|
||||
|
||||
match name {
|
||||
"flex" => {}
|
||||
"flex-direction" => {
|
||||
use FlexDirection::*;
|
||||
style.style.flex_direction = match value {
|
||||
"row" => Row,
|
||||
"row-reverse" => RowReverse,
|
||||
"column" => Column,
|
||||
"column-reverse" => ColumnReverse,
|
||||
_ => Row,
|
||||
};
|
||||
}
|
||||
"flex-basis" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.style.flex_basis = match v {
|
||||
UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
|
||||
UnitSystem::Point(v) => Dimension::Points(v),
|
||||
};
|
||||
}
|
||||
}
|
||||
"flex-flow" => {}
|
||||
"flex-grow" => {
|
||||
if let Ok(val) = value.parse::<f32>() {
|
||||
style.style.flex_grow = val;
|
||||
}
|
||||
}
|
||||
"flex-shrink" => {
|
||||
if let Ok(px) = value.parse::<f32>() {
|
||||
style.style.flex_shrink = px;
|
||||
}
|
||||
}
|
||||
"flex-wrap" => {
|
||||
use FlexWrap::*;
|
||||
style.style.flex_wrap = match value {
|
||||
"nowrap" => NoWrap,
|
||||
"wrap" => Wrap,
|
||||
"wrap-reverse" => WrapReverse,
|
||||
_ => NoWrap,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_font(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
use tui::style::Modifier;
|
||||
match name {
|
||||
"font" => (),
|
||||
"font-family" => (),
|
||||
"font-size" => (),
|
||||
"font-size-adjust" => (),
|
||||
"font-stretch" => (),
|
||||
"font-style" => match value {
|
||||
"italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
|
||||
"oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
|
||||
_ => (),
|
||||
},
|
||||
"font-variant" => todo!(),
|
||||
"font-weight" => match value {
|
||||
"bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD),
|
||||
"normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD),
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match parse_value(value) {
|
||||
Some(UnitSystem::Percent(v)) => match name {
|
||||
"padding" => {
|
||||
let v = Dimension::Percent(v / 100.0);
|
||||
style.style.padding.top = v;
|
||||
style.style.padding.bottom = v;
|
||||
style.style.padding.start = v;
|
||||
style.style.padding.end = v;
|
||||
}
|
||||
"padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0),
|
||||
"padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0),
|
||||
"padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0),
|
||||
"padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0),
|
||||
_ => {}
|
||||
},
|
||||
Some(UnitSystem::Point(v)) => match name {
|
||||
"padding" => {
|
||||
style.style.padding.top = Dimension::Points(v);
|
||||
style.style.padding.bottom = Dimension::Points(v);
|
||||
style.style.padding.start = Dimension::Points(v);
|
||||
style.style.padding.end = Dimension::Points(v);
|
||||
}
|
||||
"padding-bottom" => style.style.padding.bottom = Dimension::Points(v),
|
||||
"padding-left" => style.style.padding.start = Dimension::Points(v),
|
||||
"padding-right" => style.style.padding.end = Dimension::Points(v),
|
||||
"padding-top" => style.style.padding.top = Dimension::Points(v),
|
||||
_ => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_text(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
use tui::style::Modifier;
|
||||
|
||||
match name {
|
||||
"text-align" => todo!(),
|
||||
"text-align-last" => todo!(),
|
||||
"text-decoration" | "text-decoration-line" => {
|
||||
for v in value.split(' ') {
|
||||
match v {
|
||||
"line-through" => {
|
||||
style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT)
|
||||
}
|
||||
"underline" => {
|
||||
style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
"text-decoration-color" => todo!(),
|
||||
"text-decoration-style" => todo!(),
|
||||
"text-indent" => todo!(),
|
||||
"text-justify" => todo!(),
|
||||
"text-overflow" => todo!(),
|
||||
"text-shadow" => todo!(),
|
||||
"text-transform" => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_align(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match name {
|
||||
"align-items" => {
|
||||
use AlignItems::*;
|
||||
style.style.align_items = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-content" => {
|
||||
use AlignContent::*;
|
||||
style.style.align_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-self" => {
|
||||
use AlignSelf::*;
|
||||
style.style.align_self = match value {
|
||||
"auto" => Auto,
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => Auto,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) {
|
||||
//
|
||||
}
|
||||
|
||||
pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match parse_value(value) {
|
||||
Some(UnitSystem::Percent(v)) => match name {
|
||||
"margin" => {
|
||||
let v = Dimension::Percent(v / 100.0);
|
||||
style.style.margin.top = v;
|
||||
style.style.margin.bottom = v;
|
||||
style.style.margin.start = v;
|
||||
style.style.margin.end = v;
|
||||
}
|
||||
"margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0),
|
||||
"margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0),
|
||||
"margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0),
|
||||
"margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0),
|
||||
_ => {}
|
||||
},
|
||||
Some(UnitSystem::Point(v)) => match name {
|
||||
"margin" => {
|
||||
style.style.margin.top = Dimension::Points(v);
|
||||
style.style.margin.bottom = Dimension::Points(v);
|
||||
style.style.margin.start = Dimension::Points(v);
|
||||
style.style.margin.end = Dimension::Points(v);
|
||||
}
|
||||
"margin-top" => style.style.margin.top = Dimension::Points(v),
|
||||
"margin-bottom" => style.style.margin.bottom = Dimension::Points(v),
|
||||
"margin-left" => style.style.margin.start = Dimension::Points(v),
|
||||
"margin-right" => style.style.margin.end = Dimension::Points(v),
|
||||
_ => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_ctrl_c_quit(self) -> Self {
|
||||
pub fn without_ctrl_c_quit(self) -> Self {
|
||||
Self {
|
||||
ctrl_c_quit: true,
|
||||
ctrl_c_quit: false,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
+461
-359
@@ -2,20 +2,25 @@ use crossterm::event::{
|
||||
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
|
||||
};
|
||||
use dioxus_core::*;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
|
||||
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
|
||||
use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use dioxus_html::input_data::keyboard_types::Modifiers;
|
||||
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
||||
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
||||
use dioxus_html::{on::*, KeyCode};
|
||||
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use stretch2::geometry::{Point, Size};
|
||||
use stretch2::{prelude::Layout, Stretch};
|
||||
|
||||
use crate::TuiNode;
|
||||
use crate::{Dom, Node};
|
||||
|
||||
// a wrapper around the input state for easier access
|
||||
// todo: fix loop
|
||||
@@ -31,12 +36,12 @@ use crate::TuiNode;
|
||||
|
||||
// pub fn mouse(&self) -> Option<MouseData> {
|
||||
// let data = (**self.0).borrow();
|
||||
// data.mouse.as_ref().map(|m| clone_mouse_data(m))
|
||||
// data.mouse.as_ref().map(|m| m.clone())
|
||||
// }
|
||||
|
||||
// pub fn wheel(&self) -> Option<WheelData> {
|
||||
// let data = (**self.0).borrow();
|
||||
// data.wheel.as_ref().map(|w| clone_wheel_data(w))
|
||||
// data.wheel.as_ref().map(|w| w.clone())
|
||||
// }
|
||||
|
||||
// pub fn screen(&self) -> Option<(u16, u16)> {
|
||||
@@ -48,7 +53,7 @@ use crate::TuiNode;
|
||||
// let data = (**self.0).borrow();
|
||||
// data.last_key_pressed
|
||||
// .as_ref()
|
||||
// .map(|k| clone_keyboard_data(&k.0))
|
||||
// .map(|k| &k.0.clone())
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -75,7 +80,7 @@ impl EventData {
|
||||
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
||||
|
||||
pub struct InnerInputState {
|
||||
mouse: Option<(MouseData, Vec<u16>)>,
|
||||
mouse: Option<MouseData>,
|
||||
wheel: Option<WheelData>,
|
||||
last_key_pressed: Option<(KeyboardData, Instant)>,
|
||||
screen: Option<(u16, u16)>,
|
||||
@@ -97,55 +102,39 @@ impl InnerInputState {
|
||||
fn apply_event(&mut self, evt: &mut EventCore) {
|
||||
match evt.1 {
|
||||
// limitations: only two buttons may be held at once
|
||||
EventData::Mouse(ref mut m) => match &mut self.mouse {
|
||||
Some(state) => {
|
||||
let mut buttons = state.0.buttons;
|
||||
state.0 = clone_mouse_data(m);
|
||||
match evt.0 {
|
||||
// this code only runs when there are no buttons down
|
||||
"mouseup" => {
|
||||
buttons = 0;
|
||||
state.1 = Vec::new();
|
||||
}
|
||||
"mousedown" => {
|
||||
if state.1.contains(&m.buttons) {
|
||||
// if we already pressed a button and there is another button released the button crossterm sends is the button remaining
|
||||
if state.1.len() > 1 {
|
||||
evt.0 = "mouseup";
|
||||
state.1 = vec![m.buttons];
|
||||
}
|
||||
// otherwise some other button was pressed. In testing it was consistantly this mapping
|
||||
else {
|
||||
match m.buttons {
|
||||
0x01 => state.1.push(0x02),
|
||||
0x02 => state.1.push(0x01),
|
||||
0x04 => state.1.push(0x01),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.1.push(m.buttons);
|
||||
}
|
||||
EventData::Mouse(ref mut m) => {
|
||||
let mut held_buttons = match &self.mouse {
|
||||
Some(previous_data) => previous_data.held_buttons(),
|
||||
None => MouseButtonSet::empty(),
|
||||
};
|
||||
|
||||
buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap();
|
||||
}
|
||||
_ => (),
|
||||
match evt.0 {
|
||||
"mousedown" => {
|
||||
held_buttons.insert(
|
||||
m.trigger_button()
|
||||
.expect("No trigger button for mousedown event"),
|
||||
);
|
||||
}
|
||||
state.0.buttons = buttons;
|
||||
m.buttons = buttons;
|
||||
"mouseup" => {
|
||||
held_buttons.remove(
|
||||
m.trigger_button()
|
||||
.expect("No trigger button for mouseup event"),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None => {
|
||||
self.mouse = Some((
|
||||
clone_mouse_data(m),
|
||||
if m.buttons == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![m.buttons]
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
|
||||
|
||||
let new_mouse_data = MouseData::new(
|
||||
m.coordinates(),
|
||||
m.trigger_button(),
|
||||
held_buttons,
|
||||
m.modifiers(),
|
||||
);
|
||||
|
||||
self.mouse = Some(new_mouse_data.clone());
|
||||
*m = new_mouse_data;
|
||||
}
|
||||
EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
|
||||
EventData::Screen(ref s) => self.screen = Some(*s),
|
||||
EventData::Keyboard(ref mut k) => {
|
||||
let repeat = self
|
||||
@@ -154,160 +143,20 @@ impl InnerInputState {
|
||||
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
|
||||
.is_some();
|
||||
k.repeat = repeat;
|
||||
let new = clone_keyboard_data(k);
|
||||
let new = k.clone();
|
||||
self.last_key_pressed = Some((new, Instant::now()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update<'a>(
|
||||
fn update(
|
||||
&mut self,
|
||||
dom: &'a VirtualDom,
|
||||
evts: &mut Vec<EventCore>,
|
||||
evts: &mut [EventCore],
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
node: &'a VNode<'a>,
|
||||
dom: &mut Dom,
|
||||
) {
|
||||
struct Data<'b> {
|
||||
new_pos: (i32, i32),
|
||||
old_pos: Option<(i32, i32)>,
|
||||
clicked: bool,
|
||||
released: bool,
|
||||
wheel_delta: f64,
|
||||
mouse_data: &'b MouseData,
|
||||
wheel_data: &'b Option<WheelData>,
|
||||
}
|
||||
|
||||
fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
|
||||
layout.location.x as i32 <= point.0
|
||||
&& layout.location.x as i32 + layout.size.width as i32 >= point.0
|
||||
&& layout.location.y as i32 <= point.1
|
||||
&& layout.location.y as i32 + layout.size.height as i32 >= point.1
|
||||
}
|
||||
|
||||
fn get_mouse_events<'c, 'd>(
|
||||
dom: &'c VirtualDom,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
layout: &Stretch,
|
||||
layouts: &HashMap<ElementId, TuiNode<'c>>,
|
||||
node: &'c VNode<'c>,
|
||||
data: &'d Data<'d>,
|
||||
) -> HashSet<&'static str> {
|
||||
match node {
|
||||
VNode::Fragment(f) => {
|
||||
let mut union = HashSet::new();
|
||||
for child in f.children {
|
||||
union = union
|
||||
.union(&get_mouse_events(
|
||||
dom,
|
||||
resolved_events,
|
||||
layout,
|
||||
layouts,
|
||||
child,
|
||||
data,
|
||||
))
|
||||
.copied()
|
||||
.collect();
|
||||
}
|
||||
return union;
|
||||
}
|
||||
|
||||
VNode::Component(vcomp) => {
|
||||
let idx = vcomp.scope.get().unwrap();
|
||||
let new_node = dom.get_scope(idx).unwrap().root_node();
|
||||
return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
|
||||
}
|
||||
|
||||
VNode::Placeholder(_) => return HashSet::new(),
|
||||
|
||||
VNode::Element(_) | VNode::Text(_) => {}
|
||||
}
|
||||
|
||||
let id = node.try_mounted_id().unwrap();
|
||||
let node = layouts.get(&id).unwrap();
|
||||
|
||||
let node_layout = layout.layout(node.layout).unwrap();
|
||||
|
||||
let previously_contained = data
|
||||
.old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||
|
||||
match node.node {
|
||||
VNode::Element(el) => {
|
||||
let mut events = HashSet::new();
|
||||
if previously_contained || currently_contains {
|
||||
for c in el.children {
|
||||
events = events
|
||||
.union(&get_mouse_events(
|
||||
dom,
|
||||
resolved_events,
|
||||
layout,
|
||||
layouts,
|
||||
c,
|
||||
data,
|
||||
))
|
||||
.copied()
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
let mut try_create_event = |name| {
|
||||
// only trigger event if the event was not triggered already by a child
|
||||
if events.insert(name) {
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name,
|
||||
element: Some(el.id.get().unwrap()),
|
||||
data: Arc::new(clone_mouse_data(data.mouse_data)),
|
||||
})
|
||||
}
|
||||
};
|
||||
if currently_contains {
|
||||
if !previously_contained {
|
||||
try_create_event("mouseenter");
|
||||
try_create_event("mouseover");
|
||||
}
|
||||
if data.clicked {
|
||||
try_create_event("mousedown");
|
||||
}
|
||||
if data.released {
|
||||
try_create_event("mouseup");
|
||||
match data.mouse_data.button {
|
||||
0 => try_create_event("click"),
|
||||
2 => try_create_event("contextmenu"),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if let Some(w) = data.wheel_data {
|
||||
if data.wheel_delta != 0.0 {
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name: "wheel",
|
||||
element: Some(el.id.get().unwrap()),
|
||||
data: Arc::new(clone_wheel_data(w)),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if previously_contained {
|
||||
try_create_event("mouseleave");
|
||||
try_create_event("mouseout");
|
||||
}
|
||||
events
|
||||
}
|
||||
VNode::Text(_) => HashSet::new(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
let previous_mouse = self
|
||||
.mouse
|
||||
.as_ref()
|
||||
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
|
||||
// println!("{previous_mouse:?}");
|
||||
let previous_mouse = self.mouse.clone();
|
||||
|
||||
self.wheel = None;
|
||||
|
||||
@@ -315,36 +164,324 @@ impl InnerInputState {
|
||||
self.apply_event(e);
|
||||
}
|
||||
|
||||
// resolve hover events
|
||||
if let Some(mouse) = &self.mouse {
|
||||
let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
|
||||
let old_pos = previous_mouse
|
||||
.as_ref()
|
||||
.map(|m| (m.0.screen_x, m.0.screen_y));
|
||||
let clicked =
|
||||
(!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
||||
let released =
|
||||
(mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
||||
let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
|
||||
let mouse_data = &mouse.0;
|
||||
let wheel_data = &self.wheel;
|
||||
let data = Data {
|
||||
new_pos,
|
||||
old_pos,
|
||||
clicked,
|
||||
released,
|
||||
wheel_delta,
|
||||
mouse_data,
|
||||
wheel_data,
|
||||
};
|
||||
get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
|
||||
}
|
||||
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
|
||||
|
||||
// for s in &self.subscribers {
|
||||
// s();
|
||||
// }
|
||||
}
|
||||
|
||||
fn resolve_mouse_events(
|
||||
&self,
|
||||
previous_mouse: Option<MouseData>,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
layout: &Stretch,
|
||||
dom: &mut Dom,
|
||||
) {
|
||||
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
|
||||
let Point { x, y } = layout.location;
|
||||
let Size { width, height } = layout.size;
|
||||
|
||||
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
|
||||
layout_rect.contains(point.cast())
|
||||
}
|
||||
|
||||
fn try_create_event(
|
||||
name: &'static str,
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
will_bubble: &mut FxHashSet<ElementId>,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
node: &Node,
|
||||
dom: &Dom,
|
||||
) {
|
||||
// only trigger event if the event was not triggered already by a child
|
||||
if will_bubble.insert(node.id) {
|
||||
let mut parent = node.parent;
|
||||
while let Some(parent_id) = parent {
|
||||
will_bubble.insert(parent_id);
|
||||
parent = dom[parent_id.0].parent;
|
||||
}
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name,
|
||||
element: Some(node.id),
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
|
||||
let Point { x, y } = layout.location;
|
||||
let node_origin = ClientPoint::new(x.into(), y.into());
|
||||
|
||||
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
|
||||
.to_point()
|
||||
.cast_unit();
|
||||
|
||||
let coordinates = Coordinates::new(
|
||||
mouse_data.screen_coordinates(),
|
||||
mouse_data.client_coordinates(),
|
||||
new_client_coordinates,
|
||||
mouse_data.page_coordinates(),
|
||||
);
|
||||
|
||||
MouseData::new(
|
||||
coordinates,
|
||||
mouse_data.trigger_button(),
|
||||
mouse_data.held_buttons(),
|
||||
mouse_data.modifiers(),
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(mouse_data) = &self.mouse {
|
||||
let new_pos = mouse_data.screen_coordinates();
|
||||
let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates());
|
||||
|
||||
// a mouse button is pressed if a button was not down and is now down
|
||||
let previous_buttons = previous_mouse
|
||||
.map_or(MouseButtonSet::empty(), |previous_data| {
|
||||
previous_data.held_buttons()
|
||||
});
|
||||
let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty();
|
||||
|
||||
// a mouse button is released if a button was down and is now not down
|
||||
let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
|
||||
|
||||
let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
|
||||
let wheel_data = &self.wheel;
|
||||
|
||||
{
|
||||
// mousemove
|
||||
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 previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
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)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// 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 previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
||||
|
||||
if currently_contains && !previously_contained {
|
||||
try_create_event(
|
||||
"mouseenter",
|
||||
Arc::new(mouse_data.clone()),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// 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 previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
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)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mousedown
|
||||
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);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"mousedown",
|
||||
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// mouseup
|
||||
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);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"mouseup",
|
||||
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// click
|
||||
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);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"click",
|
||||
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// contextmenu
|
||||
if mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) && was_released
|
||||
{
|
||||
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);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"contextmenu",
|
||||
Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// wheel
|
||||
if let Some(w) = wheel_data {
|
||||
if wheel_delta != 0.0 {
|
||||
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);
|
||||
|
||||
if currently_contains {
|
||||
try_create_event(
|
||||
"wheel",
|
||||
Arc::new(w.clone()),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// 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 previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
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)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// 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 previously_contained = old_pos
|
||||
.filter(|pos| layout_contains_point(node_layout, *pos))
|
||||
.is_some();
|
||||
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)),
|
||||
&mut will_bubble,
|
||||
resolved_events,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
|
||||
// self.subscribers.push(f)
|
||||
// }
|
||||
@@ -358,24 +495,21 @@ pub struct RinkInputHandler {
|
||||
impl RinkInputHandler {
|
||||
/// global context that handles events
|
||||
/// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
|
||||
pub fn new(
|
||||
mut receiver: UnboundedReceiver<TermEvent>,
|
||||
cx: &ScopeState,
|
||||
) -> (Self, Rc<RefCell<InnerInputState>>) {
|
||||
pub fn new() -> (
|
||||
Self,
|
||||
Rc<RefCell<InnerInputState>>,
|
||||
impl FnMut(crossterm::event::Event),
|
||||
) {
|
||||
let queued_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
|
||||
let queued_events2 = Rc::downgrade(&queued_events);
|
||||
|
||||
cx.push_future(async move {
|
||||
while let Some(evt) = receiver.next().await {
|
||||
if let Some(evt) = get_event(evt) {
|
||||
if let Some(v) = queued_events2.upgrade() {
|
||||
(*v).borrow_mut().push(evt);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
let regester_event = move |evt: crossterm::event::Event| {
|
||||
if let Some(evt) = get_event(evt) {
|
||||
if let Some(v) = queued_events2.upgrade() {
|
||||
(*v).borrow_mut().push(evt);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let state = Rc::new(RefCell::new(InnerInputState::new()));
|
||||
|
||||
@@ -385,73 +519,65 @@ impl RinkInputHandler {
|
||||
queued_events,
|
||||
},
|
||||
state,
|
||||
regester_event,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_events<'a>(
|
||||
&self,
|
||||
dom: &'a VirtualDom,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
node: &'a VNode<'a>,
|
||||
) -> Vec<UserEvent> {
|
||||
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
||||
fn inner(
|
||||
queue: &[(&'static str, Arc<dyn Any + Send + Sync>)],
|
||||
resolved: &mut Vec<UserEvent>,
|
||||
node: &VNode,
|
||||
) {
|
||||
match node {
|
||||
VNode::Fragment(frag) => {
|
||||
for c in frag.children {
|
||||
inner(queue, resolved, c);
|
||||
}
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
for l in el.listeners {
|
||||
for (name, data) in queue.iter() {
|
||||
if *name == l.event {
|
||||
if let Some(id) = el.id.get() {
|
||||
resolved.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name: *name,
|
||||
element: Some(id),
|
||||
data: data.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for c in el.children {
|
||||
inner(queue, resolved, c);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
|
||||
let mut resolved_events = Vec::new();
|
||||
|
||||
(*self.state).borrow_mut().update(
|
||||
dom,
|
||||
&mut (*self.queued_events).borrow_mut(),
|
||||
&mut resolved_events,
|
||||
layout,
|
||||
layouts,
|
||||
node,
|
||||
dom,
|
||||
);
|
||||
|
||||
let events: Vec<_> = self
|
||||
let events = self
|
||||
.queued_events
|
||||
.replace(Vec::new())
|
||||
.into_iter()
|
||||
// these events were added in the update stage
|
||||
.filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
|
||||
.map(|e| (e.0, e.1.into_any()))
|
||||
.collect();
|
||||
.filter(|e| {
|
||||
![
|
||||
"mouseenter",
|
||||
"mouseover",
|
||||
"mouseleave",
|
||||
"mouseout",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"mousemove",
|
||||
"drag",
|
||||
"wheel",
|
||||
"click",
|
||||
"contextmenu",
|
||||
]
|
||||
.contains(&e.0)
|
||||
})
|
||||
.map(|evt| (evt.0, evt.1.into_any()));
|
||||
|
||||
inner(&events, &mut resolved_events, node);
|
||||
// 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) {
|
||||
v.push(data);
|
||||
} else {
|
||||
hm.insert(event, vec![data]);
|
||||
}
|
||||
}
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolved_events
|
||||
}
|
||||
@@ -468,34 +594,47 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
||||
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
|
||||
let meta = false;
|
||||
|
||||
let get_mouse_data = |b| {
|
||||
let buttons = match b {
|
||||
None => 0,
|
||||
Some(MouseButton::Left) => 1,
|
||||
Some(MouseButton::Right) => 2,
|
||||
Some(MouseButton::Middle) => 4,
|
||||
};
|
||||
let button_state = match b {
|
||||
None => 0,
|
||||
Some(MouseButton::Left) => 0,
|
||||
Some(MouseButton::Middle) => 1,
|
||||
Some(MouseButton::Right) => 2,
|
||||
};
|
||||
let get_mouse_data = |crossterm_button: Option<MouseButton>| {
|
||||
let button = crossterm_button.map(|b| match b {
|
||||
MouseButton::Left => DioxusMouseButton::Primary,
|
||||
MouseButton::Right => DioxusMouseButton::Secondary,
|
||||
MouseButton::Middle => DioxusMouseButton::Auxiliary,
|
||||
});
|
||||
|
||||
// from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
|
||||
EventData::Mouse(MouseData {
|
||||
alt_key: alt,
|
||||
button: button_state,
|
||||
buttons,
|
||||
client_x: x,
|
||||
client_y: y,
|
||||
ctrl_key: ctrl,
|
||||
meta_key: meta,
|
||||
page_x: x,
|
||||
page_y: y,
|
||||
screen_x: x,
|
||||
screen_y: y,
|
||||
shift_key: shift,
|
||||
})
|
||||
|
||||
// The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
|
||||
// todo?
|
||||
// But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
|
||||
let coordinates = Coordinates::new(
|
||||
ScreenPoint::new(x, y),
|
||||
ClientPoint::new(x, y),
|
||||
// offset x/y are set when the origin of the event is assigned to an element
|
||||
ElementPoint::new(0., 0.),
|
||||
PagePoint::new(x, y),
|
||||
);
|
||||
|
||||
let mut modifiers = Modifiers::empty();
|
||||
if shift {
|
||||
modifiers.insert(Modifiers::SHIFT);
|
||||
}
|
||||
if ctrl {
|
||||
modifiers.insert(Modifiers::CONTROL);
|
||||
}
|
||||
if meta {
|
||||
modifiers.insert(Modifiers::META);
|
||||
}
|
||||
if alt {
|
||||
modifiers.insert(Modifiers::ALT);
|
||||
}
|
||||
|
||||
// held mouse buttons get set later by maintaining state, as crossterm does not provide them
|
||||
EventData::Mouse(MouseData::new(
|
||||
coordinates,
|
||||
button,
|
||||
DioxusMouseButtons::empty(),
|
||||
modifiers,
|
||||
))
|
||||
};
|
||||
|
||||
let get_wheel_data = |up| {
|
||||
@@ -525,6 +664,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
||||
|
||||
fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
||||
let (code, key_str);
|
||||
let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT);
|
||||
if let TermKeyCode::Char(c) = event.code {
|
||||
code = match c {
|
||||
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
|
||||
@@ -638,7 +778,11 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
||||
12 => KeyCode::F12,
|
||||
_ => return None,
|
||||
},
|
||||
TermKeyCode::BackTab => return None,
|
||||
// backtab is Shift + Tab
|
||||
TermKeyCode::BackTab => {
|
||||
shift_key = true;
|
||||
KeyCode::Tab
|
||||
}
|
||||
TermKeyCode::Null => return None,
|
||||
_ => return None,
|
||||
};
|
||||
@@ -651,57 +795,15 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
||||
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
|
||||
Some(EventData::Keyboard(KeyboardData {
|
||||
char_code: code.raw_code(),
|
||||
key: key_str.to_string(),
|
||||
key: key_str,
|
||||
key_code: code,
|
||||
alt_key: event.modifiers.contains(KeyModifiers::ALT),
|
||||
ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),
|
||||
meta_key: false,
|
||||
shift_key: event.modifiers.contains(KeyModifiers::SHIFT),
|
||||
shift_key,
|
||||
locale: Default::default(),
|
||||
location: 0x00,
|
||||
repeat: Default::default(),
|
||||
which: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn clone_mouse_data(m: &MouseData) -> MouseData {
|
||||
MouseData {
|
||||
client_x: m.client_x,
|
||||
client_y: m.client_y,
|
||||
page_x: m.page_x,
|
||||
page_y: m.page_y,
|
||||
screen_x: m.screen_x,
|
||||
screen_y: m.screen_y,
|
||||
alt_key: m.alt_key,
|
||||
ctrl_key: m.ctrl_key,
|
||||
meta_key: m.meta_key,
|
||||
shift_key: m.shift_key,
|
||||
button: m.button,
|
||||
buttons: m.buttons,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
|
||||
KeyboardData {
|
||||
char_code: k.char_code,
|
||||
key: k.key.clone(),
|
||||
key_code: k.key_code,
|
||||
alt_key: k.alt_key,
|
||||
ctrl_key: k.ctrl_key,
|
||||
meta_key: k.meta_key,
|
||||
shift_key: k.shift_key,
|
||||
locale: k.locale.clone(),
|
||||
location: k.location,
|
||||
repeat: k.repeat,
|
||||
which: k.which,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_wheel_data(w: &WheelData) -> WheelData {
|
||||
WheelData {
|
||||
delta_mode: w.delta_mode,
|
||||
delta_x: w.delta_x,
|
||||
delta_y: w.delta_y,
|
||||
delta_z: w.delta_x,
|
||||
}
|
||||
}
|
||||
+248
-102
@@ -1,32 +1,64 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_core::*;
|
||||
use std::collections::HashMap;
|
||||
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 crate::{
|
||||
attributes::{apply_attributes, StyleModifer},
|
||||
style::RinkStyle,
|
||||
TuiModifier, TuiNode,
|
||||
};
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum PossiblyUninitalized<T> {
|
||||
Uninitalized,
|
||||
Initialized(T),
|
||||
}
|
||||
impl<T> PossiblyUninitalized<T> {
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
Self::Initialized(i) => i,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Default for PossiblyUninitalized<T> {
|
||||
fn default() -> Self {
|
||||
Self::Uninitalized
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The layout system uses the lineheight as one point.
|
||||
#[derive(Clone, PartialEq, Default, Debug)]
|
||||
pub(crate) struct StretchLayout {
|
||||
pub style: Style,
|
||||
pub node: PossiblyUninitalized<Node>,
|
||||
}
|
||||
|
||||
stretch uses fractional points, so we can rasterize if we need too, but not with characters
|
||||
this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger
|
||||
*/
|
||||
pub fn collect_layout<'a>(
|
||||
layout: &mut stretch2::Stretch,
|
||||
nodes: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
vdom: &'a VirtualDom,
|
||||
node: &'a VNode<'a>,
|
||||
) {
|
||||
use stretch2::prelude::*;
|
||||
impl ChildDepState for StretchLayout {
|
||||
type Ctx = Rc<RefCell<Stretch>>;
|
||||
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))
|
||||
.with_text()
|
||||
.with_tag();
|
||||
|
||||
match node {
|
||||
VNode::Text(t) => {
|
||||
let id = t.id.get().unwrap();
|
||||
let char_len = t.text.chars().count();
|
||||
/// Setup the layout
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
children: impl Iterator<Item = &'a Self::DepState>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
let mut changed = false;
|
||||
let mut stretch = ctx.borrow_mut();
|
||||
let mut style = Style::default();
|
||||
if let Some(text) = node.text() {
|
||||
let char_len = text.chars().count();
|
||||
|
||||
let style = Style {
|
||||
style = Style {
|
||||
size: Size {
|
||||
// characters are 1 point tall
|
||||
height: Dimension::Points(1.0),
|
||||
@@ -36,96 +68,210 @@ pub fn collect_layout<'a>(
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
nodes.insert(
|
||||
id,
|
||||
TuiNode {
|
||||
node,
|
||||
block_style: RinkStyle::default(),
|
||||
tui_modifier: TuiModifier::default(),
|
||||
layout: layout.new_node(style, &[]).unwrap(),
|
||||
},
|
||||
);
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
if let PossiblyUninitalized::Initialized(n) = self.node {
|
||||
if self.style != style {
|
||||
stretch.set_style(n, style).unwrap();
|
||||
}
|
||||
} else {
|
||||
self.node =
|
||||
PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap());
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
// gather up all the styles from the attribute list
|
||||
let mut modifier = StyleModifer {
|
||||
style: Style::default(),
|
||||
tui_style: RinkStyle::default(),
|
||||
tui_modifier: TuiModifier::default(),
|
||||
};
|
||||
|
||||
// handle text modifier elements
|
||||
if el.namespace.is_none() {
|
||||
match el.tag {
|
||||
"b" => apply_attributes("font-weight", "bold", &mut modifier),
|
||||
"strong" => apply_attributes("font-weight", "bold", &mut modifier),
|
||||
"u" => apply_attributes("text-decoration", "underline", &mut modifier),
|
||||
"ins" => apply_attributes("text-decoration", "underline", &mut modifier),
|
||||
"del" => apply_attributes("text-decoration", "line-through", &mut modifier),
|
||||
"i" => apply_attributes("font-style", "italic", &mut modifier),
|
||||
"em" => apply_attributes("font-style", "italic", &mut modifier),
|
||||
"mark" => apply_attributes(
|
||||
"background-color",
|
||||
"rgba(241, 231, 64, 50%)",
|
||||
&mut modifier,
|
||||
),
|
||||
_ => (),
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes(name, text, &mut style);
|
||||
}
|
||||
}
|
||||
|
||||
for &Attribute { name, value, .. } in el.attributes {
|
||||
apply_attributes(name, value, &mut modifier);
|
||||
}
|
||||
|
||||
// Layout the children
|
||||
for child in el.children {
|
||||
collect_layout(layout, nodes, vdom, child);
|
||||
// the root node fills the entire area
|
||||
if node.id() == 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 el in el.children {
|
||||
let ite = ElementIdIterator::new(vdom, el);
|
||||
for node in ite {
|
||||
match node {
|
||||
VNode::Element(_) | VNode::Text(_) => {
|
||||
//
|
||||
child_layout.push(nodes[&node.mounted_id()].layout)
|
||||
}
|
||||
VNode::Placeholder(_) => {}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
for l in children {
|
||||
child_layout.push(l.node.unwrap());
|
||||
}
|
||||
|
||||
// child_layout.push(nodes[&node.mounted_id()].layout)
|
||||
if let PossiblyUninitalized::Initialized(n) = self.node {
|
||||
if self.style != style {
|
||||
stretch.set_style(n, style).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
nodes.insert(
|
||||
node.mounted_id(),
|
||||
TuiNode {
|
||||
node,
|
||||
block_style: modifier.tui_style,
|
||||
tui_modifier: modifier.tui_modifier,
|
||||
layout: layout.new_node(modifier.style, &child_layout).unwrap(),
|
||||
},
|
||||
);
|
||||
}
|
||||
VNode::Fragment(el) => {
|
||||
//
|
||||
for child in el.children {
|
||||
collect_layout(layout, nodes, vdom, child);
|
||||
if stretch.children(n).unwrap() != child_layout {
|
||||
stretch.set_children(n, &child_layout).unwrap();
|
||||
}
|
||||
} else {
|
||||
self.node = PossiblyUninitalized::Initialized(
|
||||
stretch.new_node(style, &child_layout).unwrap(),
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
VNode::Component(sc) => {
|
||||
//
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
let root = scope.root_node();
|
||||
collect_layout(layout, nodes, vdom, root);
|
||||
if self.style != style {
|
||||
changed = true;
|
||||
self.style = style;
|
||||
}
|
||||
VNode::Placeholder(_) => {
|
||||
//
|
||||
}
|
||||
};
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
// these are the attributes in layout_attiributes in native-core
|
||||
const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([
|
||||
"align-content",
|
||||
"align-items",
|
||||
"align-self",
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
"animation-duration",
|
||||
"animation-fill-mode",
|
||||
"animation-iteration-count",
|
||||
"animation-name",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"backface-visibility",
|
||||
"border",
|
||||
"border-bottom",
|
||||
"border-bottom-color",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-style",
|
||||
"border-bottom-width",
|
||||
"border-collapse",
|
||||
"border-color",
|
||||
"border-image",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-left",
|
||||
"border-left-color",
|
||||
"border-left-style",
|
||||
"border-left-width",
|
||||
"border-radius",
|
||||
"border-right",
|
||||
"border-right-color",
|
||||
"border-right-style",
|
||||
"border-right-width",
|
||||
"border-spacing",
|
||||
"border-style",
|
||||
"border-top",
|
||||
"border-top-color",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-top-style",
|
||||
"border-top-width",
|
||||
"border-width",
|
||||
"bottom",
|
||||
"box-shadow",
|
||||
"box-sizing",
|
||||
"caption-side",
|
||||
"clear",
|
||||
"clip",
|
||||
"column-count",
|
||||
"column-fill",
|
||||
"column-gap",
|
||||
"column-rule",
|
||||
"column-rule-color",
|
||||
"column-rule-style",
|
||||
"column-rule-width",
|
||||
"column-span",
|
||||
"column-width",
|
||||
"columns",
|
||||
"content",
|
||||
"counter-increment",
|
||||
"counter-reset",
|
||||
"cursor",
|
||||
"direction",
|
||||
"ltr",
|
||||
"rtl",
|
||||
"display",
|
||||
"empty-cells",
|
||||
"flex",
|
||||
"flex-basis",
|
||||
"flex-direction",
|
||||
"flex-flow",
|
||||
"flex-grow",
|
||||
"flex-shrink",
|
||||
"flex-wrap",
|
||||
"float",
|
||||
"height",
|
||||
"justify-content",
|
||||
"flex-start",
|
||||
"flex-end",
|
||||
"center",
|
||||
"space-between",
|
||||
"space-around",
|
||||
"space-evenly",
|
||||
"left",
|
||||
"letter-spacing",
|
||||
"line-height",
|
||||
"list-style",
|
||||
"list-style-image",
|
||||
"list-style-position",
|
||||
"list-style-type",
|
||||
"margin",
|
||||
"margin-bottom",
|
||||
"margin-left",
|
||||
"margin-right",
|
||||
"margin-top",
|
||||
"max-height",
|
||||
"max-width",
|
||||
"min-height",
|
||||
"min-width",
|
||||
"opacity",
|
||||
"order",
|
||||
"outline",
|
||||
"outline-color",
|
||||
"outline-offset",
|
||||
"outline-style",
|
||||
"outline-width",
|
||||
"overflow",
|
||||
"overflow-x",
|
||||
"overflow-y",
|
||||
"padding",
|
||||
"padding-bottom",
|
||||
"padding-left",
|
||||
"padding-right",
|
||||
"padding-top",
|
||||
"page-break-after",
|
||||
"page-break-before",
|
||||
"page-break-inside",
|
||||
"perspective",
|
||||
"perspective-origin",
|
||||
"position",
|
||||
"static",
|
||||
"relative",
|
||||
"fixed",
|
||||
"absolute",
|
||||
"sticky",
|
||||
"pointer-events",
|
||||
"quotes",
|
||||
"resize",
|
||||
"right",
|
||||
"tab-size",
|
||||
"table-layout",
|
||||
"top",
|
||||
"transform",
|
||||
"transform-origin",
|
||||
"transform-style",
|
||||
"transition",
|
||||
"transition-delay",
|
||||
"transition-duration",
|
||||
"transition-property",
|
||||
"transition-timing-function",
|
||||
"vertical-align",
|
||||
"visibility",
|
||||
"white-space",
|
||||
"width",
|
||||
"word-break",
|
||||
"word-spacing",
|
||||
"word-wrap",
|
||||
"z-index"
|
||||
]);
|
||||
+109
-118
@@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use anymap::AnyMap;
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
|
||||
execute,
|
||||
@@ -6,35 +7,42 @@ 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 futures::{
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
pin_mut, StreamExt,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use stretch2::{
|
||||
prelude::{Node, Size},
|
||||
Stretch,
|
||||
};
|
||||
use style::RinkStyle;
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
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 tui::{backend::CrosstermBackend, layout::Rect, Terminal};
|
||||
|
||||
mod attributes;
|
||||
mod config;
|
||||
mod hooks;
|
||||
mod layout;
|
||||
mod render;
|
||||
mod style;
|
||||
mod style_attributes;
|
||||
mod widget;
|
||||
|
||||
pub use attributes::*;
|
||||
pub use config::*;
|
||||
pub use hooks::*;
|
||||
pub use layout::*;
|
||||
pub use render::*;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TuiContext {
|
||||
@@ -52,66 +60,64 @@ pub fn launch(app: Component<()>) {
|
||||
|
||||
pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let (tx, rx) = unbounded();
|
||||
|
||||
let (handler, state, register_event) = RinkInputHandler::new();
|
||||
|
||||
// Setup input handling
|
||||
let (event_tx, event_rx) = unbounded();
|
||||
let event_tx_clone = event_tx.clone();
|
||||
if !cfg.headless {
|
||||
std::thread::spawn(move || {
|
||||
let tick_rate = Duration::from_millis(100);
|
||||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(1000);
|
||||
loop {
|
||||
// poll for tick rate duration, if no events, sent tick event.
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
|
||||
if crossterm::event::poll(timeout).unwrap() {
|
||||
if crossterm::event::poll(tick_rate).unwrap() {
|
||||
// if crossterm::event::poll(timeout).unwrap() {
|
||||
let evt = crossterm::event::read().unwrap();
|
||||
event_tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
event_tx.unbounded_send(InputEvent::Tick).unwrap();
|
||||
last_tick = Instant::now();
|
||||
if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let cx = dom.base_scope();
|
||||
cx.provide_root_context(state);
|
||||
cx.provide_root_context(TuiContext { tx: event_tx_clone });
|
||||
|
||||
let (handler, state) = RinkInputHandler::new(rx, cx);
|
||||
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 mut any_map = AnyMap::new();
|
||||
any_map.insert(stretch.clone());
|
||||
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
|
||||
|
||||
cx.provide_root_context(state);
|
||||
|
||||
dom.rebuild();
|
||||
|
||||
render_vdom(&mut dom, event_rx, tx, handler, cfg).unwrap();
|
||||
}
|
||||
|
||||
pub struct TuiNode<'a> {
|
||||
pub layout: stretch2::node::Node,
|
||||
pub block_style: RinkStyle,
|
||||
pub tui_modifier: TuiModifier,
|
||||
pub node: &'a VNode<'a>,
|
||||
render_vdom(
|
||||
&mut dom,
|
||||
event_rx,
|
||||
handler,
|
||||
cfg,
|
||||
rdom,
|
||||
stretch,
|
||||
register_event,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn render_vdom(
|
||||
vdom: &mut VirtualDom,
|
||||
mut event_reciever: UnboundedReceiver<InputEvent>,
|
||||
ctx: UnboundedSender<TermEvent>,
|
||||
handler: RinkInputHandler,
|
||||
cfg: Config,
|
||||
mut rdom: Dom,
|
||||
stretch: Rc<RefCell<Stretch>>,
|
||||
mut register_event: impl FnMut(crossterm::event::Event),
|
||||
) -> Result<()> {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(async {
|
||||
/*
|
||||
Get the terminal to calcualte the layout from
|
||||
*/
|
||||
let mut terminal = (!cfg.headless).then(|| {
|
||||
enable_raw_mode().unwrap();
|
||||
let mut stdout = std::io::stdout();
|
||||
@@ -119,85 +125,61 @@ fn render_vdom(
|
||||
let backend = CrosstermBackend::new(io::stdout());
|
||||
Terminal::new(backend).unwrap()
|
||||
});
|
||||
|
||||
if let Some(terminal) = &mut terminal {
|
||||
terminal.clear().unwrap();
|
||||
}
|
||||
|
||||
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
||||
let mut resized = true;
|
||||
|
||||
loop {
|
||||
/*
|
||||
-> collect all the nodes with their layout
|
||||
-> solve their layout
|
||||
-> render the nodes in the right place with tui/crossterm
|
||||
-> wait for changes
|
||||
-> resolve events
|
||||
-> render the nodes in the right place with tui/crosstream
|
||||
-> while rendering, apply styling
|
||||
-> lazily update the layout and style based on nodes changed
|
||||
|
||||
use simd to compare lines for diffing?
|
||||
|
||||
|
||||
todo: reuse the layout and node objects.
|
||||
our work_with_deadline method can tell us which nodes are dirty.
|
||||
todo: lazy re-rendering
|
||||
*/
|
||||
let mut layout = Stretch::new();
|
||||
let mut nodes = HashMap::new();
|
||||
|
||||
let root_node = vdom.base_scope().root_node();
|
||||
layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
|
||||
/*
|
||||
Compute the layout given the terminal size
|
||||
*/
|
||||
let node_id = root_node.try_mounted_id().unwrap();
|
||||
let root_layout = nodes[&node_id].layout;
|
||||
let mut events = Vec::new();
|
||||
if !to_rerender.is_empty() || resized {
|
||||
resized = false;
|
||||
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
|
||||
let width = dims.width;
|
||||
let height = dims.height;
|
||||
let root_node = rdom[0].state.layout.node.unwrap();
|
||||
|
||||
fn resize(dims: tui::layout::Rect, stretch: &mut Stretch, root_layout: Node) {
|
||||
let width = dims.width;
|
||||
let height = dims.height;
|
||||
|
||||
stretch
|
||||
.compute_layout(
|
||||
root_layout,
|
||||
Size {
|
||||
width: stretch2::prelude::Number::Defined((width - 1) as f32),
|
||||
height: stretch2::prelude::Number::Defined((height - 1) as f32),
|
||||
stretch
|
||||
.compute_layout(
|
||||
root_node,
|
||||
Size {
|
||||
width: stretch2::prelude::Number::Defined((width - 1) as f32),
|
||||
height: stretch2::prelude::Number::Defined((height - 1) as f32),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
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);
|
||||
let root = &rdom[0];
|
||||
render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
|
||||
})?;
|
||||
} else {
|
||||
resize(
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 300,
|
||||
height: 300,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if let Some(terminal) = &mut terminal {
|
||||
terminal.draw(|frame| {
|
||||
// size is guaranteed to not change when rendering
|
||||
resize(frame.size(), &mut layout, root_layout);
|
||||
|
||||
// resolve events before rendering
|
||||
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
|
||||
render::render_vnode(
|
||||
frame,
|
||||
&layout,
|
||||
&mut nodes,
|
||||
vdom,
|
||||
root_node,
|
||||
&RinkStyle::default(),
|
||||
cfg,
|
||||
&mut stretch.borrow_mut(),
|
||||
&rdom,
|
||||
);
|
||||
assert!(nodes.is_empty());
|
||||
})?;
|
||||
} else {
|
||||
resize(
|
||||
tui::layout::Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
&mut layout,
|
||||
root_layout,
|
||||
);
|
||||
}
|
||||
|
||||
for e in events {
|
||||
vdom.handle_message(SchedulerMsg::Event(e));
|
||||
}
|
||||
}
|
||||
|
||||
use futures::future::{select, Either};
|
||||
@@ -220,20 +202,32 @@ fn render_vdom(
|
||||
break;
|
||||
}
|
||||
}
|
||||
TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
|
||||
TermEvent::Resize(_, _) => resized = true,
|
||||
TermEvent::Mouse(_) => {}
|
||||
},
|
||||
InputEvent::Tick => {} // tick
|
||||
InputEvent::Close => break,
|
||||
};
|
||||
|
||||
if let InputEvent::UserInput(evt) = evt.unwrap() {
|
||||
ctx.unbounded_send(evt).unwrap();
|
||||
register_event(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vdom.work_with_deadline(|| false);
|
||||
{
|
||||
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
|
||||
for e in evts {
|
||||
vdom.handle_message(SchedulerMsg::Event(e));
|
||||
}
|
||||
let mutations = vdom.work_with_deadline(|| false);
|
||||
// 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());
|
||||
to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(terminal) = &mut terminal {
|
||||
@@ -252,8 +246,5 @@ fn render_vdom(
|
||||
|
||||
enum InputEvent {
|
||||
UserInput(TermEvent),
|
||||
Tick,
|
||||
|
||||
#[allow(dead_code)]
|
||||
Close,
|
||||
}
|
||||
+35
-61
@@ -1,5 +1,5 @@
|
||||
use dioxus_core::*;
|
||||
use std::{collections::HashMap, io::Stdout};
|
||||
use dioxus_native_core::layout_attributes::UnitSystem;
|
||||
use std::io::Stdout;
|
||||
use stretch2::{
|
||||
geometry::Point,
|
||||
prelude::{Layout, Size},
|
||||
@@ -9,52 +9,33 @@ use tui::{backend::CrosstermBackend, layout::Rect};
|
||||
|
||||
use crate::{
|
||||
style::{RinkColor, RinkStyle},
|
||||
style_attributes::{BorderEdge, BorderStyle},
|
||||
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
|
||||
BorderEdge, BorderStyle, Config, TuiNode, UnitSystem,
|
||||
Config, Dom, Node,
|
||||
};
|
||||
|
||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||
|
||||
pub fn render_vnode<'a>(
|
||||
pub(crate) fn render_vnode(
|
||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
vdom: &'a VirtualDom,
|
||||
node: &'a VNode<'a>,
|
||||
// this holds the accumulated syle state for styled text rendering
|
||||
style: &RinkStyle,
|
||||
rdom: &Dom,
|
||||
node: &Node,
|
||||
cfg: Config,
|
||||
) {
|
||||
match node {
|
||||
VNode::Fragment(f) => {
|
||||
for child in f.children {
|
||||
render_vnode(frame, layout, layouts, vdom, child, style, cfg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
use dioxus_native_core::real_dom::NodeType;
|
||||
|
||||
VNode::Component(vcomp) => {
|
||||
let idx = vcomp.scope.get().unwrap();
|
||||
let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||
render_vnode(frame, layout, layouts, vdom, new_node, style, cfg);
|
||||
return;
|
||||
}
|
||||
|
||||
VNode::Placeholder(_) => return,
|
||||
|
||||
VNode::Element(_) | VNode::Text(_) => {}
|
||||
if let NodeType::Placeholder = &node.node_type {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = node.try_mounted_id().unwrap();
|
||||
let mut node = layouts.remove(&id).unwrap();
|
||||
|
||||
let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
|
||||
let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
|
||||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match node.node {
|
||||
VNode::Text(t) => {
|
||||
match &node.node_type {
|
||||
NodeType::Text { text } => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
text: &'a str,
|
||||
@@ -67,14 +48,14 @@ pub fn render_vnode<'a>(
|
||||
let mut new_cell = RinkCell::default();
|
||||
new_cell.set_style(self.style);
|
||||
new_cell.symbol = c.to_string();
|
||||
buf.set(area.left() + i as u16, area.top(), &new_cell);
|
||||
buf.set(area.left() + i as u16, area.top(), new_cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let label = Label {
|
||||
text: t.text,
|
||||
style: *style,
|
||||
text,
|
||||
style: node.state.style.core,
|
||||
};
|
||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||
|
||||
@@ -83,30 +64,23 @@ pub fn render_vnode<'a>(
|
||||
frame.render_widget(WidgetWithContext::new(label, cfg), area);
|
||||
}
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
NodeType::Element { children, .. } => {
|
||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||
|
||||
let mut new_style = node.block_style.merge(*style);
|
||||
node.block_style = new_style;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// do not pass background color to children
|
||||
new_style.bg = None;
|
||||
for el in el.children {
|
||||
render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg);
|
||||
for c in children {
|
||||
render_vnode(frame, layout, rdom, &rdom[c.0], cfg);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
VNode::Placeholder(_) => todo!(),
|
||||
NodeType::Placeholder => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RinkWidget for TuiNode<'a> {
|
||||
impl RinkWidget for &Node {
|
||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||
use tui::symbols::line::*;
|
||||
|
||||
@@ -176,7 +150,7 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
buf.set(
|
||||
(current[0] + pos[0] as i32) as u16,
|
||||
(current[1] + pos[1] as i32) as u16,
|
||||
&new_cell,
|
||||
new_cell,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -270,8 +244,8 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
|
||||
fn get_radius(border: &BorderEdge, area: Rect) -> f32 {
|
||||
match border.style {
|
||||
BorderStyle::HIDDEN => 0.0,
|
||||
BorderStyle::NONE => 0.0,
|
||||
BorderStyle::Hidden => 0.0,
|
||||
BorderStyle::None => 0.0,
|
||||
_ => match border.radius {
|
||||
UnitSystem::Percent(p) => p * area.width as f32 / 100.0,
|
||||
UnitSystem::Point(p) => p,
|
||||
@@ -290,14 +264,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = self.block_style.bg {
|
||||
if let Some(c) = self.state.style.core.bg {
|
||||
new_cell.bg = c;
|
||||
}
|
||||
buf.set(x, y, &new_cell);
|
||||
buf.set(x, y, new_cell);
|
||||
}
|
||||
}
|
||||
|
||||
let borders = self.tui_modifier.borders;
|
||||
let borders = &self.state.style.modifier.borders;
|
||||
|
||||
let last_edge = &borders.left;
|
||||
let current_edge = &borders.top;
|
||||
@@ -314,14 +288,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.block_style.fg);
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
}
|
||||
for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
|
||||
new_cell.symbol = symbols.horizontal.to_string();
|
||||
buf.set(x, area.top(), &new_cell);
|
||||
buf.set(x, area.top(), new_cell.clone());
|
||||
}
|
||||
draw_arc(
|
||||
[area.right() - radius[0] - 1, area.top() + radius[1]],
|
||||
@@ -349,14 +323,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.block_style.fg);
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
}
|
||||
for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
|
||||
new_cell.symbol = symbols.vertical.to_string();
|
||||
buf.set(area.right() - 1, y, &new_cell);
|
||||
buf.set(area.right() - 1, y, new_cell.clone());
|
||||
}
|
||||
draw_arc(
|
||||
[area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
|
||||
@@ -384,14 +358,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.block_style.fg);
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
}
|
||||
for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
|
||||
new_cell.symbol = symbols.horizontal.to_string();
|
||||
buf.set(x, area.bottom() - 1, &new_cell);
|
||||
buf.set(x, area.bottom() - 1, new_cell.clone());
|
||||
}
|
||||
draw_arc(
|
||||
[area.left() + radius[0], area.bottom() - radius[1] - 1],
|
||||
@@ -419,14 +393,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
|
||||
(last_r * RADIUS_MULTIPLIER[0]) as u16,
|
||||
(last_r * RADIUS_MULTIPLIER[1]) as u16,
|
||||
];
|
||||
let color = current_edge.color.or(self.block_style.fg);
|
||||
let color = current_edge.color.or(self.state.style.core.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
}
|
||||
for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
|
||||
new_cell.symbol = symbols.vertical.to_string();
|
||||
buf.set(area.left(), y, &new_cell);
|
||||
buf.set(area.left(), y, new_cell.clone());
|
||||
}
|
||||
draw_arc(
|
||||
[area.left() + radius[0], area.top() + radius[1]],
|
||||
|
||||
+46
-42
@@ -4,17 +4,17 @@ use tui::style::{Color, Modifier, Style};
|
||||
|
||||
use crate::RenderingMode;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct RinkColor {
|
||||
pub color: Color,
|
||||
pub alpha: f32,
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
impl Default for RinkColor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: Color::Black,
|
||||
alpha: 0.0,
|
||||
alpha: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,22 +23,17 @@ impl RinkColor {
|
||||
pub fn blend(self, other: Color) -> Color {
|
||||
if self.color == Color::Reset {
|
||||
Color::Reset
|
||||
} else if self.alpha == 0.0 {
|
||||
} else if self.alpha == 0 {
|
||||
other
|
||||
} else {
|
||||
let [sr, sg, sb] = to_rgb(self.color);
|
||||
let [or, og, ob] = to_rgb(other);
|
||||
let (sr, sg, sb, sa) = (
|
||||
sr as f32 / 255.0,
|
||||
sg as f32 / 255.0,
|
||||
sb as f32 / 255.0,
|
||||
self.alpha,
|
||||
);
|
||||
let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0);
|
||||
let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
|
||||
let [or, og, ob] = to_rgb(other).map(|e| e as u16);
|
||||
let sa = self.alpha as u16;
|
||||
let rsa = 255 - sa;
|
||||
Color::Rgb(
|
||||
(255.0 * (sr * sa + or * (1.0 - sa))) as u8,
|
||||
(255.0 * (sg * sa + og * (1.0 - sa))) as u8,
|
||||
(255.0 * (sb * sa + ob * (1.0 - sa))) as u8,
|
||||
((sr * sa + or * rsa) / 255) as u8,
|
||||
((sg * sa + og * rsa) / 255) as u8,
|
||||
((sb * sa + ob * rsa) / 255) as u8,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -151,75 +146,75 @@ impl FromStr for RinkColor {
|
||||
match color {
|
||||
"red" => Ok(RinkColor {
|
||||
color: Color::Red,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"black" => Ok(RinkColor {
|
||||
color: Color::Black,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"green" => Ok(RinkColor {
|
||||
color: Color::Green,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"yellow" => Ok(RinkColor {
|
||||
color: Color::Yellow,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"blue" => Ok(RinkColor {
|
||||
color: Color::Blue,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"magenta" => Ok(RinkColor {
|
||||
color: Color::Magenta,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"cyan" => Ok(RinkColor {
|
||||
color: Color::Cyan,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"gray" => Ok(RinkColor {
|
||||
color: Color::Gray,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"darkgray" => Ok(RinkColor {
|
||||
color: Color::DarkGray,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
// light red does not exist
|
||||
"orangered" => Ok(RinkColor {
|
||||
color: Color::LightRed,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"lightgreen" => Ok(RinkColor {
|
||||
color: Color::LightGreen,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"lightyellow" => Ok(RinkColor {
|
||||
color: Color::LightYellow,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"lightblue" => Ok(RinkColor {
|
||||
color: Color::LightBlue,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
// light magenta does not exist
|
||||
"orchid" => Ok(RinkColor {
|
||||
color: Color::LightMagenta,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"lightcyan" => Ok(RinkColor {
|
||||
color: Color::LightCyan,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
"white" => Ok(RinkColor {
|
||||
color: Color::White,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
_ => {
|
||||
if color.len() == 7 && color.starts_with('#') {
|
||||
parse_hex(color).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
})
|
||||
} else if let Some(stripped) = color.strip_prefix("rgb(") {
|
||||
let color_values = stripped.trim_end_matches(')');
|
||||
@@ -234,7 +229,7 @@ impl FromStr for RinkColor {
|
||||
} else {
|
||||
parse_rgb(color_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
})
|
||||
}
|
||||
} else if let Some(stripped) = color.strip_prefix("rgba(") {
|
||||
@@ -243,14 +238,17 @@ impl FromStr for RinkColor {
|
||||
let (rgb_values, alpha) =
|
||||
color_values.rsplit_once(',').ok_or(ParseColorError)?;
|
||||
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
|
||||
parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
|
||||
parse_rgb(rgb_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: (a * 255.0) as u8,
|
||||
})
|
||||
} else {
|
||||
Err(ParseColorError)
|
||||
}
|
||||
} else {
|
||||
parse_rgb(color_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
})
|
||||
}
|
||||
} else if let Some(stripped) = color.strip_prefix("hsl(") {
|
||||
@@ -259,14 +257,17 @@ impl FromStr for RinkColor {
|
||||
let (rgb_values, alpha) =
|
||||
color_values.rsplit_once(',').ok_or(ParseColorError)?;
|
||||
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
|
||||
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
|
||||
parse_hsl(rgb_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: (a * 255.0) as u8,
|
||||
})
|
||||
} else {
|
||||
Err(ParseColorError)
|
||||
}
|
||||
} else {
|
||||
parse_hsl(color_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
})
|
||||
}
|
||||
} else if let Some(stripped) = color.strip_prefix("hsla(") {
|
||||
@@ -275,14 +276,17 @@ impl FromStr for RinkColor {
|
||||
let (rgb_values, alpha) =
|
||||
color_values.rsplit_once(',').ok_or(ParseColorError)?;
|
||||
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
|
||||
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
|
||||
parse_hsl(rgb_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: (a * 255.0) as u8,
|
||||
})
|
||||
} else {
|
||||
Err(ParseColorError)
|
||||
}
|
||||
} else {
|
||||
parse_hsl(color_values).map(|c| RinkColor {
|
||||
color: c,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -393,7 +397,7 @@ fn rgb_to_ansi() {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct RinkStyle {
|
||||
pub fg: Option<RinkColor>,
|
||||
pub bg: Option<RinkColor>,
|
||||
@@ -406,7 +410,7 @@ impl Default for RinkStyle {
|
||||
Self {
|
||||
fg: Some(RinkColor {
|
||||
color: Color::White,
|
||||
alpha: 1.0,
|
||||
alpha: 255,
|
||||
}),
|
||||
bg: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
|
||||
@@ -0,0 +1,797 @@
|
||||
/*
|
||||
- [ ] pub display: Display,
|
||||
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
|
||||
- [ ] pub direction: Direction,
|
||||
|
||||
- [x] pub flex_direction: FlexDirection,
|
||||
- [x] pub flex_wrap: FlexWrap,
|
||||
- [x] pub flex_grow: f32,
|
||||
- [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 align_items: AlignItems,
|
||||
- [x] pub align_self: AlignSelf,
|
||||
- [x] pub align_content: AlignContent,
|
||||
|
||||
- [x] pub margin: Rect<Dimension>,
|
||||
- [x] pub padding: Rect<Dimension>,
|
||||
|
||||
- [x] pub justify_content: JustifyContent,
|
||||
- [ ] pub position: Rect<Dimension>,
|
||||
- [x] pub border: Rect<Dimension>,
|
||||
|
||||
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
|
||||
- [ ] pub min_size: Size<Dimension>,
|
||||
- [ ] pub max_size: Size<Dimension>,
|
||||
|
||||
- [ ] pub aspect_ratio: Number,
|
||||
*/
|
||||
|
||||
use dioxus_core::Attribute;
|
||||
use dioxus_native_core::{
|
||||
layout_attributes::{parse_value, UnitSystem},
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
state::ParentDepState,
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
|
||||
use crate::style::{RinkColor, RinkStyle};
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub struct StyleModifier {
|
||||
pub core: RinkStyle,
|
||||
pub modifier: TuiModifier,
|
||||
}
|
||||
|
||||
impl ParentDepState for StyleModifier {
|
||||
type Ctx = ();
|
||||
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 {
|
||||
let mut new = StyleModifier::default();
|
||||
if parent.is_some() {
|
||||
new.core.fg = None;
|
||||
}
|
||||
|
||||
// handle text modifier elements
|
||||
if node.namespace().is_none() {
|
||||
if let Some(tag) = node.tag() {
|
||||
match tag {
|
||||
"b" => apply_style_attributes("font-weight", "bold", &mut new),
|
||||
"strong" => apply_style_attributes("font-weight", "bold", &mut new),
|
||||
"u" => apply_style_attributes("text-decoration", "underline", &mut new),
|
||||
"ins" => apply_style_attributes("text-decoration", "underline", &mut new),
|
||||
"del" => apply_style_attributes("text-decoration", "line-through", &mut new),
|
||||
"i" => apply_style_attributes("font-style", "italic", &mut new),
|
||||
"em" => apply_style_attributes("font-style", "italic", &mut new),
|
||||
"mark" => {
|
||||
apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gather up all the styles from the attribute list
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_style_attributes(name, text, &mut new);
|
||||
}
|
||||
}
|
||||
|
||||
// keep the text styling from the parent element
|
||||
if let Some(parent) = parent {
|
||||
let mut new_style = new.core.merge(parent.core);
|
||||
new_style.bg = new.core.bg;
|
||||
new.core = new_style;
|
||||
}
|
||||
if &mut new != self {
|
||||
*self = new;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub struct TuiModifier {
|
||||
pub borders: Borders,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub struct Borders {
|
||||
pub top: BorderEdge,
|
||||
pub right: BorderEdge,
|
||||
pub bottom: BorderEdge,
|
||||
pub left: BorderEdge,
|
||||
}
|
||||
|
||||
impl Borders {
|
||||
fn slice(&mut self) -> [&mut BorderEdge; 4] {
|
||||
[
|
||||
&mut self.top,
|
||||
&mut self.right,
|
||||
&mut self.bottom,
|
||||
&mut self.left,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct BorderEdge {
|
||||
pub color: Option<RinkColor>,
|
||||
pub style: BorderStyle,
|
||||
pub width: UnitSystem,
|
||||
pub radius: UnitSystem,
|
||||
}
|
||||
|
||||
impl Default for BorderEdge {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: None,
|
||||
style: BorderStyle::None,
|
||||
width: UnitSystem::Point(0.0),
|
||||
radius: UnitSystem::Point(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum BorderStyle {
|
||||
Dotted,
|
||||
Dashed,
|
||||
Solid,
|
||||
Double,
|
||||
Groove,
|
||||
Ridge,
|
||||
Inset,
|
||||
Outset,
|
||||
Hidden,
|
||||
None,
|
||||
}
|
||||
|
||||
impl BorderStyle {
|
||||
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
|
||||
use tui::symbols::line::*;
|
||||
const DASHED: Set = Set {
|
||||
horizontal: "╌",
|
||||
vertical: "╎",
|
||||
..NORMAL
|
||||
};
|
||||
const DOTTED: Set = Set {
|
||||
horizontal: "┈",
|
||||
vertical: "┊",
|
||||
..NORMAL
|
||||
};
|
||||
match self {
|
||||
BorderStyle::Dotted => Some(DOTTED),
|
||||
BorderStyle::Dashed => Some(DASHED),
|
||||
BorderStyle::Solid => Some(NORMAL),
|
||||
BorderStyle::Double => Some(DOUBLE),
|
||||
BorderStyle::Groove => Some(NORMAL),
|
||||
BorderStyle::Ridge => Some(NORMAL),
|
||||
BorderStyle::Inset => Some(NORMAL),
|
||||
BorderStyle::Outset => Some(NORMAL),
|
||||
BorderStyle::Hidden => None,
|
||||
BorderStyle::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// applies the entire html namespace defined in dioxus-html
|
||||
pub fn apply_style_attributes(
|
||||
//
|
||||
name: &str,
|
||||
value: &str,
|
||||
style: &mut StyleModifier,
|
||||
) {
|
||||
match name {
|
||||
"animation"
|
||||
| "animation-delay"
|
||||
| "animation-direction"
|
||||
| "animation-duration"
|
||||
| "animation-fill-mode"
|
||||
| "animation-iteration-count"
|
||||
| "animation-name"
|
||||
| "animation-play-state"
|
||||
| "animation-timing-function" => apply_animation(name, value, style),
|
||||
|
||||
"backface-visibility" => {}
|
||||
|
||||
"background"
|
||||
| "background-attachment"
|
||||
| "background-clip"
|
||||
| "background-color"
|
||||
| "background-image"
|
||||
| "background-origin"
|
||||
| "background-position"
|
||||
| "background-repeat"
|
||||
| "background-size" => apply_background(name, value, style),
|
||||
|
||||
"border"
|
||||
| "border-bottom"
|
||||
| "border-bottom-color"
|
||||
| "border-bottom-left-radius"
|
||||
| "border-bottom-right-radius"
|
||||
| "border-bottom-style"
|
||||
| "border-bottom-width"
|
||||
| "border-collapse"
|
||||
| "border-color"
|
||||
| "border-image"
|
||||
| "border-image-outset"
|
||||
| "border-image-repeat"
|
||||
| "border-image-slice"
|
||||
| "border-image-source"
|
||||
| "border-image-width"
|
||||
| "border-left"
|
||||
| "border-left-color"
|
||||
| "border-left-style"
|
||||
| "border-left-width"
|
||||
| "border-radius"
|
||||
| "border-right"
|
||||
| "border-right-color"
|
||||
| "border-right-style"
|
||||
| "border-right-width"
|
||||
| "border-spacing"
|
||||
| "border-style"
|
||||
| "border-top"
|
||||
| "border-top-color"
|
||||
| "border-top-left-radius"
|
||||
| "border-top-right-radius"
|
||||
| "border-top-style"
|
||||
| "border-top-width"
|
||||
| "border-width" => apply_border(name, value, style),
|
||||
|
||||
"bottom" => {}
|
||||
"box-shadow" => {}
|
||||
"box-sizing" => {}
|
||||
"caption-side" => {}
|
||||
"clear" => {}
|
||||
"clip" => {}
|
||||
|
||||
"color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.core.fg.replace(c);
|
||||
}
|
||||
}
|
||||
|
||||
"columns" => {}
|
||||
|
||||
"content" => {}
|
||||
"counter-increment" => {}
|
||||
"counter-reset" => {}
|
||||
|
||||
"cursor" => {}
|
||||
|
||||
"empty-cells" => {}
|
||||
|
||||
"float" => {}
|
||||
|
||||
"font" | "font-family" | "font-size" | "font-size-adjust" | "font-stretch"
|
||||
| "font-style" | "font-variant" | "font-weight" => apply_font(name, value, style),
|
||||
|
||||
"letter-spacing" => {}
|
||||
"line-height" => {}
|
||||
|
||||
"list-style" | "list-style-image" | "list-style-position" | "list-style-type" => {}
|
||||
|
||||
"opacity" => {}
|
||||
"order" => {}
|
||||
"outline" => {}
|
||||
|
||||
"outline-color" | "outline-offset" | "outline-style" | "outline-width" => {}
|
||||
|
||||
"page-break-after" | "page-break-before" | "page-break-inside" => {}
|
||||
|
||||
"perspective" | "perspective-origin" => {}
|
||||
|
||||
"pointer-events" => {}
|
||||
|
||||
"quotes" => {}
|
||||
"resize" => {}
|
||||
"tab-size" => {}
|
||||
"table-layout" => {}
|
||||
|
||||
"text-align"
|
||||
| "text-align-last"
|
||||
| "text-decoration"
|
||||
| "text-decoration-color"
|
||||
| "text-decoration-line"
|
||||
| "text-decoration-style"
|
||||
| "text-indent"
|
||||
| "text-justify"
|
||||
| "text-overflow"
|
||||
| "text-shadow"
|
||||
| "text-transform" => apply_text(name, value, style),
|
||||
|
||||
"transition"
|
||||
| "transition-delay"
|
||||
| "transition-duration"
|
||||
| "transition-property"
|
||||
| "transition-timing-function" => apply_transition(name, value, style),
|
||||
|
||||
"visibility" => {}
|
||||
"white-space" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_background(name: &str, value: &str, style: &mut StyleModifier) {
|
||||
match name {
|
||||
"background-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.core.bg.replace(c);
|
||||
}
|
||||
}
|
||||
"background" => {}
|
||||
"background-attachment" => {}
|
||||
"background-clip" => {}
|
||||
"background-image" => {}
|
||||
"background-origin" => {}
|
||||
"background-position" => {}
|
||||
"background-repeat" => {}
|
||||
"background-size" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_border(name: &str, value: &str, style: &mut StyleModifier) {
|
||||
fn parse_border_style(v: &str) -> BorderStyle {
|
||||
match v {
|
||||
"dotted" => BorderStyle::Dotted,
|
||||
"dashed" => BorderStyle::Dashed,
|
||||
"solid" => BorderStyle::Solid,
|
||||
"double" => BorderStyle::Double,
|
||||
"groove" => BorderStyle::Groove,
|
||||
"ridge" => BorderStyle::Ridge,
|
||||
"inset" => BorderStyle::Inset,
|
||||
"outset" => BorderStyle::Outset,
|
||||
"none" => BorderStyle::None,
|
||||
"hidden" => BorderStyle::Hidden,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
match name {
|
||||
"border" => {}
|
||||
"border-bottom" => {}
|
||||
"border-bottom-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.modifier.borders.bottom.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-bottom-left-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.left.radius = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-right-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.right.radius = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-style" => style.modifier.borders.bottom.style = parse_border_style(value),
|
||||
"border-bottom-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.bottom.width = v;
|
||||
}
|
||||
}
|
||||
"border-collapse" => {}
|
||||
"border-color" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Ok(c) = values[0].parse() {
|
||||
style
|
||||
.modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.color = Some(c));
|
||||
}
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.modifier.borders.slice().iter_mut())
|
||||
{
|
||||
if let Ok(c) = v.parse() {
|
||||
b.color = Some(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-image" => {}
|
||||
"border-image-outset" => {}
|
||||
"border-image-repeat" => {}
|
||||
"border-image-slice" => {}
|
||||
"border-image-source" => {}
|
||||
"border-image-width" => {}
|
||||
"border-left" => {}
|
||||
"border-left-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.modifier.borders.left.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-left-style" => style.modifier.borders.left.style = parse_border_style(value),
|
||||
"border-left-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.left.width = v;
|
||||
}
|
||||
}
|
||||
"border-radius" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(r) = parse_value(values[0]) {
|
||||
style
|
||||
.modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.radius = r);
|
||||
}
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.modifier.borders.slice().iter_mut())
|
||||
{
|
||||
if let Some(r) = parse_value(v) {
|
||||
b.radius = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-right" => {}
|
||||
"border-right-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.modifier.borders.right.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-right-style" => style.modifier.borders.right.style = parse_border_style(value),
|
||||
"border-right-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.right.width = v;
|
||||
}
|
||||
}
|
||||
"border-spacing" => {}
|
||||
"border-style" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
let border_style = parse_border_style(values[0]);
|
||||
style
|
||||
.modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.style = border_style);
|
||||
} else {
|
||||
for (v, b) in values
|
||||
.into_iter()
|
||||
.zip(style.modifier.borders.slice().iter_mut())
|
||||
{
|
||||
b.style = parse_border_style(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
"border-top" => {}
|
||||
"border-top-color" => {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.modifier.borders.top.color = Some(c);
|
||||
}
|
||||
}
|
||||
"border-top-left-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.left.radius = v;
|
||||
}
|
||||
}
|
||||
"border-top-right-radius" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.right.radius = v;
|
||||
}
|
||||
}
|
||||
"border-top-style" => style.modifier.borders.top.style = parse_border_style(value),
|
||||
"border-top-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.modifier.borders.top.width = v;
|
||||
}
|
||||
}
|
||||
"border-width" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(w) = parse_value(values[0]) {
|
||||
style
|
||||
.modifier
|
||||
.borders
|
||||
.slice()
|
||||
.iter_mut()
|
||||
.for_each(|b| b.width = w);
|
||||
}
|
||||
} else {
|
||||
for (v, width) in values
|
||||
.into_iter()
|
||||
.zip(style.modifier.borders.slice().iter_mut())
|
||||
{
|
||||
if let Some(w) = parse_value(v) {
|
||||
width.width = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) {
|
||||
match name {
|
||||
"animation" => {}
|
||||
"animation-delay" => {}
|
||||
"animation-direction =>{}" => {}
|
||||
"animation-duration" => {}
|
||||
"animation-fill-mode" => {}
|
||||
"animation-itera =>{}tion-count" => {}
|
||||
"animation-name" => {}
|
||||
"animation-play-state" => {}
|
||||
"animation-timing-function" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_font(name: &str, value: &str, style: &mut StyleModifier) {
|
||||
use tui::style::Modifier;
|
||||
match name {
|
||||
"font" => (),
|
||||
"font-family" => (),
|
||||
"font-size" => (),
|
||||
"font-size-adjust" => (),
|
||||
"font-stretch" => (),
|
||||
"font-style" => match value {
|
||||
"italic" => style.core = style.core.add_modifier(Modifier::ITALIC),
|
||||
"oblique" => style.core = style.core.add_modifier(Modifier::ITALIC),
|
||||
_ => (),
|
||||
},
|
||||
"font-variant" => todo!(),
|
||||
"font-weight" => match value {
|
||||
"bold" => style.core = style.core.add_modifier(Modifier::BOLD),
|
||||
"normal" => style.core = style.core.remove_modifier(Modifier::BOLD),
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_text(name: &str, value: &str, style: &mut StyleModifier) {
|
||||
use tui::style::Modifier;
|
||||
|
||||
match name {
|
||||
"text-align" => todo!(),
|
||||
"text-align-last" => todo!(),
|
||||
"text-decoration" | "text-decoration-line" => {
|
||||
for v in value.split(' ') {
|
||||
match v {
|
||||
"line-through" => style.core = style.core.add_modifier(Modifier::CROSSED_OUT),
|
||||
"underline" => style.core = style.core.add_modifier(Modifier::UNDERLINED),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
"text-decoration-color" => todo!(),
|
||||
"text-decoration-style" => todo!(),
|
||||
"text-indent" => todo!(),
|
||||
"text-justify" => todo!(),
|
||||
"text-overflow" => todo!(),
|
||||
"text-shadow" => todo!(),
|
||||
"text-transform" => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
"animation-duration",
|
||||
"animation-fill-mode",
|
||||
"animation-iteration-count",
|
||||
"animation-name",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"backface-visibility",
|
||||
"background",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-color",
|
||||
"background-image",
|
||||
"background-origin",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-size",
|
||||
"border",
|
||||
"border-bottom",
|
||||
"border-bottom-color",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-style",
|
||||
"border-bottom-width",
|
||||
"border-collapse",
|
||||
"border-color",
|
||||
"border-image",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-left",
|
||||
"border-left-color",
|
||||
"border-left-style",
|
||||
"border-left-width",
|
||||
"border-radius",
|
||||
"border-right",
|
||||
"border-right-color",
|
||||
"border-right-style",
|
||||
"border-right-width",
|
||||
"border-spacing",
|
||||
"border-style",
|
||||
"border-top",
|
||||
"border-top-color",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-top-style",
|
||||
"border-top-width",
|
||||
"border-width",
|
||||
"bottom",
|
||||
"box-shadow",
|
||||
"box-sizing",
|
||||
"caption-side",
|
||||
"clear",
|
||||
"clip",
|
||||
"color",
|
||||
"columns",
|
||||
"content",
|
||||
"counter-increment",
|
||||
"counter-reset",
|
||||
"cursor",
|
||||
"empty-cells",
|
||||
"float",
|
||||
"font",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"font-variant",
|
||||
"font-weight",
|
||||
"letter-spacing",
|
||||
"line-height",
|
||||
"list-style",
|
||||
"list-style-image",
|
||||
"list-style-position",
|
||||
"list-style-type",
|
||||
"opacity",
|
||||
"order",
|
||||
"outline",
|
||||
"outline-color",
|
||||
"outline-offset",
|
||||
"outline-style",
|
||||
"outline-width",
|
||||
"page-break-after",
|
||||
"page-break-before",
|
||||
"page-break-inside",
|
||||
"perspective",
|
||||
"perspective-origin",
|
||||
"pointer-events",
|
||||
"quotes",
|
||||
"resize",
|
||||
"tab-size",
|
||||
"table-layout",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-decoration",
|
||||
"text-decoration-color",
|
||||
"text-decoration-line",
|
||||
"text-decoration-style",
|
||||
"text-indent",
|
||||
"text-justify",
|
||||
"text-overflow",
|
||||
"text-shadow",
|
||||
"text-transform",
|
||||
"transition",
|
||||
"transition-delay",
|
||||
"transition-duration",
|
||||
"transition-property",
|
||||
"transition-timing-function",
|
||||
"visibility",
|
||||
"white-space",
|
||||
"background-color",
|
||||
"background",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-image",
|
||||
"background-origin",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-size",
|
||||
"dotted",
|
||||
"dashed",
|
||||
"solid",
|
||||
"double",
|
||||
"groove",
|
||||
"ridge",
|
||||
"inset",
|
||||
"outset",
|
||||
"none",
|
||||
"hidden",
|
||||
"border",
|
||||
"border-bottom",
|
||||
"border-bottom-color",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
"border-bottom-style",
|
||||
"border-bottom-width",
|
||||
"border-collapse",
|
||||
"border-color",
|
||||
"border-image",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-left",
|
||||
"border-left-color",
|
||||
"border-left-style",
|
||||
"border-left-width",
|
||||
"border-radius",
|
||||
"border-right",
|
||||
"border-right-color",
|
||||
"border-right-style",
|
||||
"border-right-width",
|
||||
"border-spacing",
|
||||
"border-style",
|
||||
"border-top",
|
||||
"border-top-color",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-top-style",
|
||||
"border-top-width",
|
||||
"border-width",
|
||||
"animation",
|
||||
"animation-delay",
|
||||
"animation-direction",
|
||||
"animation-duration",
|
||||
"animation-fill-mode",
|
||||
"animation-itera ",
|
||||
"animation-name",
|
||||
"animation-play-state",
|
||||
"animation-timing-function",
|
||||
"font",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"font-style",
|
||||
"italic",
|
||||
"oblique",
|
||||
"font-variant",
|
||||
"font-weight",
|
||||
"bold",
|
||||
"normal",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-decoration",
|
||||
"text-decoration-line",
|
||||
"line-through",
|
||||
"underline",
|
||||
"text-decoration-color",
|
||||
"text-decoration-style",
|
||||
"text-indent",
|
||||
"text-justify",
|
||||
"text-overflow",
|
||||
"text-shadow",
|
||||
"text-transform"
|
||||
]);
|
||||
@@ -20,7 +20,11 @@ impl<'a> RinkBuffer<'a> {
|
||||
Self { buf, cfg }
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
|
||||
pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
|
||||
let area = self.buf.area();
|
||||
if x < area.x || x > area.width || y < area.y || y > area.height {
|
||||
panic!("({x}, {y}) is not in {area:?}");
|
||||
}
|
||||
let mut cell = self.buf.get_mut(x, y);
|
||||
cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));
|
||||
if new.symbol.is_empty() {
|
||||
@@ -30,7 +34,7 @@ impl<'a> RinkBuffer<'a> {
|
||||
}
|
||||
} else {
|
||||
cell.modifier = new.modifier;
|
||||
cell.symbol = new.symbol.clone();
|
||||
cell.symbol = new.symbol;
|
||||
cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
|
||||
}
|
||||
}
|
||||
@@ -71,11 +75,11 @@ impl Default for RinkCell {
|
||||
symbol: "".to_string(),
|
||||
fg: RinkColor {
|
||||
color: Color::Rgb(0, 0, 0),
|
||||
alpha: 0.0,
|
||||
alpha: 0,
|
||||
},
|
||||
bg: RinkColor {
|
||||
color: Color::Rgb(0, 0, 0),
|
||||
alpha: 0.0,
|
||||
alpha: 0,
|
||||
},
|
||||
modifier: Modifier::empty(),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-web"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for the web browser using websys"
|
||||
@@ -11,10 +11,10 @@ documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0", features = [
|
||||
"web",
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
|
||||
"web"
|
||||
] }
|
||||
|
||||
js-sys = "0.3.56"
|
||||
|
||||
+8
-91
@@ -146,7 +146,7 @@ impl WebsysDom {
|
||||
value,
|
||||
ns,
|
||||
} => {
|
||||
let value = serde_wasm_bindgen::to_value(value).unwrap();
|
||||
let value = serde_wasm_bindgen::to_value(&value).unwrap();
|
||||
self.interpreter.SetAttribute(root, field, value, ns)
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,6 @@ fn virtual_event_from_websys_event(
|
||||
target: Element,
|
||||
) -> Arc<dyn Any + Send + Sync> {
|
||||
use dioxus_html::on::*;
|
||||
use dioxus_html::KeyCode;
|
||||
|
||||
match event.type_().as_str() {
|
||||
"copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
|
||||
@@ -178,22 +177,7 @@ fn virtual_event_from_websys_event(
|
||||
data: evt.data().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
"keydown" | "keypress" | "keyup" => {
|
||||
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(KeyboardData {
|
||||
alt_key: evt.alt_key(),
|
||||
char_code: evt.char_code(),
|
||||
key: evt.key(),
|
||||
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
|
||||
ctrl_key: evt.ctrl_key(),
|
||||
locale: "not implemented".to_string(),
|
||||
location: evt.location() as usize,
|
||||
meta_key: evt.meta_key(),
|
||||
repeat: evt.repeat(),
|
||||
shift_key: evt.shift_key(),
|
||||
which: evt.which() as usize,
|
||||
})
|
||||
}
|
||||
"keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
|
||||
"focus" | "blur" => Arc::new(FocusData {}),
|
||||
|
||||
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
|
||||
@@ -277,88 +261,21 @@ fn virtual_event_from_websys_event(
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(MouseData {
|
||||
alt_key: evt.alt_key(),
|
||||
button: evt.button(),
|
||||
buttons: evt.buttons(),
|
||||
client_x: evt.client_x(),
|
||||
client_y: evt.client_y(),
|
||||
ctrl_key: evt.ctrl_key(),
|
||||
meta_key: evt.meta_key(),
|
||||
screen_x: evt.screen_x(),
|
||||
screen_y: evt.screen_y(),
|
||||
shift_key: evt.shift_key(),
|
||||
page_x: evt.page_x(),
|
||||
page_y: evt.page_y(),
|
||||
})
|
||||
Arc::new(MouseData::from(event))
|
||||
}
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(PointerData {
|
||||
alt_key: evt.alt_key(),
|
||||
button: evt.button(),
|
||||
buttons: evt.buttons(),
|
||||
client_x: evt.client_x(),
|
||||
client_y: evt.client_y(),
|
||||
ctrl_key: evt.ctrl_key(),
|
||||
meta_key: evt.meta_key(),
|
||||
page_x: evt.page_x(),
|
||||
page_y: evt.page_y(),
|
||||
screen_x: evt.screen_x(),
|
||||
screen_y: evt.screen_y(),
|
||||
shift_key: evt.shift_key(),
|
||||
pointer_id: evt.pointer_id(),
|
||||
width: evt.width(),
|
||||
height: evt.height(),
|
||||
pressure: evt.pressure(),
|
||||
tangential_pressure: evt.tangential_pressure(),
|
||||
tilt_x: evt.tilt_x(),
|
||||
tilt_y: evt.tilt_y(),
|
||||
twist: evt.twist(),
|
||||
pointer_type: evt.pointer_type(),
|
||||
is_primary: evt.is_primary(),
|
||||
// get_modifier_state: evt.get_modifier_state(),
|
||||
})
|
||||
Arc::new(PointerData::from(event))
|
||||
}
|
||||
"select" => Arc::new(SelectionData {}),
|
||||
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
|
||||
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(TouchData {
|
||||
alt_key: evt.alt_key(),
|
||||
ctrl_key: evt.ctrl_key(),
|
||||
meta_key: evt.meta_key(),
|
||||
shift_key: evt.shift_key(),
|
||||
})
|
||||
}
|
||||
"touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)),
|
||||
|
||||
"scroll" => Arc::new(()),
|
||||
"wheel" => {
|
||||
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(WheelData {
|
||||
delta_x: evt.delta_x(),
|
||||
delta_y: evt.delta_y(),
|
||||
delta_z: evt.delta_z(),
|
||||
delta_mode: evt.delta_mode(),
|
||||
})
|
||||
}
|
||||
"wheel" => Arc::new(WheelData::from(event)),
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(AnimationData {
|
||||
elapsed_time: evt.elapsed_time(),
|
||||
animation_name: evt.animation_name(),
|
||||
pseudo_element: evt.pseudo_element(),
|
||||
})
|
||||
}
|
||||
"transitionend" => {
|
||||
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
|
||||
Arc::new(TransitionData {
|
||||
elapsed_time: evt.elapsed_time(),
|
||||
property_name: evt.property_name(),
|
||||
pseudo_element: evt.pseudo_element(),
|
||||
})
|
||||
Arc::new(AnimationData::from(event))
|
||||
}
|
||||
"transitionend" => Arc::new(TransitionData::from(event)),
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
|
||||
Reference in new issue
Block a user