From 6833e39d52e272ba00654c6fe20d1da6b18140a5 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sun, 26 May 2019 00:27:07 -0700 Subject: [PATCH] Move the reconciler into the lifecycle crate, so that we can (properly) support state changes. Wrap the reconciliation phase in a RenderEngine, which root-level objects (windows, etc) can subscribe to and interface with. Migrate a few things relating to themes out of alchemy core and into styles, because the reconciliation phase requires them and they fit well enough there... solving a circular dependency. --- alchemy/src/app.rs | 6 +- alchemy/src/components/view.rs | 4 +- alchemy/src/lib.rs | 6 +- alchemy/src/reconciler.rs | 13 +- alchemy/src/theme/mod.rs | 121 ------------- alchemy/src/theme/stylesheet.rs | 303 -------------------------------- lifecycle/Cargo.toml | 1 + lifecycle/src/lib.rs | 12 ++ lifecycle/src/rsx/mod.rs | 11 -- lifecycle/src/rsx/props.rs | 4 +- lifecycle/src/rsx/spacedlist.rs | 262 --------------------------- lifecycle/src/rsx/spacedset.rs | 293 ------------------------------ lifecycle/src/rsx/style_keys.rs | 83 --------- macros/src/lib.rs | 6 +- styles/Cargo.toml | 2 + styles/src/lib.rs | 28 +++ 16 files changed, 57 insertions(+), 1098 deletions(-) delete mode 100644 alchemy/src/theme/mod.rs delete mode 100644 alchemy/src/theme/stylesheet.rs delete mode 100644 lifecycle/src/rsx/spacedlist.rs delete mode 100644 lifecycle/src/rsx/spacedset.rs delete mode 100644 lifecycle/src/rsx/style_keys.rs diff --git a/alchemy/src/app.rs b/alchemy/src/app.rs index 08e03dd..f1230d2 100644 --- a/alchemy/src/app.rs +++ b/alchemy/src/app.rs @@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex}; +use alchemy_styles::{StyleSheet, THEME_ENGINE}; use alchemy_lifecycle::traits::AppDelegate; -use crate::theme::{ThemeEngine, StyleSheet}; use crate::window::WindowManager; #[cfg(feature = "cocoa")] @@ -29,7 +29,6 @@ impl AppDelegate for DefaultAppDelegate {} pub struct App { pub(crate) bridge: Mutex>, pub delegate: Mutex>, - pub themes: ThemeEngine, pub windows: WindowManager } @@ -41,7 +40,6 @@ impl App { let app = Arc::new(App { bridge: Mutex::new(None), delegate: Mutex::new(Box::new(DefaultAppDelegate {})), - themes: ThemeEngine::new(), windows: WindowManager::new() }); @@ -61,7 +59,7 @@ impl App { /// conceivable that you might want to just have them in your app, too, and this enables /// that use case. pub fn register_styles(&self, theme_key: &str, stylesheet: StyleSheet) { - self.themes.register_styles(theme_key, stylesheet); + THEME_ENGINE.register_styles(theme_key, stylesheet); } /// Runs the app instance, by setting the necessary delegate and forwarding the run call diff --git a/alchemy/src/components/view.rs b/alchemy/src/components/view.rs index ff99dfc..f572ada 100644 --- a/alchemy/src/components/view.rs +++ b/alchemy/src/components/view.rs @@ -5,10 +5,10 @@ use std::sync::{Arc, Mutex, RwLock}; -use alchemy_styles::styles::{Layout, Style}; +use alchemy_styles::{Layout, Style, StylesList}; use alchemy_lifecycle::error::Error; -use alchemy_lifecycle::rsx::{Props, RSX, StylesList}; +use alchemy_lifecycle::rsx::{Props, RSX}; use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType}; use crate::components::Fragment; diff --git a/alchemy/src/lib.rs b/alchemy/src/lib.rs index 48f20bb..d9b2376 100644 --- a/alchemy/src/lib.rs +++ b/alchemy/src/lib.rs @@ -15,8 +15,7 @@ pub use alchemy_lifecycle::traits::{ pub use alchemy_lifecycle::error::Error; pub use alchemy_lifecycle::rsx::{ - Props, RSX, StyleKey, StylesList, - SpacedSet, VirtualNode, VirtualText + Props, RSX, VirtualNode, VirtualText }; #[proc_macro_hack(support_nested)] @@ -25,6 +24,8 @@ pub use alchemy_macros::rsx; #[proc_macro_hack] pub use alchemy_macros::styles; +pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList}; + mod app; use app::App; @@ -32,7 +33,6 @@ pub mod components; pub use components::{Fragment, Text, View}; pub(crate) mod reconciler; -pub mod theme; pub mod window; pub use window::Window; diff --git a/alchemy/src/reconciler.rs b/alchemy/src/reconciler.rs index 97e630f..fa9ef7d 100644 --- a/alchemy/src/reconciler.rs +++ b/alchemy/src/reconciler.rs @@ -7,7 +7,7 @@ use std::mem::{discriminant, swap}; use alchemy_styles::Stretch; use alchemy_styles::styles::Style; -use alchemy_lifecycle::rsx::{StylesList, RSX, VirtualNode}; +use alchemy_lifecycle::rsx::{RSX, VirtualNode}; /// Given two node trees, will compare, diff, and apply changes in a recursive fashion. pub fn diff_and_patch_tree(old: RSX, new: RSX, stretch: &mut Stretch, depth: usize) -> Result> { @@ -163,16 +163,6 @@ pub fn diff_and_patch_tree(old: RSX, new: RSX, stretch: &mut Stretch, depth: usi } } -/// Given a set of style keys, and a mutable style to update, this will walk the keys -/// and configure the Style node for the upcoming layout + render pass. Where appropriate, -/// it will mark the node explicitly as dirty. -/// -/// This may not need to be it's own function, we'll see down the road. -fn configure_styles(style_keys: &StylesList, style: &mut Style) { - let app = crate::shared_app(); - app.themes.configure_style_for_keys(style_keys, style); -} - /// 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) { @@ -234,7 +224,6 @@ fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> if is_native_backed { let mut style = Style::default(); - configure_styles(&new_element.props.styles, &mut style); let layout_node = stretch.new_node(style, vec![])?; new_element.layout_node = Some(layout_node); diff --git a/alchemy/src/theme/mod.rs b/alchemy/src/theme/mod.rs deleted file mode 100644 index 8e98889..0000000 --- a/alchemy/src/theme/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Implements a Theme loader, which scans a few places and loads any -//! CSS files that are necessary. - -use std::fs; -use std::env; -use std::sync::RwLock; -use std::path::PathBuf; -use std::collections::HashMap; - -use toml; -use serde::Deserialize; - -use alchemy_lifecycle::rsx::StylesList; - -pub use alchemy_styles::color; -pub use alchemy_styles::styles; -pub use styles::{Style, Styles}; - -pub mod stylesheet; -pub use stylesheet::StyleSheet; - -static CONFIG_FILE_NAME: &str = "alchemy.toml"; - -#[derive(Debug, Deserialize)] -struct RawConfig<'d> { - #[serde(borrow)] - general: Option>, -} - -#[derive(Debug, Deserialize)] -struct General<'a> { - #[serde(borrow)] - dirs: Option> -} - -/// The `ThemeEngine` controls loading themes and registering associated -/// styles. -#[derive(Debug)] -pub struct ThemeEngine { - pub dirs: Vec, - pub themes: RwLock> -} - -impl ThemeEngine { - /// Creates a new 'ThemeEngine` instance. - pub fn new() -> ThemeEngine { - // This env var is set by Cargo... so if this code breaks, there's - // bigger concerns, lol - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - - let root = PathBuf::from(manifest_dir); - let default_dirs = vec![root.join("themes")]; - - let toml_contents = read_config_file(); - let raw: RawConfig<'_> = toml::from_str(&toml_contents).expect(&format!("Invalid TOML in {}!", CONFIG_FILE_NAME)); - - let dirs = match raw.general { - Some(General { dirs }) => ( - dirs.map_or(default_dirs, |v| { - v.into_iter().map(|dir| root.join(dir)).collect() - }) - ), - - None => default_dirs - }; - - ThemeEngine { dirs, themes: RwLock::new(HashMap::new()) } - } - - /// Registers a stylesheet (typically created by the `styles! {}` macro) for a given - /// theme. - pub fn register_styles(&self, key: &str, stylesheet: StyleSheet) { - let mut themes = self.themes.write().unwrap(); - if !themes.contains_key(key) { - themes.insert(key.to_string(), stylesheet); - return; - } - - // if let Some(existing_stylesheet) = self.themes.get_mut(key) { - // *existing_stylesheet.merge(stylesheet); - //} - } - - /// 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) { - let themes = self.themes.read().unwrap(); - - match themes.get(theme) { - Some(theme) => { - for key in &keys.0 { - theme.apply_styles(key, style); - } - }, - - None => { - eprintln!("No styles for theme!"); - } - } - } - - /// 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) - } -} - -/// Utility method for reading a config file from the `CARGO_MANIFEST_DIR`. Hat tip to -/// [askama](https://github.com/djc/askama) for this! -pub fn read_config_file() -> String { - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let root = PathBuf::from(manifest_dir); - let filename = root.join(CONFIG_FILE_NAME); - - if filename.exists() { - fs::read_to_string(&filename) - .expect(&format!("Unable to read {}", filename.to_str().unwrap())) - } else { - "".to_string() - } -} diff --git a/alchemy/src/theme/stylesheet.rs b/alchemy/src/theme/stylesheet.rs deleted file mode 100644 index 589f497..0000000 --- a/alchemy/src/theme/stylesheet.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Implements a `StyleSheet`, which contains inner logic for -//! determining what styles should be applied to a given widget. - -use std::collections::HashMap; -use alchemy_styles::styles::{Dimension, Rect, Size, Style, 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. -#[derive(Debug)] -pub struct StyleSheet(HashMap<&'static str, Vec>); - -impl StyleSheet { - /// Creates a new `Stylesheet`. - pub fn new(styles: HashMap<&'static str, Vec>) -> Self { - StyleSheet(styles) - } - - pub fn apply_styles(&self, key: &str, style: &mut Style) { - match self.0.get(key) { - Some(styles) => { reduce_styles_into_style(styles, style); }, - None => {} - } - } -} - -/// 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) { - 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::BorderColor(_val) => { }, - Styles::BorderEndColor(_val) => { }, - Styles::BorderBottomColor(_val) => { }, - Styles::BorderLeftColor(_val) => { }, - Styles::BorderRightColor(_val) => { }, - Styles::BorderTopColor(_val) => { }, - Styles::BorderStartColor(_val) => { }, - - Styles::BorderStyle(_val) => { }, - Styles::BorderEndStyle(_val) => { }, - Styles::BorderBottomStyle(_val) => { }, - Styles::BorderLeftStyle(_val) => { }, - Styles::BorderRightStyle(_val) => { }, - Styles::BorderTopStyle(_val) => { }, - Styles::BorderStartStyle(_val) => { }, - - Styles::BorderWidth(_val) => { }, - Styles::BorderEndWidth(_val) => { }, - Styles::BorderBottomWidth(_val) => { }, - Styles::BorderLeftWidth(_val) => { }, - Styles::BorderRightWidth(_val) => { }, - Styles::BorderTopWidth(_val) => { }, - Styles::BorderStartWidth(_val) => { }, - - Styles::BorderRadius(_val) => { }, - Styles::BorderBottomEndRadius(_val) => { }, - Styles::BorderBottomLeftRadius(_val) => { }, - Styles::BorderBottomRightRadius(_val) => { }, - Styles::BorderBottomStartRadius(_val) => { }, - Styles::BorderTopLeftRadius(_val) => { }, - Styles::BorderTopRightRadius(_val) => { }, - Styles::BorderTopEndRadius(_val) => { }, - Styles::BorderTopStartRadius(_val) => { }, - - Styles::Bottom(val) => { - layout.position = Rect { - start: layout.position.start, - end: layout.position.end, - top: layout.position.top, - bottom: Dimension::Points(*val) - }; - }, - - Styles::Direction(val) => { layout.direction = *val; }, - Styles::Display(val) => { layout.display = *val; }, - - Styles::End(val) => { - layout.position = Rect { - start: layout.position.start, - end: Dimension::Points(*val), - top: layout.position.top, - bottom: layout.position.bottom - }; - }, - - Styles::FlexBasis(val) => { layout.flex_basis = Dimension::Points(*val); }, - Styles::FlexDirection(val) => { layout.flex_direction = *val; }, - Styles::FlexGrow(val) => { layout.flex_grow = *val; }, - Styles::FlexShrink(val) => { layout.flex_shrink = *val; }, - Styles::FlexWrap(val) => { layout.flex_wrap = *val; }, - - 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::Height(val) => { - layout.size = Size { - width: layout.size.width, - height: Dimension::Points(*val) - }; - }, - - Styles::JustifyContent(val) => { layout.justify_content = *val; }, - - Styles::Left(val) => { - layout.position = Rect { - start: Dimension::Points(*val), - end: layout.position.end, - top: layout.position.top, - bottom: layout.position.bottom - }; - }, - - Styles::MarginBottom(val) => { - layout.margin = Rect { - start: layout.margin.start, - end: layout.margin.end, - top: layout.margin.top, - bottom: Dimension::Points(*val) - }; - }, - - Styles::MarginEnd(val) => { - layout.margin = Rect { - start: layout.margin.start, - end: Dimension::Points(*val), - top: layout.margin.top, - bottom: layout.margin.bottom - }; - }, - - Styles::MarginLeft(val) => { - layout.margin = Rect { - start: Dimension::Points(*val), - end: layout.margin.end, - top: layout.margin.top, - bottom: layout.margin.bottom - }; - }, - - Styles::MarginRight(val) => { - layout.margin = Rect { - start: layout.margin.start, - end: Dimension::Points(*val), - top: layout.margin.top, - bottom: layout.margin.bottom - }; - }, - - Styles::MarginStart(val) => { - layout.margin = Rect { - start: Dimension::Points(*val), - end: layout.margin.end, - top: layout.margin.top, - bottom: layout.margin.bottom - }; - }, - - Styles::MarginTop(val) => { - layout.margin = Rect { - start: layout.margin.start, - end: layout.margin.end, - top: Dimension::Points(*val), - bottom: layout.margin.bottom - }; - }, - - Styles::MaxHeight(val) => { - layout.max_size = Size { - width: layout.max_size.width, - height: Dimension::Points(*val) - }; - }, - - Styles::MaxWidth(val) => { - layout.max_size = Size { - width: Dimension::Points(*val), - height: layout.max_size.height - }; - }, - - Styles::MinHeight(val) => { - layout.min_size = Size { - width: layout.min_size.width, - height: Dimension::Points(*val) - }; - }, - - Styles::MinWidth(val) => { - layout.min_size = Size { - width: Dimension::Points(*val), - height: layout.min_size.height - }; - }, - - Styles::Opacity(val) => { layout.opacity = *val; }, - Styles::Overflow(val) => { layout.overflow = *val; }, - - Styles::PaddingBottom(val) => { - layout.padding = Rect { - start: layout.padding.start, - end: layout.padding.end, - top: layout.padding.top, - bottom: Dimension::Points(*val) - }; - }, - - Styles::PaddingEnd(val) => { - layout.padding = Rect { - start: layout.padding.start, - end: Dimension::Points(*val), - top: layout.padding.top, - bottom: layout.padding.bottom - }; - }, - - Styles::PaddingLeft(val) => { - layout.padding = Rect { - start: Dimension::Points(*val), - end: layout.padding.end, - top: layout.padding.top, - bottom: layout.padding.bottom - }; - }, - - Styles::PaddingRight(val) => { - layout.padding = Rect { - start: layout.padding.start, - end: Dimension::Points(*val), - top: layout.padding.top, - bottom: layout.padding.bottom - }; - }, - - Styles::PaddingStart(val) => { - layout.padding = Rect { - start: Dimension::Points(*val), - end: layout.padding.end, - top: layout.padding.top, - bottom: layout.padding.bottom - }; - }, - - Styles::PaddingTop(val) => { - layout.padding = Rect { - start: layout.padding.start, - end: layout.padding.end, - top: Dimension::Points(*val), - bottom: layout.padding.bottom - }; - }, - - Styles::PositionType(val) => { layout.position_type = *val; }, - - Styles::Right(val) => { - layout.position = Rect { - start: layout.position.start, - end: Dimension::Points(*val), - top: layout.position.top, - bottom: layout.position.bottom - }; - }, - - Styles::Start(val) => { - layout.position = Rect { - start: Dimension::Points(*val), - end: layout.position.end, - top: layout.position.top, - bottom: layout.position.bottom - }; - }, - - 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::Top(val) => { - layout.position = Rect { - start: layout.position.start, - end: layout.position.end, - top: Dimension::Points(*val), - bottom: layout.position.bottom - }; - }, - - Styles::Width(val) => { - layout.size = Size { - width: Dimension::Points(*val), - height: layout.size.height - }; - } - }} -} diff --git a/lifecycle/Cargo.toml b/lifecycle/Cargo.toml index a4694db..e178598 100644 --- a/lifecycle/Cargo.toml +++ b/lifecycle/Cargo.toml @@ -17,3 +17,4 @@ 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 b2bc4a5..c666053 100644 --- a/lifecycle/src/lib.rs +++ b/lifecycle/src/lib.rs @@ -7,7 +7,19 @@ //! It also includes the `RSX` enum, which is what `render()` methods generally //! return. It's common enough to multiple crates, and is intricately linked to the //! `Component` lifecycle, so it'll live here. +//! +//! 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. + +use alchemy_styles::lazy_static; pub mod error; pub mod rsx; pub mod traits; + +mod reconciler; +use reconciler::RenderEngine; + +lazy_static! { + pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new(); +} diff --git a/lifecycle/src/rsx/mod.rs b/lifecycle/src/rsx/mod.rs index 4d2fa9e..e85338f 100644 --- a/lifecycle/src/rsx/mod.rs +++ b/lifecycle/src/rsx/mod.rs @@ -15,17 +15,6 @@ pub use virtual_text::VirtualText; mod props; pub use props::Props; -mod style_keys; -pub use self::style_keys::StyleKey; - -mod spacedlist; -pub use self::spacedlist::SpacedList; - -mod spacedset; -pub use self::spacedset::SpacedSet; - -pub type StylesList = SpacedSet; - use crate::traits::Component; /// An enum representing the types of nodes that the diff --git a/lifecycle/src/rsx/props.rs b/lifecycle/src/rsx/props.rs index b7c3017..580f802 100644 --- a/lifecycle/src/rsx/props.rs +++ b/lifecycle/src/rsx/props.rs @@ -5,8 +5,10 @@ use std::sync::{Arc, RwLock}; use serde_json::Value; use std::collections::HashMap; +use alchemy_styles::StylesList; + +use crate::rsx::RSX; use crate::traits::{Component}; -use crate::rsx::{RSX, StylesList}; /// A value stored inside the `attributes` field on a `Props` instance. /// It shadows `serde_json::Value`, but also allows for some other value diff --git a/lifecycle/src/rsx/spacedlist.rs b/lifecycle/src/rsx/spacedlist.rs deleted file mode 100644 index 0deefeb..0000000 --- a/lifecycle/src/rsx/spacedlist.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! A space separated list of values. -//! -//! This type represents a list of non-unique values represented as a string of -//! values separated by spaces in HTML attributes. This is rarely used; a -//! SpacedSet of unique values is much more common. - - -use std::fmt::{Debug, Display, Error, Formatter}; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -/// A space separated list of values. -/// -/// This type represents a list of non-unique values represented as a string of -/// values separated by spaces in HTML attributes. This is rarely used; a -/// SpacedSet of unique values is much more common. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SpacedList(Vec); - -impl SpacedList { - /// Construct an empty `SpacedList`. - pub fn new() -> Self { - SpacedList(Vec::new()) - } -} - -impl Default for SpacedList { - fn default() -> Self { - Self::new() - } -} - -impl FromIterator for SpacedList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedList(iter.into_iter().collect()) - } -} - -impl<'a, A: 'a + Clone> FromIterator<&'a A> for SpacedList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedList(iter.into_iter().cloned().collect()) - } -} - -impl<'a, A: FromStr> From<&'a str> for SpacedList -where - ::Err: Debug, -{ - fn from(s: &'a str) -> Self { - Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) - } -} - -impl Deref for SpacedList { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SpacedList { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for SpacedList { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let mut it = self.0.iter().peekable(); - while let Some(class) = it.next() { - Display::fmt(class, f)?; - if it.peek().is_some() { - Display::fmt(" ", f)?; - } - } - Ok(()) - } -} - -impl Debug for SpacedList { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - f.debug_list().entries(self.0.iter()).finish() - } -} - -impl<'a, 'b, A: FromStr> From<(&'a str, &'b str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list - } -} - -impl<'a, 'b, 'c, A: FromStr> From<(&'a str, &'b str, &'c str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, A: FromStr> From<(&'a str, &'b str, &'c str, &'d str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, A: FromStr> From<(&'a str, &'b str, &'c str, &'d str, &'e str)> - for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, A: FromStr> - From<(&'a str, &'b str, &'c str, &'d str, &'e str, &'f str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, A: FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - )> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list.push(FromStr::from_str(s.6).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, A: FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - &'h str, - )> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list.push(FromStr::from_str(s.6).unwrap()); - list.push(FromStr::from_str(s.7).unwrap()); - list - } -} - -macro_rules! spacedlist_from_array { - ($num:tt) => { - impl<'a, A: FromStr> From<[&'a str; $num]> for SpacedList - where - ::Err: Debug, - { - fn from(s: [&str; $num]) -> Self { - Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) - } - } - }; -} -spacedlist_from_array!(1); -spacedlist_from_array!(2); -spacedlist_from_array!(3); -spacedlist_from_array!(4); -spacedlist_from_array!(5); -spacedlist_from_array!(6); -spacedlist_from_array!(7); -spacedlist_from_array!(8); -spacedlist_from_array!(9); -spacedlist_from_array!(10); -spacedlist_from_array!(11); -spacedlist_from_array!(12); -spacedlist_from_array!(13); -spacedlist_from_array!(14); -spacedlist_from_array!(15); -spacedlist_from_array!(16); -spacedlist_from_array!(17); -spacedlist_from_array!(18); -spacedlist_from_array!(19); -spacedlist_from_array!(20); -spacedlist_from_array!(21); -spacedlist_from_array!(22); -spacedlist_from_array!(23); -spacedlist_from_array!(24); -spacedlist_from_array!(25); -spacedlist_from_array!(26); -spacedlist_from_array!(27); -spacedlist_from_array!(28); -spacedlist_from_array!(29); -spacedlist_from_array!(30); -spacedlist_from_array!(31); -spacedlist_from_array!(32); diff --git a/lifecycle/src/rsx/spacedset.rs b/lifecycle/src/rsx/spacedset.rs deleted file mode 100644 index 8096b56..0000000 --- a/lifecycle/src/rsx/spacedset.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! A space separated set of unique values. -//! -//! This type represents a set of unique values represented as a string of -//! values separated by spaces in HTML attributes. - -use std::collections::BTreeSet; -use std::fmt::{Debug, Display, Error, Formatter}; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -/// A space separated set of unique values. -/// -/// This type represents a set of unique values represented as a string of -/// values separated by spaces in HTML attributes. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SpacedSet(pub BTreeSet); - -impl SpacedSet { - /// Construct an empty `SpacedSet`. - pub fn new() -> Self { - SpacedSet(BTreeSet::new()) - } - - /// Add a value to the `SpacedSet`. - pub fn add>(&mut self, value: T) -> bool { - self.0.insert(value.into()) - } -} - -impl Default for SpacedSet { - fn default() -> Self { - Self::new() - } -} - -impl FromIterator for SpacedSet { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedSet(iter.into_iter().collect()) - } -} - -impl<'a, A: 'a + Ord + Clone> FromIterator<&'a A> for SpacedSet { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedSet(iter.into_iter().cloned().collect()) - } -} - -impl<'a, A: Ord + FromStr> FromStr for SpacedSet -where - ::Err: Debug, -{ - type Err = ::Err; - - fn from_str(s: &str) -> Result { - let result: Result, Self::Err> = - s.split_whitespace().map(|s| FromStr::from_str(s)).collect(); - result.map(Self::from_iter) - } -} - -impl<'a, A: Ord + FromStr> From<&'a str> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: &'a str) -> Self { - Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) - } -} - -impl Deref for SpacedSet { - type Target = BTreeSet; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SpacedSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for SpacedSet { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let mut it = self.0.iter().peekable(); - while let Some(class) = it.next() { - Display::fmt(class, f)?; - if it.peek().is_some() { - Display::fmt(" ", f)?; - } - } - Ok(()) - } -} - -impl Debug for SpacedSet { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - f.debug_list().entries(self.0.iter()).finish() - } -} - -impl<'a, A: Ord + FromStr> From> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: Vec<&'a str>) -> Self { - let mut list = Self::new(); - - for key in s { - list.insert(FromStr::from_str(key).unwrap()); - } - - list - } -} - -impl<'a, 'b, A: Ord + FromStr> From<(&'a str, &'b str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list - } -} - -impl<'a, 'b, 'c, A: Ord + FromStr> From<(&'a str, &'b str, &'c str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, A: Ord + FromStr> From<(&'a str, &'b str, &'c str, &'d str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, A: Ord + FromStr> From<(&'a str, &'b str, &'c str, &'d str, &'e str)> - for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, A: Ord + FromStr> - From<(&'a str, &'b str, &'c str, &'d str, &'e str, &'f str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, A: Ord + FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - )> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list.insert(FromStr::from_str(s.6).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, A: Ord + FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - &'h str, - )> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list.insert(FromStr::from_str(s.6).unwrap()); - list.insert(FromStr::from_str(s.7).unwrap()); - list - } -} - -macro_rules! spacedlist_from_array { - ($num:tt) => { - impl<'a, A: Ord + FromStr> From<[&'a str; $num]> for SpacedSet - where - ::Err: Debug, - { - fn from(s: [&str; $num]) -> Self { - Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) - } - } - }; -} -spacedlist_from_array!(1); -spacedlist_from_array!(2); -spacedlist_from_array!(3); -spacedlist_from_array!(4); -spacedlist_from_array!(5); -spacedlist_from_array!(6); -spacedlist_from_array!(7); -spacedlist_from_array!(8); -spacedlist_from_array!(9); -spacedlist_from_array!(10); -spacedlist_from_array!(11); -spacedlist_from_array!(12); -spacedlist_from_array!(13); -spacedlist_from_array!(14); -spacedlist_from_array!(15); -spacedlist_from_array!(16); -spacedlist_from_array!(17); -spacedlist_from_array!(18); -spacedlist_from_array!(19); -spacedlist_from_array!(20); -spacedlist_from_array!(21); -spacedlist_from_array!(22); -spacedlist_from_array!(23); -spacedlist_from_array!(24); -spacedlist_from_array!(25); -spacedlist_from_array!(26); -spacedlist_from_array!(27); -spacedlist_from_array!(28); -spacedlist_from_array!(29); -spacedlist_from_array!(30); -spacedlist_from_array!(31); -spacedlist_from_array!(32); diff --git a/lifecycle/src/rsx/style_keys.rs b/lifecycle/src/rsx/style_keys.rs deleted file mode 100644 index 75ca840..0000000 --- a/lifecycle/src/rsx/style_keys.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! A valid CSS class. -//! -//! A CSS class is a non-empty string that starts with an alphanumeric character -//! and is followed by any number of alphanumeric characters and the -//! `_`, `-` and `.` characters. - -use std::fmt::{Display, Error, Formatter}; -use std::ops::Deref; -use std::str::FromStr; - -/// A valid CSS class. -/// -/// A CSS class is a non-empty string that starts with an alphanumeric character -/// and is followed by any number of alphanumeric characters and the -/// `_`, `-` and `.` characters. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct StyleKey(String); - -impl StyleKey { - /// Construct a new styles list from a string. - /// - /// Returns `Err` if the provided string is invalid. - pub fn try_new>(id: S) -> Result { - let id = id.into(); - { - let mut chars = id.chars(); - match chars.next() { - None => return Err("style keys cannot be empty"), - Some(c) if !c.is_alphabetic() => { - return Err("style keys must start with an alphabetic character") - } - _ => (), - } - for c in chars { - if !c.is_alphanumeric() && c != '-' { - return Err( - "style keys can only contain alphanumerics (dash included)", - ); - } - } - } - Ok(StyleKey(id)) - } - - /// Construct a new class name from a string. - /// - /// Panics if the provided string is invalid. - pub fn new>(id: S) -> Self { - let id = id.into(); - Self::try_new(id.clone()).unwrap_or_else(|err| { - panic!( - "alchemy::dom::types::StyleKey: {:?} is not a valid class name: {}", - id, err - ) - }) - } -} - -impl FromStr for StyleKey { - type Err = &'static str; - fn from_str(s: &str) -> Result { - StyleKey::try_new(s) - } -} - -impl<'a> From<&'a str> for StyleKey { - fn from(str: &'a str) -> Self { - StyleKey::from_str(str).unwrap() - } -} - -impl Display for StyleKey { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - Display::fmt(&self.0, f) - } -} - -impl Deref for StyleKey { - type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 24e7873..802ce4e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -69,9 +69,9 @@ pub fn styles(input: TokenStream) -> TokenStream { body.extend(quote!(styles.insert(#key, vec![#stream]);)) } - quote!(alchemy::theme::StyleSheet::new({ - use alchemy::theme::styles::*; - use alchemy::theme::color::Color; + quote!(alchemy::StyleSheet::new({ + use alchemy::style_attributes::*; + use alchemy::Color; let mut styles = std::collections::HashMap::new(); #body styles diff --git a/styles/Cargo.toml b/styles/Cargo.toml index 6615009..42d5716 100644 --- a/styles/Cargo.toml +++ b/styles/Cargo.toml @@ -21,3 +21,5 @@ cssparser = { version = "0.25.5", optional = true } lazy_static = "1.3" proc-macro2 = { version = "0.4.24", optional = true } quote = { version = "0.6.10", optional = true } +serde = { version = "1.0", features = ["derive"] } +toml = "0.5" diff --git a/styles/src/lib.rs b/styles/src/lib.rs index 7c48235..1a0b361 100644 --- a/styles/src/lib.rs +++ b/styles/src/lib.rs @@ -2,14 +2,42 @@ //! Flexbox in Alchemy. For all intents and purposes, you can essentially consider //! this to be the root crate for Alchemy, as just about everything ends up using it. +// We hoist this for ease of use in other crates, since... well, pretty much +// every other crate in the project imports this already. +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; + +mod engine; +use engine::ThemeEngine; + +mod spacedlist; +pub use spacedlist::SpacedList; + +mod spacedset; +pub use spacedset::SpacedSet; + +mod style_keys; +pub use style_keys::StyleKey; +pub type StylesList = SpacedSet; + pub mod styles; +pub use styles::{Style, Styles}; + +pub mod stylesheet; +pub use stylesheet::StyleSheet; #[cfg(feature="parser")] pub mod styles_parser; + +lazy_static! { + pub static ref THEME_ENGINE: ThemeEngine = ThemeEngine::new(); +}