Merge pull request #329 from Demonthos/lazy_tui

Tui Lazy Attributes and Layout
This commit is contained in:
Jon Kelley authored and GitHub committed 2022-05-02 21:38:18 -04:00
commit f7e67cb2d7
26 files changed
+5546 -1567

No files matched your search

+1 -1
View File
@@ -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>,
+15 -15
View File
@@ -398,21 +398,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 +481,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,7 +495,7 @@ pub mod on {
pub type MouseEvent = UiEvent<MouseData>;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MouseData {
pub alt_key: bool,
pub button: i16,
@@ -514,7 +514,7 @@ pub mod on {
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 +544,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 +563,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 +573,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 +594,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 +603,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 {}
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "dioxus-native-core-macro"
version = "0.2.0"
edition = "2021"
[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" }
[dev-dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-html = { path = "../html", version = "^0.2.0" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"
+703
View File
@@ -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,533 @@
use anymap::AnyMap;
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: "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: "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,
}
+20
View File
@@ -0,0 +1,20 @@
[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"
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-html = { path = "../html", version = "^0.2.0" }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
stretch2 = { git = "https://github.com/DioxusLabs/stretch" }
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 => {}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod layout_attributes;
pub mod node_ref;
pub mod real_dom;
pub mod state;
+249
View File
@@ -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
}
}
+774
View File
@@ -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(&current);
} 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);
}
}
+202
View File
@@ -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;
}
}
+6 -2
View File
@@ -15,11 +15,15 @@ license = "MIT/Apache-2.0"
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.0" }
dioxus-html = { path = "../html", version = "^0.2.0" }
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 = { git = "https://github.com/DioxusLabs/stretch" }
smallvec = "1.6"
fxhash = "0.2"
anymap = "0.12.1"
-968
View File
@@ -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 => {}
}
}
+331 -253
View File
@@ -2,20 +2,19 @@ use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus_core::*;
use fxhash::{FxHashMap, FxHashSet};
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::{prelude::Layout, Stretch};
use crate::TuiNode;
use crate::{Dom, Node};
// a wrapper around the input state for easier access
// todo: fix loop
@@ -31,12 +30,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 +47,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())
// }
// }
@@ -100,7 +99,7 @@ impl InnerInputState {
EventData::Mouse(ref mut m) => match &mut self.mouse {
Some(state) => {
let mut buttons = state.0.buttons;
state.0 = clone_mouse_data(m);
state.0 = m.clone();
match evt.0 {
// this code only runs when there are no buttons down
"mouseup" => {
@@ -136,7 +135,7 @@ impl InnerInputState {
}
None => {
self.mouse = Some((
clone_mouse_data(m),
m.clone(),
if m.buttons == 0 {
Vec::new()
} else {
@@ -145,7 +144,7 @@ impl InnerInputState {
));
}
},
EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
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,20 +153,40 @@ 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,
) {
let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone()));
self.wheel = None;
for e in evts.iter_mut() {
self.apply_event(e);
}
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, Vec<u16>)>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
dom: &mut Dom,
) {
struct Data<'b> {
new_pos: (i32, i32),
@@ -186,136 +205,31 @@ impl InnerInputState {
&& layout.location.y as i32 + layout.size.height as i32 >= point.1
}
fn get_mouse_events<'c, 'd>(
dom: &'c VirtualDom,
fn try_create_event(
name: &'static str,
data: Arc<dyn Any + Send + Sync>,
will_bubble: &mut FxHashSet<ElementId>,
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;
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;
}
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!(),
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(node.id),
data,
})
}
}
let previous_mouse = self
.mouse
.as_ref()
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
// println!("{previous_mouse:?}");
self.wheel = None;
for e in evts.iter_mut() {
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
@@ -337,12 +251,229 @@ impl InnerInputState {
mouse_data,
wheel_data,
};
get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
}
// for s in &self.subscribers {
// s();
// }
{
// mousemove
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 = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains && previously_contained {
try_create_event(
"mousemove",
Arc::new(data.mouse_data.clone()),
&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 = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
Arc::new(data.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 = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// mousedown
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, data.new_pos);
if currently_contains && data.clicked {
try_create_event(
"mousedown",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// mouseup
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, data.new_pos);
if currently_contains && data.released {
try_create_event(
"mouseup",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// click
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, data.new_pos);
if currently_contains && data.released && data.mouse_data.button == 0 {
try_create_event(
"click",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// contextmenu
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, data.new_pos);
if currently_contains && data.released && data.mouse_data.button == 2 {
try_create_event(
"contextmenu",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
// wheel
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, data.new_pos);
if let Some(w) = data.wheel_data {
if currently_contains && data.wheel_delta != 0.0 {
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 = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
Arc::new(data.mouse_data.clone()),
&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 = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
Arc::new(data.mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
}
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
@@ -358,24 +489,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 +513,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
}
@@ -651,7 +771,7 @@ 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),
@@ -663,45 +783,3 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
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,
}
}
+247 -103
View File
@@ -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,208 @@ 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) => {
// 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,
),
_ => (),
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
for &Attribute { name, value, .. } in node.attributes() {
assert!(SORTED_LAYOUT_ATTRS.binary_search(&name).is_ok());
apply_layout_attributes(name, value, &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
View File
@@ -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 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());
let _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
View File
@@ -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
View File
@@ -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(),
+795
View File
@@ -0,0 +1,795 @@
/*
- [ ] 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() {
apply_style_attributes(name, value, &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"
]);
+8 -4
View File
@@ -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(),
}