From 91266cc841d44fc733e3a2ad537f216c2ed7ad41 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Mon, 27 May 2019 00:22:33 -0700 Subject: [PATCH] Move towards a new reconciler model, undo a lot of the weird stretch integrations I had done, separate out Layout and Appearance concepts, introduce ComponentKey and constructor lifecycle for components --- alchemy/src/components/fragment.rs | 7 +- alchemy/src/components/text.rs | 25 +- alchemy/src/components/view.rs | 19 +- alchemy/src/lib.rs | 1 + alchemy/src/window/window.rs | 19 +- cocoa/src/text.rs | 10 +- cocoa/src/view.rs | 8 +- examples/layout/src/main.rs | 6 +- lifecycle/Cargo.toml | 1 - lifecycle/src/lib.rs | 3 +- lifecycle/src/reconciler/error.rs | 34 +- lifecycle/src/reconciler/key.rs | 51 +++ lifecycle/src/reconciler/mod.rs | 491 ++++------------------------ lifecycle/src/reconciler/storage.rs | 163 +++++++++ lifecycle/src/rsx/mod.rs | 13 +- lifecycle/src/rsx/props.rs | 9 + lifecycle/src/rsx/virtual_node.rs | 30 +- lifecycle/src/traits.rs | 24 +- macros/src/rsx.rs | 25 +- styles/src/engine.rs | 18 +- styles/src/lib.rs | 9 +- styles/src/stretch/algo.rs | 17 +- styles/src/stretch/geometry.rs | 7 +- styles/src/stretch/id.rs | 5 +- styles/src/stretch/mod.rs | 6 +- styles/src/stretch/node.rs | 13 +- styles/src/stretch/number.rs | 3 + styles/src/stretch/result.rs | 7 +- styles/src/stretch/style.rs | 335 +++++++++++++++++++ styles/src/styles.rs | 373 +-------------------- styles/src/stylesheet.rs | 29 +- 31 files changed, 820 insertions(+), 941 deletions(-) create mode 100644 lifecycle/src/reconciler/key.rs create mode 100644 lifecycle/src/reconciler/storage.rs create mode 100644 styles/src/stretch/style.rs diff --git a/alchemy/src/components/fragment.rs b/alchemy/src/components/fragment.rs index 2f2a6e9..80e3e12 100644 --- a/alchemy/src/components/fragment.rs +++ b/alchemy/src/components/fragment.rs @@ -4,6 +4,7 @@ //! but as the language stabilizes even further I'd love to get rid of this and //! just allow returning arbitrary iterators. +use alchemy_lifecycle::ComponentKey; use alchemy_lifecycle::traits::Component; /// Fragments are special - you can do something like the following in cases where you @@ -19,4 +20,8 @@ use alchemy_lifecycle::traits::Component; #[derive(Default, Debug)] pub struct Fragment; -impl Component for Fragment {} +impl Component for Fragment { + fn constructor(_key: ComponentKey) -> Fragment { + Fragment { } + } +} diff --git a/alchemy/src/components/text.rs b/alchemy/src/components/text.rs index 23f8a7c..1c20ed8 100644 --- a/alchemy/src/components/text.rs +++ b/alchemy/src/components/text.rs @@ -5,8 +5,9 @@ use std::sync::{Mutex}; -use alchemy_styles::styles::{Layout, Style}; +use alchemy_styles::styles::{Appearance, Layout}; +use alchemy_lifecycle::ComponentKey; use alchemy_lifecycle::error::Error; use alchemy_lifecycle::rsx::{Props, RSX}; use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType}; @@ -23,19 +24,11 @@ use alchemy_cocoa::text::{Text as PlatformTextBridge}; /// /// ``` pub struct Text { + key: ComponentKey, text: String, bridge: Mutex } -impl Default for Text { - fn default() -> Text { - Text { - text: "".into(), - bridge: Mutex::new(PlatformTextBridge::new()) - } - } -} - impl Text { // This is very naive for now, but it's fine - we probably // want to do some fun stuff here later with stylized text @@ -55,6 +48,14 @@ impl Text { } impl Component for Text { + fn constructor(key: ComponentKey) -> Text { + Text { + key: key, + text: "".into(), + bridge: Mutex::new(PlatformTextBridge::new()) + } + } + fn has_native_backing_node(&self) -> bool { true } fn borrow_native_backing_node(&self) -> Option { @@ -66,9 +67,9 @@ impl Component for Text { // Panic might not be right here, but eh, should probably do something. fn append_child_component(&self, _component: &Component) {} - fn apply_styles(&self, layout: &Layout, style: &Style) { + fn apply_styles(&self, appearance: &Appearance, layout: &Layout) { let mut bridge = self.bridge.lock().unwrap(); - bridge.apply_styles(layout, style); + bridge.apply_styles(appearance, layout); } fn component_did_mount(&mut self, props: &Props) { diff --git a/alchemy/src/components/view.rs b/alchemy/src/components/view.rs index f572ada..af122df 100644 --- a/alchemy/src/components/view.rs +++ b/alchemy/src/components/view.rs @@ -3,10 +3,11 @@ //! hence why they're all (somewhat annoyingly, but lovingly) re-implemented //! as bridges. -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::Mutex; -use alchemy_styles::{Layout, Style, StylesList}; +use alchemy_styles::{Appearance, Layout, StylesList}; +use alchemy_lifecycle::ComponentKey; use alchemy_lifecycle::error::Error; use alchemy_lifecycle::rsx::{Props, RSX}; use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType}; @@ -33,6 +34,10 @@ impl Default for View { } impl Component for View { + fn constructor(_key: ComponentKey) -> View { + View(Mutex::new(PlatformViewBridge::new())) + } + fn has_native_backing_node(&self) -> bool { true } fn borrow_native_backing_node(&self) -> Option { @@ -47,17 +52,17 @@ impl Component for View { } } - fn apply_styles(&self, layout: &Layout, style: &Style) { + fn apply_styles(&self, appearance: &Appearance, layout: &Layout) { let mut bridge = self.0.lock().unwrap(); - bridge.apply_styles(layout, style); + bridge.apply_styles(appearance, layout); } fn render(&self, props: &Props) -> Result { - Ok(RSX::node("Fragment", || Arc::new(RwLock::new(Fragment::default())), Props { + Ok(RSX::node("Fragment", |key| Box::new(Fragment::constructor(key)), Props { attributes: std::collections::HashMap::new(), key: "".into(), styles: StylesList::new(), - children: props.children.clone() - })) + children: vec![] + }, props.children.clone())) } } diff --git a/alchemy/src/lib.rs b/alchemy/src/lib.rs index 3152c39..1591c17 100644 --- a/alchemy/src/lib.rs +++ b/alchemy/src/lib.rs @@ -9,6 +9,7 @@ use std::sync::Arc; pub use lazy_static::lazy_static; use proc_macro_hack::proc_macro_hack; +pub use alchemy_lifecycle::ComponentKey; pub use alchemy_lifecycle::traits::{ AppDelegate, Component, WindowDelegate }; diff --git a/alchemy/src/window/window.rs b/alchemy/src/window/window.rs index b22c781..4412e59 100644 --- a/alchemy/src/window/window.rs +++ b/alchemy/src/window/window.rs @@ -1,15 +1,11 @@ //! Implements the Window API. It attempts to provide a nice, common interface across //! per-platform Window APIs. -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex}; -use alchemy_lifecycle::{Uuid, RENDER_ENGINE}; -use alchemy_lifecycle::rsx::{Props, RSX}; -use alchemy_lifecycle::traits::{Component, WindowDelegate}; - -use alchemy_styles::number::Number; -use alchemy_styles::geometry::Size; -use alchemy_styles::styles::{Style, Dimension}; +use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE}; +use alchemy_lifecycle::rsx::RSX; +use alchemy_lifecycle::traits::WindowDelegate; use crate::{App, SHARED_APP}; use crate::components::View; @@ -25,7 +21,7 @@ pub struct AppWindow { pub title: String, pub bridge: PlatformWindowBridge, pub delegate: Box, - pub render_key: Uuid + pub render_key: ComponentKey } impl AppWindow { @@ -74,7 +70,10 @@ impl Window { let view = View::default(); let shared_app_ptr: *const App = &**SHARED_APP; let bridge = PlatformWindowBridge::new(window_id, title, dimensions, &view, shared_app_ptr); - let key = RENDER_ENGINE.register_root_component(view); + let key = match RENDER_ENGINE.register_root_component(view) { + Ok(key) => key, + Err(_e) => { panic!("Uhhhh this really messed up"); } + }; Window(Arc::new(Mutex::new(AppWindow { id: window_id, diff --git a/cocoa/src/text.rs b/cocoa/src/text.rs index 64df5f6..30261f4 100644 --- a/cocoa/src/text.rs +++ b/cocoa/src/text.rs @@ -13,9 +13,7 @@ use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString}; use crate::color::IntoNSColor; -use alchemy_styles::color::Color; -use alchemy_styles::styles::Style; -use alchemy_styles::result::Layout; +use alchemy_styles::{Color, Layout, Appearance}; use alchemy_lifecycle::traits::PlatformSpecificNodeType; @@ -74,15 +72,15 @@ impl Text { /// Given a `&Style`, will set the frame, background color, borders and so forth. It then /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this /// view. - pub fn apply_styles(&mut self, layout: &Layout, style: &Style) { + pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) { unsafe { let rect = NSRect::new( NSPoint::new(layout.location.x.into(), layout.location.y.into()), NSSize::new(layout.size.width.into(), layout.size.height.into()) ); - self.background_color = style.background_color.into_nscolor(); - self.text_color = style.text_color.into_nscolor(); + self.background_color = appearance.background_color.into_nscolor(); + self.text_color = appearance.text_color.into_nscolor(); msg_send![&*self.inner_mut, setFrame:rect]; msg_send![&*self.inner_mut, setBackgroundColor:&*self.background_color]; diff --git a/cocoa/src/view.rs b/cocoa/src/view.rs index 58899e8..5b123cb 100644 --- a/cocoa/src/view.rs +++ b/cocoa/src/view.rs @@ -13,9 +13,7 @@ use cocoa::foundation::{NSRect, NSPoint, NSSize}; use crate::color::IntoNSColor; -use alchemy_styles::color::Color; -use alchemy_styles::styles::Style; -use alchemy_styles::result::Layout; +use alchemy_styles::{Appearance, Color, Layout}; use alchemy_lifecycle::traits::PlatformSpecificNodeType; @@ -70,14 +68,14 @@ impl View { /// Given a `&Style`, will set the frame, background color, borders and so forth. It then /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this /// view. - pub fn apply_styles(&mut self, layout: &Layout, style: &Style) { + pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) { unsafe { let rect = NSRect::new( NSPoint::new(layout.location.x.into(), layout.location.y.into()), NSSize::new(layout.size.width.into(), layout.size.height.into()) ); - self.background_color = style.background_color.into_nscolor(); + self.background_color = appearance.background_color.into_nscolor(); self.inner_mut.set_ivar(BACKGROUND_COLOR, &*self.background_color); msg_send![&*self.inner_mut, setFrame:rect]; diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 96a5ec1..f0b8fc1 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -8,7 +8,7 @@ /// @created March 26th, 2019 use alchemy::{ - AppDelegate, Component, Fragment, Props, Error, rsx, RSX, styles, + AppDelegate, Component, ComponentKey, Fragment, Props, Error, rsx, RSX, styles, Text, View, Window, WindowDelegate }; @@ -26,6 +26,10 @@ impl AppDelegate for AppState { pub struct Banner; impl Component for Banner { + fn constructor(_key: ComponentKey) -> Banner { + Banner {} + } + fn render(&self, props: &Props) -> Result { Ok(rsx! { diff --git a/lifecycle/Cargo.toml b/lifecycle/Cargo.toml index e178598..a4694db 100644 --- a/lifecycle/Cargo.toml +++ b/lifecycle/Cargo.toml @@ -17,4 +17,3 @@ alchemy-styles = { version = "0.1", path = "../styles" } objc = { version = "0.2.6", optional = true } objc_id = { version = "0.1.1", optional = true } serde_json = "1" -uuid = { version = "0.7", features = ["v4"] } diff --git a/lifecycle/src/lib.rs b/lifecycle/src/lib.rs index f2d3a60..8fdc032 100644 --- a/lifecycle/src/lib.rs +++ b/lifecycle/src/lib.rs @@ -11,7 +11,7 @@ //! This crate also includes the diffing and patching system for the widget tree - //! it needs to live with the `Component` lifecycle to enable state updating. -pub use uuid::Uuid; +pub use std::sync::Arc; use alchemy_styles::lazy_static; @@ -21,6 +21,7 @@ pub mod traits; mod reconciler; use reconciler::RenderEngine; +pub use reconciler::key::ComponentKey; lazy_static! { pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new(); diff --git a/lifecycle/src/reconciler/error.rs b/lifecycle/src/reconciler/error.rs index 8c2bbc4..ea72825 100644 --- a/lifecycle/src/reconciler/error.rs +++ b/lifecycle/src/reconciler/error.rs @@ -2,29 +2,33 @@ //! run. These are mostly internal to the rendering engine itself, but could potentially //! show up elsewhere. -use std::fmt; -use std::error::Error; +use core::any::Any; +use crate::reconciler::key::ComponentKey; + +#[derive(Debug)] pub enum RenderEngineError { - InvalidKeyError + InvalidKey, + InvalidRootComponent, + InvalidComponentKey(ComponentKey) } -impl fmt::Display for RenderEngineError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - RenderEngineError::InvalidKeyError => write!(f, "An invalid key was passed to the render engine.") +impl std::fmt::Display for RenderEngineError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + RenderEngineError::InvalidComponentKey(ref node) => write!(f, "Invalid component key {:?}", node), + RenderEngineError::InvalidRootComponent => write!(f, "Invalid component type! Root nodes must be a natively backed node."), + RenderEngineError::InvalidKey => write!(f, "An invalid key was passed to the render engine.") } } } -impl fmt::Debug for RenderEngineError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - RenderEngineError::InvalidKeyError => write!(f, "An invalid key was passed to the render engine: {{ file: {}, line: {} }}", file!(), line!()) +impl std::error::Error for RenderEngineError { + fn description(&self) -> &str { + match *self { + RenderEngineError::InvalidComponentKey(_) => "The key is not part of the component storage instance", + RenderEngineError::InvalidRootComponent => "The root component must be a natively backed Component instance.", + RenderEngineError::InvalidKey => "An invalid key was passed to the render engine." } } } - -impl Error for RenderEngineError { - -} diff --git a/lifecycle/src/reconciler/key.rs b/lifecycle/src/reconciler/key.rs new file mode 100644 index 0000000..d348ec8 --- /dev/null +++ b/lifecycle/src/reconciler/key.rs @@ -0,0 +1,51 @@ +//! Implements an auto-incrementing ID for Component instances. + +use std::sync::Mutex; + +use alchemy_styles::lazy_static; + +lazy_static! { + /// Global stretch instance id allocator. + pub(crate) static ref INSTANCE_ALLOCATOR: Mutex = Mutex::new(Allocator::new()); +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct Id { + id: u32 +} + +pub(crate) struct Allocator { + new_id: u32 +} + +impl Allocator { + pub fn new() -> Self { + Allocator { new_id: 1 } + } + + pub fn allocate(&mut self) -> Id { + let id = self.new_id; + self.new_id += 1; + Id { id: id } + } +} + +/// Used as a key for Component storage. Component instances receive these +/// in their constructor methods, and should retain them as a tool to update their +/// state. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct ComponentKey { + pub(crate) instance: Id, + pub(crate) local: Id, +} + +impl ComponentKey { + /// A placeholder value, used purely for ensuring the diffing algorithm remains + /// readable by reducing some unwrapping hell. + pub fn placeholder() -> ComponentKey { + ComponentKey { + instance: Id { id: 0 }, + local: Id { id: 0 } + } + } +} diff --git a/lifecycle/src/reconciler/mod.rs b/lifecycle/src/reconciler/mod.rs index 344d0e5..0fd48b1 100644 --- a/lifecycle/src/reconciler/mod.rs +++ b/lifecycle/src/reconciler/mod.rs @@ -6,71 +6,73 @@ use std::collections::HashMap; use std::error::Error; use std::mem::{discriminant, swap}; -use uuid::Uuid; - -use alchemy_styles::{Stretch, THEME_ENGINE}; -use alchemy_styles::styles::{Style, Dimension}; -use alchemy_styles::number::Number; -use alchemy_styles::geometry::Size; +use alchemy_styles::THEME_ENGINE; +use alchemy_styles::styles::{Appearance,Dimension, Number, Size, Style}; use crate::traits::Component; use crate::rsx::{Props, RSX, VirtualNode}; -mod error; +use alchemy_styles::stretch::node::{Node as StyleNode, Stretch as LayoutStore}; + +pub mod key; +use key::ComponentKey; + +pub mod storage; +use storage::ComponentStore; + +pub mod error; use error::RenderEngineError; // This is never actually created, it's just to satisfy the fact that View // is defined in the core crate, which we can't import here without creating a // circular dependency. struct StubView; -impl Component for StubView {} +impl Component for StubView { + fn constructor(key: ComponentKey) -> StubView { + StubView {} + } +} pub struct RenderEngine { - pending_state_updates: Mutex>, - trees: Mutex> + queued_state_updates: Mutex>, + components: Mutex, + layouts: Mutex } impl RenderEngine { pub(crate) fn new() -> RenderEngine { RenderEngine { - pending_state_updates: Mutex::new(vec![]), - trees: Mutex::new(HashMap::new()) + queued_state_updates: Mutex::new(vec![]), + components: Mutex::new(ComponentStore::new()), + layouts: Mutex::new(LayoutStore::new()) } } + // pub fn queue_update_for(&self, component_ptr: usize, updater: Box Component + Send + Sync + 'static>) { + // } + /// `Window`'s (or anything "root" in nature) need to register with the /// reconciler for things like setState to work properly. When they do so, /// they get a key back. When they want to instruct the global `RenderEngine` /// to re-render or update their tree, they pass that key and whatever the new tree /// should be. - pub fn register_root_component(&self, instance: C) -> Uuid { - let mut root_node = RSX::node("root", || { - Arc::new(RwLock::new(StubView {})) - }, { - let mut props = Props::default(); - props.styles = "root".into(); - props - }); - - let mut stretch = Stretch::new(); - if let RSX::VirtualNode(root) = &mut root_node { - let mut style = Style::default(); - style.size = Size { - width: Dimension::Points(600.), - height: Dimension::Points(600.) - }; - - root.instance = Some(Arc::new(RwLock::new(instance))); - root.layout_node = match stretch.new_node(style, vec![]) { - Ok(node) => Some(node), - Err(e) => { None } - } + pub fn register_root_component(&self, instance: C) -> Result> { + // Conceivably, this doesn't NEED to be a thing... but for now it is. If you've stumbled + // upon here, wayward traveler, in need of a non-native-root-component, please open an + // issue to discuss. :) + if !instance.has_native_backing_node() { + return Err(Box::new(RenderEngineError::InvalidRootComponent {})); } - - let key = Uuid::new_v4(); - let mut trees = self.trees.lock().unwrap(); - trees.insert(key, (root_node, stretch)); - key + + let layout_key = { + let style = Style::default(); + let mut layouts = self.layouts.lock().unwrap(); + Some(layouts.new_node(style, vec![])?) + }; + + let mut components = self.components.lock().unwrap(); + let component_key = components.new_node(instance, layout_key, vec![])?; + Ok(component_key) } /// Given a key, and a new root tree, will diff the tree structure (position, components, @@ -80,36 +82,37 @@ impl RenderEngine { /// the new tree before discarding the old tree. /// /// This calls the necessary component lifecycles per-component. - pub fn diff_and_render_root(&self, key: &Uuid, child: RSX) -> Result<(), Box> { + pub fn diff_and_render_root(&self, key: &ComponentKey, child: RSX) -> Result<(), Box> { + /* let mut new_root = RSX::node("root", || { - Arc::new(RwLock::new(StubView {})) + Box::new(StubView {}) }, { let mut props = Props::default(); props.styles = "root".into(); props + }, match child { + RSX::VirtualNode(mut child) => { + let mut children = vec![]; + + if child.tag == "Fragment" { + children.append(&mut child.children); + } else { + children.push(RSX::VirtualNode(child)); + } + + children + }, + + // If it's an RSX::None or RSX::VirtualText, we'll just do nothing, as... one + // requires nothing, and one isn't supported unless it's inside a tag, and + // we know the root element isn't a if we're here. + _ => vec![] }); - // If it's an RSX::None, or a RSX::VirtualText, we do nothing, as... one - // requires nothing, and one isn't supported unless it's inside a tag. - if let RSX::VirtualNode(mut child) = child { - if let RSX::VirtualNode(new_root_node) = &mut new_root { - if child.tag == "Fragment" { - new_root_node.children.append(&mut child.children); - } else { - new_root_node.children.push(RSX::VirtualNode(child)); - } - } - } - let mut trees = self.trees.lock().unwrap(); - let (old_root, mut stretch) = trees.remove(key).ok_or_else(|| RenderEngineError::InvalidKeyError {})?; + let (old_root, mut stretch) = trees.remove(key).ok_or_else(|| RenderEngineError::InvalidKey {})?; let patched_new_root = diff_and_patch_trees(old_root, new_root, &mut stretch, 0)?; - /*let window_size = Size { - width: Number::Defined(600.), - height: Number::Defined(600.) - };*/ - if let RSX::VirtualNode(node) = &patched_new_root { if let Some(layout_node) = &node.layout_node { stretch.compute_layout(*layout_node, Size { @@ -120,375 +123,7 @@ impl RenderEngine { } } - trees.insert(*key, (patched_new_root, stretch)); + trees.insert(*key, (patched_new_root, stretch));*/ Ok(()) } } - -/// Given two node trees, will compare, diff, and apply changes in a recursive fashion. -pub fn diff_and_patch_trees(old: RSX, new: RSX, stretch: &mut Stretch, depth: usize) -> Result> { - // Whether we replace or not depends on a few things. If we're working on two different node - // types (text vs node), if the node tags are different, or if the key (in some cases) is - // different. - let is_replace = match discriminant(&old) != discriminant(&new) { - true => true, - false => { - if let (RSX::VirtualNode(old_element), RSX::VirtualNode(new_element)) = (&old, &new) { - old_element.tag != new_element.tag - } else { - false - } - } - }; - - match (old, new) { - (RSX::VirtualNode(mut old_element), RSX::VirtualNode(mut new_element)) => { - if is_replace { - // Do something different in here... - //let mut mounted = mount_component_tree(new_tree); - // unmount_component_tree(old_tree); - // Swap them in memory, copy any layout + etc as necessary - // append, link layout nodes, etc - return Ok(RSX::VirtualNode(new_element)); - } - - // If we get here, it's an update to an existing element. This means a cached Component - // instance might exist, and we want to keep it around and reuse it if possible. Let's check - // and do some swapping action to handle it. - // - // These need to move to the new tree, since we always keep 'em. We also wanna cache a - // reference to our content view. - swap(&mut old_element.instance, &mut new_element.instance); - swap(&mut old_element.layout_node, &mut new_element.layout_node); - - // For the root tag, which is usually the content view of the Window, we don't want to - // perform the whole render/component lifecycle routine. It's a special case element, - // where the Window (or other root element) patches in the output of a render method - // specific to that object. An easy way to handle this is the depth parameter - in - // fact, it's why it exists. Depth 0 should be considered special and skip the - // rendering phase. - if depth > 0 { - // diff props, set new props - // instance.get_derived_state_from_props() - - if let Some(instance) = &mut new_element.instance { - // diff props, set new props - // instance.get_derived_state_from_props() - - //if instance.should_component_update() { - // instance.render() { } - // instance.get_snapshot_before_update() - // apply changes - //instance.component_did_update(); - //} else { - // If should_component_update() returns false, then we want to take the - // children from the old node, move them to the new node, and recurse into - // that tree instead. - //} - } - } - - // This None path should never be hit, we just need to use a rather verbose pattern - // here. It's unsightly, I know. - let is_native_backed = match &new_element.instance { - Some(instance) => { - let lock = instance.read().unwrap(); - lock.has_native_backing_node() - }, - None => false - }; - - // There is probably a nicer way to do this that doesn't allocate as much, and I'm open - // to revisiting it. Platforms outside of Rust allocate far more than this, though, and - // in general the whole "avoid allocations" thing is fear mongering IMO. Revisit later. - // - // tl;dr we allocate a new Vec that's equal to the length of our new children, and - // then swap it on our (owned) node... it's safe, as we own it. This allows us to - // iterate and dodge the borrow checker. - let mut children: Vec = Vec::with_capacity(new_element.children.len()); - std::mem::swap(&mut children, &mut new_element.children); - - old_element.children.reverse(); - for new_child_tree in children { - match old_element.children.pop() { - // A matching child in the old tree means we can recurse right back into the - // update phase. - Some(old_child_tree) => { - let updated = diff_and_patch_trees(old_child_tree, new_child_tree, stretch, depth + 1)?; - new_element.children.push(updated); - }, - - // If there's no matching child in the old tree, this is a new Component and we - // can feel free to mount/connect it. - None => { - if let RSX::VirtualNode(new_el) = new_child_tree { - let mut mounted = mount_component_tree(new_el, stretch)?; - - // Link the layout nodes, handle the appending, etc. - // This happens inside mount_component_tree, but that only handles that - // specific tree. Think of this step as joining two trees in the graph. - if is_native_backed { - find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?; - } - - new_element.children.push(RSX::VirtualNode(mounted)); - } - } - } - } - - // Trim the fat - more children in the old tree than the new one means we gonna be - // droppin'. We need to send unmount lifecycle calls to these, and break any links we - // have (e.g, layout, backing view tree, etc). - loop { - match old_element.children.pop() { - Some(child) => { - if let RSX::VirtualNode(mut old_child) = child { - unmount_component_tree(&mut old_child, stretch)?; - } - }, - - None => { break; } - } - } - - Ok(RSX::VirtualNode(new_element)) - } - - // We're comparing two text nodes. Realistically... this requires nothing from us, because - // the tag (or any other component instance, if it desires) should handle it. - (RSX::VirtualText(_), RSX::VirtualText(text)) => { - Ok(RSX::VirtualText(text)) - } - - // These are all edge cases that shouldn't get hit. In particular: - // - // - VirtualText being replaced by VirtualNode should be caught by the discriminant check - // in the beginning of this function, which registers as a replace/mount. - // - VirtualNode being replaced with VirtualText is the same scenario as above. - // - The (RSX::None, ...) checks are to shut the compiler up; we never store the RSX::None - // return value, as it's mostly a value in place for return signature usability. Thus, - // these should quite literally never register. - // - // This goes without saying, but: never ever store RSX::None lol - (RSX::VirtualText(_), RSX::VirtualNode(_)) | (RSX::VirtualNode(_), RSX::VirtualText(_)) | - (RSX::None, RSX::VirtualText(_)) | (RSX::None, RSX::VirtualNode(_)) | (RSX::None, RSX::None) | - (RSX::VirtualNode(_), RSX::None) | (RSX::VirtualText(_), RSX::None) => { - unreachable!("Unequal variant discriminants should already have been handled."); - } - } -} - -/// Walks the tree and applies styles. This happens after a layout computation, typically. -pub(crate) fn walk_and_apply_styles(node: &VirtualNode, layout_manager: &mut Stretch) -> Result<(), Box> { - if let (Some(layout_node), Some(instance)) = (node.layout_node, &node.instance) { - let component = instance.write().unwrap(); - component.apply_styles( - layout_manager.layout(layout_node)?, - layout_manager.style(layout_node)? - ); - } - - for child in &node.children { - if let RSX::VirtualNode(child_node) = child { - walk_and_apply_styles(child_node, layout_manager)?; - } - } - - Ok(()) -} - -/// Given a tree, will walk the branches until it finds the next root nodes to connect. -/// While this sounds slow, in practice it rarely has to go far in any direction. -fn find_and_link_layout_nodes(parent_node: &mut VirtualNode, child_tree: &mut VirtualNode, stretch: &mut Stretch) -> Result<(), Box> { - if let (Some(parent_instance), Some(child_instance)) = (&mut parent_node.instance, &mut child_tree.instance) { - if let (Some(parent_layout_node), Some(child_layout_node)) = (&parent_node.layout_node, &child_tree.layout_node) { - stretch.add_child(*parent_layout_node, *child_layout_node)?; - - let parent_component = parent_instance.write().unwrap(); - let child_component = child_instance.read().unwrap(); - parent_component.append_child_component(&*child_component); - - return Ok(()); - } - } - - for child in child_tree.children.iter_mut() { - if let RSX::VirtualNode(child_tree) = child { - find_and_link_layout_nodes(parent_node, child_tree, stretch)?; - } - } - - Ok(()) -} - -/// Recursively constructs a Component tree. This entails adding it to the backing -/// view tree, firing various lifecycle methods, and ensuring that nodes for layout -/// passes are configured. -/// -/// In the future, this would ideally return patch-sets for the backing layer or something. -fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> Result> { - let instance = (new_element.create_component_fn)(); - - let mut is_native_backed = false; - - let rendered = { - let component = instance.read().unwrap(); - // instance.get_derived_state_from_props(props) - - is_native_backed = component.has_native_backing_node(); - - if is_native_backed { - let mut style = Style::default(); - THEME_ENGINE.configure_style_for_keys(&new_element.props.styles, &mut style); - - let layout_node = stretch.new_node(style, vec![])?; - new_element.layout_node = Some(layout_node); - } - - component.render(&new_element.props) - }; - - // instance.get_snapshot_before_update() - - new_element.instance = Some(instance); - - let mut children = match rendered { - Ok(opt) => match opt { - RSX::VirtualNode(child) => { - let mut children = vec![]; - - // We want to support Components being able to return arbitrary iteratable - // elements, but... well, it's not quite that simple. Thus we'll offer a - // tag similar to what React does, which just hoists the children out of it and - // discards the rest. - if child.tag == "Fragment" { - for child_node in child.props.children { - if let RSX::VirtualNode(node) = child_node { - let mut mounted = mount_component_tree(node, stretch)?; - - if is_native_backed { - find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?; - } - - children.push(RSX::VirtualNode(mounted)); - } - } - } else { - let mut mounted = mount_component_tree(child, stretch)?; - - if is_native_backed { - find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?; - } - - children.push(RSX::VirtualNode(mounted)); - } - - children - }, - - // If a Component renders nothing (or this is a Text string, which we do nothing with) - // that's totally fine. - _ => vec![] - }, - - Err(e) => { - // return an RSX::VirtualNode(ErrorComponentView) or something? - /* instance.get_derived_state_from_error(e) */ - // render error state or something I guess? - /* instance.component_did_catch(e, info) */ - eprintln!("Error rendering: {}", e); - vec![] - } - }; - - new_element.children.append(&mut children); - - if let Some(instance) = &mut new_element.instance { - let mut component = instance.write().unwrap(); - component.component_did_mount(&new_element.props); - } - - Ok(new_element) -} - -/// Walk the tree and unmount Component instances. This means we fire the -/// `component_will_unmount` hook and remove the node(s) from their respective trees. -/// -/// This fires the hooks from a recursive inward-out pattern; that is, the deepest nodes in the tree -/// are the first to go, ensuring that everything is properly cleaned up. -fn unmount_component_tree(old_element: &mut VirtualNode, stretch: &mut Stretch) -> Result<(), Box> { - // We only need to recurse on VirtualNodes. Text and so on will automagically drop - // because we don't support freeform text, it has to be inside a at all times. - for child in old_element.children.iter_mut() { - if let RSX::VirtualNode(child_element) = child { - unmount_component_tree(child_element, stretch)?; - } - } - - // Fire the appropriate lifecycle method and then remove the node from the underlying - // graph. Remember that a Component can actually not necessarily have a native backing - // node, hence our necessary check. - if let Some(old_component) = &mut old_element.instance { - let mut component = old_component.write().unwrap(); - component.component_will_unmount(&old_element.props); - - /*if let Some(view) = old_component.get_native_backing_node() { - if let Some(native_view) = replace_native_view { - //replace_view(&view, &native_view); - } else { - //remove_view(&view); - } - }*/ - } - - // Rather than try to keep track of parent/child stuff for removal... just obliterate it, - // the underlying library does a good job of killing the links anyway. - if let Some(layout_node) = &mut old_element.layout_node { - stretch.set_children(*layout_node, vec![])?; - } - - Ok(()) -} - -/*let mut add_attributes: HashMap<&str, &str> = HashMap::new(); -let mut remove_attributes: Vec<&str> = vec![]; - -// TODO: -> split out into func -for (new_attr_name, new_attr_val) in new_element.attrs.iter() { - match old_element.attrs.get(new_attr_name) { - Some(ref old_attr_val) => { - if old_attr_val != &new_attr_val { - add_attributes.insert(new_attr_name, new_attr_val); - } - } - None => { - add_attributes.insert(new_attr_name, new_attr_val); - } - }; -} - -// TODO: -> split out into func -for (old_attr_name, old_attr_val) in old_element.attrs.iter() { - if add_attributes.get(&old_attr_name[..]).is_some() { - continue; - }; - - match new_element.attrs.get(old_attr_name) { - Some(ref new_attr_val) => { - if new_attr_val != &old_attr_val { - remove_attributes.push(old_attr_name); - } - } - None => { - remove_attributes.push(old_attr_name); - } - }; -} - -if add_attributes.len() > 0 { - patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes)); -} -if remove_attributes.len() > 0 { - patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes)); -}*/ diff --git a/lifecycle/src/reconciler/storage.rs b/lifecycle/src/reconciler/storage.rs new file mode 100644 index 0000000..3138f13 --- /dev/null +++ b/lifecycle/src/reconciler/storage.rs @@ -0,0 +1,163 @@ +//! Implements storage for Component instances, in a way that allows us to +//! short-circuit the rendering process so we don't have to re-scan entire +//! tree structures when updating state. + +use std::collections::HashMap; + +pub use alchemy_styles::Appearance; +use alchemy_styles::stretch::node::{Node as LayoutNode}; + +use crate::reconciler::error::{RenderEngineError as Error}; +use crate::reconciler::key::{Allocator, Id, INSTANCE_ALLOCATOR, ComponentKey}; +use crate::traits::Component; + +/// This is a clone of a structure you'll also find over in stretch. We do this separately +/// here for two reasons. +/// +/// - First, a Component may have children that don't require styles or layout passes. These nodes +/// should not have `Style` or `Appearance` nodes created, but we do need the correct parent/child +/// relationships in place. +/// - The `Storage` pieces of stretch are realistically an implementation detail that we shouldn't +/// rely on. +struct Storage(HashMap); + +impl Storage { + pub fn new() -> Self { + Storage(HashMap::new()) + } + + pub fn get(&self, key: ComponentKey) -> Result<&T, Error> { + match self.0.get(&key) { + Some(v) => Ok(v), + None => Err(Error::InvalidComponentKey(key)), + } + } + + pub fn get_mut(&mut self, key: ComponentKey) -> Result<&mut T, Error> { + match self.0.get_mut(&key) { + Some(v) => Ok(v), + None => Err(Error::InvalidComponentKey(key)), + } + } + + pub fn insert(&mut self, key: ComponentKey, value: T) -> Option { + self.0.insert(key, value) + } +} + +impl std::ops::Index<&ComponentKey> for Storage { + type Output = T; + + fn index(&self, idx: &ComponentKey) -> &T { + &(self.0)[idx] + } +} + +pub struct Instance { + component: Box, + appearance: Appearance, + layout: Option +} + +pub(crate) struct ComponentStore { + id: Id, + nodes: Allocator, + components: Storage, + parents: Storage>, + children: Storage> +} + +impl ComponentStore { + pub fn new() -> Self { + ComponentStore { + id: INSTANCE_ALLOCATOR.lock().unwrap().allocate(), + nodes: Allocator::new(), + components: Storage::new(), + parents: Storage::new(), + children: Storage::new() + } + } + + fn allocate_node(&mut self) -> ComponentKey { + let local = self.nodes.allocate(); + ComponentKey { instance: self.id, local } + } + + pub fn new_node(&mut self, component: C, layout_key: Option, children: Vec) -> Result { + let key = self.allocate_node(); + + for child in &children { + self.parents.get_mut(*child)?.push(key); + } + + self.components.insert(key, Instance { + component: Box::new(component), + appearance: Appearance::default(), + layout: layout_key + }); + + self.parents.insert(key, Vec::with_capacity(1)); + self.children.insert(key, children); + + Ok(key) + } + + pub fn add_child(&mut self, key: ComponentKey, child: ComponentKey) -> Result<(), Error> { + self.parents.get_mut(child)?.push(key); + self.children.get_mut(key)?.push(child); + Ok(()) + } + + pub fn set_children(&mut self, key: ComponentKey, children: Vec) -> Result<(), Error> { + // Remove node as parent from all its current children. + for child in self.children.get(key)? { + self.parents.get_mut(*child)?.retain(|p| *p != key); + } + + *self.children.get_mut(key)? = Vec::with_capacity(children.len()); + + // Build up relation node <-> child + for child in children { + self.parents.get_mut(child)?.push(key); + self.children.get_mut(key)?.push(child); + } + + Ok(()) + } + + pub fn remove_child(&mut self, key: ComponentKey, child: ComponentKey) -> Result { + match self.children(key)?.iter().position(|n| *n == child) { + Some(index) => self.remove_child_at_index(key, index), + None => Err(Error::InvalidComponentKey(child)), + } + } + + pub fn remove_child_at_index(&mut self, key: ComponentKey, index: usize) -> Result { + let child = self.children.get_mut(key)?.remove(index); + self.parents.get_mut(child)?.retain(|p| *p != key); + Ok(child) + } + + pub fn replace_child_at_index(&mut self, key: ComponentKey, index: usize, child: ComponentKey) -> Result { + self.parents.get_mut(child)?.push(key); + let old_child = std::mem::replace(&mut self.children.get_mut(key)?[index], child); + self.parents.get_mut(old_child)?.retain(|p| *p != key); + Ok(old_child) + } + + pub fn children(&self, key: ComponentKey) -> Result, Error> { + self.children.get(key).map(Clone::clone) + } + + pub fn child_count(&self, key: ComponentKey) -> Result { + self.children.get(key).map(Vec::len) + } + + pub fn get(&self, key: ComponentKey) -> Result<&Instance, Error> { + self.components.get(key) + } + + pub fn get_mut(&mut self, key: ComponentKey) -> Result<&mut Instance, Error> { + self.components.get_mut(key) + } +} diff --git a/lifecycle/src/rsx/mod.rs b/lifecycle/src/rsx/mod.rs index e85338f..ad3bcf0 100644 --- a/lifecycle/src/rsx/mod.rs +++ b/lifecycle/src/rsx/mod.rs @@ -3,7 +3,6 @@ //! uses these to build and alter UI; they're typically returned from `render()` //! methods. -use std::sync::{Arc, RwLock}; use std::fmt::{Debug, Display}; mod virtual_node; @@ -15,6 +14,7 @@ pub use virtual_text::VirtualText; mod props; pub use props::Props; +use crate::reconciler::key::ComponentKey; use crate::traits::Component; /// An enum representing the types of nodes that the @@ -31,16 +31,15 @@ impl RSX { /// this yourself; the `rsx! {}` macro handles this for you. pub fn node( tag: &'static str, - create_fn: fn() -> Arc>, - props: Props + create_fn: fn(key: ComponentKey) -> Box, + props: Props, + children: Vec ) -> RSX { RSX::VirtualNode(VirtualNode { tag: tag, create_component_fn: create_fn, - instance: None, - layout_node: None, - props: props, - children: vec![] + props: Some(props), + children: children }) } diff --git a/lifecycle/src/rsx/props.rs b/lifecycle/src/rsx/props.rs index 580f802..8d297c9 100644 --- a/lifecycle/src/rsx/props.rs +++ b/lifecycle/src/rsx/props.rs @@ -39,6 +39,15 @@ pub struct Props { } impl Props { + pub fn new(key: String, styles: StylesList, attributes: HashMap<&'static str, AttributeType>) -> Props { + Props { + attributes: attributes, + children: vec![], + key: key, + styles: styles + } + } + /// Returns a Vec of RSX nodes, which are really just cloned pointers for the most part. pub fn children(&self) -> Vec { self.children.clone() diff --git a/lifecycle/src/rsx/virtual_node.rs b/lifecycle/src/rsx/virtual_node.rs index e8f3be0..df3ec57 100644 --- a/lifecycle/src/rsx/virtual_node.rs +++ b/lifecycle/src/rsx/virtual_node.rs @@ -1,17 +1,14 @@ //! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive //! structure. -use std::sync::{Arc, RwLock}; use std::fmt::{Display, Debug}; -use alchemy_styles::node::Node; - -use crate::traits::Component; +use crate::reconciler::key::ComponentKey; use crate::rsx::{RSX, Props}; +use crate::traits::Component; /// A VirtualNode is akin to an `Element` in React terms. Here, we provide a way -/// for lazy `Component` instantiation, along with storage for things like layout nodes, -/// properties, children and so on. +/// for lazy `Component` instantiation, properties, children and so on. #[derive(Clone)] pub struct VirtualNode { /// Used in debugging/printing/etc. @@ -19,22 +16,17 @@ pub struct VirtualNode { /// `Component` instances are created on-demand, if the reconciler deems it be so. This /// is a closure that should return an instance of the correct type. - pub create_component_fn: fn() -> Arc>, + pub create_component_fn: fn(key: ComponentKey) -> Box, - /// A cached component instance, which is transferred between trees. Since `Component` - /// instances are lazily created, this is an `Option`, and defaults to `None`. - pub instance: Option>>, - - /// A cached `Node` for computing `Layout` with `Stretch`. Some components may not have - /// a need for layout (e.g, if they don't have a backing node), and thus this is optional. + /// `Props`, which are to be passed to this `Component` at various lifecycle methods. Once + /// the reconciler takes ownership of this VirtualNode, these props are moved to a different + /// location - thus, you shouldn't rely on them for anything unless you specifically keep + /// ownership of a VirtualNode. /// - /// The reconciler will handle bridging tree structures as necessary. - pub layout_node: Option, + /// This aspect of functionality may be pulled in a later release if it causes too many issues. + pub props: Option, - /// `Props`, which are to be passed to this `Component` at various lifecycle methods. - pub props: Props, - - /// Computed children get stored here. + /// pub children: Vec } diff --git a/lifecycle/src/traits.rs b/lifecycle/src/traits.rs index c0384b8..0831560 100644 --- a/lifecycle/src/traits.rs +++ b/lifecycle/src/traits.rs @@ -1,11 +1,11 @@ //! Traits that are used in Alchemy. Alchemy implements a React-based Component //! lifecycle, coupled with a delegate pattern inspired by those found in AppKit/UIKit. -use std::sync::Arc; - -use alchemy_styles::styles::{Layout, Style}; +use alchemy_styles::styles::{Appearance, Layout}; +//use crate::RENDER_ENGINE; use crate::error::Error; +use crate::reconciler::key::ComponentKey; use crate::rsx::{RSX, Props}; /// A per-platform wrapped Pointer type, used for attaching views/widgets. @@ -16,6 +16,11 @@ pub type PlatformSpecificNodeType = objc_id::ShareId; #[cfg(not(feature = "cocoa"))] pub type PlatformSpecificNodeType = (); +/*fn update Box + Send + Sync + 'static>(component: &Component, updater: F) { + let component_ptr = component as *const C as usize; + RENDER_ENGINE.queue_update_for(component_ptr, Box::new(updater)); +}*/ + /// Each platform tends to have their own startup routine, their own runloop, and so on. /// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system /// level and allows the user to operate within the established framework per-system. @@ -75,6 +80,8 @@ pub trait State {} /// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting /// proposal for this, feel free to open an issue! pub trait Component: Send + Sync { + fn constructor(key: ComponentKey) -> Self where Self: Sized; + /// Indicates whether a Component instance carries a native backing node. If you return `true` /// from this, the reconciler will opt-in to the native backing layer. Returns `false` by /// default. @@ -89,16 +96,15 @@ pub trait Component: Send + Sync { /// If you implement a Native-backed component, you'll need to implement this. Given a /// `component`, you need to instruct the system how to replace it in the tree at your point. - fn replace_child_component(&self, _component: Arc) {} + fn replace_child_component(&self, _component: &Component) {} /// If you implement a Native-backed component, you'll need to implement this. Given a /// `component`, you need to instruct the system how to remove it from the tree at your point. - fn remove_child_component(&self, _component: Arc) {} + fn remove_child_component(&self, _component: &Component) {} - /// Given a computed `layout`, and an accompanying `Style` (which holds appearance-based - /// styles, like colors), this method should transform them into appropriate calls to the - /// backing native node. - fn apply_styles(&self, _layout: &Layout, _style: &Style) {} + /// Given a configured 'appearance' and computed `layout`, this method should transform them + /// into appropriate calls to the backing native node. + fn apply_styles(&self, _appearance: &Appearance, _layout: &Layout) {} /// Invoked right before calling the render method, both on the initial mount and on subsequent updates. /// It should return an object to update the state, or null to update nothing. diff --git a/macros/src/rsx.rs b/macros/src/rsx.rs index 1ea4818..8dbf15b 100644 --- a/macros/src/rsx.rs +++ b/macros/src/rsx.rs @@ -220,21 +220,16 @@ impl Element { let component_name = Literal::string(&typename.to_string()); Ok(quote!( - alchemy::RSX::node(#component_name, || { - std::sync::Arc::new(std::sync::RwLock::new(#typename::default())) - }, alchemy::Props { - attributes: { - let mut attributes = std::collections::HashMap::new(); - #attributes - attributes - }, - children: { - let mut children = vec![]; - #children - children - }, - key: "".into(), - styles: #styles + alchemy::RSX::node(#component_name, |key| { + Box::new(#typename::constructor(key)) + }, alchemy::Props::new("".into(), #styles, { + let mut attributes = std::collections::HashMap::new(); + #attributes + attributes + }), { + let mut children = vec![]; + #children + children }) )) } diff --git a/styles/src/engine.rs b/styles/src/engine.rs index 4b00481..da41ee5 100644 --- a/styles/src/engine.rs +++ b/styles/src/engine.rs @@ -17,8 +17,10 @@ use std::collections::HashMap; use toml; use serde::Deserialize; +use crate::stretch::style::Style; + use crate::StylesList; -use crate::styles::Style; +use crate::styles::Appearance; use crate::stylesheet::StyleSheet; static CONFIG_FILE_NAME: &str = "alchemy.toml"; @@ -85,13 +87,19 @@ impl ThemeEngine { /// Given a theme key, style keys, and a style, configures the style for layout /// and appearance. - pub fn configure_style_for_keys_in_theme(&self, theme: &str, keys: &StylesList, style: &mut Style) { + pub fn configure_style_for_keys_in_theme( + &self, + theme: &str, + keys: &StylesList, + style: &mut Style, + appearance: &mut Appearance + ) { let themes = self.themes.read().unwrap(); match themes.get(theme) { Some(theme) => { for key in &keys.0 { - theme.apply_styles(key, style); + theme.apply_styles(key, style, appearance); } }, @@ -102,8 +110,8 @@ impl ThemeEngine { } /// The same logic as `configure_style_for_keys_in_theme`, but defaults to the default theme. - pub fn configure_style_for_keys(&self, keys: &StylesList, style: &mut Style) { - self.configure_style_for_keys_in_theme("default", keys, style) + pub fn configure_styles_for_keys(&self, keys: &StylesList, style: &mut Style, appearance: &mut Appearance) { + self.configure_style_for_keys_in_theme("default", keys, style, appearance) } } diff --git a/styles/src/lib.rs b/styles/src/lib.rs index 1a0b361..71245a6 100644 --- a/styles/src/lib.rs +++ b/styles/src/lib.rs @@ -9,10 +9,6 @@ pub use lazy_static::lazy_static; #[cfg(feature="parser")] #[macro_use] pub extern crate cssparser; -mod stretch; -pub use stretch::{geometry, node, number, result, Stretch, Error}; -pub use stretch::result::Layout; - pub mod color; pub use color::Color; @@ -25,12 +21,15 @@ pub use spacedlist::SpacedList; mod spacedset; pub use spacedset::SpacedSet; +pub mod stretch; +pub use stretch::result::Layout; + mod style_keys; pub use style_keys::StyleKey; pub type StylesList = SpacedSet; pub mod styles; -pub use styles::{Style, Styles}; +pub use styles::{Appearance, Styles}; pub mod stylesheet; pub use stylesheet::StyleSheet; diff --git a/styles/src/stretch/algo.rs b/styles/src/stretch/algo.rs index e7ae038..8ef6948 100644 --- a/styles/src/stretch/algo.rs +++ b/styles/src/stretch/algo.rs @@ -1,14 +1,15 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + use core::any::Any; use core::f32; -use crate::node::{Node, Storage, Stretch}; -use crate::result; -use crate::styles::*; - -use crate::number::Number::*; -use crate::number::*; - -use crate::geometry::{Point, Rect, Size}; +use crate::stretch::node::{Node, Storage, Stretch}; +use crate::stretch::result; +use crate::stretch::style::*; +use crate::stretch::number::Number::*; +use crate::stretch::number::*; +use crate::stretch::geometry::{Point, Rect, Size}; #[derive(Debug, Clone)] pub struct ComputeResult { diff --git a/styles/src/stretch/geometry.rs b/styles/src/stretch/geometry.rs index a56755d..9984bbc 100644 --- a/styles/src/stretch/geometry.rs +++ b/styles/src/stretch/geometry.rs @@ -1,7 +1,10 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + use core::ops::Add; -use crate::number::Number; -use crate::styles as style; +use crate::stretch::number::Number; +use crate::stretch::style; #[derive(Debug, Copy, Clone, PartialEq)] pub struct Rect { diff --git a/styles/src/stretch/id.rs b/styles/src/stretch/id.rs index ce8cb3e..2f5af6f 100644 --- a/styles/src/stretch/id.rs +++ b/styles/src/stretch/id.rs @@ -1,6 +1,5 @@ -//! Identifier for a Node -//! -//! +///! This module is included while awaiting an upstream merge in stretch proper. +///! You should not rely on it, and consider it an implementation detail. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct Id { diff --git a/styles/src/stretch/mod.rs b/styles/src/stretch/mod.rs index b262030..4eedcf3 100644 --- a/styles/src/stretch/mod.rs +++ b/styles/src/stretch/mod.rs @@ -1,13 +1,15 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + pub mod geometry; pub mod node; pub mod number; pub mod result; +pub mod style; mod algo; mod id; -pub use crate::node::Stretch; - use core::any::Any; #[derive(Debug)] diff --git a/styles/src/stretch/node.rs b/styles/src/stretch/node.rs index 3e70ea0..52b9e46 100644 --- a/styles/src/stretch/node.rs +++ b/styles/src/stretch/node.rs @@ -1,3 +1,6 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + use core::any::Any; use std::collections::HashMap; @@ -6,12 +9,12 @@ use std::sync::Mutex; use lazy_static::lazy_static; -use crate::geometry::Size; +use crate::stretch::geometry::Size; use crate::stretch::id; -use crate::number::Number; -use crate::result::{Cache, Layout}; -use crate::styles::*; -use crate::Error; +use crate::stretch::number::Number; +use crate::stretch::result::{Cache, Layout}; +use crate::stretch::style::*; +use crate::stretch::Error; type MeasureFunc = Box) -> Result, Box> + Send + Sync + 'static>; diff --git a/styles/src/stretch/number.rs b/styles/src/stretch/number.rs index 1cccedd..5522d44 100644 --- a/styles/src/stretch/number.rs +++ b/styles/src/stretch/number.rs @@ -1,3 +1,6 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + use core::ops; #[derive(Copy, Clone, PartialEq, Debug)] diff --git a/styles/src/stretch/result.rs b/styles/src/stretch/result.rs index 6cd60e5..015a554 100644 --- a/styles/src/stretch/result.rs +++ b/styles/src/stretch/result.rs @@ -1,6 +1,9 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + use crate::stretch::algo::ComputeResult; -use crate::geometry::{Point, Size}; -use crate::number::Number; +use crate::stretch::geometry::{Point, Size}; +use crate::stretch::number::Number; #[derive(Copy, Debug, Clone)] pub struct Layout { diff --git a/styles/src/stretch/style.rs b/styles/src/stretch/style.rs new file mode 100644 index 0000000..40af68c --- /dev/null +++ b/styles/src/stretch/style.rs @@ -0,0 +1,335 @@ +//! This module is included while awaiting an upstream merge in stretch proper. +//! You should not rely on it, and consider it an implementation detail. + +use crate::stretch::geometry::{Rect, Size}; +use crate::stretch::number::Number; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum AlignItems { + FlexStart, + FlexEnd, + Center, + Baseline, + Stretch, +} + +impl Default for AlignItems { + fn default() -> AlignItems { + AlignItems::Stretch + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum AlignSelf { + Auto, + FlexStart, + FlexEnd, + Center, + Baseline, + Stretch, +} + +impl Default for AlignSelf { + fn default() -> AlignSelf { + AlignSelf::Auto + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum AlignContent { + FlexStart, + FlexEnd, + Center, + Stretch, + SpaceBetween, + SpaceAround, +} + +impl Default for AlignContent { + fn default() -> AlignContent { + AlignContent::Stretch + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Direction { + Inherit, + LTR, + RTL, +} + +impl Default for Direction { + fn default() -> Direction { + Direction::Inherit + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Display { + Flex, + None, +} + +impl Default for Display { + fn default() -> Display { + Display::Flex + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FlexDirection { + Row, + Column, + RowReverse, + ColumnReverse, +} + +impl Default for FlexDirection { + fn default() -> FlexDirection { + FlexDirection::Row + } +} + +impl FlexDirection { + pub(crate) fn is_row(self) -> bool { + self == FlexDirection::Row || self == FlexDirection::RowReverse + } + + pub(crate) fn is_column(self) -> bool { + self == FlexDirection::Column || self == FlexDirection::ColumnReverse + } + + pub(crate) fn is_reverse(self) -> bool { + self == FlexDirection::RowReverse || self == FlexDirection::ColumnReverse + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum JustifyContent { + FlexStart, + FlexEnd, + Center, + SpaceBetween, + SpaceAround, + SpaceEvenly, +} + +impl Default for JustifyContent { + fn default() -> JustifyContent { + JustifyContent::FlexStart + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Overflow { + Visible, + Hidden, + Scroll, +} + +impl Default for Overflow { + fn default() -> Overflow { + Overflow::Visible + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PositionType { + Relative, + Absolute, +} + +impl Default for PositionType { + fn default() -> PositionType { + PositionType::Relative + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FlexWrap { + NoWrap, + Wrap, + WrapReverse, +} + +impl Default for FlexWrap { + fn default() -> FlexWrap { + FlexWrap::NoWrap + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Dimension { + Undefined, + Auto, + Points(f32), + Percent(f32), +} + +impl Default for Dimension { + fn default() -> Dimension { + Dimension::Undefined + } +} + +impl Dimension { + pub(crate) fn resolve(self, parent_width: Number) -> Number { + match self { + Dimension::Points(points) => Number::Defined(points), + Dimension::Percent(percent) => parent_width * percent, + _ => Number::Undefined, + } + } + + pub(crate) fn is_defined(self) -> bool { + match self { + Dimension::Points(_) => true, + Dimension::Percent(_) => true, + _ => false, + } + } +} + +impl Default for Rect { + fn default() -> Rect { + Rect { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() } + } +} + +impl Default for Size { + fn default() -> Size { + Size { width: Dimension::Auto, height: Dimension::Auto } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Style { + pub display: Display, + pub position_type: PositionType, + pub direction: Direction, + pub flex_direction: FlexDirection, + pub flex_wrap: FlexWrap, + pub overflow: Overflow, + pub align_items: AlignItems, + pub align_self: AlignSelf, + pub align_content: AlignContent, + pub justify_content: JustifyContent, + pub position: Rect, + pub margin: Rect, + pub padding: Rect, + pub border: Rect, + pub flex_grow: f32, + pub flex_shrink: f32, + pub flex_basis: Dimension, + pub size: Size, + pub min_size: Size, + pub max_size: Size, + pub aspect_ratio: Number, +} + +impl Default for Style { + fn default() -> Style { + Style { + display: Default::default(), + position_type: Default::default(), + direction: Default::default(), + flex_direction: Default::default(), + flex_wrap: Default::default(), + overflow: Default::default(), + align_items: Default::default(), + align_self: Default::default(), + align_content: Default::default(), + justify_content: Default::default(), + position: Default::default(), + margin: Default::default(), + padding: Default::default(), + border: Default::default(), + flex_grow: 0.0, + flex_shrink: 1.0, + flex_basis: Dimension::Auto, + size: Default::default(), + min_size: Default::default(), + max_size: Default::default(), + aspect_ratio: Default::default(), + } + } +} + +impl Style { + pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.min_size.width, + FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.height, + } + } + + pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.max_size.width, + FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.height, + } + } + + pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.margin.start, + FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top, + } + } + + pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.margin.end, + FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom, + } + } + + pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.size.height, + FlexDirection::Column | FlexDirection::ColumnReverse => self.size.width, + } + } + + pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.min_size.height, + FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.width, + } + } + + pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.max_size.height, + FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.width, + } + } + + pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.margin.top, + FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start, + } + } + + pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension { + match direction { + FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom, + FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end, + } + } + + pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf { + if self.align_self == AlignSelf::Auto { + match parent.align_items { + AlignItems::FlexStart => AlignSelf::FlexStart, + AlignItems::FlexEnd => AlignSelf::FlexEnd, + AlignItems::Center => AlignSelf::Center, + AlignItems::Baseline => AlignSelf::Baseline, + AlignItems::Stretch => AlignSelf::Stretch, + } + } else { + self.align_self + } + } +} diff --git a/styles/src/styles.rs b/styles/src/styles.rs index cf7bb64..d961744 100644 --- a/styles/src/styles.rs +++ b/styles/src/styles.rs @@ -7,224 +7,17 @@ use proc_macro2::{TokenStream, Ident, Span}; #[cfg(feature="tokenize")] use quote::{quote, ToTokens}; -pub use crate::geometry::{Rect, Size}; -pub use crate::number::Number; pub use crate::color::Color; + +pub use crate::stretch::geometry::{Point, Rect, Size}; +pub use crate::stretch::number::Number; pub use crate::stretch::result::Layout; -/// Describes how items should be aligned. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum AlignItems { - FlexStart, - FlexEnd, - Center, - Baseline, - Stretch, -} - -impl Default for AlignItems { - fn default() -> AlignItems { - AlignItems::Stretch - } -} - -/// Describes how this item should be aligned. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum AlignSelf { - Auto, - FlexStart, - FlexEnd, - Center, - Baseline, - Stretch, -} - -impl Default for AlignSelf { - fn default() -> AlignSelf { - AlignSelf::Auto - } -} - -/// Describes how content should be aligned. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum AlignContent { - FlexStart, - FlexEnd, - Center, - Stretch, - SpaceBetween, - SpaceAround, -} - -impl Default for AlignContent { - fn default() -> AlignContent { - AlignContent::Stretch - } -} - -/// Describes how things should flow - particularly important for start/end positions. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Direction { - Inherit, - LTR, - RTL, -} - -impl Default for Direction { - fn default() -> Direction { - Direction::Inherit - } -} - -/// Describes whether an item is visible or not. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Display { - Flex, - None, -} - -impl Default for Display { - fn default() -> Display { - Display::Flex - } -} - -/// Describes how items should be aligned. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum FlexDirection { - Row, - Column, - RowReverse, - ColumnReverse, -} - -impl Default for FlexDirection { - fn default() -> FlexDirection { - FlexDirection::Row - } -} - -impl FlexDirection { - /// Checks if this is a row. - pub(crate) fn is_row(self) -> bool { - self == FlexDirection::Row || self == FlexDirection::RowReverse - } - - /// Checks if this is a column. - pub(crate) fn is_column(self) -> bool { - self == FlexDirection::Column || self == FlexDirection::ColumnReverse - } - - /// Checks if this is a reversed direction. - pub(crate) fn is_reverse(self) -> bool { - self == FlexDirection::RowReverse || self == FlexDirection::ColumnReverse - } -} - -/// Describes how content should be justified. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum JustifyContent { - FlexStart, - FlexEnd, - Center, - SpaceBetween, - SpaceAround, - SpaceEvenly, -} - -impl Default for JustifyContent { - fn default() -> JustifyContent { - JustifyContent::FlexStart - } -} - -/// Describes how content should overflow. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Overflow { - Visible, - Hidden, - Scroll, -} - -impl Default for Overflow { - fn default() -> Overflow { - Overflow::Visible - } -} - -/// Describes how content should be positioned. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum PositionType { - Relative, - Absolute, -} - -impl Default for PositionType { - fn default() -> PositionType { - PositionType::Relative - } -} - -/// Describes how content should wrap. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum FlexWrap { - NoWrap, - Wrap, - WrapReverse, -} - -impl Default for FlexWrap { - fn default() -> FlexWrap { - FlexWrap::NoWrap - } -} - -/// Describes a Dimension; automatic, undefined, or a value. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Dimension { - Undefined, - Auto, - Points(f32), - Percent(f32), -} - -impl Default for Dimension { - fn default() -> Dimension { - Dimension::Undefined - } -} - -impl Dimension { - /// Internal method for Stretch. - pub(crate) fn resolve(self, parent_width: Number) -> Number { - match self { - Dimension::Points(points) => Number::Defined(points), - Dimension::Percent(percent) => parent_width * percent, - _ => Number::Undefined, - } - } - - /// Whether this Dimension is defined by a value or not. - pub(crate) fn is_defined(self) -> bool { - match self { - Dimension::Points(_) => true, - Dimension::Percent(_) => true, - _ => false, - } - } -} - -impl Default for Rect { - fn default() -> Rect { - Rect { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() } - } -} - -impl Default for Size { - fn default() -> Size { - Size { width: Dimension::Auto, height: Dimension::Auto } - } -} +pub use crate::stretch::style::{ + Style, + AlignContent, AlignItems, AlignSelf, Dimension, Direction, Display, + FlexDirection, JustifyContent, Overflow, PositionType, FlexWrap +}; /// Describes the backface-visibility for a view. This may be removed in a later release. #[derive(Copy, Clone, PartialEq, Debug)] @@ -308,34 +101,10 @@ impl Default for FontFamily { } } -/// `Style` is passed into the Stretch Flexbox rendering system to produce a computed -/// `Layout`. This is also passed to native nodes, to transform into per-platform style -/// commands. -#[derive(Copy, Clone, Debug)] -pub struct Style { - pub display: Display, - pub position_type: PositionType, - pub direction: Direction, - pub flex_direction: FlexDirection, - pub flex_wrap: FlexWrap, - pub overflow: Overflow, - pub align_items: AlignItems, - pub align_self: AlignSelf, - pub align_content: AlignContent, - pub justify_content: JustifyContent, - pub position: Rect, - pub margin: Rect, - pub padding: Rect, - pub border: Rect, - pub flex_grow: f32, - pub flex_shrink: f32, - pub flex_basis: Dimension, - pub size: Size, - pub min_size: Size, - pub max_size: Size, - pub aspect_ratio: Number, - - // Appearance-based styles +/// When applying layout to a backing view, you'll get two calls - one with a `Layout`, +/// which contains the computed frame, and one with an `Appearance`, which contains things +/// like colors, fonts, and so on. +pub struct Appearance { pub background_color: Color, pub font_size: f32, pub font_style: FontStyle, @@ -348,35 +117,12 @@ pub struct Style { pub tint_color: Color } -impl Default for Style { - fn default() -> Style { - Style { - display: Default::default(), - position_type: Default::default(), - direction: Default::default(), - flex_direction: Default::default(), - flex_wrap: Default::default(), - overflow: Default::default(), - align_items: Default::default(), - align_self: Default::default(), - align_content: Default::default(), - justify_content: Default::default(), - position: Default::default(), - margin: Default::default(), - padding: Default::default(), - border: Default::default(), - flex_grow: 0.0, - flex_shrink: 1.0, - flex_basis: Dimension::Auto, - size: Default::default(), - min_size: Default::default(), - max_size: Default::default(), - aspect_ratio: Default::default(), +impl Default for Appearance { + fn default() -> Appearance { + Appearance { background_color: Color::transparent(), - // @TODO: We can definitely judge a default value better here. font_size: 14., - font_style: FontStyle::default(), font_weight: FontWeight::default(), opacity: 1., @@ -389,95 +135,6 @@ impl Default for Style { } } -impl Style { - /// Determines the minimum main size, given flex direction. - pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.min_size.width, - FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.height, - } - } - - /// Determines the maximum main size, given flex direction. - pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.max_size.width, - FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.height, - } - } - - /// Determines the main margin start, given flex direction. - pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.margin.start, - FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top, - } - } - - /// Determines the main margin end, given flex direction. - pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.margin.end, - FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom, - } - } - - /// Determines the cross size, given flex direction. - pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.size.height, - FlexDirection::Column | FlexDirection::ColumnReverse => self.size.width, - } - } - - /// Determines the minimum cross size, given flex direction. - pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.min_size.height, - FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.width, - } - } - - /// Determines the maximum cross size, given flex direction. - pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.max_size.height, - FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.width, - } - } - - /// Determines the cross margin start, given flex direction. - pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.margin.top, - FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start, - } - } - - /// Determines the cross margin end, given flex direction. - pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension { - match direction { - FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom, - FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end, - } - } - - /// Determines the inherited align_self style, given a parent `&Style`. - pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf { - if self.align_self == AlignSelf::Auto { - match parent.align_items { - AlignItems::FlexStart => AlignSelf::FlexStart, - AlignItems::FlexEnd => AlignSelf::FlexEnd, - AlignItems::Center => AlignSelf::Center, - AlignItems::Baseline => AlignSelf::Baseline, - AlignItems::Stretch => AlignSelf::Stretch, - } - } else { - self.align_self - } - } -} - /// These exist purely for use in the parser code. /// /// A `Style` is what's used for a node; `Styles` are what's parsed and stored. diff --git a/styles/src/stylesheet.rs b/styles/src/stylesheet.rs index 6a563e7..2bf54ab 100644 --- a/styles/src/stylesheet.rs +++ b/styles/src/stylesheet.rs @@ -7,7 +7,8 @@ use std::collections::HashMap; -use crate::styles::{Dimension, Rect, Size, Style, Styles}; +use crate::stretch::style::Style; +use crate::styles::{Appearance, Dimension, Rect, Size, Styles}; /// A `StyleSheet` contains selectors and parsed `Styles` attributes. /// It also has some logic to apply styles for n keys to a given `Style` node. @@ -20,9 +21,9 @@ impl StyleSheet { StyleSheet(styles) } - pub fn apply_styles(&self, key: &str, style: &mut Style) { + pub fn apply_styles(&self, key: &str, style: &mut Style, appearance: &mut Appearance) { match self.0.get(key) { - Some(styles) => { reduce_styles_into_style(styles, style); }, + Some(styles) => { reduce_styles_into_style(styles, style, appearance); }, None => {} } } @@ -30,14 +31,14 @@ impl StyleSheet { /// This takes a list of styles, and a mutable style object, and attempts to configure the /// style object in a way that makes sense given n styles. -fn reduce_styles_into_style(styles: &Vec, layout: &mut Style) { +fn reduce_styles_into_style(styles: &Vec, layout: &mut Style, appearance: &mut Appearance) { for style in styles { match style { Styles::AlignContent(val) => { layout.align_content = *val; }, Styles::AlignItems(val) => { layout.align_items = *val; }, Styles::AlignSelf(val) => { layout.align_self = *val; }, Styles::AspectRatio(val) => { layout.aspect_ratio = *val; }, Styles::BackfaceVisibility(_val) => { }, - Styles::BackgroundColor(val) => { layout.background_color = *val; }, + Styles::BackgroundColor(val) => { appearance.background_color = *val; }, Styles::BorderColor(_val) => { }, Styles::BorderEndColor(_val) => { }, @@ -102,9 +103,9 @@ fn reduce_styles_into_style(styles: &Vec, layout: &mut Style) { Styles::FontFamily(_val) => { }, Styles::FontLineHeight(_val) => { }, - Styles::FontSize(val) => { layout.font_size = *val; }, - Styles::FontStyle(val) => { layout.font_style = *val; }, - Styles::FontWeight(val) => { layout.font_weight = *val; }, + Styles::FontSize(val) => { appearance.font_size = *val; }, + Styles::FontStyle(val) => { appearance.font_style = *val; }, + Styles::FontWeight(val) => { appearance.font_weight = *val; }, Styles::Height(val) => { layout.size = Size { @@ -206,7 +207,7 @@ fn reduce_styles_into_style(styles: &Vec, layout: &mut Style) { }; }, - Styles::Opacity(val) => { layout.opacity = *val; }, + Styles::Opacity(val) => { appearance.opacity = *val; }, Styles::Overflow(val) => { layout.overflow = *val; }, Styles::PaddingBottom(val) => { @@ -283,11 +284,11 @@ fn reduce_styles_into_style(styles: &Vec, layout: &mut Style) { }; }, - Styles::TextAlignment(val) => { layout.text_alignment = *val; }, - Styles::TextColor(val) => { layout.text_color = *val; }, - Styles::TextDecorationColor(val) => { layout.text_decoration_color = *val; }, - Styles::TextShadowColor(val) => { layout.text_shadow_color = *val; }, - Styles::TintColor(val) => { layout.tint_color = *val; }, + Styles::TextAlignment(val) => { appearance.text_alignment = *val; }, + Styles::TextColor(val) => { appearance.text_color = *val; }, + Styles::TextDecorationColor(val) => { appearance.text_decoration_color = *val; }, + Styles::TextShadowColor(val) => { appearance.text_shadow_color = *val; }, + Styles::TintColor(val) => { appearance.tint_color = *val; }, Styles::Top(val) => { layout.position = Rect {