Guess I should put this under version control LOL

This commit is contained in:
Ryan McGrath 2019-05-23 22:11:07 -07:00
commit 2035318460
No known key found for this signature in database
GPG key ID: 811674B62B666830
73 changed files with 8836 additions and 0 deletions

19
lifecycle/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "alchemy-lifecycle"
description = "A crate containing traits used in Alchemy, the Rust cross-platform GUI framework."
version = "0.1.0"
edition = "2018"
authors = ["Ryan McGrath <ryan@rymc.io>"]
license = "MPL-2.0+"
repository = "https://github.com/ryanmcgrath/alchemy"
categories = ["gui", "rendering::engine", "multimedia"]
keywords = ["gui", "css", "styles", "layout", "ui"]
[features]
cocoa = ["objc", "objc_id"]
[dependencies]
alchemy-styles = { version = "0.1", path = "../styles" }
objc = { version = "0.2.6", optional = true }
objc_id = { version = "0.1.1", optional = true }
serde_json = "1"

5
lifecycle/README.md Normal file
View file

@ -0,0 +1,5 @@
# Alchemy-Lifecycle
This crate holds traits used in Alchemy, such as `AppDelegate`, `WindowDelegate`, and `Component`. It also holds the RSX node/tag system, since it's standalone and `Component` ends up requiring it anyway.
## Questions, Comments?
Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).

7
lifecycle/src/error.rs Normal file
View file

@ -0,0 +1,7 @@
//! Implements an Error type. Currently we just alias this to
//! Box<Error>, because I'm not sure how this should really look. Consider
//! it an implementation detail hook that could change down the road.
/// A generic Error type that we use. It currently just aliases to `Box<std::error::Error>`,
/// but could change in the future.
pub type Error = Box<std::error::Error>;

13
lifecycle/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
//! Lifecycle aspects for Alchemy.
//!
//! What's a lifecycle? Well, it includes things like delegates (App+Window),
//! where they act as hooks for the system to inform you of events. It includes
//! things like `Component`s, which instruct your views how to exist.
//!
//! It also includes the `RSX` enum, which is what `render()` methods generally
//! return. It's common enough to multiple crates, and is intricately linked to the
//! `Component` lifecycle, so it'll live here.
pub mod error;
pub mod rsx;
pub mod traits;

91
lifecycle/src/rsx/mod.rs Normal file
View file

@ -0,0 +1,91 @@
//! This module holds pieces pertaining to `RSX` element(s), which are lightweight
//! structs that represent how something should be flushed to the screen. Alchemy
//! uses these to build and alter UI; they're typically returned from `render()`
//! methods.
use std::sync::Arc;
use std::fmt::{Debug, Display};
mod virtual_node;
pub use virtual_node::VirtualNode;
mod virtual_text;
pub use virtual_text::VirtualText;
mod props;
pub use props::Props;
mod style_keys;
pub use self::style_keys::StyleKey;
mod spacedlist;
pub use self::spacedlist::SpacedList;
mod spacedset;
pub use self::spacedset::SpacedSet;
pub type StylesList = SpacedSet<StyleKey>;
use crate::traits::Component;
/// An enum representing the types of nodes that the
/// system can work with. `None`, `VirtualText`, or `VirtualNode`.
#[derive(Clone)]
pub enum RSX {
None,
VirtualText(VirtualText),
VirtualNode(VirtualNode)
}
impl RSX {
/// Shorthand method for creating a new `RSX::VirtualNode` instance. Rarely should you call
/// this yourself; the `rsx! {}` macro handles this for you.
pub fn node<F: Fn() -> Box<Component> + Send + Sync + 'static>(tag: &'static str, create_fn: F, props: Props) -> RSX {
RSX::VirtualNode(VirtualNode {
tag: tag,
create_component_fn: Arc::new(create_fn),
instance: None,
layout_node: None,
props: props,
children: vec![]
})
}
/// Shorthand method for creating a new `RSX::VirtualText` instance. Rarely should you call
/// this yourself; the `rsx! {}` and `text!()` macros handle this for you.
pub fn text(s: String) -> RSX {
RSX::VirtualText(VirtualText(s))
}
}
impl IntoIterator for RSX {
type Item = RSX;
type IntoIter = std::vec::IntoIter<RSX>;
/// Turn a single `RSX` node into an iterable instance.
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
impl Display for RSX {
/// Specialized rendering for displaying RSX nodes.
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
RSX::VirtualNode(node) => { std::fmt::Display::fmt(&node, f) },
RSX::VirtualText(text) => { std::fmt::Display::fmt(&text, f) }
RSX::None => { Ok(()) }
}
}
}
impl Debug for RSX {
/// Specialized rendering for debugging RSX nodes.
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
RSX::VirtualNode(node) => { std::fmt::Debug::fmt(&node, f) },
RSX::VirtualText(text) => { std::fmt::Debug::fmt(&text, f) }
RSX::None => { Ok(()) }
}
}
}

