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.
This commit is contained in:
parent
f1cb5fea93
commit
6833e39d52
16 changed files with 57 additions and 1098 deletions
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use alchemy_styles::{StyleSheet, THEME_ENGINE};
|
||||||
use alchemy_lifecycle::traits::AppDelegate;
|
use alchemy_lifecycle::traits::AppDelegate;
|
||||||
|
|
||||||
use crate::theme::{ThemeEngine, StyleSheet};
|
|
||||||
use crate::window::WindowManager;
|
use crate::window::WindowManager;
|
||||||
|
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
|
|
@ -29,7 +29,6 @@ impl AppDelegate for DefaultAppDelegate {}
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub(crate) bridge: Mutex<Option<PlatformAppBridge>>,
|
pub(crate) bridge: Mutex<Option<PlatformAppBridge>>,
|
||||||
pub delegate: Mutex<Box<AppDelegate>>,
|
pub delegate: Mutex<Box<AppDelegate>>,
|
||||||
pub themes: ThemeEngine,
|
|
||||||
pub windows: WindowManager
|
pub windows: WindowManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +40,6 @@ impl App {
|
||||||
let app = Arc::new(App {
|
let app = Arc::new(App {
|
||||||
bridge: Mutex::new(None),
|
bridge: Mutex::new(None),
|
||||||
delegate: Mutex::new(Box::new(DefaultAppDelegate {})),
|
delegate: Mutex::new(Box::new(DefaultAppDelegate {})),
|
||||||
themes: ThemeEngine::new(),
|
|
||||||
windows: WindowManager::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
|
/// conceivable that you might want to just have them in your app, too, and this enables
|
||||||
/// that use case.
|
/// that use case.
|
||||||
pub fn register_styles(&self, theme_key: &str, stylesheet: StyleSheet) {
|
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
|
/// Runs the app instance, by setting the necessary delegate and forwarding the run call
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
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::error::Error;
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX, StylesList};
|
use alchemy_lifecycle::rsx::{Props, RSX};
|
||||||
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
||||||
|
|
||||||
use crate::components::Fragment;
|
use crate::components::Fragment;
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ pub use alchemy_lifecycle::traits::{
|
||||||
|
|
||||||
pub use alchemy_lifecycle::error::Error;
|
pub use alchemy_lifecycle::error::Error;
|
||||||
pub use alchemy_lifecycle::rsx::{
|
pub use alchemy_lifecycle::rsx::{
|
||||||
Props, RSX, StyleKey, StylesList,
|
Props, RSX, VirtualNode, VirtualText
|
||||||
SpacedSet, VirtualNode, VirtualText
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[proc_macro_hack(support_nested)]
|
#[proc_macro_hack(support_nested)]
|
||||||
|
|
@ -25,6 +24,8 @@ pub use alchemy_macros::rsx;
|
||||||
#[proc_macro_hack]
|
#[proc_macro_hack]
|
||||||
pub use alchemy_macros::styles;
|
pub use alchemy_macros::styles;
|
||||||
|
|
||||||
|
pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
use app::App;
|
use app::App;
|
||||||
|
|
||||||
|
|
@ -32,7 +33,6 @@ pub mod components;
|
||||||
pub use components::{Fragment, Text, View};
|
pub use components::{Fragment, Text, View};
|
||||||
|
|
||||||
pub(crate) mod reconciler;
|
pub(crate) mod reconciler;
|
||||||
pub mod theme;
|
|
||||||
|
|
||||||
pub mod window;
|
pub mod window;
|
||||||
pub use window::Window;
|
pub use window::Window;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use std::mem::{discriminant, swap};
|
||||||
use alchemy_styles::Stretch;
|
use alchemy_styles::Stretch;
|
||||||
use alchemy_styles::styles::Style;
|
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.
|
/// 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<RSX, Box<Error>> {
|
pub fn diff_and_patch_tree(old: RSX, new: RSX, stretch: &mut Stretch, depth: usize) -> Result<RSX, Box<Error>> {
|
||||||
|
|
@ -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.
|
/// 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<Error>> {
|
pub(crate) fn walk_and_apply_styles(node: &VirtualNode, layout_manager: &mut Stretch) -> Result<(), Box<Error>> {
|
||||||
if let (Some(layout_node), Some(instance)) = (node.layout_node, &node.instance) {
|
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 {
|
if is_native_backed {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
configure_styles(&new_element.props.styles, &mut style);
|
|
||||||
|
|
||||||
let layout_node = stretch.new_node(style, vec![])?;
|
let layout_node = stretch.new_node(style, vec![])?;
|
||||||
new_element.layout_node = Some(layout_node);
|
new_element.layout_node = Some(layout_node);
|
||||||
|
|
|
||||||
|
|
@ -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<General<'d>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct General<'a> {
|
|
||||||
#[serde(borrow)]
|
|
||||||
dirs: Option<Vec<&'a str>>
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `ThemeEngine` controls loading themes and registering associated
|
|
||||||
/// styles.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ThemeEngine {
|
|
||||||
pub dirs: Vec<PathBuf>,
|
|
||||||
pub themes: RwLock<HashMap<String, StyleSheet>>
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<Styles>>);
|
|
||||||
|
|
||||||
impl StyleSheet {
|
|
||||||
/// Creates a new `Stylesheet`.
|
|
||||||
pub fn new(styles: HashMap<&'static str, Vec<Styles>>) -> 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<Styles>, 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
@ -17,3 +17,4 @@ alchemy-styles = { version = "0.1", path = "../styles" }
|
||||||
objc = { version = "0.2.6", optional = true }
|
objc = { version = "0.2.6", optional = true }
|
||||||
objc_id = { version = "0.1.1", optional = true }
|
objc_id = { version = "0.1.1", optional = true }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
uuid = { version = "0.7", features = ["v4"] }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,19 @@
|
||||||
//! It also includes the `RSX` enum, which is what `render()` methods generally
|
//! 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
|
//! return. It's common enough to multiple crates, and is intricately linked to the
|
||||||
//! `Component` lifecycle, so it'll live here.
|
//! `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 error;
|
||||||
pub mod rsx;
|
pub mod rsx;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
|
mod reconciler;
|
||||||
|
use reconciler::RenderEngine;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,6 @@ pub use virtual_text::VirtualText;
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::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<StyleKey>;
|
|
||||||
|
|
||||||
use crate::traits::Component;
|
use crate::traits::Component;
|
||||||
|
|
||||||
/// An enum representing the types of nodes that the
|
/// An enum representing the types of nodes that the
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ use std::sync::{Arc, RwLock};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use alchemy_styles::StylesList;
|
||||||
|
|
||||||
|
use crate::rsx::RSX;
|
||||||
use crate::traits::{Component};
|
use crate::traits::{Component};
|
||||||
use crate::rsx::{RSX, StylesList};
|
|
||||||
|
|
||||||
/// A value stored inside the `attributes` field on a `Props` instance.
|
/// A value stored inside the `attributes` field on a `Props` instance.
|
||||||
/// It shadows `serde_json::Value`, but also allows for some other value
|
/// It shadows `serde_json::Value`, but also allows for some other value
|
||||||
|
|
|
||||||
|
|
@ -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<A>(Vec<A>);
|
|
||||||
|
|
||||||
impl<A> SpacedList<A> {
|
|
||||||
/// Construct an empty `SpacedList`.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
SpacedList(Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> Default for SpacedList<A> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> FromIterator<A> for SpacedList<A> {
|
|
||||||
fn from_iter<I>(iter: I) -> Self
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = A>,
|
|
||||||
{
|
|
||||||
SpacedList(iter.into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: 'a + Clone> FromIterator<&'a A> for SpacedList<A> {
|
|
||||||
fn from_iter<I>(iter: I) -> Self
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = &'a A>,
|
|
||||||
{
|
|
||||||
SpacedList(iter.into_iter().cloned().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: FromStr> From<&'a str> for SpacedList<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::Err: Debug,
|
|
||||||
{
|
|
||||||
fn from(s: &'a str) -> Self {
|
|
||||||
Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> Deref for SpacedList<A> {
|
|
||||||
type Target = Vec<A>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> DerefMut for SpacedList<A> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Display> Display for SpacedList<A> {
|
|
||||||
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<A: Debug> Debug for SpacedList<A> {
|
|
||||||
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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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);
|
|
||||||
|
|
@ -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<A: Ord>(pub BTreeSet<A>);
|
|
||||||
|
|
||||||
impl<A: Ord> SpacedSet<A> {
|
|
||||||
/// Construct an empty `SpacedSet`.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
SpacedSet(BTreeSet::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a value to the `SpacedSet`.
|
|
||||||
pub fn add<T: Into<A>>(&mut self, value: T) -> bool {
|
|
||||||
self.0.insert(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord> Default for SpacedSet<A> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord> FromIterator<A> for SpacedSet<A> {
|
|
||||||
fn from_iter<I>(iter: I) -> Self
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = A>,
|
|
||||||
{
|
|
||||||
SpacedSet(iter.into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: 'a + Ord + Clone> FromIterator<&'a A> for SpacedSet<A> {
|
|
||||||
fn from_iter<I>(iter: I) -> Self
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = &'a A>,
|
|
||||||
{
|
|
||||||
SpacedSet(iter.into_iter().cloned().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: Ord + FromStr> FromStr for SpacedSet<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::Err: Debug,
|
|
||||||
{
|
|
||||||
type Err = <A as FromStr>::Err;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let result: Result<Vec<A>, 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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::Err: Debug,
|
|
||||||
{
|
|
||||||
fn from(s: &'a str) -> Self {
|
|
||||||
Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord> Deref for SpacedSet<A> {
|
|
||||||
type Target = BTreeSet<A>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord> DerefMut for SpacedSet<A> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord + Display> Display for SpacedSet<A> {
|
|
||||||
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<A: Ord + Debug> Debug for SpacedSet<A> {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
|
||||||
f.debug_list().entries(self.0.iter()).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: Ord + FromStr> From<Vec<&'a str>> for SpacedSet<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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<A>
|
|
||||||
where
|
|
||||||
<A as FromStr>::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);
|
|
||||||
|
|
@ -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<S: Into<String>>(id: S) -> Result<Self, &'static str> {
|
|
||||||
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<S: Into<String>>(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<Self, Self::Err> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -69,9 +69,9 @@ pub fn styles(input: TokenStream) -> TokenStream {
|
||||||
body.extend(quote!(styles.insert(#key, vec![#stream]);))
|
body.extend(quote!(styles.insert(#key, vec![#stream]);))
|
||||||
}
|
}
|
||||||
|
|
||||||
quote!(alchemy::theme::StyleSheet::new({
|
quote!(alchemy::StyleSheet::new({
|
||||||
use alchemy::theme::styles::*;
|
use alchemy::style_attributes::*;
|
||||||
use alchemy::theme::color::Color;
|
use alchemy::Color;
|
||||||
let mut styles = std::collections::HashMap::new();
|
let mut styles = std::collections::HashMap::new();
|
||||||
#body
|
#body
|
||||||
styles
|
styles
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,5 @@ cssparser = { version = "0.25.5", optional = true }
|
||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
proc-macro2 = { version = "0.4.24", optional = true }
|
proc-macro2 = { version = "0.4.24", optional = true }
|
||||||
quote = { version = "0.6.10", optional = true }
|
quote = { version = "0.6.10", optional = true }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
toml = "0.5"
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,42 @@
|
||||||
//! Flexbox in Alchemy. For all intents and purposes, you can essentially consider
|
//! 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.
|
//! 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")]
|
#[cfg(feature="parser")]
|
||||||
#[macro_use] pub extern crate cssparser;
|
#[macro_use] pub extern crate cssparser;
|
||||||
|
|
||||||
mod stretch;
|
mod stretch;
|
||||||
pub use stretch::{geometry, node, number, result, Stretch, Error};
|
pub use stretch::{geometry, node, number, result, Stretch, Error};
|
||||||
|
pub use stretch::result::Layout;
|
||||||
|
|
||||||
pub mod color;
|
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<StyleKey>;
|
||||||
|
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
|
pub use styles::{Style, Styles};
|
||||||
|
|
||||||
|
pub mod stylesheet;
|
||||||
|
pub use stylesheet::StyleSheet;
|
||||||
|
|
||||||
#[cfg(feature="parser")]
|
#[cfg(feature="parser")]
|
||||||
pub mod styles_parser;
|
pub mod styles_parser;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref THEME_ENGINE: ThemeEngine = ThemeEngine::new();
|
||||||
|
}
|
||||||
|
|
|
||||||
Reference in a new issue