wip lazy layout

This commit is contained in:
Evan Almloff 2022-03-18 10:43:43 -05:00
parent 14099e9889
commit 499971e9b3
14 changed files with 2132 additions and 1238 deletions

View 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"

View 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("\\", ""));
}

View file

@ -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"

View file

@ -1,968 +0,0 @@
/*
- [ ] pub display: Display,
- [x] pub position_type: PositionType, --> kinda, stretch doesnt support everything
- [ ] pub direction: Direction,
- [x] pub flex_direction: FlexDirection,
- [x] pub flex_wrap: FlexWrap,
- [x] pub flex_grow: f32,
- [x] pub flex_shrink: f32,
- [x] pub flex_basis: Dimension,
- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
- [x] pub align_items: AlignItems,
- [x] pub align_self: AlignSelf,
- [x] pub align_content: AlignContent,
- [x] pub margin: Rect<Dimension>,
- [x] pub padding: Rect<Dimension>,
- [x] pub justify_content: JustifyContent,
- [ ] pub position: Rect<Dimension>,
- [ ] pub border: Rect<Dimension>,
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
- [ ] pub min_size: Size<Dimension>,
- [ ] pub max_size: Size<Dimension>,
- [ ] pub aspect_ratio: Number,
*/
use stretch2::{prelude::*, style::PositionType, style::Style};
use crate::style::{RinkColor, RinkStyle};
pub struct StyleModifer {
pub style: Style,
pub tui_style: RinkStyle,
pub tui_modifier: TuiModifier,
}
#[derive(Default)]
pub struct TuiModifier {
pub borders: Borders,
}
#[derive(Default)]
pub struct Borders {
pub top: BorderEdge,
pub right: BorderEdge,
pub bottom: BorderEdge,
pub left: BorderEdge,
}
impl Borders {
fn slice(&mut self) -> [&mut BorderEdge; 4] {
[
&mut self.top,
&mut self.right,
&mut self.bottom,
&mut self.left,
]
}
}
pub struct BorderEdge {
pub color: Option<RinkColor>,
pub style: BorderStyle,
pub width: UnitSystem,
pub radius: UnitSystem,
}
impl Default for BorderEdge {
fn default() -> Self {
Self {
color: None,
style: BorderStyle::NONE,
width: UnitSystem::Point(0.0),
radius: UnitSystem::Point(0.0),
}
}
}
#[derive(Clone, Copy)]
pub enum BorderStyle {
DOTTED,
DASHED,
SOLID,
DOUBLE,
GROOVE,
RIDGE,
INSET,
OUTSET,
HIDDEN,
NONE,
}
impl BorderStyle {
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
use tui::symbols::line::*;
const DASHED: Set = Set {
horizontal: "",
vertical: "",
..NORMAL
};
const DOTTED: Set = Set {
horizontal: "",
vertical: "",
..NORMAL
};
match self {
BorderStyle::DOTTED => Some(DOTTED),
BorderStyle::DASHED => Some(DASHED),
BorderStyle::SOLID => Some(NORMAL),
BorderStyle::DOUBLE => Some(DOUBLE),
BorderStyle::GROOVE => Some(NORMAL),
BorderStyle::RIDGE => Some(NORMAL),
BorderStyle::INSET => Some(NORMAL),
BorderStyle::OUTSET => Some(NORMAL),
BorderStyle::HIDDEN => None,
BorderStyle::NONE => None,
}
}
}
/// applies the entire html namespace defined in dioxus-html
pub fn apply_attributes(
//
name: &str,
value: &str,
style: &mut StyleModifer,
) {
match name {
"align-content"
| "align-items"
| "align-self" => apply_align(name, value, style),
"animation"
| "animation-delay"
| "animation-direction"
| "animation-duration"
| "animation-fill-mode"
| "animation-iteration-count"
| "animation-name"
| "animation-play-state"
| "animation-timing-function" => apply_animation(name, value, style),
"backface-visibility" => {}
"background"
| "background-attachment"
| "background-clip"
| "background-color"
| "background-image"
| "background-origin"
| "background-position"
| "background-repeat"
| "background-size" => apply_background(name, value, style),
"border"
| "border-bottom"
| "border-bottom-color"
| "border-bottom-left-radius"
| "border-bottom-right-radius"
| "border-bottom-style"
| "border-bottom-width"
| "border-collapse"
| "border-color"
| "border-image"
| "border-image-outset"
| "border-image-repeat"
| "border-image-slice"
| "border-image-source"
| "border-image-width"
| "border-left"
| "border-left-color"
| "border-left-style"
| "border-left-width"
| "border-radius"
| "border-right"
| "border-right-color"
| "border-right-style"
| "border-right-width"
| "border-spacing"
| "border-style"
| "border-top"
| "border-top-color"
| "border-top-left-radius"
| "border-top-right-radius"
| "border-top-style"
| "border-top-width"
| "border-width" => apply_border(name, value, style),
"bottom" => {}
"box-shadow" => {}
"box-sizing" => {}
"caption-side" => {}
"clear" => {}
"clip" => {}
"color" => {
if let Ok(c) = value.parse() {
style.tui_style.fg.replace(c);
}
}
"column-count"
| "column-fill"
| "column-gap"
| "column-rule"
| "column-rule-color"
| "column-rule-style"
| "column-rule-width"
| "column-span"
// add column-width
| "column-width" => apply_column(name, value, style),
"columns" => {}
"content" => {}
"counter-increment" => {}
"counter-reset" => {}
"cursor" => {}
"direction" => {
match value {
"ltr" => style.style.direction = Direction::LTR,
"rtl" => style.style.direction = Direction::RTL,
_ => {}
}
}
"display" => apply_display(name, value, style),
"empty-cells" => {}
"flex"
| "flex-basis"
| "flex-direction"
| "flex-flow"
| "flex-grow"
| "flex-shrink"
| "flex-wrap" => apply_flex(name, value, style),
"float" => {}
"font"
| "font-family"
| "font-size"
| "font-size-adjust"
| "font-stretch"
| "font-style"
| "font-variant"
| "font-weight" => apply_font(name, value, style),
"height" => {
if let Some(v) = parse_value(value){
style.style.size.height = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"justify-content" => {
use JustifyContent::*;
style.style.justify_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
"space-evenly" => SpaceEvenly,
_ => FlexStart,
};
}
"left" => {}
"letter-spacing" => {}
"line-height" => {}
"list-style"
| "list-style-image"
| "list-style-position"
| "list-style-type" => {}
"margin"
| "margin-bottom"
| "margin-left"
| "margin-right"
| "margin-top" => apply_margin(name, value, style),
"max-height" => {}
"max-width" => {}
"min-height" => {}
"min-width" => {}
"opacity" => {}
"order" => {}
"outline" => {}
"outline-color"
| "outline-offset"
| "outline-style"
| "outline-width" => {}
"overflow"
| "overflow-x"
| "overflow-y" => apply_overflow(name, value, style),
"padding"
| "padding-bottom"
| "padding-left"
| "padding-right"
| "padding-top" => apply_padding(name, value, style),
"page-break-after"
| "page-break-before"
| "page-break-inside" => {}
"perspective"
| "perspective-origin" => {}
"position" => {
match value {
"static" => {}
"relative" => style.style.position_type = PositionType::Relative,
"fixed" => {}
"absolute" => style.style.position_type = PositionType::Absolute,
"sticky" => {}
_ => {}
}
}
"pointer-events" => {}
"quotes" => {}
"resize" => {}
"right" => {}
"tab-size" => {}
"table-layout" => {}
"text-align"
| "text-align-last"
| "text-decoration"
| "text-decoration-color"
| "text-decoration-line"
| "text-decoration-style"
| "text-indent"
| "text-justify"
| "text-overflow"
| "text-shadow"
| "text-transform" => apply_text(name, value, style),
"top" => {}
"transform"
| "transform-origin"
| "transform-style" => apply_transform(name, value, style),
"transition"
| "transition-delay"
| "transition-duration"
| "transition-property"
| "transition-timing-function" => apply_transition(name, value, style),
"vertical-align" => {}
"visibility" => {}
"white-space" => {}
"width" => {
if let Some(v) = parse_value(value){
style.style.size.width = match v {
UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
UnitSystem::Point(v)=> Dimension::Points(v),
};
}
}
"word-break" => {}
"word-spacing" => {}
"word-wrap" => {}
"z-index" => {}
_ => {}
}
}
#[derive(Clone, Copy)]
pub enum UnitSystem {
Percent(f32),
Point(f32),
}
impl Into<Dimension> for UnitSystem {
fn into(self) -> Dimension {
match self {
Self::Percent(v) => Dimension::Percent(v),
Self::Point(v) => Dimension::Points(v),
}
}
}
fn parse_value(value: &str) -> Option<UnitSystem> {
if value.ends_with("px") {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
Some(UnitSystem::Point(px))
} else {
None
}
} else if value.ends_with('%') {
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
Some(UnitSystem::Percent(pct))
} else {
None
}
} else {
None
}
}
fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
match name {
// todo: add more overflow support to stretch2
"overflow" | "overflow-x" | "overflow-y" => {
style.style.overflow = match value {
"auto" => Overflow::Visible,
"hidden" => Overflow::Hidden,
"scroll" => Overflow::Scroll,
"visible" => Overflow::Visible,
_ => Overflow::Visible,
};
}
_ => {}
}
}
fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
style.style.display = match value {
"flex" => Display::Flex,
"block" => Display::None,
_ => Display::Flex,
}
// TODO: there are way more variants
// stretch needs to be updated to handle them
//
// "block" => Display::Block,
// "inline" => Display::Inline,
// "inline-block" => Display::InlineBlock,
// "inline-table" => Display::InlineTable,
// "list-item" => Display::ListItem,
// "run-in" => Display::RunIn,
// "table" => Display::Table,
// "table-caption" => Display::TableCaption,
// "table-cell" => Display::TableCell,
// "table-column" => Display::TableColumn,
// "table-column-group" => Display::TableColumnGroup,
// "table-footer-group" => Display::TableFooterGroup,
// "table-header-group" => Display::TableHeaderGroup,
// "table-row" => Display::TableRow,
// "table-row-group" => Display::TableRowGroup,
// "none" => Display::None,
// _ => Display::Inline,
}
fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
match name {
"background-color" => {
if let Ok(c) = value.parse() {
style.tui_style.bg.replace(c);
}
}
"background" => {}
"background-attachment" => {}
"background-clip" => {}
"background-image" => {}
"background-origin" => {}
"background-position" => {}
"background-repeat" => {}
"background-size" => {}
_ => {}
}
}
fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
fn parse_border_style(v: &str) -> BorderStyle {
match v {
"dotted" => BorderStyle::DOTTED,
"dashed" => BorderStyle::DASHED,
"solid" => BorderStyle::SOLID,
"double" => BorderStyle::DOUBLE,
"groove" => BorderStyle::GROOVE,
"ridge" => BorderStyle::RIDGE,
"inset" => BorderStyle::INSET,
"outset" => BorderStyle::OUTSET,
"none" => BorderStyle::NONE,
"hidden" => BorderStyle::HIDDEN,
_ => todo!(),
}
}
match name {
"border" => {}
"border-bottom" => {}
"border-bottom-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.bottom.color = Some(c);
}
}
"border-bottom-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-bottom-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-bottom-style" => {
if style.style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.bottom = v;
}
style.tui_modifier.borders.bottom.style = parse_border_style(value)
}
"border-bottom-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.bottom.width = v;
style.style.border.bottom = v.into();
}
}
"border-collapse" => {}
"border-color" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Ok(c) = values[0].parse() {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.color = Some(c));
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Ok(c) = v.parse() {
b.color = Some(c);
}
}
}
}
"border-image" => {}
"border-image-outset" => {}
"border-image-repeat" => {}
"border-image-slice" => {}
"border-image-source" => {}
"border-image-width" => {}
"border-left" => {}
"border-left-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.left.color = Some(c);
}
}
"border-left-style" => {
if style.style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.start = v;
}
style.tui_modifier.borders.left.style = parse_border_style(value)
}
"border-left-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.width = v;
style.style.border.start = v.into();
}
}
"border-radius" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(r) = parse_value(values[0]) {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.radius = r);
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Some(r) = parse_value(v) {
b.radius = r;
}
}
}
}
"border-right" => {}
"border-right-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.right.color = Some(c);
}
}
"border-right-style" => {
let v = Dimension::Points(1.0);
style.style.border.end = v;
style.tui_modifier.borders.right.style = parse_border_style(value)
}
"border-right-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.width = v;
}
}
"border-spacing" => {}
"border-style" => {
let values: Vec<_> = value.split(' ').collect();
if style.style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.top = v;
}
if style.style.border.bottom == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.bottom = v;
}
if style.style.border.start == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.start = v;
}
if style.style.border.end == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.end = v;
}
if values.len() == 1 {
let border_style = parse_border_style(values[0]);
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.style = border_style);
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
b.style = parse_border_style(v);
}
}
}
"border-top" => {}
"border-top-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.top.color = Some(c);
}
}
"border-top-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-top-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-top-style" => {
if style.style.border.top == Dimension::default() {
let v = Dimension::Points(1.0);
style.style.border.top = v;
}
style.tui_modifier.borders.top.style = parse_border_style(value)
}
"border-top-width" => {
if let Some(v) = parse_value(value) {
style.style.border.top = v.into();
style.tui_modifier.borders.top.width = v;
}
}
"border-width" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(w) = parse_value(values[0]) {
style.style.border.top = w.into();
style.style.border.bottom = w.into();
style.style.border.start = w.into();
style.style.border.end = w.into();
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.width = w);
}
} else {
let border_widths = [
&mut style.style.border.top,
&mut style.style.border.bottom,
&mut style.style.border.start,
&mut style.style.border.end,
];
for ((v, b), width) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
.zip(border_widths)
{
if let Some(w) = parse_value(v) {
*width = w.into();
b.width = w;
}
}
}
}
_ => (),
}
}
fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) {
match name {
"animation" => {}
"animation-delay" => {}
"animation-direction =>{}" => {}
"animation-duration" => {}
"animation-fill-mode" => {}
"animation-itera =>{}tion-count" => {}
"animation-name" => {}
"animation-play-state" => {}
"animation-timing-function" => {}
_ => {}
}
}
fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) {
match name {
"column-count" => {}
"column-fill" => {}
"column-gap" => {}
"column-rule" => {}
"column-rule-color" => {}
"column-rule-style" => {}
"column-rule-width" => {}
"column-span" => {}
"column-width" => {}
_ => {}
}
}
fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) {
// - [x] pub flex_direction: FlexDirection,
// - [x] pub flex_wrap: FlexWrap,
// - [x] pub flex_grow: f32,
// - [x] pub flex_shrink: f32,
// - [x] pub flex_basis: Dimension,
match name {
"flex" => {}
"flex-direction" => {
use FlexDirection::*;
style.style.flex_direction = match value {
"row" => Row,
"row-reverse" => RowReverse,
"column" => Column,
"column-reverse" => ColumnReverse,
_ => Row,
};
}
"flex-basis" => {
if let Some(v) = parse_value(value) {
style.style.flex_basis = match v {
UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
UnitSystem::Point(v) => Dimension::Points(v),
};
}
}
"flex-flow" => {}
"flex-grow" => {
if let Ok(val) = value.parse::<f32>() {
style.style.flex_grow = val;
}
}
"flex-shrink" => {
if let Ok(px) = value.parse::<f32>() {
style.style.flex_shrink = px;
}
}
"flex-wrap" => {
use FlexWrap::*;
style.style.flex_wrap = match value {
"nowrap" => NoWrap,
"wrap" => Wrap,
"wrap-reverse" => WrapReverse,
_ => NoWrap,
};
}
_ => {}
}
}
fn apply_font(name: &str, value: &str, style: &mut StyleModifer) {
use tui::style::Modifier;
match name {
"font" => (),
"font-family" => (),
"font-size" => (),
"font-size-adjust" => (),
"font-stretch" => (),
"font-style" => match value {
"italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
"oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
_ => (),
},
"font-variant" => todo!(),
"font-weight" => match value {
"bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD),
"normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD),
_ => (),
},
_ => (),
}
}
fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"padding" => {
let v = Dimension::Percent(v / 100.0);
style.style.padding.top = v;
style.style.padding.bottom = v;
style.style.padding.start = v;
style.style.padding.end = v;
}
"padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0),
"padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0),
"padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0),
"padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"padding" => {
style.style.padding.top = Dimension::Points(v);
style.style.padding.bottom = Dimension::Points(v);
style.style.padding.start = Dimension::Points(v);
style.style.padding.end = Dimension::Points(v);
}
"padding-bottom" => style.style.padding.bottom = Dimension::Points(v),
"padding-left" => style.style.padding.start = Dimension::Points(v),
"padding-right" => style.style.padding.end = Dimension::Points(v),
"padding-top" => style.style.padding.top = Dimension::Points(v),
_ => {}
},
None => {}
}
}
fn apply_text(name: &str, value: &str, style: &mut StyleModifer) {
use tui::style::Modifier;
match name {
"text-align" => todo!(),
"text-align-last" => todo!(),
"text-decoration" | "text-decoration-line" => {
for v in value.split(' ') {
match v {
"line-through" => {
style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT)
}
"underline" => {
style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED)
}
_ => (),
}
}
}
"text-decoration-color" => todo!(),
"text-decoration-style" => todo!(),
"text-indent" => todo!(),
"text-justify" => todo!(),
"text-overflow" => todo!(),
"text-shadow" => todo!(),
"text-transform" => todo!(),
_ => todo!(),
}
}
fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!()
}
fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!()
}
fn apply_align(name: &str, value: &str, style: &mut StyleModifer) {
match name {
"align-items" => {
use AlignItems::*;
style.style.align_items = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => FlexStart,
};
}
"align-content" => {
use AlignContent::*;
style.style.align_content = match value {
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"space-between" => SpaceBetween,
"space-around" => SpaceAround,
_ => FlexStart,
};
}
"align-self" => {
use AlignSelf::*;
style.style.align_self = match value {
"auto" => Auto,
"flex-start" => FlexStart,
"flex-end" => FlexEnd,
"center" => Center,
"baseline" => Baseline,
"stretch" => Stretch,
_ => Auto,
};
}
_ => {}
}
}
pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) {
//
}
pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"margin" => {
let v = Dimension::Percent(v / 100.0);
style.style.margin.top = v;
style.style.margin.bottom = v;
style.style.margin.start = v;
style.style.margin.end = v;
}
"margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0),
"margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0),
"margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0),
"margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0),
_ => {}
},
Some(UnitSystem::Point(v)) => match name {
"margin" => {
style.style.margin.top = Dimension::Points(v);
style.style.margin.bottom = Dimension::Points(v);
style.style.margin.start = Dimension::Points(v);
style.style.margin.end = Dimension::Points(v);
}
"margin-top" => style.style.margin.top = Dimension::Points(v),
"margin-bottom" => style.style.margin.bottom = Dimension::Points(v),
"margin-left" => style.style.margin.start = Dimension::Points(v),
"margin-right" => style.style.margin.end = Dimension::Points(v),
_ => {}
},
None => {}
}
}

View file

@ -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

View file

@ -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;
}
_ => (),
}
}
}

View 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 => {}
}
}

View file

@ -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()?;

View file

@ -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;

View file

@ -4,17 +4,17 @@ use tui::style::{Color, Modifier, Style};
use crate::RenderingMode;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RinkColor {
pub color: Color,
pub alpha: f32,
pub alpha: u8,
}
impl Default for RinkColor {
fn default() -> Self {
Self {
color: Color::Black,
alpha: 0.0,
alpha: 0,
}
}
}
@ -23,22 +23,17 @@ impl RinkColor {
pub fn blend(self, other: Color) -> Color {
if self.color == Color::Reset {
Color::Reset
} else if self.alpha == 0.0 {
} else if self.alpha == 0 {
other
} else {
let [sr, sg, sb] = to_rgb(self.color);
let [or, og, ob] = to_rgb(other);
let (sr, sg, sb, sa) = (
sr as f32 / 255.0,
sg as f32 / 255.0,
sb as f32 / 255.0,
self.alpha,
);
let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0);
let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
let [or, og, ob] = to_rgb(other).map(|e| e as u16);
let sa: 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(),

View 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
View 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)
// }
// }

View file

@ -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(),
}

View 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);
}
}