View file

@ -0,0 +1,52 @@
//! Implements a Props struct that mostly acts as expected. For arbitrary primitive values,
//! it shadows a `serde_json::Value`.
use serde_json::Value;
use std::collections::HashMap;
use crate::rsx::{RSX, StylesList};
/// A value stored inside the `attributes` field on a `Props` instance.
/// It shadows `serde_json::Value`, but also allows for some other value
/// types common to Alchemy.
#[derive(Clone, Debug)]
pub enum AttributeType {
Value(Value),
//RSX(RSX)
//EventHandler(Box<ComponentEventHandler>)
}
impl<'a> From<&'a str> for AttributeType {
/// Converts a &str to a storable AttributeType.
fn from(f: &str) -> Self {
AttributeType::Value(Value::String(f.to_string()))
}
}
/// Emulates props from React, in a sense. Common keys such as `children`, `key` and `styles`
/// are extracted out for fast access, and everything else found gets put into the `attributes`
/// HashMap.
#[derive(Clone, Debug, Default)]
pub struct Props {
pub attributes: HashMap<&'static str, AttributeType>,
pub children: Vec<RSX>,
pub key: String,
pub styles: StylesList
}
impl Props {
/// Returns a Vec of RSX nodes, which are really just cloned pointers for the most part.
pub fn children(&self) -> Vec<RSX> {
self.children.clone()
}
/// Returns a Option<&AttributeType> from the `attributes` inner HashMap.
pub fn get(&self, key: &str) -> Option<&AttributeType> {
match key {
"children" => { None },
"key" => { None },
"styles" => { None },
_ => { None } //self.attributes.get(key) }
}
}
}

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

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

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

View file

@ -0,0 +1,59 @@
//! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive
//! structure.
use std::sync::Arc;
use std::fmt::{Display, Debug};
use alchemy_styles::node::Node;
use crate::traits::Component;
use crate::rsx::{RSX, Props};
/// A VirtualNode is akin to an `Element` in React terms. Here, we provide a way
/// for lazy `Component` instantiation, along with storage for things like layout nodes,
/// properties, children and so on.
#[derive(Clone)]
pub struct VirtualNode {
/// Used in debugging/printing/etc.
pub tag: &'static str,
/// `Component` instances are created on-demand, if the reconciler deems it be so. This
/// is a closure that should return an instance of the correct type.
pub create_component_fn: Arc<Fn() -> Box<Component> + Send + Sync + 'static>,
/// A cached component instance, which is transferred between trees. Since `Component`
/// instances are lazily created, this is an `Option`, and defaults to `None`.
pub instance: Option<Arc<Component>>,
/// A cached `Node` for computing `Layout` with `Stretch`. Some components may not have
/// a need for layout (e.g, if they don't have a backing node), and thus this is optional.
///
/// The reconciler will handle bridging tree structures as necessary.
pub layout_node: Option<Node>,
/// `Props`, which are to be passed to this `Component` at various lifecycle methods.
pub props: Props,
/// Computed children get stored here.
pub children: Vec<RSX>
}
impl Display for VirtualNode {
/// Special formatting for displaying nodes.
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "<{}>", self.tag)?;
for child in &self.children {
write!(f, "{:?}", child)?;
}
write!(f, "</{}>", self.tag)
}
}
impl Debug for VirtualNode {
/// Special formatting for debugging nodes.
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "VirtualNode({})", self.tag)
}
}

