mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
wip lazy layout
This commit is contained in:
parent
14099e9889
commit
499971e9b3
14 changed files with 2132 additions and 1238 deletions
17
packages/native-core/Cargo.toml
Normal file
17
packages/native-core/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "dioxus-native-core"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT/Apache-2.0"
|
||||
authors = ["@dementhos"]
|
||||
description = "TUI-based renderer for Dioxus"
|
||||
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 = "0.4.1"
|
||||
smallvec = "1.6"
|
517
packages/native-core/src/lib.rs
Normal file
517
packages/native-core/src/lib.rs
Normal file
|
@ -0,0 +1,517 @@
|
|||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
||||
|
||||
/// A tree that can sync with dioxus mutations backed by a hashmap.
|
||||
/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
|
||||
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state].
|
||||
#[derive(Debug)]
|
||||
|
||||
pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> {
|
||||
pub root: usize,
|
||||
pub nodes: Vec<Option<TreeNode<US, DS>>>,
|
||||
pub listeners: HashMap<usize, HashSet<&'static str>>,
|
||||
}
|
||||
|
||||
impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
|
||||
pub fn new() -> Tree<US, DS> {
|
||||
Tree {
|
||||
root: 0,
|
||||
nodes: {
|
||||
let mut v = Vec::new();
|
||||
v.push(Some(TreeNode::new(
|
||||
0,
|
||||
TreeNodeType::Element {
|
||||
tag: "Root".to_string(),
|
||||
namespace: Some("Root"),
|
||||
children: Vec::new(),
|
||||
},
|
||||
)));
|
||||
v
|
||||
},
|
||||
listeners: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the tree, up and down state and return a set of nodes that were updated
|
||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
|
||||
let mut nodes_updated = Vec::new();
|
||||
for mutations in mutations_vec {
|
||||
let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new();
|
||||
for e in mutations.edits {
|
||||
use dioxus_core::DomEdit::*;
|
||||
match e {
|
||||
PushRoot { root } => node_stack.push(root as usize),
|
||||
AppendChildren { many } => {
|
||||
let target = if node_stack.len() >= many as usize + 1 {
|
||||
*node_stack
|
||||
.get(node_stack.len() - (many as usize + 1))
|
||||
.unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
for ns in node_stack.drain(node_stack.len() - many as usize..).rev() {
|
||||
self.link_child(ns, target).unwrap();
|
||||
nodes_updated.push(ns);
|
||||
}
|
||||
}
|
||||
ReplaceWith { root, m } => {
|
||||
let root = self.remove(root as usize).unwrap();
|
||||
let target = root.parent.unwrap().0;
|
||||
for ns in node_stack.drain(0..m as usize) {
|
||||
nodes_updated.push(ns);
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
InsertAfter { root, n } => {
|
||||
let target = self.get(root as usize).parent.unwrap().0;
|
||||
for ns in node_stack.drain(0..n as usize) {
|
||||
nodes_updated.push(ns);
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
InsertBefore { root, n } => {
|
||||
let target = self.get(root as usize).parent.unwrap().0;
|
||||
for ns in node_stack.drain(0..n as usize) {
|
||||
nodes_updated.push(ns);
|
||||
self.link_child(ns, target).unwrap();
|
||||
}
|
||||
}
|
||||
Remove { root } => {
|
||||
if let Some(parent) = self.get(root as usize).parent {
|
||||
nodes_updated.push(parent.0);
|
||||
}
|
||||
self.remove(root as usize).unwrap();
|
||||
}
|
||||
CreateTextNode { root, text } => {
|
||||
let n = TreeNode::new(
|
||||
root,
|
||||
TreeNodeType::Text {
|
||||
text: text.to_string(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
node_stack.push(root as usize)
|
||||
}
|
||||
CreateElement { root, tag } => {
|
||||
let n = TreeNode::new(
|
||||
root,
|
||||
TreeNodeType::Element {
|
||||
tag: tag.to_string(),
|
||||
namespace: None,
|
||||
children: Vec::new(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
node_stack.push(root as usize)
|
||||
}
|
||||
CreateElementNs { root, tag, ns } => {
|
||||
let n = TreeNode::new(
|
||||
root,
|
||||
TreeNodeType::Element {
|
||||
tag: tag.to_string(),
|
||||
namespace: Some(ns),
|
||||
children: Vec::new(),
|
||||
},
|
||||
);
|
||||
self.insert(n);
|
||||
node_stack.push(root as usize)
|
||||
}
|
||||
CreatePlaceholder { root } => {
|
||||
let n = TreeNode::new(root, TreeNodeType::Placeholder);
|
||||
self.insert(n);
|
||||
node_stack.push(root as usize)
|
||||
}
|
||||
|
||||
NewEventListener {
|
||||
event_name,
|
||||
scope: _,
|
||||
root,
|
||||
} => {
|
||||
if let Some(v) = self.listeners.get_mut(&(root as usize)) {
|
||||
v.insert(event_name);
|
||||
} else {
|
||||
let mut hs = HashSet::new();
|
||||
hs.insert(event_name);
|
||||
self.listeners.insert(root as usize, hs);
|
||||
}
|
||||
}
|
||||
RemoveEventListener { root, event } => {
|
||||
let v = self.listeners.get_mut(&(root as usize)).unwrap();
|
||||
v.remove(event);
|
||||
}
|
||||
SetText {
|
||||
root,
|
||||
text: new_text,
|
||||
} => {
|
||||
let target = self.get_mut(root as usize);
|
||||
nodes_updated.push(root as usize);
|
||||
match &mut target.node_type {
|
||||
TreeNodeType::Text { text } => {
|
||||
*text = new_text.to_string();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
SetAttribute { root, .. } => {
|
||||
nodes_updated.push(root as usize);
|
||||
}
|
||||
RemoveAttribute { root, .. } => {
|
||||
nodes_updated.push(root as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes_updated
|
||||
}
|
||||
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
vdom: &VirtualDom,
|
||||
nodes_updated: Vec<usize>,
|
||||
us_ctx: &mut US::Ctx,
|
||||
ds_ctx: &mut DS::Ctx,
|
||||
) -> Option<HashSet<usize>> {
|
||||
let mut to_rerender = HashSet::new();
|
||||
let mut nodes_updated: Vec<_> = nodes_updated
|
||||
.into_iter()
|
||||
.map(|id| (id, self.get(id).height))
|
||||
.collect();
|
||||
nodes_updated.dedup();
|
||||
nodes_updated.sort_by_key(|(_, h)| *h);
|
||||
|
||||
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
||||
// todo: this is called multable times per element?
|
||||
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
|
||||
while let Some((id, height)) = to_bubble.pop_back() {
|
||||
let node = self.get_mut(id as usize);
|
||||
let vnode = node.element(vdom);
|
||||
let node_type = &node.node_type;
|
||||
let up_state = &mut node.up_state;
|
||||
let children = match node_type {
|
||||
TreeNodeType::Element { children, .. } => Some(children),
|
||||
_ => None,
|
||||
};
|
||||
// todo: reduce cloning state
|
||||
let old = up_state.clone();
|
||||
let mut new = up_state.clone();
|
||||
let parent = node.parent.clone();
|
||||
new.reduce(
|
||||
children
|
||||
.unwrap_or(&Vec::new())
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|c| &self.get(c.0).up_state),
|
||||
vnode,
|
||||
us_ctx,
|
||||
);
|
||||
if new != old {
|
||||
to_rerender.insert(id);
|
||||
if let Some(p) = parent {
|
||||
let i = to_bubble.partition_point(|(_, h)| *h < height - 1);
|
||||
// println!("{i}");
|
||||
// println!("{to_bubble:?}");
|
||||
// make sure the parent is not already queued
|
||||
if to_bubble.len() == 0 || to_bubble.get(i).unwrap().0 != p.0 {
|
||||
to_bubble.insert(i, (p.0, height - 1));
|
||||
}
|
||||
}
|
||||
let node = self.get_mut(id as usize);
|
||||
node.up_state = new;
|
||||
}
|
||||
}
|
||||
|
||||
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
||||
let mut to_push: VecDeque<_> = nodes_updated.clone().into();
|
||||
while let Some((id, height)) = to_push.pop_front() {
|
||||
let node = self.get_mut(id as usize);
|
||||
// todo: reduce cloning state
|
||||
let old = node.down_state.clone();
|
||||
let mut new = node.down_state.clone();
|
||||
let vnode = node.element(vdom);
|
||||
new.reduce(
|
||||
node.parent.map(|e| &self.get(e.0).down_state),
|
||||
vnode,
|
||||
ds_ctx,
|
||||
);
|
||||
if new != old {
|
||||
to_rerender.insert(id);
|
||||
let node = self.get_mut(id as usize);
|
||||
match &node.node_type {
|
||||
TreeNodeType::Element { children, .. } => {
|
||||
for c in children {
|
||||
let i = to_bubble.partition_point(|(_, h)| *h < height + 1);
|
||||
to_bubble.insert(i, (c.0, height + 1));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
node.down_state = new;
|
||||
}
|
||||
}
|
||||
|
||||
Some(to_rerender)
|
||||
}
|
||||
|
||||
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
|
||||
debug_assert_ne!(child_id, parent_id);
|
||||
let parent = self.get_mut(parent_id);
|
||||
parent.add_child(ElementId(child_id));
|
||||
let parent_height = parent.height + 1;
|
||||
self.get_mut(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 = self.get_mut(id);
|
||||
n.height += amount;
|
||||
match &n.node_type {
|
||||
TreeNodeType::Element { children, .. } => {
|
||||
for c in children.clone() {
|
||||
self.increase_height(c.0, amount);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
|
||||
let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap();
|
||||
match &mut node.node_type {
|
||||
TreeNodeType::Element { children, .. } => {
|
||||
for c in children {
|
||||
self.remove(c.0).unwrap();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn insert(&mut self, node: TreeNode<US, DS>) {
|
||||
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) -> &TreeNode<US, DS> {
|
||||
self.nodes.get(id).unwrap().as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> {
|
||||
self.nodes.get_mut(id).unwrap().as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The node is stored client side and stores render data
|
||||
#[derive(Debug)]
|
||||
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
|
||||
pub id: ElementId,
|
||||
pub parent: Option<ElementId>,
|
||||
pub up_state: US,
|
||||
pub down_state: DS,
|
||||
pub node_type: TreeNodeType,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TreeNodeType {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Element {
|
||||
tag: String,
|
||||
namespace: Option<&'static str>,
|
||||
children: Vec<ElementId>,
|
||||
},
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
|
||||
fn new(id: u64, node_type: TreeNodeType) -> Self {
|
||||
TreeNode {
|
||||
id: ElementId(id as usize),
|
||||
parent: None,
|
||||
node_type,
|
||||
down_state: DS::default(),
|
||||
up_state: US::default(),
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
|
||||
vdom.get_element(self.id).unwrap()
|
||||
}
|
||||
|
||||
fn add_child(&mut self, child: ElementId) {
|
||||
match &mut self.node_type {
|
||||
TreeNodeType::Element { children, .. } => {
|
||||
children.push(child);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parent(&mut self, parent: ElementId) {
|
||||
self.parent = Some(parent);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 PushedDownState: Default + PartialEq + Clone {
|
||||
type Ctx;
|
||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx);
|
||||
}
|
||||
impl PushedDownState for () {
|
||||
type Ctx = ();
|
||||
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {}
|
||||
}
|
||||
|
||||
/// This state is derived from children. For example a non-flexbox div'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 BubbledUpState: Default + PartialEq + Clone {
|
||||
type Ctx;
|
||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a;
|
||||
}
|
||||
impl BubbledUpState for () {
|
||||
type Ctx = ();
|
||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// /// The nodes that need to be updated after updating a state.
|
||||
// pub struct Update {
|
||||
// children: bool,
|
||||
// parent: bool,
|
||||
// }
|
||||
|
||||
// /// This state is derived from children and parents.
|
||||
// /// Called when the current node's node properties are modified or a parent or child's [State] is modified.
|
||||
// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this.
|
||||
// pub trait State: Default + PartialEq + Clone {
|
||||
// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update
|
||||
// where
|
||||
// I: Iterator<Item = &'a Self>,
|
||||
// Self: 'a;
|
||||
// }
|
||||
// impl State for () {
|
||||
// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update
|
||||
// where
|
||||
// I: Iterator<Item = &'a Self>,
|
||||
// Self: 'a,
|
||||
// {
|
||||
// Update {
|
||||
// children: false,
|
||||
// parent: false,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
use dioxus_core::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
struct Rect {
|
||||
x: u16,
|
||||
y: u16,
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
impl BubbledUpState for Rect {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
match vnode {
|
||||
VNode::Text(t) => {
|
||||
*self = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: t.text.len().try_into().unwrap(),
|
||||
height: 1,
|
||||
};
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
self.width = 2;
|
||||
self.height = 2;
|
||||
for c in children {
|
||||
println!("\t{c:?}");
|
||||
self.width = self.width.max(c.width);
|
||||
self.height += c.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
rsx!(cx, div {})
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let node_1 = rsx! {
|
||||
div{
|
||||
div{
|
||||
"hello"
|
||||
"hello world"
|
||||
}
|
||||
}
|
||||
};
|
||||
let node_2 = rsx! {
|
||||
div{
|
||||
div{
|
||||
"hello"
|
||||
"hello world"
|
||||
}
|
||||
}
|
||||
};
|
||||
let mutations = vdom.diff_lazynodes(node_1, node_2);
|
||||
|
||||
let mut tree: Tree<Rect, ()> = Tree {
|
||||
root: 0,
|
||||
nodes: {
|
||||
let mut v = Vec::new();
|
||||
v.push(Some(TreeNode::new(
|
||||
0,
|
||||
TreeNodeType::Element {
|
||||
tag: "Root".to_string(),
|
||||
namespace: Some("Root"),
|
||||
children: Vec::new(),
|
||||
},
|
||||
)));
|
||||
v
|
||||
},
|
||||
listeners: HashMap::new(),
|
||||
};
|
||||
println!("{:?}", mutations);
|
||||
let to_update = tree.apply_mutations(vec![mutations.0]);
|
||||
let to_rerender = tree
|
||||
.update_state(&vdom, to_update, &mut (), &mut ())
|
||||
.unwrap();
|
||||
println!("{to_rerender:?}");
|
||||
panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", ""));
|
||||
}
|
|
@ -15,6 +15,7 @@ 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" }
|
||||
|
||||
tui = "0.17.0"
|
||||
crossterm = "0.23.0"
|
||||
|
@ -22,4 +23,4 @@ anyhow = "1.0.42"
|
|||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
futures = "0.3.19"
|
||||
stretch2 = "0.4.1"
|
||||
|
||||
smallvec = "1.6"
|
||||
|
|
|
@ -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 => {}
|
||||
}
|
||||
}
|
|
@ -4,18 +4,19 @@ use crossterm::event::{
|
|||
use dioxus_core::*;
|
||||
|
||||
use dioxus_html::{on::*, KeyCode};
|
||||
use dioxus_native_core::{Tree, TreeNodeType};
|
||||
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashSet,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use stretch2::{prelude::Layout, Stretch};
|
||||
|
||||
use crate::TuiNode;
|
||||
use crate::{style_attributes::StyleModifier, RinkLayout};
|
||||
|
||||
// a wrapper around the input state for easier access
|
||||
// todo: fix loop
|
||||
|
@ -166,7 +167,7 @@ impl InnerInputState {
|
|||
evts: &mut Vec<EventCore>,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
layouts: &mut Tree<RinkLayout, StyleModifier>,
|
||||
node: &'a VNode<'a>,
|
||||
) {
|
||||
struct Data<'b> {
|
||||
|
@ -190,7 +191,7 @@ impl InnerInputState {
|
|||
dom: &'c VirtualDom,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
layout: &Stretch,
|
||||
layouts: &HashMap<ElementId, TuiNode<'c>>,
|
||||
layouts: &Tree<RinkLayout, StyleModifier>,
|
||||
node: &'c VNode<'c>,
|
||||
data: &'d Data<'d>,
|
||||
) -> HashSet<&'static str> {
|
||||
|
@ -225,9 +226,9 @@ impl InnerInputState {
|
|||
}
|
||||
|
||||
let id = node.try_mounted_id().unwrap();
|
||||
let node = layouts.get(&id).unwrap();
|
||||
let node = layouts.get(id.0);
|
||||
|
||||
let node_layout = layout.layout(node.layout).unwrap();
|
||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||
|
||||
let previously_contained = data
|
||||
.old_pos
|
||||
|
@ -235,18 +236,18 @@ impl InnerInputState {
|
|||
.is_some();
|
||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||
|
||||
match node.node {
|
||||
VNode::Element(el) => {
|
||||
match &node.node_type {
|
||||
TreeNodeType::Element { children, .. } => {
|
||||
let mut events = HashSet::new();
|
||||
if previously_contained || currently_contains {
|
||||
for c in el.children {
|
||||
for c in children {
|
||||
events = events
|
||||
.union(&get_mouse_events(
|
||||
dom,
|
||||
resolved_events,
|
||||
layout,
|
||||
layouts,
|
||||
c,
|
||||
dom.get_element(*c).unwrap(),
|
||||
data,
|
||||
))
|
||||
.copied()
|
||||
|
@ -260,7 +261,7 @@ impl InnerInputState {
|
|||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name,
|
||||
element: Some(el.id.get().unwrap()),
|
||||
element: Some(node.id),
|
||||
data: Arc::new(clone_mouse_data(data.mouse_data)),
|
||||
})
|
||||
}
|
||||
|
@ -287,7 +288,7 @@ impl InnerInputState {
|
|||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name: "wheel",
|
||||
element: Some(el.id.get().unwrap()),
|
||||
element: Some(node.id),
|
||||
data: Arc::new(clone_wheel_data(w)),
|
||||
})
|
||||
}
|
||||
|
@ -298,7 +299,7 @@ impl InnerInputState {
|
|||
}
|
||||
events
|
||||
}
|
||||
VNode::Text(_) => HashSet::new(),
|
||||
TreeNodeType::Text { .. } => HashSet::new(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
@ -392,7 +393,7 @@ impl RinkInputHandler {
|
|||
&self,
|
||||
dom: &'a VirtualDom,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
layouts: &mut Tree<RinkLayout, StyleModifier>,
|
||||
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
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use dioxus_core::*;
|
||||
use std::collections::HashMap;
|
||||
use dioxus_native_core::BubbledUpState;
|
||||
use stretch2::prelude::*;
|
||||
|
||||
use crate::{
|
||||
attributes::{apply_attributes, StyleModifer},
|
||||
style::RinkStyle,
|
||||
TuiModifier, TuiNode,
|
||||
};
|
||||
use crate::layout_attributes::apply_layout_attributes;
|
||||
|
||||
/*
|
||||
The layout system uses the lineheight as one point.
|
||||
|
@ -13,119 +10,84 @@ The layout system uses the lineheight as one point.
|
|||
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::*;
|
||||
|
||||
match node {
|
||||
VNode::Text(t) => {
|
||||
let id = t.id.get().unwrap();
|
||||
let char_len = t.text.chars().count();
|
||||
|
||||
let style = Style {
|
||||
size: Size {
|
||||
// characters are 1 point tall
|
||||
height: Dimension::Points(1.0),
|
||||
|
||||
// text is as long as it is declared
|
||||
width: Dimension::Points(char_len as f32),
|
||||
},
|
||||
..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,
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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!(),
|
||||
}
|
||||
|
||||
// child_layout.push(nodes[&node.mounted_id()].layout)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
VNode::Component(sc) => {
|
||||
//
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
let root = scope.root_node();
|
||||
collect_layout(layout, nodes, vdom, root);
|
||||
}
|
||||
VNode::Placeholder(_) => {
|
||||
//
|
||||
}
|
||||
};
|
||||
#[derive(Clone, PartialEq, Default, Debug)]
|
||||
pub struct RinkLayout {
|
||||
pub style: Style,
|
||||
pub node: Option<Node>,
|
||||
}
|
||||
|
||||
impl BubbledUpState for RinkLayout {
|
||||
type Ctx = Stretch;
|
||||
|
||||
// Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes
|
||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx)
|
||||
where
|
||||
I: Iterator<Item = &'a Self>,
|
||||
Self: 'a,
|
||||
{
|
||||
match vnode {
|
||||
VNode::Text(t) => {
|
||||
let char_len = t.text.chars().count();
|
||||
|
||||
let style = Style {
|
||||
size: Size {
|
||||
// characters are 1 point tall
|
||||
height: Dimension::Points(1.0),
|
||||
|
||||
// text is as long as it is declared
|
||||
width: Dimension::Points(char_len as f32),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(n) = self.node {
|
||||
if self.style != style {
|
||||
panic!("new style: {style:?}");
|
||||
stretch.set_style(n, style).unwrap();
|
||||
}
|
||||
} else {
|
||||
self.node = Some(stretch.new_node(style, &[]).unwrap());
|
||||
}
|
||||
|
||||
self.style = style;
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
// gather up all the styles from the attribute list
|
||||
let mut style = Style::default();
|
||||
|
||||
for &Attribute { name, value, .. } in el.attributes {
|
||||
apply_layout_attributes(name, value, &mut style);
|
||||
}
|
||||
|
||||
// todo: move
|
||||
if el.id.get() == Some(ElementId(0)) {
|
||||
apply_layout_attributes("width", "100%", &mut style);
|
||||
apply_layout_attributes("height", "100%", &mut style);
|
||||
}
|
||||
|
||||
// Set all direct nodes as our children
|
||||
let mut child_layout = vec![];
|
||||
for l in children {
|
||||
child_layout.push(l.node.unwrap());
|
||||
}
|
||||
child_layout.reverse();
|
||||
|
||||
if let Some(n) = self.node {
|
||||
if &stretch.children(n).unwrap() != &child_layout {
|
||||
panic!("new children: {child_layout:?}");
|
||||
stretch.set_children(n, &child_layout).unwrap();
|
||||
}
|
||||
if self.style != style {
|
||||
panic!("new style: {style:?}");
|
||||
stretch.set_style(n, style).unwrap();
|
||||
}
|
||||
} else {
|
||||
self.node = Some(stretch.new_node(style, &child_layout).unwrap());
|
||||
}
|
||||
|
||||
self.style = style;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
643
packages/tui/src/layout_attributes.rs
Normal file
643
packages/tui/src/layout_attributes.rs
Normal file
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
- [ ] 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, style::Style};
|
||||
|
||||
/// 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" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_size(_name: &str, _value: &str, _style: &mut Style) {
|
||||
//
|
||||
}
|
||||
|
||||
pub 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 => {}
|
||||
}
|
||||
}
|
|
@ -6,28 +6,32 @@ use crossterm::{
|
|||
};
|
||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||
use dioxus_core::*;
|
||||
use dioxus_native_core::Tree;
|
||||
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use stretch2::{prelude::Size, Stretch};
|
||||
use style::RinkStyle;
|
||||
use stretch2::{
|
||||
prelude::{Layout, Size},
|
||||
Stretch,
|
||||
};
|
||||
use style_attributes::StyleModifier;
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
mod attributes;
|
||||
mod config;
|
||||
mod hooks;
|
||||
mod layout;
|
||||
mod layout_attributes;
|
||||
mod render;
|
||||
mod style;
|
||||
mod style_attributes;
|
||||
mod widget;
|
||||
|
||||
pub use attributes::*;
|
||||
pub use config::*;
|
||||
pub use hooks::*;
|
||||
pub use layout::*;
|
||||
pub use layout_attributes::*;
|
||||
pub use render::*;
|
||||
|
||||
pub fn launch(app: Component<()>) {
|
||||
|
@ -44,16 +48,15 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
|||
|
||||
cx.provide_root_context(state);
|
||||
|
||||
dom.rebuild();
|
||||
let mut tree: Tree<RinkLayout, StyleModifier> = Tree::new();
|
||||
let mutations = dom.rebuild();
|
||||
let to_update = tree.apply_mutations(vec![mutations]);
|
||||
let mut stretch = Stretch::new();
|
||||
let _to_rerender = tree
|
||||
.update_state(&dom, to_update, &mut stretch, &mut ())
|
||||
.unwrap();
|
||||
|
||||
render_vdom(&mut dom, 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, tx, handler, cfg, tree, stretch).unwrap();
|
||||
}
|
||||
|
||||
pub fn render_vdom(
|
||||
|
@ -61,6 +64,8 @@ pub fn render_vdom(
|
|||
ctx: UnboundedSender<TermEvent>,
|
||||
handler: RinkInputHandler,
|
||||
cfg: Config,
|
||||
mut tree: Tree<RinkLayout, StyleModifier>,
|
||||
mut stretch: Stretch,
|
||||
) -> Result<()> {
|
||||
// Setup input handling
|
||||
let (tx, mut rx) = unbounded();
|
||||
|
@ -102,11 +107,11 @@ pub fn render_vdom(
|
|||
|
||||
loop {
|
||||
/*
|
||||
-> collect all the nodes with their layout
|
||||
-> solve their layout
|
||||
-> collect all the nodes
|
||||
-> resolve events
|
||||
-> render the nodes in the right place with tui/crosstream
|
||||
-> while rendering, apply styling
|
||||
-> render the nodes in the right place with tui/crossterm
|
||||
-> rendering
|
||||
-> lazily update the layout and style based on nodes changed
|
||||
|
||||
use simd to compare lines for diffing?
|
||||
|
||||
|
@ -114,45 +119,47 @@ pub fn render_vdom(
|
|||
todo: reuse the layout and node objects.
|
||||
our work_with_deadline method can tell us which nodes are dirty.
|
||||
*/
|
||||
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();
|
||||
|
||||
terminal.draw(|frame| {
|
||||
// size is guaranteed to not change when rendering
|
||||
let dims = frame.size();
|
||||
println!("{dims:?}");
|
||||
let width = dims.width;
|
||||
let height = dims.height;
|
||||
layout
|
||||
let root_id = tree.root;
|
||||
let root_node = tree.get(root_id).up_state.node.unwrap();
|
||||
stretch
|
||||
.compute_layout(
|
||||
root_layout,
|
||||
root_node,
|
||||
Size {
|
||||
width: stretch2::prelude::Number::Defined(width as f32),
|
||||
height: stretch2::prelude::Number::Defined(height as f32),
|
||||
width: stretch2::prelude::Number::Defined((width - 1) as f32),
|
||||
height: stretch2::prelude::Number::Defined((height - 1) as f32),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// resolve events before rendering
|
||||
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
|
||||
render::render_vnode(
|
||||
frame,
|
||||
&layout,
|
||||
&mut nodes,
|
||||
events = handler.get_events(
|
||||
vdom,
|
||||
root_node,
|
||||
&RinkStyle::default(),
|
||||
cfg,
|
||||
&stretch,
|
||||
&mut tree,
|
||||
vdom.base_scope().root_node(),
|
||||
);
|
||||
assert!(nodes.is_empty());
|
||||
for n in &tree.nodes {
|
||||
if let Some(node) = n {
|
||||
let Layout { location, size, .. } =
|
||||
stretch.layout(node.up_state.node.unwrap()).unwrap();
|
||||
println!("{node:#?}");
|
||||
println!("\t{location:?}: {size:?}");
|
||||
}
|
||||
}
|
||||
let root = tree.get(tree.root);
|
||||
// render::render_vnode(frame, &stretch, &tree, &root, cfg);
|
||||
})?;
|
||||
|
||||
for e in events {
|
||||
|
@ -191,7 +198,11 @@ pub fn render_vdom(
|
|||
}
|
||||
}
|
||||
|
||||
vdom.work_with_deadline(|| false);
|
||||
let mutations = vdom.work_with_deadline(|| false);
|
||||
let to_update = tree.apply_mutations(mutations);
|
||||
let _to_rerender = tree
|
||||
.update_state(&vdom, to_update, &mut stretch, &mut ())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use dioxus_core::*;
|
||||
use std::{collections::HashMap, io::Stdout};
|
||||
use dioxus_native_core::{Tree, TreeNode};
|
||||
use std::io::Stdout;
|
||||
use stretch2::{
|
||||
geometry::Point,
|
||||
prelude::{Layout, Size},
|
||||
|
@ -9,8 +9,9 @@ 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, RinkLayout, StyleModifier, UnitSystem,
|
||||
};
|
||||
|
||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||
|
@ -18,43 +19,22 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
|||
pub fn render_vnode<'a>(
|
||||
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,
|
||||
tree: &Tree<RinkLayout, StyleModifier>,
|
||||
node: &TreeNode<RinkLayout, StyleModifier>,
|
||||
cfg: Config,
|
||||
) {
|
||||
match node {
|
||||
VNode::Fragment(f) => {
|
||||
for child in f.children {
|
||||
render_vnode(frame, layout, layouts, vdom, child, style, cfg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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(_) => {}
|
||||
match &node.node_type {
|
||||
dioxus_native_core::TreeNodeType::Placeholder => 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.up_state.node.unwrap()).unwrap();
|
||||
|
||||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match node.node {
|
||||
VNode::Text(t) => {
|
||||
match &node.node_type {
|
||||
dioxus_native_core::TreeNodeType::Text { text } => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
text: &'a str,
|
||||
|
@ -65,6 +45,7 @@ pub fn render_vnode<'a>(
|
|||
fn render(self, area: Rect, mut buf: RinkBuffer) {
|
||||
for (i, c) in self.text.char_indices() {
|
||||
let mut new_cell = RinkCell::default();
|
||||
// println!("{:?}", self.style);
|
||||
new_cell.set_style(self.style);
|
||||
new_cell.symbol = c.to_string();
|
||||
buf.set(area.left() + i as u16, area.top(), &new_cell);
|
||||
|
@ -73,8 +54,8 @@ pub fn render_vnode<'a>(
|
|||
}
|
||||
|
||||
let label = Label {
|
||||
text: t.text,
|
||||
style: *style,
|
||||
text: &text,
|
||||
style: node.down_state.style,
|
||||
};
|
||||
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) => {
|
||||
dioxus_native_core::TreeNodeType::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, tree, tree.get(c.0), cfg);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
VNode::Placeholder(_) => todo!(),
|
||||
dioxus_native_core::TreeNodeType::Placeholder => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RinkWidget for TuiNode<'a> {
|
||||
impl RinkWidget for &TreeNode<RinkLayout, StyleModifier> {
|
||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||
use tui::symbols::line::*;
|
||||
|
||||
|
@ -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.down_state.style.bg {
|
||||
new_cell.bg = c;
|
||||
}
|
||||
buf.set(x, y, &new_cell);
|
||||
}
|
||||
}
|
||||
|
||||
let borders = self.tui_modifier.borders;
|
||||
let borders = &self.down_state.modifier.borders;
|
||||
|
||||
let last_edge = &borders.left;
|
||||
let current_edge = &borders.top;
|
||||
|
@ -314,7 +288,7 @@ 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.down_state.style.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -349,7 +323,7 @@ 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.down_state.style.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -384,7 +358,7 @@ 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.down_state.style.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
@ -419,7 +393,7 @@ 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.down_state.style.fg);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
|
|
|
@ -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: u16 = 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(),
|
||||
|
|
582
packages/tui/src/style_attributes.rs
Normal file
582
packages/tui/src/style_attributes.rs
Normal file
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
- [ ] 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, VNode};
|
||||
use dioxus_native_core::PushedDownState;
|
||||
|
||||
use crate::{
|
||||
parse_value,
|
||||
style::{RinkColor, RinkStyle},
|
||||
UnitSystem,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug)]
|
||||
pub struct StyleModifier {
|
||||
pub style: RinkStyle,
|
||||
pub modifier: TuiModifier,
|
||||
}
|
||||
|
||||
impl PushedDownState for StyleModifier {
|
||||
type Ctx = ();
|
||||
|
||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||
*self = StyleModifier::default();
|
||||
self.style.fg = None;
|
||||
match vnode {
|
||||
VNode::Element(el) => {
|
||||
// handle text modifier elements
|
||||
if el.namespace.is_none() {
|
||||
match el.tag {
|
||||
"b" => apply_style_attributes("font-weight", "bold", self),
|
||||
"strong" => apply_style_attributes("font-weight", "bold", self),
|
||||
"u" => apply_style_attributes("text-decoration", "underline", self),
|
||||
"ins" => apply_style_attributes("text-decoration", "underline", self),
|
||||
"del" => apply_style_attributes("text-decoration", "line-through", self),
|
||||
"i" => apply_style_attributes("font-style", "italic", self),
|
||||
"em" => apply_style_attributes("font-style", "italic", self),
|
||||
"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 el.attributes {
|
||||
apply_style_attributes(name, value, self);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// keep the text styling from the parent element
|
||||
if let Some(parent) = parent {
|
||||
let mut new_style = self.style.merge(parent.style);
|
||||
new_style.bg = self.style.bg;
|
||||
self.style = new_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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.style.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.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 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.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
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.style = style.style.add_modifier(Modifier::ITALIC),
|
||||
"oblique" => style.style = style.style.add_modifier(Modifier::ITALIC),
|
||||
_ => (),
|
||||
},
|
||||
"font-variant" => todo!(),
|
||||
"font-weight" => match value {
|
||||
"bold" => style.style = style.style.add_modifier(Modifier::BOLD),
|
||||
"normal" => style.style = style.style.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.style = style.style.add_modifier(Modifier::CROSSED_OUT),
|
||||
"underline" => style.style = style.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_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
|
||||
todo!()
|
||||
}
|
83
packages/tui/src/utils.rs
Normal file
83
packages/tui/src/utils.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
// use dioxus_core::{Element, ElementId, Mutations, VNode, VirtualDom, DomEdit};
|
||||
|
||||
// /// The focus system needs a iterator that can persist through changes in the [VirtualDom]. Iterate through it with [ElementIter::next], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]).
|
||||
// pub(crate) struct ElementIter {
|
||||
// // stack of elements and fragments
|
||||
// stack: smallvec::SmallVec<[(ElementId, usize); 5]>,
|
||||
// }
|
||||
|
||||
// impl ElementIter {
|
||||
// pub(crate) fn new(initial: ElementId) -> Self {
|
||||
// ElementIter {
|
||||
// stack: smallvec::smallvec![(initial, 0)],
|
||||
// }
|
||||
// }
|
||||
// /// remove stale element refreneces
|
||||
// pub(crate) fn update(&mut self, mutations: &Mutations, vdom: &VirtualDom) {
|
||||
// let ids_removed: Vec<_> = mutations.edits.iter().filter_map(|e| if let DomEdit::Remove{root: }).collect();
|
||||
// for node in self.stack {
|
||||
|
||||
// match node.0 {
|
||||
// VNode::Fragment(f) => {
|
||||
|
||||
// }
|
||||
|
||||
// VNode::Element(_) => {}
|
||||
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// pub(crate) fn next<'a>(&mut self, vdom: &'a VirtualDom) -> Option<&'a VNode<'a>> {
|
||||
// let last = self.stack.last()?.0;
|
||||
// let node = vdom.get_element(last)?;
|
||||
// match node {
|
||||
// VNode::Fragment(f) => {
|
||||
// let mut last_mut = self.stack.last_mut()?;
|
||||
// if last_mut.1 + 1 >= f.children.len() {
|
||||
// self.stack.pop();
|
||||
// self.next(vdom)
|
||||
// } else {
|
||||
// last_mut.1 += 1;
|
||||
// let new_node = &f.children[last_mut.1];
|
||||
// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) {
|
||||
// self.stack.push((new_node.mounted_id(), 0));
|
||||
// }
|
||||
// self.next(vdom)
|
||||
// }
|
||||
// }
|
||||
|
||||
// VNode::Component(vcomp) => {
|
||||
// let idx = vcomp.scope.get().unwrap();
|
||||
// let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||
// *self.stack.last_mut()? = (new_node.mounted_id(), 0);
|
||||
// self.next(vdom)
|
||||
// }
|
||||
|
||||
// VNode::Placeholder(_) | VNode::Text(_) => {
|
||||
// self.stack.pop();
|
||||
// self.next(vdom)
|
||||
// }
|
||||
|
||||
// VNode::Element(e) => {
|
||||
// let mut last_mut = self.stack.last_mut()?;
|
||||
// if last_mut.1 + 1 >= e.children.len() {
|
||||
// self.stack.pop();
|
||||
// self.next(vdom);
|
||||
// } else {
|
||||
// last_mut.1 += 1;
|
||||
// let new_node = &e.children[last_mut.1];
|
||||
// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) {
|
||||
// self.stack.push((new_node.mounted_id(), 0));
|
||||
// }
|
||||
// self.next(vdom);
|
||||
// }
|
||||
// Some(node)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub(crate) fn peak(&self) -> Option<&ElementId> {
|
||||
// self.stack.last().map(|(e, c)| e)
|
||||
// }
|
||||
// }
|
|
@ -21,6 +21,10 @@ impl<'a> RinkBuffer<'a> {
|
|||
}
|
||||
|
||||
pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
|
||||
let area = self.buf.area();
|
||||
if x < area.x || x > area.width || y < area.y || y > area.height {
|
||||
panic!("({x}, {y}) is not in {area:?}");
|
||||
}
|
||||
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() {
|
||||
|
@ -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(),
|
||||
}
|
||||
|
|
63
packages/tui/tests/relayout.rs
Normal file
63
packages/tui/tests/relayout.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use stretch2 as stretch;
|
||||
|
||||
#[test]
|
||||
fn relayout() {
|
||||
let mut stretch = stretch::Stretch::new();
|
||||
let node1 = stretch
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
position: stretch::geometry::Point {
|
||||
x: stretch::style::Dimension::Points(10f32),
|
||||
y: stretch::style::Dimension::Points(10f32),
|
||||
},
|
||||
size: stretch::geometry::Size {
|
||||
width: stretch::style::Dimension::Points(10f32),
|
||||
height: stretch::style::Dimension::Points(10f32),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let node0 = stretch
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
size: stretch::geometry::Size {
|
||||
width: stretch::style::Dimension::Percent(1f32),
|
||||
height: stretch::style::Dimension::Percent(1f32),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
&[node1],
|
||||
)
|
||||
.unwrap();
|
||||
let node = stretch
|
||||
.new_node(
|
||||
stretch::style::Style {
|
||||
size: stretch::geometry::Size {
|
||||
width: stretch::style::Dimension::Points(100f32),
|
||||
height: stretch::style::Dimension::Points(100f32),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
&[node0],
|
||||
)
|
||||
.unwrap();
|
||||
for _ in 0..10 {
|
||||
stretch
|
||||
.compute_layout(node, stretch::geometry::Size::undefined())
|
||||
.unwrap();
|
||||
assert_eq!(stretch.layout(node).unwrap().size.width, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
|
||||
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
|
||||
assert_eq!(stretch.layout(node1).unwrap().size.width, 10f32);
|
||||
assert_eq!(stretch.layout(node1).unwrap().size.height, 10f32);
|
||||
assert_eq!(stretch.layout(node1).unwrap().location.x, 0f32);
|
||||
assert_eq!(stretch.layout(node1).unwrap().location.y, 0f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().size.width, 100f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().location.x, 0f32);
|
||||
assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue