A few straggling files that got left out of the prior commit
This commit is contained in:
parent
6833e39d52
commit
bb44f31dda
6 changed files with 1494 additions and 0 deletions
425
lifecycle/src/reconciler.rs
Normal file
425
lifecycle/src/reconciler.rs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
//! Implements tree diffing, and attempts to cache Component instances where
|
||||
//! possible.
|
||||
|
||||
use std::sync::Mutex;
|
||||
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;
|
||||
|
||||
use crate::rsx::{RSX, VirtualNode};
|
||||
|
||||
pub struct RenderEngine {
|
||||
pending_state_updates: Mutex<Vec<i32>>,
|
||||
trees: Mutex<HashMap<Uuid, (RSX, Stretch)>>
|
||||
}
|
||||
|
||||
impl RenderEngine {
|
||||
pub(crate) fn new() -> RenderEngine {
|
||||
RenderEngine {
|
||||
pending_state_updates: Mutex::new(vec![]),
|
||||
trees: Mutex::new(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// `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(&self, root: RSX) -> Uuid {
|
||||
let key = Uuid::new_v4();
|
||||
let stretch = Stretch::new();
|
||||
let mut trees = self.trees.lock().unwrap();
|
||||
trees.insert(key, (root, stretch));
|
||||
key
|
||||
}
|
||||
|
||||
/// Given a key, and a new root tree, will diff the tree structure (position, components,
|
||||
/// attributes and so on), and then queue the changes for application to the backing
|
||||
/// framework tree. As it goes through the tree, if a `Component` at a given position
|
||||
/// in the two trees is deemed to be the same, it will move instances from the old tree to
|
||||
/// the new tree before discarding the old tree.
|
||||
///
|
||||
/// This calls the necessary component lifecycles per-component.
|
||||
pub fn diff_and_apply_root(&self, key: &Uuid, new_root: RSX) -> Result<(), Box<Error>> {
|
||||
/*let trees = self.trees.lock().unwrap();
|
||||
let (old_root, stretch) = trees.remove(key)?;
|
||||
diff_and_patch_trees(old_root, new_root, &mut stretch, 0)?;
|
||||
trees.insert(*key, (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<RSX, Box<Error>> {
|
||||
// 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<RSX> 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<RSX> = 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 <Text> 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<Error>> {
|
||||
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<Error>> {
|
||||
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<VirtualNode, Box<Error>> {
|
||||
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 <Fragment>
|
||||
// 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<Error>> {
|
||||
// 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 <Text> 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));
|
||||
}*/
|
||||
123
styles/src/engine.rs
Normal file
123
styles/src/engine.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
//! Implements a Theme Engine. This behaves a bit differently depending on
|
||||
//! the mode your application is compiled in.
|
||||
//!
|
||||
//! - In `debug`, it scans a few places and loads any CSS files that are
|
||||
//! necessary. It will also hot-reload CSS files as they change.
|
||||
//! - In `release`, it scans those same places, and compiles your CSS into
|
||||
//! your resulting binary. The hot-reloading functionality is not in release,
|
||||
//! however it can be enabled if desired.
|
||||
//!
|
||||
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use std::sync::RwLock;
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use toml;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::StylesList;
|
||||
use crate::styles::Style;
|
||||
use crate::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()
|
||||
}
|
||||
}
|
||||
262
styles/src/spacedlist.rs
Normal file
262
styles/src/spacedlist.rs
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
//! 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);
|
||||
293
styles/src/spacedset.rs
Normal file
293
styles/src/spacedset.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
//! 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);
|
||||
83
styles/src/style_keys.rs
Normal file
83
styles/src/style_keys.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
//! 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
|
||||
}
|
||||
}
|
||||
308
styles/src/stylesheet.rs
Normal file
308
styles/src/stylesheet.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
//! Implements a `StyleSheet`, which contains inner logic for
|
||||
//! determining what styles should be applied to a given widget.
|
||||
//!
|
||||
//! You can think of this as a compiled form of your CSS. You generally
|
||||
//! don't need to create these structs yourself, but feel free to if
|
||||
//! you have some creative use.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::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
|
||||
};
|
||||
}
|
||||
}}
|
||||
}
|
||||
Reference in a new issue