View file

@ -0,0 +1,29 @@
//! Implements `RSX::VirtualText`, which holds data pertaining to <Text>, primarily.
use std::fmt::{Display, Debug};
/// Currently a wrapper for `String`, but could be something else down the road. Frees
/// us from needing to change the public API later.
#[derive(Clone)]
pub struct VirtualText(pub String);
impl VirtualText {
/// Given a `String`, returns a `VirtualText` node.
pub fn new(s: String) -> VirtualText {
VirtualText(s)
}
}
impl Display for VirtualText {
/// Formatting for `VirtualText` display.
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.0)
}
}
impl Debug for VirtualText {
/// Formatting for `VirtualText` debugging.
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "VirtualText({})", self.0)
}
}

183
lifecycle/src/traits.rs Normal file
View file

@ -0,0 +1,183 @@
//! Traits that are used in Alchemy. Alchemy implements a React-based Component
//! lifecycle, coupled with a delegate pattern inspired by those found in AppKit/UIKit.
use std::sync::Arc;
use alchemy_styles::styles::{Layout, Style};
use crate::error::Error;
use crate::rsx::{RSX, Props};
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
#[cfg(feature = "cocoa")]
pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
#[cfg(not(feature = "cocoa"))]
pub type PlatformSpecificNodeType = ();
/// Each platform tends to have their own startup routine, their own runloop, and so on.
/// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system
/// level and allows the user to operate within the established framework per-system.
pub trait AppDelegate: Send + Sync {
/// Fired when an Application is about to finish launching.
fn will_finish_launching(&mut self) {}
/// Fired when an Application has finished launching - this is a good place to, say, show your
/// window.
fn did_finish_launching(&mut self) {}
/// Fired when an Application will become active.
fn will_become_active(&mut self) {}
/// Fired when an Application became active.
fn did_become_active(&mut self) {}
/// Fired when an Application will resign active. You can use this to, say, persist resources
/// or state.
fn will_resign_active(&mut self) {}
/// Fired when an Application has resigned active.
fn did_resign_active(&mut self) {}
/// Fired when an Application is going to terminate. You can use this to, say, instruct the
/// system to "wait a minute, lemme finish".
fn should_terminate(&self) -> bool { true }
/// Fired when the Application has determined "no, you're done, stop the world".
fn will_terminate(&mut self) {}
/// A private trait method that you shouldn't call. This may change or disappear in later
/// releases. Do not rely on this.
fn _window_will_close(&self, _window_id: usize) {}
}
/// Each platform has their own `Window` API, which Alchemy attempts to pair down to one consistent
/// API. This also acts as the bootstrapping point for a `render` tree.
pub trait WindowDelegate: Send + Sync {
/// Fired when this Window will close. You can use this to clean up or destroy resources,
/// timers, and other things.
fn will_close(&mut self) { }
/// Called as the first step in the `render` tree. Every Window contains its own content view
/// that is special, called the root. Widget trees are added to it as necessary, bootstrapped
/// from here.
fn render(&self) -> Result<RSX, Error> { Ok(RSX::None) }
}
pub trait State {}
/// The `Component` lifecycle, mostly inspired from React, with a few extra methods for views that
/// need to have a backing native layer. A good breakdown of the React Component lifecycle can be
/// found [in this tweet](https://twitter.com/dan_abramov/status/981712092611989509?lang=en).
///
/// Alchemy does not currently implement Hooks, and at the moment has no plans to do so (the API
/// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting
/// proposal for this, feel free to open an issue!
pub trait Component: Send + Sync {
/// Indicates whether a Component instance carries a native backing node. If you return `true`
/// from this, the reconciler will opt-in to the native backing layer. Returns `false` by
/// default.
fn has_native_backing_node(&self) -> bool { false }
/// Returns a wrapped-per-platform pointer type that the backing framework tree can use.
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> { None }
/// If you implement a Native-backed component, you'll need to implement this. Given a
/// `component`, you need to instruct the system how to append it to the tree at your point.
fn append_child_component(&self, _component: &Arc<Component>) {}
/// If you implement a Native-backed component, you'll need to implement this. Given a
/// `component`, you need to instruct the system how to replace it in the tree at your point.
fn replace_child_component(&self, _component: Arc<Component>) {}
/// If you implement a Native-backed component, you'll need to implement this. Given a
/// `component`, you need to instruct the system how to remove it from the tree at your point.
fn remove_child_component(&self, _component: Arc<Component>) {}
/// Given a computed `layout`, and an accompanying `Style` (which holds appearance-based
/// styles, like colors), this method should transform them into appropriate calls to the
/// backing native node.
fn apply_styles(&self, _layout: &Layout, _style: &Style) {}
/// Invoked right before calling the render method, both on the initial mount and on subsequent updates.
/// It should return an object to update the state, or null to update nothing.
/// This method exists for rare use cases where the state depends on changes in props over time.
fn get_derived_state_from_props(&self, _props: Props) {}
/// Invoked right before the most recently rendered output is committed to the backing layer tree.
/// It enables your component to capture some information from the tree (e.g. scroll position) before it's
/// potentially changed. Any value returned by this lifecycle will be passed as a parameter
/// to component_did_update().
///
/// This use case is not common, but it may occur in UIs like a chat thread that need to handle scroll
/// position in a special way. A snapshot value (or None) should be returned.
fn get_snapshot_before_update(&self, _props: Props) {}
/// Invoked immediately after a component is mounted (inserted into the tree).
/// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
/// This method is also a good place to set up any subscriptions. If you do that, dont forget to unsubscribe
/// in component_will_unmount().
fn component_did_mount(&mut self, _props: &Props) {}
/// Invoked immediately after updating occurs. This method is not called for the initial render.
/// This is also a good place to do network requests as long as you compare the current props to previous props
/// (e.g. a network request may not be necessary if the props have not changed).
fn component_did_update(&mut self, _props: &Props) {}
/// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this
/// method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that
/// were created in component_did_mount().
///
/// You should not call set state in this method because the component will never be re-rendered. Once a
/// component instance is unmounted, it will never be mounted again.
fn component_will_unmount(&mut self, _props: &Props) {}
/// Invoked after an error has been thrown by a descendant component. Called during the "commit" phase,
/// so side-effects are permitted. It should be used for things like logging errors (e.g,
/// Sentry).
fn component_did_catch(&mut self, _props: &Props/* error: */) {}
/// Use this to let Alchemy know if a components output is not affected by the current change in state
/// or props. The default behavior is to re-render on every state change, and in the vast majority of
/// cases you should rely on the default behavior.
///
/// This is invoked before rendering when new props or state are being received. Defaults to true. This
/// method is not called for the initial render or when force_update() is used. This method only exists
/// as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.
fn should_component_update(&self) -> bool { true }
/// The only required method for a `Component`. Should return a Result of RSX nodes, or an
/// Error (in very rare cases, such as trying to get a key from a strange HashMap or
/// something).
///
/// The render() function should be pure, meaning that it does not modify component state, it
/// returns the same result each time its invoked, and it does not directly interact with the
/// backing rendering framework.
///
/// If you need to interact with the browser, perform your work in component_did_mount() or the other
/// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
///
/// This method is not called if should_component_update() returns `false`.
fn render(&self, _props: &Props) -> Result<RSX, Error> { Ok(RSX::None) }
/// This lifecycle is invoked after an error has been thrown by a descendant component. It receives
/// the error that was thrown as a parameter and should return a value to update state.
///
/// This is called during the "render" phase, so side-effects are not permitted.
/// For those use cases, use component_did_catch() instead.
fn get_derived_state_from_error(&self, _error: ()) {}
/// By default, when your components state or props change, your component will re-render.
/// If your `render()` method depends on some other data, you can tell Alchemy that the component
/// needs re-rendering by calling `force_update()`.
///
/// Calling `force_update()` will cause `render()` to be called on the component, skipping
/// `should_component_update()`. This will trigger the normal lifecycle methods for child components,
/// including the `should_component_update()` method of each child. Alchemy will still only update the
/// backing widget tree if the markup changes.
///
/// Normally, you should try to avoid all uses of `force_update()` and only read from `this.props`
/// and `this.state` in `render()`.
fn force_update(&self) {}
}