diff --git a/404.html b/404.html
deleted file mode 100644
index f7d50b1..0000000
--- a/404.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- File Not Found: 404.
-
-
- Oops!
- File Not Found: 404.
-
-
diff --git a/CNAME b/CNAME
deleted file mode 100644
index daf9254..0000000
--- a/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-alchemy.rs
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..665d640
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[workspace]
+members = [
+ "styles",
+ "macros",
+ "lifecycle",
+ "alchemy",
+ "examples/layout"
+]
+
+[profile.release]
+lto = true
+panic = "abort"
diff --git a/README.md b/README.md
index e76ac28..0100bd6 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,145 @@
-# Alchemy Website
-The source for `alchemy.rs`, which is hosted on GitHub Pages.
+# Notice
+This project is on indefinite hiatus for right now. I appreciate the Rust community's interest in GUI frameworks, but this project is personal for me - I worked on it extensively during a time when my younger brother was battling Leukemia, and so returning to it brings up a lot of things that I prefer to take time dealing with.
-## To Edit...
-This website is a static site, backed by [Zola](https://www.getzola.org/documentation/getting-started/installation/). You'll need to install it to develop it locally. Otherwise... have at it.
+If you're interested in following work I'm doing in the GUI space with regards to Rust, feel free to follow [appkit-rs](https://github.com/ryanmcgrath/appkit-rs), which would end up being one of the underlying layers of this anyway (much the same way that gtk-rs would need to back, well, Gtk).
-If you make substantial changes, make sure the build command outputs to the proper directory:
+
-To build:
+Alchemy - A Rust GUI Framework
+==========================================================
-```
-cd src
-./build
+[](https://crates.io/crates/alchemy)
+
+[Homepage](https://alchemy.rs) • [API Documentation](https://docs.rs/alchemy/)
+
+Alchemy is an _experimental_ Rust GUI Framework, backed by native widgets on each platform it supports, with an API that's a blend of those found in AppKit, UIKit, and React Native. It aims to provide an API that feels at home in Rust, while striving to provide a visual appearance that's easy to scan and parse. It does not, and will never, require nightly. It's still early stages, but feedback and contributions are welcome.
+
+## Supported Platforms
+Alchemy will, ideally, support the platforms listed below. At the moment, the `Cocoa` backend is the most complete, as I develop on a Mac and know the framework more than I'd care to admit. This list will be updated as more frameworks are added.
+
+- `cocoa`, which provides backing widgets, windows and assorted frameworks for `macOS`.
+- `cocoa-touch`, which provides backing widgets, windows and assorted frameworks for `iOS`.
+- `gtk`, which affords a `GTK` layer. This is mostly intended for GNOME users; if you'd like to run it elsewhere, you're on your own.
+- `uwp`, which affords a `"UWP"` layer for Microsoft platforms that support it. This will be a bit of a hack, provided by linking into the [microsoft/WinObjC](https://github.com/Microsoft/WinObjC/) framework, originally intended for porting `iOS` applications to `UWP`. Down the road, if or when a proper `UWP` library for Rust surfaces, I'd be happy to look at replacing this.
+
+Support for more platforms is desired - for example, I think an [`OrbTk`](https://gitlab.redox-os.org/redox-os/orbtk) or [`Piston`](https://www.piston.rs) backend could be cool to see. A `web` backend would be awesome to support. A [`winapi-rs`](https://github.com/retep998/winapi-rs) backend could be cool, too!
+
+## What Currently Works...?
+At the moment, the following is implemented:
+
+- A basic `cocoa` API, which implements the `Application` and `Window` lifecycles. ` `, ` `, and ` ` are supported as well.
+- A basic `reconciliation` module, which handles computing changes to the widget tree and applying them as necessary. It currently follows a design similar to React pre-16; I'm open to changing this if someone wants to collaborate.
+- A CSS parser, based on the work done over in [servo/servo](https://github.com/servo/servo). It doesn't support cascading, and follows an API closer to that of React Native's. This is intentional.
+- An RSX system, based on work done in [bodil/typed-html](https://github.com/bodil/typed-html) by Bodil Stokke. This was actually the project that made me circle back to the entire thing, too.
+- Macros for easy UI construction - `rsx! {}`, which transforms JSX-ish syntax into element trees for the reconciler to work with, and `styles! {}`, which pre-process CSS into their styles.
+- A CSS layout system, based off the work done over in [vislyhq/stretch](https://github.com/vislyhq/stretch). At the moment, this project includes a fork with a newer underlying API by [msiglreith](https://github.com/msiglreith/stretch/tree/index). Once the API is merged upstream, it's likely the dependency would change to `stretch` proper.
+
+You can clone this repo and `cargo run` from the root to see the example app.
+
+## What's it look like?
+``` rust
+use alchemy::{AppDelegate, Error, RSX, rsx, styles, View, Window, WindowDelegate};
+
+struct AppState {
+ window: Window
+}
+
+impl AppDelegate for AppState {
+ fn did_finish_launching(&mut self) {
+ self.window.set_title("Test");
+ self.window.set_dimensions(10., 10., 600., 600.);
+ self.window.show();
+ }
+}
+
+struct WindowState;
+
+impl WindowDelegate for WindowState {
+ fn render(&self) -> Result {
+ Ok(rsx! {
+
+
+
+ })
+ }
+}
+
+fn main() {
+ let app = alchemy::shared_app();
+
+ app.register_styles("default", styles! {
+ box {
+ background-color: #307ace;
+ width: 300;
+ height: 300;
+ margin-top: 10;
+ padding-top: 10;
+ }
+
+ innerbox {
+ background-color: #003366;
+ width: 200;
+ height: 200;
+ }
+ });
+
+ app.run(AppState {
+ window: Window::new(WindowState {
+
+ })
+ });
+}
```
-To run:
+## Does it support custom Components?
+Yes. Alchemy implements the React component lifecycle - although it does not (currently) implement Hooks, and may or may not implement them in the future. The class-based lifecycle maps fairly well to Rust idioms already, as you really never wanted to subclass in React anyway.
-```
-cd src
-zola serve
+A custom component would look like the following:
+
+``` rust
+use alchemy::{Component, ComponentKey, Error, Props, rsx, RSX};
+
+#[derive(Default)]
+pub struct MySpecialWidgetProps;
+
+#[derive(Props)]
+pub struct MySpecialWidget {
+ props: MySpecialWidgetProps
+}
+
+impl Component for MySpecialWidget {
+ fn new(key: ComponentKey) -> MySpecialWidget {
+ MySpecialWidget {}
+ }
+
+ fn component_did_mount(&mut self) {
+ // Do whatever you want. Fire a network request or something, I dunno.
+ }
+
+ fn render(&self, children: Vec) -> Result {
+ Ok(RSX::None)
+ }
+}
```
-## Questions, Comments?
-Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
+Rust allows the lifecycle to have a few cool guarantees that you can't really get in JavaScript - for instance, props don't actually belong to you... but it was a weird aspect of class-based components in JavaScript where you'd be able to arbitrarily call `this.props.whatever`. Function based components actually communicated it better, in that they were passed in - with Rust, it's very clear that you just get a reference.
+
+Alchemy follows [this diagram of React's lifecycle methods](https://twitter.com/dan_abramov/status/981712092611989509) to a T for the most part. What's cool is that methods that shouldn't have side effects, we can call as straight up borrows... and the ones that are allowed to have mutable side effects, we can call them as `&mut self`. You can, of course, still incur side effects by doing something else, but being able to imply the intention directly in the API is kind of cool.
+
+## License
+I'm dual licensing this, due to the licenses that some of the projects it depends on being that. If there's some other (more appropriate) way to do this, please feel free to open an issue.
+
+ * Mozilla Public License, Version 2.0, ([LICENSE-MPL](LICENSE-MPL.md) or https://www.mozilla.org/en-US/MPL/)
+ * MIT License ([LICENSE-MIT](LICENSE-MIT.md) or https://opensource.org/licenses/MIT)
+
+### Contributing
+Before contributing, please read the [contributors guide](https://github.com/ryanmcgrath/alchemy/blob/trunk/CONTRIBUTING.md)
+for useful information about setting up Alchemy locally, coding style and common abbreviations.
+
+Unless you explicitly state otherwise, any contribution you intentionally submit
+for inclusion in the work, should be dual-licensed as above, without any additional terms or conditions.
+
+## Notes
+- Major thanks to [David McNeil](https://github.com/davidMcneil) for graciously allowing me to take the `alchemy` name on crates.io. Hot take, if we had user namespacing, this wouldn't be an issue!
+- Cheers to [diesel-rs/diesel](https://github.com/diesel-rs/diesel), who have a very well laid out repository that a bunch of this structure was cribbed from.
+- Questions or comments that you don't think warrant an issue? Feel free to [poke me over on Twitter](https://twitter.com/ryanmcgrath/) or email me ([ryan@rymc.io](mailto:ryan@rymc.io)).
diff --git a/alchemy/Cargo.toml b/alchemy/Cargo.toml
new file mode 100644
index 0000000..a376ab6
--- /dev/null
+++ b/alchemy/Cargo.toml
@@ -0,0 +1,39 @@
+[package]
+name = "alchemy"
+description = "A cross-platform GUI framework written in Rust. Adapts to native view-layers on each platform. UIKit/React inspired."
+version = "0.2.0"
+edition = "2018"
+authors = ["Ryan McGrath "]
+license = "MPL-2.0+"
+repository = "https://github.com/ryanmcgrath/alchemy"
+categories = ["gui", "rendering::engine", "multimedia"]
+keywords = ["gui", "css", "styles", "layout", "react"]
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[features]
+cocoa = ["alchemy-cocoa", "alchemy-lifecycle/cocoa"]
+
+[dependencies]
+alchemy-cocoa = { version = "0.1", path = "../cocoa", optional = true }
+alchemy-lifecycle = { version = "0.1", path = "../lifecycle" }
+alchemy-macros = { version = "0.1", path = "../macros" }
+alchemy-styles = { version = "0.1", path = "../styles", features = ["parser"] }
+mime = "0.3.13"
+htmlescape = "0.3.1"
+language-tags = "0.2.2"
+lazy_static = "1.3"
+matches = "0.1"
+phf = "0.7"
+proc-macro-hack = "0.5.4"
+proc-macro-nested = "0.1.3"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1"
+strum = "0.15.0"
+strum_macros = "0.15.0"
+toml = "0.5"
+
+[package.metadata.docs.rs]
+features = ["cocoa"]
+default-target = "x86_64-apple-darwin"
diff --git a/alchemy/README.md b/alchemy/README.md
new file mode 100644
index 0000000..80c44de
--- /dev/null
+++ b/alchemy/README.md
@@ -0,0 +1,16 @@
+# Alchemy Core
+This crate implements the core Alchemy application, which is what users ultimately import. Applications are a singleton; some might not like this, but it enables a design pattern that meshes a bit better with existing GUI framework systems and patterns.
+
+The general pattern for developing with Alchemy is as follows:
+
+``` bash
+[Alchemy API] -> [Inner Mutability] -> [Platform Bridge (implemented in other crates)]
+ |
+ |
+ |- [Delegate]
+```
+
+The delegate pattern is cribbed from AppKit/UIKit, where it tends to work quite nicely as a way to respond to system level events.
+
+## Questions, Comments?
+Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
diff --git a/alchemy/src/app.rs b/alchemy/src/app.rs
new file mode 100644
index 0000000..f1230d2
--- /dev/null
+++ b/alchemy/src/app.rs
@@ -0,0 +1,146 @@
+//! This module implements the Application structure and associated
+//! lifecycle methods. You typically never create this struct yourself;
+//! in Alchemy, there's a global `shared_app` that you should use to work
+//! with the `App` struct.
+//!
+//! This ensures that you can respond to application lifecycles, and so
+//! routing things around works correctly.
+
+use std::sync::{Arc, Mutex};
+
+use alchemy_styles::{StyleSheet, THEME_ENGINE};
+use alchemy_lifecycle::traits::AppDelegate;
+
+use crate::window::WindowManager;
+
+#[cfg(feature = "cocoa")]
+pub use alchemy_cocoa::app::{App as PlatformAppBridge};
+
+/// A default delegate that is mostly used for creating the initial struct,
+/// without requiring the actual `AppDelegate` from the user. Will ideally
+/// never see the light of day.
+struct DefaultAppDelegate;
+impl AppDelegate for DefaultAppDelegate {}
+
+/// The Application structure itself. It holds a Mutex'd platform bridge, to
+/// handle communicating with the platform-specific app instance, along with a
+/// delegate to forward events to. The `ThemeEngine` and `WindowManager` are
+/// also stored here for easy access.
+pub struct App {
+ pub(crate) bridge: Mutex>,
+ pub delegate: Mutex>,
+ pub windows: WindowManager
+}
+
+impl App {
+ /// Creates a new app, allocated on the heap. Provides a pointer to
+ /// said allocated instance so that the platform-specific app instances
+ /// can loop events back around.
+ pub(crate) fn new() -> Arc {
+ let app = Arc::new(App {
+ bridge: Mutex::new(None),
+ delegate: Mutex::new(Box::new(DefaultAppDelegate {})),
+ windows: WindowManager::new()
+ });
+
+ let app_ptr: *const App = &*app;
+ app.configure_bridge(app_ptr);
+ app
+ }
+
+ /// Handles providing the app pointer to the inner bridge.
+ pub(crate) fn configure_bridge(&self, ptr: *const App) {
+ let mut bridge = self.bridge.lock().unwrap();
+ *bridge = Some(PlatformAppBridge::new(ptr));
+ }
+
+ /// Convenience method for registering one-off styles. Typically, you would want
+ /// to store your stylesheets as separate files, to enable hot-reloading - but it's
+ /// conceivable that you might want to just have them in your app, too, and this enables
+ /// that use case.
+ pub fn register_styles(&self, theme_key: &str, stylesheet: StyleSheet) {
+ THEME_ENGINE.register_styles(theme_key, stylesheet);
+ }
+
+ /// Runs the app instance, by setting the necessary delegate and forwarding the run call
+ /// to the inner backing application. This is a blocking operation; if you run this, you
+ /// will want to begin your app (for real) in `AppDelegate::did_finish_launching()`.
+ pub fn run(&self, state: S) {
+ {
+ let mut delegate = self.delegate.lock().unwrap();
+ *delegate = Box::new(state);
+ }
+
+ let lock = self.bridge.lock().unwrap();
+ if let Some(bridge) = &*lock {
+ bridge.run();
+ }
+ }
+}
+
+/// Implementing `AppDelegate` for `App` serves two purposes - for one, we're able to
+/// separate the inner implementaton from the abstract one by referring to a trait type, avoiding
+/// a cyclical dependency... and two, it allows us to react to these events on the App layer for
+/// our own purposes, while still forwarding them on to the delegate.
+impl AppDelegate for App {
+ /// Called when the application will finish launching.
+ fn will_finish_launching(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.will_finish_launching();
+ }
+
+ /// Called when the application did finish launching.
+ fn did_finish_launching(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.did_finish_launching();
+ }
+
+ /// Called when the application will become active. We can use this, for instance,
+ /// to resume rendering cycles and so on.
+ fn will_become_active(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.will_become_active();
+ }
+
+ /// Called when the application did become active. We can use this, for instance,
+ /// to resume rendering cycles and so on.
+ fn did_become_active(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.did_become_active();
+ }
+
+ /// Called when the application will resigned active. We can use this, for instance,
+ /// to pause rendering cycles and so on.
+ fn will_resign_active(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.will_resign_active();
+ }
+
+ /// Called when the application has resigned active. We can use this, for instance,
+ /// to pause rendering cycles and so on.
+ fn did_resign_active(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.did_resign_active();
+ }
+
+ /// Called when the application should terminate - we can use it
+ /// to avoid termination if Alchemy needs more time for something,
+ /// for whatever reason.
+ fn should_terminate(&self) -> bool {
+ let delegate = self.delegate.lock().unwrap();
+ delegate.should_terminate()
+ }
+
+ /// Called when the application is about to terminate.
+ fn will_terminate(&mut self) {
+ let mut delegate = self.delegate.lock().unwrap();
+ delegate.will_terminate();
+ }
+
+ /// This is a private method, and you should not attempt to call it or
+ /// rely on it. It exists to enable easy loopback of Window-level events
+ /// on some platforms.
+ fn _window_will_close(&self, window_id: usize) {
+ self.windows.will_close(window_id);
+ }
+}
diff --git a/alchemy/src/components/fragment.rs b/alchemy/src/components/fragment.rs
new file mode 100644
index 0000000..a3c940e
--- /dev/null
+++ b/alchemy/src/components/fragment.rs
@@ -0,0 +1,39 @@
+//! A Fragment is for components that want to return or hoist multiple inner
+//! child nodes. `impl IntoIterator` can't be used in trait returns right now,
+//! and this API more or less matches what React presents, so I'm fine with it...
+//! but as the language stabilizes even further I'd love to get rid of this and
+//! just allow returning arbitrary iterators.
+
+use alchemy_lifecycle::ComponentKey;
+use alchemy_lifecycle::traits::{Component, Props};
+
+pub struct FragmentProps;
+
+/// Fragments are special - you can do something like the following in cases where you
+/// want to render some views without requiring an intermediate view.
+///
+/// ```
+///
+///
+///
+///
+///
+/// ```
+#[derive(Default, Debug)]
+pub struct Fragment;
+
+impl Fragment {
+ pub fn default_props() -> FragmentProps {
+ FragmentProps {}
+ }
+}
+
+impl Props for Fragment {
+ fn set_props(&mut self, _: &mut std::any::Any) {}
+}
+
+impl Component for Fragment {
+ fn new(_: ComponentKey) -> Fragment {
+ Fragment {}
+ }
+}
diff --git a/alchemy/src/components/mod.rs b/alchemy/src/components/mod.rs
new file mode 100644
index 0000000..4015a88
--- /dev/null
+++ b/alchemy/src/components/mod.rs
@@ -0,0 +1,12 @@
+//! This module implements basic core Components (View, Label, etc).
+//! End-users are of course free to implement their own; the core
+//! Components in this module should just be enough to build a
+//! functioning app.
+
+pub mod fragment;
+pub mod view;
+pub mod text;
+
+pub use fragment::Fragment;
+pub use view::View;
+pub use text::Text;
diff --git a/alchemy/src/components/text.rs b/alchemy/src/components/text.rs
new file mode 100644
index 0000000..105a537
--- /dev/null
+++ b/alchemy/src/components/text.rs
@@ -0,0 +1,84 @@
+//! Handles hoisting per-platform specific Text components.
+//! Each platform needs the freedom to do some specific things,
+//! hence why they're all (somewhat annoyingly, but lovingly) re-implemented
+//! as bridges.
+
+use std::sync::{Mutex};
+
+use alchemy_styles::styles::{Appearance, Layout};
+
+use alchemy_lifecycle::ComponentKey;
+use alchemy_lifecycle::error::Error;
+use alchemy_lifecycle::rsx::RSX;
+use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType};
+
+#[cfg(feature = "cocoa")]
+use alchemy_cocoa::text::{Text as PlatformTextBridge};
+
+pub struct TextProps;
+
+/// Text rendering is a complicated mess, and being able to defer to the
+/// backing platform for this is amazing. This is a very common Component.
+///
+/// Views accept styles and event callbacks as props. For example:
+///
+/// ```
+///
+/// ```
+pub struct Text(Mutex);
+
+impl Text {
+ pub fn default_props() -> TextProps { TextProps {} }
+ // This is very naive for now, but it's fine - we probably
+ // want to do some fun stuff here later with stylized text
+ // rendering anyway.
+ //fn compare_and_update_text(&mut self, props: &Props) {
+ /*let text = props.*/
+ //}
+}
+
+impl Props for Text {
+ fn set_props(&mut self, _: &mut std::any::Any) {}
+}
+
+impl Component for Text {
+ fn new(_: ComponentKey) -> Text {
+ Text(Mutex::new(PlatformTextBridge::new()))
+ }
+
+ fn has_native_backing_node(&self) -> bool { true }
+
+ fn borrow_native_backing_node(&self) -> Option {
+ let bridge = self.0.lock().unwrap();
+ Some(bridge.borrow_native_backing_node())
+ }
+
+ // Shouldn't be allowed to have child elements... or, should it?
+ // Panic might not be right here, but eh, should probably do something.
+ //fn append_child_component(&self, _component: &Component) {}
+
+ fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
+ let mut bridge = self.0.lock().unwrap();
+ bridge.apply_styles(appearance, layout);
+ }
+
+ fn component_did_mount(&mut self) {
+ let mut bridge = self.0.lock().unwrap();
+ bridge.render();
+ }
+
+ // This one is a bit tricky, due to the way we have to do props + children in Rust.
+ // Here, we set it as the new text on render(), and then ensure it gets rendered on
+ // `component_did_update()` and `component_did_mount()`.
+ fn render(&self, children: Vec) -> Result {
+ let text = children.iter().map(|child| match child {
+ RSX::VirtualText(s) => s.0.to_owned(),
+ _ => String::new()
+ }).collect::();
+
+ let mut bridge = self.0.lock().unwrap();
+ bridge.set_text(text);
+
+ Ok(RSX::None)
+ }
+}
diff --git a/alchemy/src/components/view.rs b/alchemy/src/components/view.rs
new file mode 100644
index 0000000..a8ccdaf
--- /dev/null
+++ b/alchemy/src/components/view.rs
@@ -0,0 +1,79 @@
+//! Handles hoisting per-platform specific View components.
+//! Each platform needs the freedom to do some specific things,
+//! hence why they're all (somewhat annoyingly, but lovingly) re-implemented
+//! as bridges.
+
+use std::sync::Mutex;
+
+use alchemy_styles::{Appearance, Layout};
+
+use alchemy_lifecycle::ComponentKey;
+use alchemy_lifecycle::error::Error;
+use alchemy_lifecycle::rsx::RSX;
+use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType};
+
+use crate::components::Fragment;
+
+#[cfg(feature = "cocoa")]
+use alchemy_cocoa::view::{View as PlatformViewBridge};
+
+pub struct ViewProps;
+
+/// Views are the most basic piece of the API. If you want to display something, you'll
+/// probably be reaching for a View first and foremost.
+///
+/// Views accept styles and event callbacks as props. For example:
+///
+/// ```
+///
+/// ```
+pub struct View {
+ bridge: Mutex
+}
+
+impl Default for View {
+ fn default() -> View {
+ View {
+ bridge: Mutex::new(PlatformViewBridge::new())
+ }
+ }
+}
+
+impl View {
+ pub fn default_props() -> ViewProps {
+ ViewProps {}
+ }
+}
+
+impl Props for View {
+ fn set_props(&mut self, _: &mut std::any::Any) {}
+}
+
+impl Component for View {
+ fn new(_: ComponentKey) -> View {
+ View::default()
+ }
+
+ fn has_native_backing_node(&self) -> bool { true }
+
+ fn borrow_native_backing_node(&self) -> Option {
+ let bridge = self.bridge.lock().unwrap();
+ Some(bridge.borrow_native_backing_node())
+ }
+
+ fn append_child_node(&self, node: PlatformSpecificNodeType) {
+ let mut bridge = self.bridge.lock().unwrap();
+ bridge.append_child(node);
+ }
+
+ fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
+ let mut bridge = self.bridge.lock().unwrap();
+ bridge.apply_styles(appearance, layout);
+ }
+
+ fn render(&self, children: Vec) -> Result {
+ Ok(RSX::node("Fragment", "".into(), |key| {
+ Box::new(::new(key))
+ }, Box::new(ViewProps {}), children))
+ }
+}
diff --git a/alchemy/src/lib.rs b/alchemy/src/lib.rs
new file mode 100644
index 0000000..3bf0bfd
--- /dev/null
+++ b/alchemy/src/lib.rs
@@ -0,0 +1,49 @@
+//! Alchemy is a Rust GUI framework that implements the React Component lifecycle on top of a
+//! delegate system inspired by those found in AppKit/UIKit. It's backed by native widgets
+//! per-platform, but doesn't bind you to any one design style or visual appearance.
+//!
+//! CSS support (no cascading) provides a familiar syntax for developers who tend to work on
+//! UI/UX projects, and the Component lifecycle is familiar enough to anyone who's touched React.
+
+use std::sync::Arc;
+pub use lazy_static::lazy_static;
+use proc_macro_hack::proc_macro_hack;
+
+pub use alchemy_lifecycle::{ComponentKey, text};
+pub use alchemy_lifecycle::traits::{
+ AppDelegate, Component, Props as ComponentProps, WindowDelegate
+};
+
+pub use alchemy_lifecycle::error::Error;
+pub use alchemy_lifecycle::rsx::{
+ RSX, VirtualNode, VirtualText
+};
+
+#[proc_macro_hack(support_nested)]
+pub use alchemy_macros::rsx;
+
+#[proc_macro_hack]
+pub use alchemy_macros::styles;
+pub use alchemy_macros::Props;
+
+pub use alchemy_styles::{Color, styles as style_attributes, SpacedSet, StyleSheet, StylesList};
+
+mod app;
+use app::App;
+
+pub mod components;
+pub use components::{Fragment, Text, View};
+
+pub mod window;
+pub use window::Window;
+
+lazy_static! {
+ pub(crate) static ref SHARED_APP: Arc = App::new();
+}
+
+/// This function supports calling the shared global application instance from anywhere in your
+/// code. It's useful in cases where you need to have an escape hatch, but if you're using it as
+/// such, you may want to double check your Application design to make sure you need it.
+pub fn shared_app() -> Arc {
+ SHARED_APP.clone()
+}
diff --git a/alchemy/src/window/manager.rs b/alchemy/src/window/manager.rs
new file mode 100644
index 0000000..630c6e0
--- /dev/null
+++ b/alchemy/src/window/manager.rs
@@ -0,0 +1,60 @@
+//! Per-platform windows have their own nuances, and typically, their own windowservers.
+//! We don't want to take away from that, but we do want to avoid scenarios where things get
+//! a bit weird.
+//!
+//! Consider the following: let's say we have a `Window` instantiated in Rust, and we call
+//! `.show()` on it. Then the window drops, on the Rust side. We should probably clean up our side,
+//! right?
+//!
+//! There's also the fact that a user could opt to close a window. If that happens, we want to be
+//! able to remove it from our structure... hence this manager that acts as a lightweight interface
+//! for managing per-platform Window instances.
+
+use std::sync::{Arc, Mutex};
+use crate::window::AppWindow;
+
+/// A struct that provides a Window Manager, via some interior mutability magic.
+pub struct WindowManager(Mutex>>>);
+
+impl WindowManager {
+ /// Creates a new WindowManager instance.
+ pub(crate) fn new() -> WindowManager {
+ WindowManager(Mutex::new(Vec::with_capacity(1)))
+ }
+
+ /// Locks and acquires a new window ID, which our Windows use to loop back for
+ /// events and callbacks.
+ pub(crate) fn allocate_new_window_id(&self) -> usize {
+ let windows = self.0.lock().unwrap();
+ windows.len() + 1
+ }
+
+ /// Adds an `AppWindow` to this instance.
+ pub(crate) fn add(&self, window: Arc>) {
+ let mut windows = self.0.lock().unwrap();
+ if let None = windows.iter().position(|w| Arc::ptr_eq(&w, &window)) {
+ windows.push(window);
+ }
+ }
+
+ /// On a `will_close` event, our delegates will loop back here and notify that a window
+ /// with x id is closing, and should be removed. The `WindowDelegate` `will_close()` event
+ /// is fired here.
+ ///
+ /// At the end of this, the window drops.
+ pub(crate) fn will_close(&self, window_id: usize) {
+ let mut windows = self.0.lock().unwrap();
+ if let Some(index) = windows.iter().position(|window| {
+ let mut w = window.lock().unwrap();
+
+ if w.id == window_id {
+ w.delegate.will_close();
+ return true;
+ }
+
+ false
+ }) {
+ windows.remove(index);
+ }
+ }
+}
diff --git a/alchemy/src/window/mod.rs b/alchemy/src/window/mod.rs
new file mode 100644
index 0000000..5cc5848
--- /dev/null
+++ b/alchemy/src/window/mod.rs
@@ -0,0 +1,7 @@
+//! This module implements Windows and their associated lifecycles.
+
+mod manager;
+pub(crate) use manager::WindowManager;
+
+pub mod window;
+pub use window::{AppWindow, Window};
diff --git a/alchemy/src/window/window.rs b/alchemy/src/window/window.rs
new file mode 100644
index 0000000..9c4a84b
--- /dev/null
+++ b/alchemy/src/window/window.rs
@@ -0,0 +1,170 @@
+//! Implements the Window API. It attempts to provide a nice, common interface across
+//! per-platform Window APIs.
+
+use std::sync::{Arc, Mutex};
+
+use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE};
+use alchemy_lifecycle::rsx::RSX;
+use alchemy_lifecycle::traits::{Component, WindowDelegate};
+
+use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE};
+
+use crate::{App, SHARED_APP};
+use crate::components::View;
+
+#[cfg(feature = "cocoa")]
+use alchemy_cocoa::window::{Window as PlatformWindowBridge};
+
+/// AppWindow contains the inner details of a Window. It's guarded by a Mutex on `Window`,
+/// and you shouldn't create this yourself, but it's documented here so you can understand what
+/// it holds.
+pub struct AppWindow {
+ pub id: usize,
+ pub style_keys: StylesList,
+ pub title: String,
+ pub dimensions: (f64, f64, f64, f64),
+ pub bridge: PlatformWindowBridge,
+ pub delegate: Box,
+ pub render_key: ComponentKey
+}
+
+impl AppWindow {
+ /// Re-renders a window. This method calls `render()` on the `WindowDelegate`, patches it into
+ /// the root tree node, and then diffs the old (current) tree against a new tree by walking it
+ /// and determining what needs to be changed. This also calculates and applies layout and
+ /// styling.
+ ///
+ /// This method is called on the `show` event, and in rare cases can be useful to call
+ /// directly.
+ pub fn render(&mut self) {
+ let mut style = Style::default();
+ let mut appearance = Appearance::default();
+ THEME_ENGINE.configure_styles_for_keys(&self.style_keys, &mut style, &mut appearance);
+
+ self.bridge.apply_styles(&appearance);
+
+ let children = match self.delegate.render() {
+ Ok(opt) => opt,
+ Err(e) => {
+ eprintln!("Error rendering window! {}", e);
+ RSX::None
+ }
+ };
+
+ match RENDER_ENGINE.diff_and_render_root(self.render_key, (
+ self.dimensions.2,
+ self.dimensions.3
+ ), children) {
+ Ok(_) => { }
+ Err(e) => { eprintln!("Error rendering window! {}", e); }
+ }
+ }
+
+ pub fn set_title(&mut self, title: &str) {
+ self.title = title.into();
+ self.bridge.set_title(title);
+ }
+
+ pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
+ self.dimensions = (x, y, width, height);
+ self.bridge.set_dimensions(x, y, width, height);
+ }
+
+ /// Renders and calls through to the native platform window show method.
+ pub fn show(&mut self) {
+ self.render();
+ self.bridge.show();
+ }
+
+ /// Calls through to the native platform window close method.
+ pub fn close(&mut self) {
+ self.bridge.close();
+ }
+}
+
+/// Windows represented... well, a Window. When you create one, you get the Window back. When you
+/// show one, a clone of the pointer is added to the window manager, and removed on close.
+pub struct Window(pub(crate) Arc>);
+
+impl Window {
+ /// Creates a new window.
+ pub fn new(delegate: S) -> Window {
+ let window_id = SHARED_APP.windows.allocate_new_window_id();
+ let view = View::default();
+ let shared_app_ptr: *const App = &**SHARED_APP;
+
+ // This unwrap() is fine, since we implement View ourselves in Alchemy
+ let backing_node = view.borrow_native_backing_node().unwrap();
+ let bridge = PlatformWindowBridge::new(window_id, backing_node, shared_app_ptr);
+
+ let key = match RENDER_ENGINE.register_root_component(view) {
+ Ok(key) => key,
+ Err(_e) => { panic!("Uhhhh this really messed up"); }
+ };
+
+ Window(Arc::new(Mutex::new(AppWindow {
+ id: window_id,
+ style_keys: "".into(),
+ title: "".into(),
+ dimensions: (0., 0., 0., 0.),
+ bridge: bridge,
+ delegate: Box::new(delegate),
+ render_key: key
+ })))
+ }
+
+ /// Renders a window. By default, a window renders nothing - make sure you implement `render()`
+ /// on your `WindowDelegate`. Note that calling `.show()` implicitly calls this for you, so you
+ /// rarely need to call this yourself.
+ pub fn render(&self) {
+ let mut window = self.0.lock().unwrap();
+ window.render();
+ }
+
+ pub fn set_title(&self, title: &str) {
+ let mut window = self.0.lock().unwrap();
+ window.set_title(title);
+ }
+
+ pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
+ let mut window = self.0.lock().unwrap();
+ window.set_dimensions(x, y, width, height);
+ }
+
+ /// Registers this window with the window manager, renders it, and shows it.
+ pub fn show(&self) {
+ SHARED_APP.windows.add(self.0.clone());
+ let mut window = self.0.lock().unwrap();
+ window.show();
+ }
+
+ /// Hides a window. On some platforms, this is minimizing... on others, like macOS, it's
+ /// actually hiding. On mobile, this shouldn't do anything.
+ pub fn hide(&self) {
+
+ }
+
+ /// Closes the window, unregistering it from the window manager in the process and ensuring the
+ /// necessary delegate method(s) are fired.
+ pub fn close(&self) {
+ let window_id = self.0.lock().unwrap().id;
+ SHARED_APP.windows.will_close(window_id);
+ let mut window = self.0.lock().unwrap();
+ window.close();
+ }
+}
+
+impl Clone for Window {
+ /// Clones a `Window` by cloning the inner `AppWindow`.
+ fn clone(&self) -> Window {
+ Window(self.0.clone())
+ }
+}
+
+impl Drop for Window {
+ /// When a `Window` is dropped, we want to ensure that it's closed, so we'll silently call
+ /// `.close()` to be safe.
+ fn drop(&mut self) {
+ self.close();
+ }
+}
diff --git a/assets/README.md b/assets/README.md
new file mode 100644
index 0000000..693af1b
--- /dev/null
+++ b/assets/README.md
@@ -0,0 +1,8 @@
+# Alchemy Assets
+This folder contains assets used for Alchemy (graphics, etc).
+
+## The Logo
+[The potion bottle graphic is a vector graphic from Vecteezy](https://www.vecteezy.com/vector-art/124561-free-magic-item-vector). Interested in contributing logo work? Please get in touch!
+
+## Questions, Comments?
+Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
diff --git a/assets/alchemy_logo_250x.png b/assets/alchemy_logo_250x.png
new file mode 100644
index 0000000..2fea384
Binary files /dev/null and b/assets/alchemy_logo_250x.png differ
diff --git a/assets/social-preview-gh.png b/assets/social-preview-gh.png
new file mode 100644
index 0000000..432fd31
Binary files /dev/null and b/assets/social-preview-gh.png differ
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..9fa08ad
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1,6 @@
+cyclomatic-complexity-threshold = 30
+doc-valid-idents = [
+ "MiB", "GiB", "TiB", "PiB", "EiB",
+ "DirectX", "OpenGL", "TrueType",
+ "GitHub"
+]
diff --git a/cocoa/Cargo.toml b/cocoa/Cargo.toml
new file mode 100644
index 0000000..16d4ff9
--- /dev/null
+++ b/cocoa/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "alchemy-cocoa"
+description = "Cocoa bindings for Alchemy, a cross-platform GUI framework written in Rust."
+version = "0.1.0"
+edition = "2018"
+authors = ["Ryan McGrath "]
+license = "MPL-2.0+"
+repository = "https://github.com/ryanmcgrath/alchemy"
+categories = ["gui", "rendering::engine", "multimedia"]
+keywords = ["gui", "cocoa", "macos", "appkit", "react"]
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+alchemy-lifecycle = { version = "0.1", path = "../lifecycle", features = ["cocoa"] }
+alchemy-styles = { version = "0.1", path = "../styles" }
+objc = "0.2.6"
+objc_id = "0.1.1"
+dispatch = "0.1.4"
+cocoa = "0.18.4"
+core-foundation = "0.6"
+core-graphics = "0.17.1"
+
+[package.metadata.docs.rs]
+features = ["cocoa"]
+default-target = "x86_64-apple-darwin"
diff --git a/cocoa/README.md b/cocoa/README.md
new file mode 100644
index 0000000..3ac6dce
--- /dev/null
+++ b/cocoa/README.md
@@ -0,0 +1,5 @@
+# Alchemy-Cocoa
+This crate implements a backend for Cocoa-based widgets, such as `NSView`, `NSTextField`, and so on. Note that while it's under development currently, the fate of AppKit is still kind of a gray area. If Apple ends up pushing Marzipan as "the" solution, it's possible this might become obsolete, or would run in tandem with the iOS crate for iOS/Marzipan.
+
+## Questions, Comments?
+Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
diff --git a/cocoa/src/app.rs b/cocoa/src/app.rs
new file mode 100644
index 0000000..f2152ea
--- /dev/null
+++ b/cocoa/src/app.rs
@@ -0,0 +1,153 @@
+//! A wrapper for `NSApplication` on macOS. If you opt in to the `cocoa` feature on
+//! Alchemy, this will loop system-level application events back to your `AppDelegate`.
+
+use std::sync::{Once, ONCE_INIT};
+
+use cocoa::base::{id, nil};
+use cocoa::appkit::{NSApplication, NSRunningApplication};
+
+use objc_id::Id;
+use objc::declare::ClassDecl;
+use objc::runtime::{Class, Object, Sel};
+use objc::{msg_send, class, sel, sel_impl};
+
+use alchemy_lifecycle::traits::AppDelegate;
+
+static ALCHEMY_APP_PTR: &str = "alchemyParentAppPtr";
+
+/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
+/// which is where our application instance lives. It also injects an `NSObject` subclass,
+/// which acts as the Delegate, looping back into our Alchemy shared application.
+pub struct App {
+ pub inner: Id,
+ pub delegate: Id
+}
+
+impl App {
+ /// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
+ /// policies), injects an `NSObject` delegate wrapper, and retains everything on the
+ /// Objective-C side of things.
+ pub fn new(parent_app_ptr: *const T) -> Self {
+ let inner = unsafe {
+ let _pool = cocoa::foundation::NSAutoreleasePool::new(nil);
+ let app = cocoa::appkit::NSApp();
+ app.setActivationPolicy_(cocoa::appkit::NSApplicationActivationPolicyRegular);
+ Id::from_ptr(app)
+ };
+
+ let delegate = unsafe {
+ let delegate_class = register_app_delegate_class::();
+ let delegate: id = msg_send![delegate_class, new];
+ (&mut *delegate).set_ivar(ALCHEMY_APP_PTR, parent_app_ptr as usize);
+ msg_send![&*inner, setDelegate:delegate];
+ Id::from_ptr(delegate)
+ };
+
+ App {
+ delegate: delegate,
+ inner: inner
+ }
+ }
+
+ /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
+ pub fn run(&self) {
+ unsafe {
+ let current_app = cocoa::appkit::NSRunningApplication::currentApplication(nil);
+ current_app.activateWithOptions_(cocoa::appkit::NSApplicationActivateIgnoringOtherApps);
+ let shared_app: id = msg_send![class!(NSApplication), sharedApplication];
+ msg_send![shared_app, run];
+ }
+ }
+}
+
+/// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification.
+extern fn will_finish_launching(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).will_finish_launching();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
+extern fn did_finish_launching(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).did_finish_launching();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
+extern fn will_become_active(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).will_become_active();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification.
+extern fn did_become_active(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).did_become_active();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationWillResignActive` notification.
+extern fn will_resign_active(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).will_resign_active();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
+extern fn did_resign_active(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).did_resign_active();
+ };
+}
+
+/// Fires when the Application Delegate receives a `applicationWillTerminate` notification.
+extern fn will_terminate(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
+ let app = app_ptr as *mut T;
+ (*app).will_terminate();
+ };
+}
+
+/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
+/// pointers we need to have.
+fn register_app_delegate_class() -> *const Class {
+ static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
+ static INIT: Once = ONCE_INIT;
+
+ INIT.call_once(|| unsafe {
+ let superclass = Class::get("NSObject").unwrap();
+ let mut decl = ClassDecl::new("AlchemyAppDelegate", superclass).unwrap();
+
+ decl.add_ivar::(ALCHEMY_APP_PTR);
+
+ // Add callback methods
+ decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationWillBecomeActive:), will_become_active:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationDidBecomeActive:), did_become_active:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationWillResignActive:), will_resign_active:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationDidResignActive:), did_resign_active:: as extern fn(&Object, _, _));
+ decl.add_method(sel!(applicationWillTerminate:), will_terminate:: as extern fn(&Object, _, _));
+
+ DELEGATE_CLASS = decl.register();
+ });
+
+ unsafe {
+ DELEGATE_CLASS
+ }
+}
diff --git a/cocoa/src/color.rs b/cocoa/src/color.rs
new file mode 100644
index 0000000..aa285be
--- /dev/null
+++ b/cocoa/src/color.rs
@@ -0,0 +1,29 @@
+//! Implements a conversion method for taking an `alchemy::Color` and turning it into
+//! an `NSColor`.
+
+use objc_id::Id;
+use objc::runtime::Object;
+use objc::{class, msg_send, sel, sel_impl};
+use core_graphics::base::CGFloat;
+
+use alchemy_styles::color::Color;
+
+pub trait IntoNSColor {
+ fn into_nscolor(&self) -> Id;
+}
+
+impl IntoNSColor for Color {
+ /// This creates an NSColor, retains it, and returns it. Dropping this value will
+ /// call `release` on the Objective-C side.
+ fn into_nscolor(&self) -> Id {
+ let red = self.red as CGFloat / 255.0;
+ let green = self.green as CGFloat / 255.0;
+ let blue = self.blue as CGFloat / 255.0;
+ let alpha = self.alpha as CGFloat / 255.0;
+
+ unsafe {
+ Id::from_ptr(msg_send![class!(NSColor), colorWithRed:red green:green blue:blue alpha:alpha])
+ }
+ }
+}
+
diff --git a/cocoa/src/lib.rs b/cocoa/src/lib.rs
new file mode 100644
index 0000000..1900be3
--- /dev/null
+++ b/cocoa/src/lib.rs
@@ -0,0 +1,24 @@
+//! This crate provides a Cocoa backend for Alchemy, the Rust GUI framework.
+//! This means that, on macOS, you'll be using native `NSView`, `NSTextField`,
+//! and other assorted controls. Where possible, it attempts to opt into
+//! smoother rendering paths (e.g, layer-backed views, drawing subview layers
+//! together where appropriate).
+//!
+//! # License
+//!
+//! Copyright 2018 Ryan McGrath. See the license files included in the root repository
+//! for more information, along with credit to applicable parties for who this project
+//! would not have happened.
+//!
+//! # Code of Conduct
+//!
+//! Please note that this project is released with a [Contributor Code of
+//! Conduct][coc]. By participating in this project you agree to abide by its terms.
+//!
+//! [coc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct
+
+pub mod color;
+pub mod app;
+pub mod text;
+pub mod view;
+pub mod window;
diff --git a/cocoa/src/text.rs b/cocoa/src/text.rs
new file mode 100644
index 0000000..2845955
--- /dev/null
+++ b/cocoa/src/text.rs
@@ -0,0 +1,140 @@
+//! This wraps NTextField on macOS, and configures it to act like a label
+//! with standard behavior that most users would expect.
+
+use std::sync::{Once, ONCE_INIT};
+
+use objc_id::{Id, ShareId};
+use objc::{msg_send, sel, sel_impl};
+use objc::declare::ClassDecl;
+use objc::runtime::{Class, Object, Sel, BOOL};
+
+use cocoa::base::{id, nil, YES, NO};
+use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString};
+
+use crate::color::IntoNSColor;
+
+use alchemy_styles::{Color, Layout, Appearance};
+
+use alchemy_lifecycle::traits::PlatformSpecificNodeType;
+
+static ALCHEMY_DELEGATE: &str = "alchemyDelegate";
+
+/// A wrapper for `NSText`. This holds retained pointers for the Objective-C
+/// runtime - namely, the view itself, and associated things such as background
+/// colors and so forth.
+#[derive(Debug)]
+pub struct Text {
+ text: String,
+ inner_mut: Id,
+ inner_share: ShareId,
+ background_color: Id,
+ text_color: Id
+}
+
+impl Text {
+ /// Allocates a new `NSTextField` on the Objective-C side, ensuring that things like coordinate
+ /// flipping occur (macOS still uses (0,0) as lower-left by default), and opting in to layer
+ /// backed views for smoother scrolling.
+ pub fn new() -> Text {
+ let (inner_mut, inner_share) = unsafe {
+ let initial_string = NSString::alloc(nil).init_str("");
+ let view: id = msg_send![register_class(), labelWithString:initial_string];
+ msg_send![view, setSelectable:YES];
+ msg_send![view, setDrawsBackground:YES];
+ msg_send![view, setBezeled:NO];
+ msg_send![view, setEditable:NO];
+ msg_send![view, setWantsLayer:YES];
+ msg_send![view, setLayerContentsRedrawPolicy:1];
+ let x = view.clone();
+ (Id::from_ptr(view), ShareId::from_ptr(x))
+ };
+
+ Text {
+ text: "".into(),
+ inner_mut: inner_mut,
+ inner_share: inner_share,
+ background_color: Color::transparent().into_nscolor(),
+ text_color: Color::transparent().into_nscolor()
+ }
+ }
+
+ /// Returns a pointer to the underlying Objective-C view. The pointer is not mutable; however,
+ /// you can send messages to it (unsafely).
+ pub fn borrow_native_backing_node(&self) -> PlatformSpecificNodeType {
+ self.inner_share.clone()
+ }
+
+ /// Appends a child NSText (or subclassed type) to this view.
+ pub fn append_child(&mut self, child: PlatformSpecificNodeType) {
+ unsafe {
+ msg_send![&*self.inner_mut, addSubview:child];
+ }
+ }
+
+ /// Given a `&Style`, will set the frame, background color, borders and so forth. It then
+ /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this
+ /// view.
+ pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) {
+ unsafe {
+ let rect = NSRect::new(
+ NSPoint::new(layout.location.x.into(), layout.location.y.into()),
+ NSSize::new(layout.size.width.into(), layout.size.height.into())
+ );
+
+ self.background_color = appearance.background_color.into_nscolor();
+ self.text_color = appearance.text_color.into_nscolor();
+
+ msg_send![&*self.inner_mut, setFrame:rect];
+ msg_send![&*self.inner_mut, setBackgroundColor:&*self.background_color];
+ msg_send![&*self.inner_mut, setTextColor:&*self.text_color];
+ }
+ }
+
+ pub fn set_text(&mut self, text: String) {
+ self.text = text;
+ }
+
+ pub fn render(&mut self) {
+ unsafe {
+ let string_value = NSString::alloc(nil).init_str(&self.text);
+ msg_send![&*self.inner_mut, setStringValue:string_value];
+ }
+ }
+}
+
+/// This is used for some specific calls, where macOS NSText needs to be
+/// forcefully dragged into the modern age (e.g, position coordinates from top left...).
+extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
+ return YES;
+}
+
+/// Registers an `NSText` subclass, and configures it to hold some ivars for various things we need
+/// to store.
+fn register_class() -> *const Class {
+ static mut VIEW_CLASS: *const Class = 0 as *const Class;
+ static INIT: Once = ONCE_INIT;
+
+ INIT.call_once(|| unsafe {
+ let superclass = Class::get("NSTextField").unwrap();
+ let mut decl = ClassDecl::new("AlchemyTextField", superclass).unwrap();
+
+ // Force NSText to render from the top-left, not bottom-left
+ //decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
+
+ // Request optimized backing layers
+ //decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
+ //decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
+
+ // Ensure mouse events and so on work
+ //decl.add_method(sel!(acceptsFirstResponder), update_layer as extern fn(&Object, _));
+
+ // A pointer back to our Text, for forwarding mouse + etc events.
+ // Note that NSText's don't really have a "delegate", I'm just using it here
+ // for common terminology sake.
+ decl.add_ivar::(ALCHEMY_DELEGATE);
+
+ VIEW_CLASS = decl.register();
+ });
+
+ unsafe { VIEW_CLASS }
+}
diff --git a/cocoa/src/view.rs b/cocoa/src/view.rs
new file mode 100644
index 0000000..5b123cb
--- /dev/null
+++ b/cocoa/src/view.rs
@@ -0,0 +1,139 @@
+//! Implements a View Component struct. The most common
+//! basic building block of any app. Wraps NSView on macOS.
+
+use std::sync::{Once, ONCE_INIT};
+
+use objc_id::{Id, ShareId};
+use objc::{msg_send, sel, sel_impl};
+use objc::declare::ClassDecl;
+use objc::runtime::{Class, Object, Sel, BOOL};
+
+use cocoa::base::{id, nil, YES};
+use cocoa::foundation::{NSRect, NSPoint, NSSize};
+
+use crate::color::IntoNSColor;
+
+use alchemy_styles::{Appearance, Color, Layout};
+
+use alchemy_lifecycle::traits::PlatformSpecificNodeType;
+
+static ALCHEMY_DELEGATE: &str = "alchemyDelegate";
+static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
+
+/// A wrapper for `NSView`. This holds retained pointers for the Objective-C
+/// runtime - namely, the view itself, and associated things such as background
+/// colors and so forth.
+#[derive(Debug)]
+pub struct View {
+ inner_mut: Id,
+ inner_share: ShareId,
+ background_color: Id
+}
+
+impl View {
+ /// Allocates a new `NSView` on the Objective-C side, ensuring that things like coordinate
+ /// flipping occur (macOS still uses (0,0) as lower-left by default), and opting in to layer
+ /// backed views for smoother scrolling.
+ pub fn new() -> View {
+ let (inner_mut, inner_share) = unsafe {
+ let rect_zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
+ let alloc: id = msg_send![register_class(), alloc];
+ let view: id = msg_send![alloc, initWithFrame:rect_zero];
+ msg_send![view, setWantsLayer:YES];
+ msg_send![view, setLayerContentsRedrawPolicy:1];
+ let x = view.clone();
+ (Id::from_ptr(view), ShareId::from_ptr(x))
+ };
+
+ View {
+ inner_mut: inner_mut,
+ inner_share: inner_share,
+ background_color: Color::transparent().into_nscolor()
+ }
+ }
+
+ /// Returns a pointer to the underlying Objective-C view. The pointer is not mutable; however,
+ /// you can send messages to it (unsafely).
+ pub fn borrow_native_backing_node(&self) -> PlatformSpecificNodeType {
+ self.inner_share.clone()
+ }
+
+ /// Appends a child NSView (or subclassed type) to this view.
+ pub fn append_child(&mut self, child: PlatformSpecificNodeType) {
+ unsafe {
+ msg_send![&*self.inner_mut, addSubview:child];
+ }
+ }
+
+ /// Given a `&Style`, will set the frame, background color, borders and so forth. It then
+ /// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this
+ /// view.
+ pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) {
+ unsafe {
+ let rect = NSRect::new(
+ NSPoint::new(layout.location.x.into(), layout.location.y.into()),
+ NSSize::new(layout.size.width.into(), layout.size.height.into())
+ );
+
+ self.background_color = appearance.background_color.into_nscolor();
+ self.inner_mut.set_ivar(BACKGROUND_COLOR, &*self.background_color);
+
+ msg_send![&*self.inner_mut, setFrame:rect];
+ msg_send![&*self.inner_mut, setNeedsDisplay:YES];
+ }
+ }
+}
+
+/// This is used for some specific calls, where macOS NSView needs to be
+/// forcefully dragged into the modern age (e.g, position coordinates from top left...).
+extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
+ return YES;
+}
+
+/// When an `NSView` has `updateLayer` called, it will get passed through here, at which point we
+/// instruct the layer how it should render (e.g, background color).
+extern fn update_layer(this: &Object, _: Sel) {
+ unsafe {
+ let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
+ if background_color != nil {
+ let layer: id = msg_send![this, layer];
+ let cg: id = msg_send![background_color, CGColor];
+ msg_send![layer, setBackgroundColor:cg];
+ }
+ }
+}
+
+/// Registers an `NSView` subclass, and configures it to hold some ivars for various things we need
+/// to store.
+fn register_class() -> *const Class {
+ static mut VIEW_CLASS: *const Class = 0 as *const Class;
+ static INIT: Once = ONCE_INIT;
+
+ INIT.call_once(|| unsafe {
+ let superclass = Class::get("NSView").unwrap();
+ let mut decl = ClassDecl::new("AlchemyView", superclass).unwrap();
+
+ // Force NSView to render from the top-left, not bottom-left
+ decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
+
+ // Opt-in to AutoLayout
+ //decl.add_method(sel!(requiresConstraintBasedLayout), enforce_normalcy as extern fn(&Object, _) -> BOOL);
+
+ // Request optimized backing layers
+ decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
+ decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
+
+ // Ensure mouse events and so on work
+ //decl.add_method(sel!(acceptsFirstResponder), update_layer as extern fn(&Object, _));
+
+ // A pointer back to our View, for forwarding mouse + etc events.
+ // Note that NSView's don't really have a "delegate", I'm just using it here
+ // for common terminology sake.
+ decl.add_ivar::(ALCHEMY_DELEGATE);
+ decl.add_ivar::(BACKGROUND_COLOR);
+
+ VIEW_CLASS = decl.register();
+ });
+
+ unsafe { VIEW_CLASS }
+}
diff --git a/cocoa/src/window.rs b/cocoa/src/window.rs
new file mode 100644
index 0000000..64b4dd9
--- /dev/null
+++ b/cocoa/src/window.rs
@@ -0,0 +1,169 @@
+//! Implements an `NSWindow` wrapper for MacOS, backed by
+//! Cocoa and associated widgets. This also handles looping back
+//! lifecycle events, such as window resizing or close events.
+
+use std::sync::{Once, ONCE_INIT};
+
+use cocoa::base::{id, nil, YES, NO};
+use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSBackingStoreType};
+use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSAutoreleasePool};
+
+use objc_id::ShareId;
+use objc::declare::ClassDecl;
+use objc::runtime::{Class, Object, Sel};
+use objc::{msg_send, sel, sel_impl};
+
+use alchemy_lifecycle::traits::{AppDelegate, Component};
+use alchemy_styles::Appearance;
+
+static APP_PTR: &str = "alchemyAppPtr";
+static WINDOW_MANAGER_ID: &str = "alchemyWindowManagerID";
+
+/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
+/// where our `NSWindow` and associated delegate live.
+pub struct Window {
+ pub inner: ShareId,
+ pub delegate: ShareId
+}
+
+impl Window {
+ /// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
+ /// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
+ /// pointers.
+ pub fn new(window_id: usize, content_view: ShareId, app_ptr: *const T) -> Window {
+ let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
+
+ let style = NSWindowStyleMask::NSResizableWindowMask |
+ NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask |
+ NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+
+ let inner = unsafe {
+ let window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_(
+ dimensions,
+ style,
+ NSBackingStoreType::NSBackingStoreBuffered,
+ NO
+ ).autorelease();
+
+ msg_send![window, setTitlebarAppearsTransparent:YES];
+ //msg_send![window, setTitleVisibility:1];
+
+ // This is very important! NSWindow is an old class and has some behavior that we need
+ // to disable, like... this. If we don't set this, we'll segfault entirely because the
+ // Objective-C runtime gets out of sync.
+ msg_send![window, setReleasedWhenClosed:NO];
+
+ //if let Some(view_ptr) = content_view.borrow_native_backing_node() {
+ msg_send![window, setContentView:content_view];
+ //}
+
+ ShareId::from_ptr(window)
+ };
+
+ let delegate = unsafe {
+ let delegate_class = register_window_class::();
+ let delegate: id = msg_send![delegate_class, new];
+ (&mut *delegate).set_ivar(APP_PTR, app_ptr as usize);
+ (&mut *delegate).set_ivar(WINDOW_MANAGER_ID, window_id);
+ msg_send![inner, setDelegate:delegate];
+ ShareId::from_ptr(delegate)
+ };
+
+ Window {
+ inner: inner,
+ delegate: delegate
+ }
+ }
+
+ pub fn set_title(&mut self, title: &str) {
+ unsafe {
+ let title = NSString::alloc(nil).init_str(title);
+ msg_send![&*self.inner, setTitle:title];
+ }
+ }
+
+ pub fn set_dimensions(&mut self, x: f64, y: f64, width: f64, height: f64) {
+ unsafe {
+ let dimensions = NSRect::new(
+ NSPoint::new(x.into(), y.into()),
+ NSSize::new(width.into(), height.into())
+ );
+
+ msg_send![&*self.inner, setFrame:dimensions display:YES];
+ }
+ }
+
+ /// Normally used for setting platform-specific styles; on macOS we choose not to do this and
+ /// just have the content view handle the background color, as calling window
+ /// setBackgroundColor causes some notable lag on resizing.
+ pub fn apply_styles(&mut self, _appearance: &Appearance) { }
+
+ /// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the
+ /// most common use case, hence why this method was chosen - if you want or need something
+ /// else, feel free to open an issue to discuss.
+ ///
+ /// You should never be calling this yourself, mind you - Alchemy core handles this for you.
+ pub fn show(&self) {
+ unsafe {
+ msg_send![&*self.inner, makeKeyAndOrderFront:nil];
+ }
+ }
+
+ /// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
+ /// window.
+ ///
+ /// I dunno what else to say here, lol.
+ ///
+ /// You should never be calling this yourself, mind you - Alchemy core handles this for you.
+ pub fn close(&self) {
+ unsafe {
+ msg_send![&*self.inner, close];
+ }
+ }
+}
+
+impl Drop for Window {
+ /// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
+ /// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
+ /// safer than sorry.
+ fn drop(&mut self) {
+ // This bridging link needs to be broken on Drop.
+ unsafe {
+ msg_send![&*self.inner, setDelegate:nil];
+ }
+ }
+}
+
+/// Called when a Window receives a `windowWillClose:` event. Loops back to the shared
+/// Alchemy app instance, so that our window manager can act appropriately.
+extern fn will_close(this: &Object, _: Sel, _: id) {
+ unsafe {
+ let app_ptr: usize = *this.get_ivar(APP_PTR);
+ let window_id: usize = *this.get_ivar(WINDOW_MANAGER_ID);
+ let app = app_ptr as *mut T;
+ (*app)._window_will_close(window_id);
+ };
+}
+
+/// Injects an `NSObject` delegate subclass, with some callback and pointer ivars for what we
+/// need to do.
+fn register_window_class() -> *const Class {
+ static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
+ static INIT: Once = ONCE_INIT;
+
+ INIT.call_once(|| unsafe {
+ let superclass = Class::get("NSObject").unwrap();
+ let mut decl = ClassDecl::new("alchemyWindowDelegateShim", superclass).unwrap();
+
+ decl.add_ivar::(APP_PTR);
+ decl.add_ivar::(WINDOW_MANAGER_ID);
+
+ decl.add_method(sel!(windowWillClose:), will_close:: as extern fn(&Object, _, _));
+
+ DELEGATE_CLASS = decl.register();
+ });
+
+ unsafe {
+ DELEGATE_CLASS
+ }
+}
diff --git a/examples/layout/Cargo.toml b/examples/layout/Cargo.toml
new file mode 100644
index 0000000..c3cc60e
--- /dev/null
+++ b/examples/layout/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "layout"
+version = "0.1.0"
+authors = ["Ryan McGrath "]
+edition = "2018"
+
+[dependencies]
+alchemy = { path = "../../alchemy", version = "0.2.0", features = ["cocoa"] }
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
new file mode 100644
index 0000000..2ea858f
--- /dev/null
+++ b/examples/layout/src/main.rs
@@ -0,0 +1,124 @@
+#![recursion_limit="256"]
+
+/// demo/main.rs
+///
+/// Used to sketch out application structure/feel/etc.
+///
+/// @author Ryan McGrath
+/// @created March 26th, 2019
+
+use alchemy::{
+ AppDelegate, Component, ComponentKey, Fragment, Error, Props, rsx, RSX, styles, text,
+ Text, View, Window, WindowDelegate
+};
+
+pub struct AppState {
+ window: Window
+}
+
+impl AppDelegate for AppState {
+ fn did_finish_launching(&mut self) {
+ self.window.set_title("Layout Test");
+ self.window.set_dimensions(100., 100., 600., 600.);
+ self.window.show();
+ }
+}
+
+#[derive(Default)]
+struct BannerProps {}
+
+#[derive(Props)]
+struct Banner;
+
+impl Component for Banner {
+ fn new(_key: ComponentKey) -> Banner {
+ Banner {}
+ }
+
+ fn render(&self, children: Vec) -> Result {
+ Ok(rsx! {
+
+
+ {children}
+
+ })
+ }
+}
+
+pub struct WindowState;
+
+impl WindowDelegate for WindowState {
+ fn will_close(&mut self) {
+ println!("Closing!?");
+ }
+
+ fn render(&self) -> Result {
+ let messages = vec!["LOL"]; //, "wut", "BERT"];
+ Ok(rsx! {
+
+ "Hello there, my name is Bert"
+
+ {messages.iter().map(|message| rsx! {
+ {text!("{}", message)}
+ })}
+
+
+
+
+
+
+ })
+ }
+}
+
+fn main() {
+ let app = alchemy::shared_app();
+
+ app.register_styles("default", styles! {
+ root { background-color: #000; }
+
+ LOL {
+ background-color: #307ace;
+ width: 500;
+ height: 230;
+ padding-top: 20;
+ padding-left: 20;
+ padding-right: 40;
+ }
+
+ message { width: 500; height: 100; background-color: yellow; color: black; }
+ text { width: 500; height: 100; background-color: blue; color: white; }
+
+ boxxx {
+ background-color: rgba(245, 217, 28, .8);
+ width: 100;
+ height: 100;
+ margin-top: 40;
+ margin-right: 20;
+ }
+
+ box1 {
+ background-color: #f51c69;
+ width: 250;
+ height: 100;
+ }
+
+ wut1 {
+ background-color: black;
+ width: 50;
+ height: 230;
+ }
+
+ innermostBox {
+ background-color: green;
+ width: 20;
+ height: 20;
+ }
+ });
+
+ app.run(AppState {
+ window: Window::new(WindowState {
+
+ })
+ });
+}
diff --git a/index.html b/index.html
deleted file mode 100644
index 941f9b1..0000000
--- a/index.html
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
- Alchemy - A Rust GUI Framework
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-A New Rust GUI Framework
-Alchemy is a Rust GUI Framework, backed by native widgets on each platform it supports, with an API that's a blend of those found in AppKit, UIKit, and React Native. It supports a JSX-ish syntax (RSX), styling with CSS, the safety of building in Rust, and a familiar API for many developers who build UI on a daily basis. The goal is to provide an API that feels at home in Rust, while striving to provide a visual appearance that's easy to scan and parse. It does not, and will never, require nightly. It's still early stages, but feedback and contributions are welcome.
-What's It Look Like?
-
-use alchemy :: {
- AppDelegate, Error, RSX , rsx,
- styles, View, Window, WindowDelegate
- } ;
-
- struct AppState {
- window : Window
- }
-
- impl AppDelegate for AppState {
- fn did_finish_launching ( &mut self ) {
- self . window . set_title ( "LOL" );
- self . window . set_dimensions ( 10. , 10. , 600. , 600. );
- self . window . show ();
- }
-}
-
- struct WindowState;
-
- impl WindowDelegate for WindowState {
- fn render ( & self ) -> Result<RSX, Error> {
- Ok (rsx! {
- < View styles = [ "box" ] >
- < View styles = [ "innerbox" ] />
- </ View >
- } )
- }
-}
-
- fn main () {
- let app = alchemy :: shared_app();
-
- app . register_styles ( "default" , styles! {
- box {
- background - color: # 307ace;
- width: 300 ;
- height: 300 ;
- margin - top: 10 ;
- padding - top: 10 ;
- }
-
- innerbox {
- background - color: # 003366 ;
- width: 200 ;
- height: 200 ;
- }
- } );
-
- app . run (AppState {
- window: Window :: new(WindowState {
-
- } )
- } );
- }
-
-
-
-
- Created by Ryan McGrath . A more complete site for this project will come later. :)
-
-
-
-
-
diff --git a/lifecycle/Cargo.toml b/lifecycle/Cargo.toml
new file mode 100644
index 0000000..a4694db
--- /dev/null
+++ b/lifecycle/Cargo.toml
@@ -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 "]
+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"
diff --git a/lifecycle/README.md b/lifecycle/README.md
new file mode 100644
index 0000000..2cef573
--- /dev/null
+++ b/lifecycle/README.md
@@ -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/).
diff --git a/lifecycle/src/error.rs b/lifecycle/src/error.rs
new file mode 100644
index 0000000..f9c1d35
--- /dev/null
+++ b/lifecycle/src/error.rs
@@ -0,0 +1,7 @@
+//! Implements an Error type. Currently we just alias this to
+//! Box, 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`,
+/// but could change in the future.
+pub type Error = Box;
diff --git a/lifecycle/src/lib.rs b/lifecycle/src/lib.rs
new file mode 100644
index 0000000..17b44a5
--- /dev/null
+++ b/lifecycle/src/lib.rs
@@ -0,0 +1,38 @@
+//! 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.
+//!
+//! This crate also includes the diffing and patching system for the widget tree -
+//! it needs to live with the `Component` lifecycle to enable state updating.
+
+pub use std::sync::Arc;
+
+use alchemy_styles::lazy_static;
+
+pub mod error;
+pub mod rsx;
+pub mod traits;
+
+mod reconciler;
+use reconciler::RenderEngine;
+pub use reconciler::key::ComponentKey;
+
+lazy_static! {
+ pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new();
+}
+
+#[macro_export]
+macro_rules! text {
+ ($t:expr) => {
+ alchemy::RSX::text($t)
+ };
+ ($format:tt, $($tail:expr),*) => {
+ alchemy::RSX::text(format!($format, $($tail),*))
+ };
+}
diff --git a/lifecycle/src/reconciler/error.rs b/lifecycle/src/reconciler/error.rs
new file mode 100644
index 0000000..c3cb923
--- /dev/null
+++ b/lifecycle/src/reconciler/error.rs
@@ -0,0 +1,32 @@
+//! Implements a set of Error types that could happen during a diff/patch/reflow
+//! run. These are mostly internal to the rendering engine itself, but could potentially
+//! show up elsewhere.
+
+use crate::reconciler::key::ComponentKey;
+
+#[derive(Debug)]
+pub enum RenderEngineError {
+ InvalidKey,
+ InvalidRootComponent,
+ InvalidComponentKey(ComponentKey)
+}
+
+impl std::fmt::Display for RenderEngineError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match *self {
+ RenderEngineError::InvalidComponentKey(ref node) => write!(f, "Invalid component key {:?}", node),
+ RenderEngineError::InvalidRootComponent => write!(f, "Invalid component type! Root nodes must be a natively backed node."),
+ RenderEngineError::InvalidKey => write!(f, "An invalid key was passed to the render engine.")
+ }
+ }
+}
+
+impl std::error::Error for RenderEngineError {
+ fn description(&self) -> &str {
+ match *self {
+ RenderEngineError::InvalidComponentKey(_) => "The key is not part of the component storage instance",
+ RenderEngineError::InvalidRootComponent => "The root component must be a natively backed Component instance.",
+ RenderEngineError::InvalidKey => "An invalid key was passed to the render engine."
+ }
+ }
+}
diff --git a/lifecycle/src/reconciler/generic_root_view_stub.rs b/lifecycle/src/reconciler/generic_root_view_stub.rs
new file mode 100644
index 0000000..4658c5f
--- /dev/null
+++ b/lifecycle/src/reconciler/generic_root_view_stub.rs
@@ -0,0 +1,31 @@
+//! This is a generic view used for avoiding a circular dependency issue.
+//! You should never need to touch this.
+
+use std::any::Any;
+
+use crate::ComponentKey;
+use crate::traits::{Component, Props};
+
+#[derive(Default)]
+pub struct GenericRootViewProps;
+
+/// This is never actually created, and is here primarily to avoid a circular
+/// depedency issue (we can't import the View from alchemy's core crate, since the core crate
+/// depends on this crate).
+pub struct GenericRootView;
+
+impl GenericRootView {
+ fn get_default_props() -> GenericRootViewProps {
+ GenericRootViewProps {}
+ }
+}
+
+impl Props for GenericRootView {
+ fn set_props(&mut self, _: &mut Any) {}
+}
+
+impl Component for GenericRootView {
+ fn new(_: ComponentKey) -> GenericRootView {
+ GenericRootView {}
+ }
+}
diff --git a/lifecycle/src/reconciler/instance.rs b/lifecycle/src/reconciler/instance.rs
new file mode 100644
index 0000000..eca423c
--- /dev/null
+++ b/lifecycle/src/reconciler/instance.rs
@@ -0,0 +1,15 @@
+//! Internal struct used for tracking component instances and their
+//! associated metadata (layout, appearance, etc).
+
+use alchemy_styles::{Appearance, StylesList};
+use alchemy_styles::stretch::node::{Node as LayoutNode};
+
+use crate::traits::Component;
+
+pub(crate) struct Instance {
+ pub(crate) tag: &'static str,
+ pub(crate) style_keys: StylesList,
+ pub(crate) component: Box,
+ pub(crate) appearance: Appearance,
+ pub(crate) layout: Option
+}
diff --git a/lifecycle/src/reconciler/key.rs b/lifecycle/src/reconciler/key.rs
new file mode 100644
index 0000000..d348ec8
--- /dev/null
+++ b/lifecycle/src/reconciler/key.rs
@@ -0,0 +1,51 @@
+//! Implements an auto-incrementing ID for Component instances.
+
+use std::sync::Mutex;
+
+use alchemy_styles::lazy_static;
+
+lazy_static! {
+ /// Global stretch instance id allocator.
+ pub(crate) static ref INSTANCE_ALLOCATOR: Mutex = Mutex::new(Allocator::new());
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct Id {
+ id: u32
+}
+
+pub(crate) struct Allocator {
+ new_id: u32
+}
+
+impl Allocator {
+ pub fn new() -> Self {
+ Allocator { new_id: 1 }
+ }
+
+ pub fn allocate(&mut self) -> Id {
+ let id = self.new_id;
+ self.new_id += 1;
+ Id { id: id }
+ }
+}
+
+/// Used as a key for Component storage. Component instances receive these
+/// in their constructor methods, and should retain them as a tool to update their
+/// state.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct ComponentKey {
+ pub(crate) instance: Id,
+ pub(crate) local: Id,
+}
+
+impl ComponentKey {
+ /// A placeholder value, used purely for ensuring the diffing algorithm remains
+ /// readable by reducing some unwrapping hell.
+ pub fn placeholder() -> ComponentKey {
+ ComponentKey {
+ instance: Id { id: 0 },
+ local: Id { id: 0 }
+ }
+ }
+}
diff --git a/lifecycle/src/reconciler/mod.rs b/lifecycle/src/reconciler/mod.rs
new file mode 100644
index 0000000..093b9dc
--- /dev/null
+++ b/lifecycle/src/reconciler/mod.rs
@@ -0,0 +1,382 @@
+//! Implements tree diffing, updating, and so on. Unlike a lot of the VDom implementations
+//! you find littered around the web, this is a bit more ECS-ish, and expects Components to retain
+//! their `ComponentKey` passed in their constructor if they want to update. Doing this
+//! enables us to avoid re-scanning or diffing an entire tree.
+
+use std::sync::Mutex;
+use std::error::Error;
+
+use alchemy_styles::THEME_ENGINE;
+use alchemy_styles::styles::{Appearance, Dimension, Number, Size, Style};
+use alchemy_styles::stretch::node::{Node as LayoutNode, Stretch as LayoutStore};
+
+use crate::rsx::{RSX, VirtualNode};
+use crate::traits::Component;
+
+pub mod key;
+use key::ComponentKey;
+
+pub mod storage;
+use storage::ComponentStore;
+
+pub mod error;
+use error::RenderEngineError;
+
+mod instance;
+use instance::Instance;
+
+mod generic_root_view_stub;
+use generic_root_view_stub::{GenericRootView, GenericRootViewProps};
+
+struct GenericRootProps;
+
+pub struct RenderEngine {
+ queued_state_updates: Mutex>,
+ components: Mutex,
+ layouts: Mutex
+}
+
+impl RenderEngine {
+ pub(crate) fn new() -> RenderEngine {
+ RenderEngine {
+ queued_state_updates: Mutex::new(vec![]),
+ components: Mutex::new(ComponentStore::new()),
+ layouts: Mutex::new(LayoutStore::new())
+ }
+ }
+
+ // pub fn queue_update_for(&self, component_ptr: usize, updater: Box Component + Send + Sync + 'static>) {
+ // }
+
+ /// `Window`'s (or anything "root" in nature) need to register with the
+ /// reconciler for things like setState to work properly. When they do so,
+ /// they get a key back. When they want to instruct the global `RenderEngine`
+ /// to re-render or update their tree, they pass that key and whatever the new tree
+ /// should be.
+ pub fn register_root_component(&self, component: C) -> Result> {
+ // Conceivably, this doesn't NEED to be a thing... but for now it is. If you've stumbled
+ // upon here, wayward traveler, in need of a non-native-root-component, please open an
+ // issue to discuss. :)
+ if !component.has_native_backing_node() {
+ return Err(Box::new(RenderEngineError::InvalidRootComponent {}));
+ }
+
+ let mut component_store = self.components.lock().unwrap();
+ let mut layouts_store = self.layouts.lock().unwrap();
+ let component_key = component_store.new_key();
+ component_store.insert(component_key, Instance {
+ tag: "root",
+ style_keys: "root".into(),
+ component: Box::new(component),
+ appearance: Appearance::default(),
+ layout: Some(layouts_store.new_node(Style::default(), vec![])?)
+ })?;
+
+ Ok(component_key)
+ }
+
+ /// Rendering the root node is a bit different than rendering or updating other nodes, as we
+ /// never want to unmount it, and the results come from a non-`Component` entity (e.g, a
+ /// `Window`). Thus, for this one, we do some manual mucking with what we know is the
+ /// root view (a `Window` or such root component would call this with it's registered
+ /// `ComponentKey`), and then recurse based on the children.
+ pub fn diff_and_render_root(
+ &self,
+ key: ComponentKey,
+ dimensions: (f64, f64),
+ child: RSX
+ ) -> Result<(), Box> {
+ let mut component_store = self.components.lock().unwrap();
+ let mut layout_store = self.layouts.lock().unwrap();
+
+ let new_root_node = RSX::node("root", "root".into(), |_| {
+ Box::new(GenericRootView {})
+ }, Box::new(GenericRootViewProps {}), match child {
+ RSX::VirtualNode(node) => {
+ if node.tag == "Fragment" {
+ node.children
+ } else {
+ vec![RSX::VirtualNode(node)]
+ }
+ },
+
+ _ => vec![]
+ });
+
+ recursively_diff_tree(key, new_root_node, &mut component_store, &mut layout_store)?;
+
+ let layout_node = {
+ let mut root_instance = component_store.get_mut(key)?;
+ let layout = root_instance.layout.unwrap();
+ let mut style = Style::default();
+ THEME_ENGINE.configure_styles_for_keys(&root_instance.style_keys, &mut style, &mut root_instance.appearance);
+ style.size = Size {
+ width: Dimension::Points(dimensions.0 as f32),
+ height: Dimension::Points(dimensions.1 as f32)
+ };
+ layout_store.set_style(layout, style);
+ layout
+ };
+
+ layout_store.compute_layout(layout_node, Size {
+ width: Number::Defined(dimensions.0 as f32),
+ height: Number::Defined(dimensions.1 as f32)
+ })?;
+
+ walk_and_apply_styles(key, &mut component_store, &mut layout_store)?;
+
+ Ok(())
+ }
+}
+
+/// Given two trees, will diff them to see if we need to replace or update. Depending on the
+/// result, we'll either recurse down a level, or tear down and build up a new tree. The final
+/// parameter on this method, `is_root_entity_view`, should only be passed for `Window` or other
+/// such instances, as it instructs us to skip the first level since these ones act different.
+fn recursively_diff_tree(
+ key: ComponentKey,
+ new_tree: RSX,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result<(), Box> {
+ // First we need to determine if this node is being replaced or updated. A replace happens if
+ // two nodes are different types - in this case, we check their tag values. This is also a case
+ // where, for instance, if the RSX tag is `::None` or `::VirtualText`, we'll treat it as
+ // replacing with nothing.
+ let is_replace = match &new_tree {
+ RSX::VirtualNode(new_tree) => {
+ let old_tree = component_store.get(key)?;
+ old_tree.tag != new_tree.tag
+ },
+
+ // The algorithm will know below not to recurse if we're trying to diff text or empty
+ // values. We return false here to avoid entering the `is_replace` phase; `Component`
+ // instances (like ) handle taking the child VirtualText instances and working with
+ // them to pass to a native widget.
+ _ => false
+ };
+
+ if is_replace {
+ unmount_component_tree(key, component_store, layout_store)?;
+ //mount_component_tree(
+ return Ok(());
+ }
+
+ // At this point, we know it's an update pass. Now we need to do a few things:
+ //
+ // - Diff our `props` and figure out what actions we can take or shortcut.
+ // - Let the `Component` instance determine what it should render.
+ // - Recurse into the child trees if necessary.
+ let mut old_children = component_store.children(key)?;
+ old_children.reverse();
+
+ if let RSX::VirtualNode(mut child) = new_tree {
+ for new_child_tree in child.children {
+ match old_children.pop() {
+ // If there's a key in the old children for this position, it's
+ // something we need to update, so let's recurse right back into it.
+ Some(old_child_key) => {
+ recursively_diff_tree(
+ old_child_key,
+ new_child_tree,
+ component_store,
+ layout_store
+ )?;
+ },
+
+ // If there's no matching old key in this position, then we've got a
+ // new component instance to mount. This part now diverts into the Mount
+ // phase.
+ None => {
+ if let RSX::VirtualNode(tr33amimustfeelohlol) = new_child_tree {
+ let new_child_key = mount_component_tree(
+ tr33amimustfeelohlol,
+ component_store,
+ layout_store
+ )?;
+
+ component_store.add_child(key, new_child_key)?;
+ link_layout_nodess(key, new_child_key, component_store, layout_store)?;
+ }
+ }
+ }
+ }
+ }
+
+ // Trim the fat. If we still have child nodes after diffing in the new child trees,
+ // then they're ones that simply need to be unmounted and dropped.
+ if old_children.len() > 0 {
+ for child in old_children {
+ unmount_component_tree(child, component_store, layout_store)?;
+ }
+ }
+
+ Ok(())
+}
+
+/// Given a new `RSX` tree, a `ComponentStore`, and a `LayoutStore`, will recursively construct the
+/// tree, emitting required lifecycle events and persisting values. This happens in an inward-out
+/// fashion, which helps avoid unnecessary reflow in environments where it can get tricky.
+///
+/// This method returns a Result, the `Ok` variant containing a tuple of Vecs. These are the child
+/// Component instances and Layout instances that need to be set in the stores.
+fn mount_component_tree(
+ tree: VirtualNode,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result> {
+ let key = component_store.new_key();
+ let component = (tree.create_component_fn)(key);
+ let is_native_backed = component.has_native_backing_node();
+
+ // let state = get_derived_state_from_props()
+ let mut instance = Instance {
+ tag: tree.tag,
+ style_keys: tree.styles,
+ component: component,
+ appearance: Appearance::default(),
+ layout: None
+ };
+
+ if is_native_backed {
+ let mut style = Style::default();
+ THEME_ENGINE.configure_styles_for_keys(&instance.style_keys, &mut style, &mut instance.appearance);
+ instance.layout = Some(layout_store.new_node(style, vec![])?);
+ }
+
+ let rendered = instance.component.render(tree.children);
+ // instance.get_snapshot_before_update()
+ component_store.insert(key, instance)?;
+
+ match rendered {
+ Ok(child) => if let RSX::VirtualNode(child) = child {
+ // We want to support Components being able to return arbitrary iteratable
+ // elements, but... well, it's not quite that simple. Thus we'll offer a
+ // tag similar to what React does, which just hoists the children out of it and
+ // discards the rest.
+ if child.tag == "Fragment" {
+ for child_tree in child.children {
+ if let RSX::VirtualNode(child_tree) = child_tree {
+ let child_key = mount_component_tree(child_tree, component_store, layout_store)?;
+
+ component_store.add_child(key, child_key)?;
+ if is_native_backed {
+ link_layout_nodess(key, child_key, component_store, layout_store)?;
+ }
+ }
+ }
+ } else {
+ let child_key = mount_component_tree(child, component_store, layout_store)?;
+
+ component_store.add_child(key, child_key)?;
+ if is_native_backed {
+ link_layout_nodess(key, child_key, component_store, layout_store)?;
+ }
+ }
+ },
+
+ 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);
+ }
+ }
+
+ let instance_lol = component_store.get_mut(key)?;
+ instance_lol.component.component_did_mount();
+
+ Ok(key)
+}
+
+/// Given a `ComponentKey`, a `ComponentStore`, and a `LayoutStore`, will recursively walk the tree found at
+/// said key, emitting required lifecycle events and dropping values. This happens in an inward-out
+/// fashion, so deepest nodes/components get destroyed first to ensure that the backing widget tree
+/// doesn't get some weird dangling issue.
+fn unmount_component_tree(
+ key: ComponentKey,
+ component_store: &mut ComponentStore,
+ layout_store: &mut LayoutStore
+) -> Result, Box> {
+ let mut instance = component_store.remove(key)?;
+ instance.component.component_will_unmount();
+
+ let mut layout_nodes = vec![];
+
+ let children = component_store.children(key)?;
+ for child in children {
+ match unmount_component_tree(child, component_store, layout_store) {
+ Ok(mut child_layout_nodes) => {
+ if let Some(parent_layout_node) = instance.layout {
+ for node in child_layout_nodes {
+ layout_store.remove_child(parent_layout_node, node)?;
+ }
+ } else {
+ layout_nodes.append(&mut child_layout_nodes);
+ }
+ },
+
+ Err(e) => { eprintln!("Error unmounting a component tree: {}", e); }
+ }
+ }
+
+ // remove node from backing tree
+
+ Ok(layout_nodes)
+}
+
+/// 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. This could
+/// potentially be done away with some hoisting magic in the `mount()` recursion, but I couldn't
+/// find a pattern that didn't feel like some utter magic in Rust.
+///
+/// It might be because I'm writing this at 3AM. Feel free to improve it.
+fn link_layout_nodess(
+ parent: ComponentKey,
+ child: ComponentKey,
+ components: &mut ComponentStore,
+ layouts: &mut LayoutStore
+) -> Result<(), Box> {
+ if let (Ok(parent_instance), Ok(child_instance)) = (components.get(parent), components.get(child)) {
+ if let (Some(parent_layout), Some(child_layout)) = (parent_instance.layout, child_instance.layout) {
+ layouts.add_child(parent_layout, child_layout)?;
+
+ if let Some(platform_node) = child_instance.component.borrow_native_backing_node() {
+ parent_instance.component.append_child_node(platform_node);
+ }
+
+ return Ok(());
+ }
+ }
+
+ let children = components.children(child)?;
+ for child_key in children {
+ link_layout_nodess(parent, child_key, components, layouts)?;
+ }
+
+ Ok(())
+}
+
+/// Walks the tree and passes necessary Layout and Appearance-based styles to Components so they can
+/// update their backing widgets accordingly. This happens after a layout computation, typically.
+fn walk_and_apply_styles(
+ key: ComponentKey,
+ components: &mut ComponentStore,
+ layouts: &mut LayoutStore
+) -> Result<(), Box> {
+ let instance = components.get_mut(key)?;
+
+ if let Some(layout_key) = instance.layout {
+ instance.component.apply_styles(
+ &instance.appearance,
+ layouts.layout(layout_key)?
+ );
+ }
+
+ for child in components.children(key)? {
+ walk_and_apply_styles(child, components, layouts)?;
+ }
+
+ Ok(())
+}
diff --git a/lifecycle/src/reconciler/storage.rs b/lifecycle/src/reconciler/storage.rs
new file mode 100644
index 0000000..63f087b
--- /dev/null
+++ b/lifecycle/src/reconciler/storage.rs
@@ -0,0 +1,166 @@
+//! Implements storage for Component instances, in a way that allows us to
+//! short-circuit the rendering process so we don't have to re-scan entire
+//! tree structures when updating state.
+
+use std::collections::HashMap;
+
+pub use alchemy_styles::Appearance;
+
+use crate::reconciler::error::{RenderEngineError as Error};
+use crate::reconciler::instance::Instance;
+use crate::reconciler::key::{Allocator, Id, INSTANCE_ALLOCATOR, ComponentKey};
+
+/// This is a clone of a structure you'll also find over in stretch. We do this separately
+/// here for two reasons.
+///
+/// - First, a Component may have children that don't require styles or layout passes. These nodes
+/// should not have `Style` or `Appearance` nodes created, but we do need the correct parent/child
+/// relationships in place.
+/// - The `Storage` pieces of stretch are realistically an implementation detail that we shouldn't
+/// rely on.
+struct Storage(HashMap);
+
+impl Storage {
+ pub fn new() -> Self {
+ Storage(HashMap::new())
+ }
+
+ pub fn get(&self, key: ComponentKey) -> Result<&T, Error> {
+ match self.0.get(&key) {
+ Some(v) => Ok(v),
+ None => Err(Error::InvalidComponentKey(key)),
+ }
+ }
+
+ pub fn get_mut(&mut self, key: ComponentKey) -> Result<&mut T, Error> {
+ match self.0.get_mut(&key) {
+ Some(v) => Ok(v),
+ None => Err(Error::InvalidComponentKey(key)),
+ }
+ }
+
+ pub fn remove(&mut self, key: ComponentKey) -> Result {
+ match self.0.remove(&key) {
+ Some(v) => Ok(v),
+ None => Err(Error::InvalidComponentKey(key))
+ }
+ }
+
+ pub fn insert(&mut self, key: ComponentKey, value: T) -> Option {
+ self.0.insert(key, value)
+ }
+}
+
+impl std::ops::Index<&ComponentKey> for Storage {
+ type Output = T;
+
+ fn index(&self, idx: &ComponentKey) -> &T {
+ &(self.0)[idx]
+ }
+}
+
+pub(crate) struct ComponentStore {
+ id: Id,
+ nodes: Allocator,
+ components: Storage,
+ parents: Storage>,
+ children: Storage>
+}
+
+impl ComponentStore {
+ pub fn new() -> Self {
+ ComponentStore {
+ id: INSTANCE_ALLOCATOR.lock().unwrap().allocate(),
+ nodes: Allocator::new(),
+ components: Storage::new(),
+ parents: Storage::new(),
+ children: Storage::new()
+ }
+ }
+
+ pub fn new_key(&mut self) -> ComponentKey {
+ let local = self.nodes.allocate();
+ ComponentKey { instance: self.id, local }
+ }
+
+ pub fn insert(
+ &mut self,
+ key: ComponentKey,
+ instance: Instance
+ ) -> Result<(), Error> {
+ /*for child in &children {
+ self.parents.get_mut(*child)?.push(key);
+ }*/
+
+ self.components.insert(key, instance);
+ self.parents.insert(key, Vec::with_capacity(1));
+ self.children.insert(key, vec![]); //children);
+
+ Ok(())
+ }
+
+ pub fn remove(&mut self, key: ComponentKey) -> Result {
+ self.parents.remove(key)?;
+ self.children.remove(key)?;
+ self.components.remove(key)
+ }
+
+ pub fn add_child(&mut self, key: ComponentKey, child: ComponentKey) -> Result<(), Error> {
+ self.parents.get_mut(child)?.push(key);
+ self.children.get_mut(key)?.push(child);
+ Ok(())
+ }
+
+ pub fn set_children(&mut self, key: ComponentKey, children: Vec) -> Result<(), Error> {
+ // Remove node as parent from all its current children.
+ for child in self.children.get(key)? {
+ self.parents.get_mut(*child)?.retain(|p| *p != key);
+ }
+
+ *self.children.get_mut(key)? = Vec::with_capacity(children.len());
+
+ // Build up relation node <-> child
+ for child in children {
+ self.parents.get_mut(child)?.push(key);
+ self.children.get_mut(key)?.push(child);
+ }
+
+ Ok(())
+ }
+
+ pub fn remove_child(&mut self, key: ComponentKey, child: ComponentKey) -> Result {
+ match self.children(key)?.iter().position(|n| *n == child) {
+ Some(index) => self.remove_child_at_index(key, index),
+ None => Err(Error::InvalidComponentKey(child)),
+ }
+ }
+
+ pub fn remove_child_at_index(&mut self, key: ComponentKey, index: usize) -> Result {
+ let child = self.children.get_mut(key)?.remove(index);
+ self.parents.get_mut(child)?.retain(|p| *p != key);
+ Ok(child)
+ }
+
+ pub fn replace_child_at_index(&mut self, key: ComponentKey, index: usize, child: ComponentKey) -> Result {
+ self.parents.get_mut(child)?.push(key);
+ let old_child = std::mem::replace(&mut self.children.get_mut(key)?[index], child);
+ self.parents.get_mut(old_child)?.retain(|p| *p != key);
+ Ok(old_child)
+ }
+
+ pub fn children(&self, key: ComponentKey) -> Result, Error> {
+ self.children.get(key).map(Clone::clone)
+ }
+
+ pub fn child_count(&self, key: ComponentKey) -> Result {
+ self.children.get(key).map(Vec::len)
+ }
+
+ pub fn get(&self, key: ComponentKey) -> Result<&Instance, Error> {
+ self.components.get(key)
+ }
+
+ pub fn get_mut(&mut self, key: ComponentKey) -> Result<&mut Instance, Error> {
+ self.components.get_mut(key)
+ }
+}
diff --git a/lifecycle/src/rsx/mod.rs b/lifecycle/src/rsx/mod.rs
new file mode 100644
index 0000000..6278f36
--- /dev/null
+++ b/lifecycle/src/rsx/mod.rs
@@ -0,0 +1,84 @@
+//! 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::any::Any;
+use std::fmt::{Debug, Display};
+
+use alchemy_styles::StylesList;
+
+mod virtual_node;
+pub use virtual_node::VirtualNode;
+
+mod virtual_text;
+pub use virtual_text::VirtualText;
+
+use crate::reconciler::key::ComponentKey;
+use crate::traits::Component;
+
+/// An enum representing the types of nodes that the
+/// system can work with. `None`, `VirtualText`, or `VirtualNode`.
+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(
+ tag: &'static str,
+ styles: StylesList,
+ create_fn: fn(key: ComponentKey) -> Box,
+ props: P,
+ children: Vec
+ ) -> RSX {
+ RSX::VirtualNode(VirtualNode {
+ tag: tag,
+ create_component_fn: create_fn,
+ styles: styles,
+ props: Box::new(props),
+ children: children
+ })
+ }
+
+ /// 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;
+
+ /// 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(()) }
+ }
+ }
+}
diff --git a/lifecycle/src/rsx/props.rs b/lifecycle/src/rsx/props.rs
new file mode 100644
index 0000000..b3a3ea0
--- /dev/null
+++ b/lifecycle/src/rsx/props.rs
@@ -0,0 +1,79 @@
+//! 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 alchemy_styles::StylesList;
+
+use crate::rsx::RSX;
+
+/// 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)
+}
+
+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,
+ pub key: String,
+ pub styles: StylesList
+}
+
+impl Props {
+ /// A helper method for constructing Properties.
+ pub fn new(
+ key: String,
+ styles: StylesList,
+ attributes: HashMap<&'static str, AttributeType>,
+ //children: Vec
+ ) -> Props {
+ Props {
+ attributes: attributes,
+ //children: children,
+ key: key,
+ styles: styles
+ }
+ }
+
+ /*/// A helper method used for constructing root-level Properties.
+ pub(crate) fn root(children: Vec) -> Props {
+ Props {
+ attributes: HashMap::new(),
+ children: children,
+ key: "".into(),
+ styles: "root".into()
+ }
+ }
+
+ /// Returns a Vec of RSX nodes, which are really just cloned pointers for the most part.
+ pub fn children(&self) -> Vec {
+ 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) }
+ }
+ }
+}
diff --git a/lifecycle/src/rsx/virtual_node.rs b/lifecycle/src/rsx/virtual_node.rs
new file mode 100644
index 0000000..aa15630
--- /dev/null
+++ b/lifecycle/src/rsx/virtual_node.rs
@@ -0,0 +1,54 @@
+//! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive
+//! structure.
+
+use std::any::Any;
+use std::fmt::{Display, Debug};
+
+use alchemy_styles::StylesList;
+
+use crate::reconciler::key::ComponentKey;
+use crate::rsx::RSX;
+use crate::traits::Component;
+
+/// A VirtualNode is akin to an `Element` in React terms. Here, we provide a way
+/// for lazy `Component` instantiation, properties, children and so on.
+pub struct VirtualNode {
+ /// Used in debugging/printing/etc.
+ pub tag: &'static str,
+
+ /// Used for determining which CSS styles should be applied to this node.
+ /// This property is accessed often enough that it's separated out here.
+ pub styles: StylesList,
+
+ /// `Component` instances are created on-demand, if the reconciler deems it be so. This
+ /// is a closure that should return an instance of the correct type.
+ pub create_component_fn: fn(key: ComponentKey) -> Box,
+
+ /// When some RSX is returned, we scoop up the props inside a special block, and then shove
+ /// them in here as an `Any` object. When you `derive(Props)` on a `Component` struct, it
+ /// creates a setter that specifically handles downcasting and persisting props for you.
+ pub props: Box,
+
+ /// Child components for this node.
+ pub children: Vec
+}
+
+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)
+ }
+}
diff --git a/lifecycle/src/rsx/virtual_text.rs b/lifecycle/src/rsx/virtual_text.rs
new file mode 100644
index 0000000..1c7814d
--- /dev/null
+++ b/lifecycle/src/rsx/virtual_text.rs
@@ -0,0 +1,29 @@
+//! Implements `RSX::VirtualText`, which holds data pertaining to , 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)
+ }
+}
diff --git a/lifecycle/src/traits.rs b/lifecycle/src/traits.rs
new file mode 100644
index 0000000..1a1d6d6
--- /dev/null
+++ b/lifecycle/src/traits.rs
@@ -0,0 +1,193 @@
+//! 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::any::Any;
+
+use alchemy_styles::styles::{Appearance, Layout};
+
+//use crate::RENDER_ENGINE;
+use crate::error::Error;
+use crate::reconciler::key::ComponentKey;
+use crate::rsx::RSX;
+
+/// A per-platform wrapped Pointer type, used for attaching views/widgets.
+#[cfg(feature = "cocoa")]
+pub type PlatformSpecificNodeType = objc_id::ShareId;
+
+/// A per-platform wrapped Pointer type, used for attaching views/widgets.
+#[cfg(not(feature = "cocoa"))]
+pub type PlatformSpecificNodeType = ();
+
+/*fn update Box + Send + Sync + 'static>(component: &Component, updater: F) {
+ let component_ptr = component as *const C as usize;
+ RENDER_ENGINE.queue_update_for(component_ptr, Box::new(updater));
+}*/
+
+/// Each platform tends to have their own startup routine, their own runloop, and so on.
+/// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system
+/// level and allows the user to operate within the established framework per-system.
+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 { Ok(RSX::None) }
+}
+
+pub trait Props {
+ fn set_props(&mut self, new_props: &mut Any);
+}
+
+/// 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: Props + Send + Sync {
+ fn new(key: ComponentKey) -> Self where Self: Sized;
+
+ /// Indicates whether a Component instance carries a native backing node. If you return `true`
+ /// from this, the reconciler will opt-in to the native backing layer. Returns `false` by
+ /// default.
+ 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 { None }
+
+ /// If you implement a Native-backed component, you'll need to implement this. Given a
+ /// `node`, you need to instruct the system how to append it to the tree at your point.
+ fn append_child_node(&self, _component: PlatformSpecificNodeType) {}
+
+ /// If you implement a Native-backed component, you'll need to implement this. Given a
+ /// `node`, you need to instruct the system how to replace it in the tree at your point.
+ fn replace_child_node(&self, _component: PlatformSpecificNodeType) {}
+
+ /// If you implement a Native-backed component, you'll need to implement this. Given a
+ /// `node`, you need to instruct the system how to remove it from the tree at your point.
+ fn remove_child_node(&self, _component: PlatformSpecificNodeType) {}
+
+ /// Given a configured 'appearance' and computed `layout`, this method should transform them
+ /// into appropriate calls to the backing native node.
+ fn apply_styles(&self, _appearance: &Appearance, _layout: &Layout) {}
+
+ /// Invoked right before calling the render method, both on the initial mount and on subsequent updates.
+ /// It should return an object to update the state, or null to update nothing.
+ /// This method exists for rare use cases where the state depends on changes in props over time.
+ fn get_derived_state_from_props(&self) {}
+
+ /// 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) {}
+
+ /// 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, don’t forget to unsubscribe
+ /// in component_will_unmount().
+ fn component_did_mount(&mut self) {}
+
+ /// 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) {}
+
+ /// 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) {}
+
+ /// 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 /* error: */) {}
+
+ /// Use this to let Alchemy know if a component’s 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 it’s invoked, and it does not directly interact with the
+ /// backing rendering framework.
+ ///
+ /// If you need to interact with the native layer, 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, children: Vec) -> Result { 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 component’s 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) {}
+}
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
new file mode 100644
index 0000000..43be491
--- /dev/null
+++ b/macros/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "alchemy-macros"
+description = "A crate containing macros used in Alchemy, the Rust cross-platform GUI framework."
+version = "0.1.0"
+edition = "2018"
+authors = ["Ryan McGrath "]
+build = "src/build.rs"
+license = "MPL-2.0+"
+repository = "https://github.com/ryanmcgrath/alchemy"
+categories = ["gui", "rendering::engine", "multimedia"]
+keywords = ["gui", "css", "styles", "layout", "ui"]
+
+[lib]
+proc-macro = true
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+ansi_term = "0.11.0"
+lalrpop-util = "0.16.1"
+proc-macro2 = { version = "0.4.24", features = ["nightly"] }
+proc-macro-hack = "0.5.2"
+quote = "0.6.10"
+alchemy-styles = { version = "0.1", path = "../styles", features = ["parser", "tokenize"] }
+syn = "0.15"
+
+[build-dependencies]
+lalrpop = "0.16.1"
+version_check = "0.1.5"
diff --git a/macros/README.md b/macros/README.md
new file mode 100644
index 0000000..6ba7efa
--- /dev/null
+++ b/macros/README.md
@@ -0,0 +1,8 @@
+# Alchemy-Macros
+This crate holds macros for two things, primarily:
+
+- `rsx! {}`, which transforms `` tags into their proper `RSX` calls. Much of this is forked from the awesome work done by [Bodil Stokke in typed-html](https://github.com/bodil/typed-html).
+- `styles! {}`, which transforms CSS style nodes into `Vec`, which the rendering engine uses to theme and style nodes. This relies on the [CSS Parser from Servo](https://github.com/servo/rust-cssparser). Styles do not support cascading; this is a design decision, as inheritance is already a bit of a taboo in Rust, so to do it in styling code feels really odd and involves a mental shift the deeper you go. Opt to apply successive style keys, conditionally if need be, to achieve the same thing with a compositional approach.
+
+## Questions, Comments?
+Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
diff --git a/macros/src/build.rs b/macros/src/build.rs
new file mode 100644
index 0000000..78d2b19
--- /dev/null
+++ b/macros/src/build.rs
@@ -0,0 +1,11 @@
+extern crate lalrpop;
+extern crate version_check;
+
+fn main() {
+ lalrpop::process_root().unwrap();
+
+ if version_check::is_nightly().unwrap_or(false) {
+ println!("cargo:rustc-cfg=can_join_spans");
+ println!("cargo:rustc-cfg=can_show_location_of_runtime_parse_error");
+ }
+}
diff --git a/macros/src/error.rs b/macros/src/error.rs
new file mode 100644
index 0000000..e4a1df5
--- /dev/null
+++ b/macros/src/error.rs
@@ -0,0 +1,116 @@
+use ansi_term::Style;
+use lalrpop_util::ParseError::*;
+use crate::lexer::Token;
+use proc_macro2::{Ident, TokenStream};
+use quote::{quote, quote_spanned};
+
+pub type ParseError = lalrpop_util::ParseError;
+
+#[derive(Debug)]
+pub enum HtmlParseError {
+ TagMismatch { open: Ident, close: Ident },
+}
+
+fn pprint_token(token: &str) -> &str {
+ match token {
+ "BraceGroupToken" => "code block",
+ "LiteralToken" => "literal",
+ "IdentToken" => "identifier",
+ a => a,
+ }
+}
+
+fn pprint_tokens(tokens: &[String]) -> String {
+ let tokens: Vec<&str> = tokens.iter().map(|s| pprint_token(&s)).collect();
+ if tokens.len() > 1 {
+ let start = tokens[..tokens.len() - 1].join(", ");
+ let end = &tokens[tokens.len() - 1];
+ format!("{} or {}", start, end)
+ } else {
+ tokens[0].to_string()
+ }
+}
+
+fn is_in_node_position(tokens: &[String]) -> bool {
+ use std::collections::HashSet;
+ let input: HashSet<&str> = tokens.iter().map(String::as_str).collect();
+ let output: HashSet<&str> = ["\"<\"", "BraceGroupToken", "LiteralToken"]
+ .iter()
+ .cloned()
+ .collect();
+ input == output
+}
+
+pub fn parse_error(input: &[Token], error: &ParseError) -> TokenStream {
+ match error {
+ InvalidToken { location } => {
+ let span = input[*location].span();
+ quote_spanned! {span=>
+ compile_error! { "invalid token" }
+ }
+ }
+ UnrecognizedToken {
+ token: None,
+ expected,
+ } => {
+ let msg = format!(
+ "unexpected end of macro; missing {}",
+ pprint_tokens(&expected)
+ );
+ quote! {
+ compile_error! { #msg }
+ }
+ }
+ UnrecognizedToken {
+ token: Some((_, token, _)),
+ expected,
+ } => {
+ let span = token.span();
+ let error_msg = format!("expected {}", pprint_tokens(&expected));
+ let error = quote_spanned! {span=>
+ compile_error! { #error_msg }
+ };
+ let help = if is_in_node_position(expected) && token.is_ident() {
+ // special case: you probably meant to quote that text
+ let help_msg = format!(
+ "text nodes need to be quoted, eg. {}",
+ Style::new().bold().paint("\"Hello Joe!\"
")
+ );
+ Some(quote_spanned! {span=>
+ compile_error! { #help_msg }
+ })
+ } else {
+ None
+ };
+ quote! {{
+ #error
+ #help
+ }}
+ }
+ ExtraToken {
+ token: (_, token, _),
+ } => {
+ let span = token.span();
+ quote_spanned! {span=>
+ compile_error! { "superfluous token" }
+ }
+ }
+ User {
+ error: HtmlParseError::TagMismatch { open, close },
+ } => {
+ let close_span = close.span();
+ let close_msg = format!("expected closing tag '{}>', found '{}>'", open, close);
+ let close_error = quote_spanned! {close_span=>
+ compile_error! { #close_msg }
+ };
+ let open_span = open.span();
+ let open_error = quote_spanned! {open_span=>
+ compile_error! { "unclosed tag" }
+ };
+ quote! {{
+ #close_error
+ #open_error
+ }}
+ }
+ }
+}
diff --git a/macros/src/grammar.lalrpop b/macros/src/grammar.lalrpop
new file mode 100644
index 0000000..a8f0a2b
--- /dev/null
+++ b/macros/src/grammar.lalrpop
@@ -0,0 +1,303 @@
+use crate::lexer::{self, Token, to_stream};
+use crate::error::HtmlParseError;
+use crate::rsx::{Node, Element};
+//use crate::declare::Declare;
+use crate::map::StringyMap;
+use proc_macro2::{Delimiter, Ident, Literal, Group, TokenTree};
+use lalrpop_util::ParseError;
+use crate::span;
+
+grammar;
+
+/// Match a B separated list of zero or more A, return a list of A.
+Separated: Vec = {
+ B)*> => match e {
+ None => v,
+ Some(e) => {
+ let mut v = v;
+ v.push(e);
+ v
+ }
+ }
+}
+
+/// Match a B separated list of one or more A, return a list of tokens, including the Bs.
+/// Both A and B must resolve to a Token.
+SeparatedInc: Vec = {
+ => {
+ let mut out = Vec::new();
+ for (a, b) in v {
+ out.push(a);
+ out.push(b);
+ }
+ out.push(e);
+ out
+ }
+}
+
+Ident: Ident = IdentToken => {
+ match <> {
+ Token::Ident(ident) => ident,
+ _ => unreachable!()
+ }
+};
+
+Literal: Literal = LiteralToken => {
+ match <> {
+ Token::Literal(literal) => literal,
+ _ => unreachable!()
+ }
+};
+
+GroupToken = {
+ BraceGroupToken,
+ BracketGroupToken,
+ ParenGroupToken,
+};
+
+/// A kebab case HTML ident, converted to a snake case ident.
+HtmlIdent: Ident = {
+ "-")*> => {
+ let mut init = init;
+ init.push(last);
+ let (span, name) = init.into_iter().fold((None, String::new()), |(span, name), token| {
+ (
+ match span {
+ None => Some(token.span().unstable()),
+ Some(span) => {
+ #[cfg(can_join_spans)]
+ {
+ span.join(token.span().unstable())
+ }
+ #[cfg(not(can_join_spans))]
+ {
+ Some(span)
+ }
+ }
+ },
+ if name.is_empty() {
+ name + &token.to_string()
+ } else {
+ name + "_" + &token.to_string()
+ }
+ )
+ });
+ Ident::new(&name, span::from_unstable(span.unwrap()))
+ }
+};
+
+
+
+// The HTML macro
+
+/// An approximation of a Rust expression.
+BareExpression: Token = "&"? (IdentToken ":" ":")* SeparatedInc ParenGroupToken? => {
+ let (reference, left, right, args) = (<>);
+ let mut out = Vec::new();
+ if let Some(reference) = reference {
+ out.push(reference);
+ }
+ for (ident, c1, c2) in left {
+ out.push(ident);
+ out.push(c1);
+ out.push(c2);
+ }
+ out.extend(right);
+ if let Some(args) = args {
+ out.push(args);
+ }
+ Group::new(Delimiter::Brace, to_stream(out)).into()
+};
+
+AttrValue: Token = {
+ LiteralToken,
+ GroupToken,
+ BareExpression,
+};
+
+Attr: (Ident, Token) = "=" => (name, value);
+
+Attrs: StringyMap = Attr* => <>.into();
+
+OpeningTag: (Ident, StringyMap) = "<" ">";
+
+ClosingTag: Ident = "<" "/" ">";
+
+SingleTag: Element = "<" "/" ">" => {
+ Element {
+ name,
+ attributes,
+ children: Vec::new(),
+ }
+};
+
+ParentTag: Element = =>? {
+ let (name, attributes) = opening;
+ let closing_name = closing.to_string();
+ if closing_name == name.to_string() {
+ Ok(Element {
+ name,
+ attributes,
+ children,
+ })
+ } else {
+ Err(ParseError::User { error: HtmlParseError::TagMismatch {
+ open: name.into(),
+ close: closing.into(),
+ }})
+ }
+};
+
+Element = {
+ SingleTag,
+ ParentTag,
+};
+
+TextNode = Literal;
+
+CodeBlock: Group = BraceGroupToken => match <> {
+ Token::Group(_, group) => group,
+ _ => unreachable!()
+};
+
+Node: Node = {
+ Element => Node::Element(<>),
+ TextNode => Node::Text(<>),
+ CodeBlock => Node::Block(<>),
+};
+
+pub NodeWithType: (Node, Option>) = {
+ Node => (<>, None),
+ ":" => {
+ let (node, spec) = (<>);
+ (node, Some(spec))
+ },
+};
+
+
+// The declare macro
+
+TypePath: Vec = {
+ IdentToken => vec![<>],
+ TypePath ":" ":" IdentToken => {
+ let (mut path, c1, c2, last) = (<>);
+ path.push(c1);
+ path.push(c2);
+ path.push(last);
+ path
+ }
+};
+
+Reference: Vec = "&" ("'" IdentToken)? => {
+ let (amp, lifetime) = (<>);
+ let mut out = vec![amp];
+ if let Some((tick, ident)) = lifetime {
+ out.push(tick);
+ out.push(ident);
+ }
+ out
+};
+
+TypeArgs: Vec = {
+ TypeSpec,
+ TypeArgs "," TypeSpec => {
+ let (mut args, comma, last) = (<>);
+ args.push(comma);
+ args.extend(last);
+ args
+ }
+};
+
+TypeArgList: Vec = "<" TypeArgs ">" => {
+ let (left, mut args, right) = (<>);
+ args.insert(0, left);
+ args.push(right);
+ args
+};
+
+FnReturnType: Vec = "-" ">" TypeSpec => {
+ let (dash, right, spec) = (<>);
+ let mut out = vec![dash, right];
+ out.extend(spec);
+ out
+};
+
+FnArgList: Vec = ParenGroupToken FnReturnType? => {
+ let (args, rt) = (<>);
+ let mut out = vec![args];
+ if let Some(rt) = rt {
+ out.extend(rt);
+ }
+ out
+};
+
+TypeArgSpec = {
+ TypeArgList,
+ FnArgList,
+};
+
+TypeSpec: Vec = Reference? TypePath TypeArgSpec? => {
+ let (reference, path, args) = (<>);
+ let mut out = Vec::new();
+ if let Some(reference) = reference {
+ out.extend(reference);
+ }
+ out.extend(path);
+ if let Some(args) = args {
+ out.extend(args);
+ }
+ out
+};
+
+TypeDecl: (Ident, Vec) = ":" ;
+
+TypeDecls: Vec<(Ident, Vec)> = {
+ TypeDecl => vec![<>],
+ "," => {
+ let mut decls = decls;
+ decls.push(decl);
+ decls
+ },
+};
+
+Attributes = "{" ","? "}";
+
+TypePathList = "[" > "]";
+
+IdentList = "[" > "]";
+
+Groups = "in" ;
+
+Children: (Option>) = "with" => {
+ opt
+};
+
+extern {
+ type Location = usize;
+ type Error = HtmlParseError;
+
+ enum lexer::Token {
+ "<" => Token::Punct('<', _),
+ ">" => Token::Punct('>', _),
+ "/" => Token::Punct('/', _),
+ "=" => Token::Punct('=', _),
+ "-" => Token::Punct('-', _),
+ ":" => Token::Punct(':', _),
+ "." => Token::Punct('.', _),
+ "," => Token::Punct(',', _),
+ "&" => Token::Punct('&', _),
+ "'" => Token::Punct('\'', _),
+ ";" => Token::Punct(';', _),
+ "{" => Token::GroupOpen(Delimiter::Brace, _),
+ "}" => Token::GroupClose(Delimiter::Brace, _),
+ "[" => Token::GroupOpen(Delimiter::Bracket, _),
+ "]" => Token::GroupClose(Delimiter::Bracket, _),
+ "in" => Token::Keyword(lexer::Keyword::In, _),
+ "with" => Token::Keyword(lexer::Keyword::With, _),
+ IdentToken => Token::Ident(_),
+ LiteralToken => Token::Literal(_),
+ ParenGroupToken => Token::Group(Delimiter::Parenthesis, _),
+ BraceGroupToken => Token::Group(Delimiter::Brace, _),
+ BracketGroupToken => Token::Group(Delimiter::Bracket, _),
+ }
+}
diff --git a/macros/src/ident.rs b/macros/src/ident.rs
new file mode 100644
index 0000000..db92caa
--- /dev/null
+++ b/macros/src/ident.rs
@@ -0,0 +1,20 @@
+//! Utility functions, originally written by Bodil Stokke
+//! over in [typed-html](https://github.com/bodil/typed-html).
+
+use proc_macro2::{Ident, Span, TokenStream, TokenTree};
+
+use std::str::FromStr;
+
+pub fn new_raw(string: &str, span: Span) -> Ident {
+ // Validate that it is an ident.
+ let _ = Ident::new(string, span);
+
+ let s = format!("r#{}", string);
+ let tts = TokenStream::from_str(&s).unwrap();
+ let mut ident = match tts.into_iter().next().unwrap() {
+ TokenTree::Ident(ident) => ident,
+ _ => unreachable!(),
+ };
+ ident.set_span(span);
+ ident
+}
diff --git a/macros/src/lexer.rs b/macros/src/lexer.rs
new file mode 100644
index 0000000..7838597
--- /dev/null
+++ b/macros/src/lexer.rs
@@ -0,0 +1,142 @@
+//! Implements the Lexer used for parsing RSX, originally
+//! written by Bodil Stokke over in
+//! [typed-html](https://github.com/bodil/typed-html).
+
+use crate::error::HtmlParseError;
+use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
+
+use std::iter::FromIterator;
+
+pub type Spanned = Result<(Loc, Tok, Loc), Error>;
+
+#[derive(Clone, Debug)]
+pub enum Token {
+ Ident(Ident),
+ Literal(Literal),
+ Punct(char, Punct),
+ Group(Delimiter, Group),
+ GroupOpen(Delimiter, Span),
+ GroupClose(Delimiter, Span),
+ Keyword(Keyword, Ident),
+}
+
+impl Token {
+ pub fn span(&self) -> Span {
+ match self {
+ Token::Ident(ident) => ident.span(),
+ Token::Literal(literal) => literal.span(),
+ Token::Punct(_, punct) => punct.span(),
+ Token::Group(_, group) => group.span(),
+ Token::GroupOpen(_, span) => *span,
+ Token::GroupClose(_, span) => *span,
+ Token::Keyword(_, ident) => ident.span(),
+ }
+ }
+
+ pub fn is_ident(&self) -> bool {
+ match self {
+ Token::Ident(_) => true,
+ _ => false,
+ }
+ }
+}
+
+impl From for TokenTree {
+ fn from(token: Token) -> Self {
+ match token {
+ Token::Ident(ident) => TokenTree::Ident(ident),
+ Token::Literal(literal) => TokenTree::Literal(literal),
+ Token::Punct(_, punct) => TokenTree::Punct(punct),
+ Token::Group(_, group) => TokenTree::Group(group),
+ Token::GroupOpen(_, _) => panic!("Can't convert a GroupOpen token to a TokenTree"),
+ Token::GroupClose(_, _) => panic!("Can't convert a GroupClose token to a TokenTree"),
+ Token::Keyword(_, ident) => TokenTree::Ident(ident),
+ }
+ }
+}
+
+impl From for TokenStream {
+ fn from(token: Token) -> Self {
+ TokenStream::from_iter(vec![TokenTree::from(token)])
+ }
+}
+
+impl From for Token {
+ fn from(ident: Ident) -> Self {
+ Token::Ident(ident)
+ }
+}
+
+impl From for Token {
+ fn from(literal: Literal) -> Self {
+ Token::Literal(literal)
+ }
+}
+
+impl From for Token {
+ fn from(punct: Punct) -> Self {
+ Token::Punct(punct.as_char(), punct)
+ }
+}
+
+impl From for Token {
+ fn from(group: Group) -> Self {
+ Token::Group(group.delimiter(), group)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Keyword {
+ In,
+ With,
+}
+
+pub fn to_stream>(tokens: I) -> TokenStream {
+ let mut stream = TokenStream::new();
+ stream.extend(tokens.into_iter().map(TokenTree::from));
+ stream
+}
+
+pub fn unroll_stream(stream: TokenStream, deep: bool) -> Vec {
+ let mut vec = Vec::new();
+ for tt in stream {
+ match tt {
+ TokenTree::Ident(ident) => vec.push(ident.into()),
+ TokenTree::Literal(literal) => vec.push(literal.into()),
+ TokenTree::Punct(punct) => vec.push(punct.into()),
+ TokenTree::Group(ref group) if deep && group.delimiter() != Delimiter::Parenthesis => {
+ vec.push(Token::GroupOpen(group.delimiter(), group.span()));
+ let sub = unroll_stream(group.stream(), deep);
+ vec.extend(sub);
+ vec.push(Token::GroupClose(group.delimiter(), group.span()));
+ }
+ TokenTree::Group(group) => vec.push(group.into()),
+ }
+ }
+ vec
+}
+
+pub struct Lexer<'a> {
+ stream: &'a [Token],
+ pos: usize,
+}
+
+impl<'a> Lexer<'a> {
+ pub fn new(stream: &'a [Token]) -> Self {
+ Lexer { stream, pos: 0 }
+ }
+}
+
+impl<'a> Iterator for Lexer<'a> {
+ type Item = Spanned;
+
+ fn next(&mut self) -> Option {
+ match self.stream.get(self.pos) {
+ None => None,
+ Some(token) => {
+ self.pos += 1;
+ Some(Ok((self.pos - 1, token.clone(), self.pos)))
+ }
+ }
+ }
+}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
new file mode 100644
index 0000000..60e31e0
--- /dev/null
+++ b/macros/src/lib.rs
@@ -0,0 +1,107 @@
+#![recursion_limit = "128"]
+#![cfg_attr(can_show_location_of_runtime_parse_error, feature(proc_macro_span))]
+
+//! Implements macros used in Alchemy.
+//!
+//! - `rsx! {}`, which turns RSX tags into `RSX` node trees.
+//! - `styles! {}`, which turns CSS stylesheet strings into `Vec`.
+//!
+//! In general, you should prefer using these to constructing the above values manually.
+//!
+//! Much of the `rsx! {}` support is achieved by forking code riginally written by Bodil Stokke
+//! over in [typed-html](https://github.com/bodil/typed-html).
+
+extern crate proc_macro;
+
+mod error;
+mod rsx;
+mod ident;
+mod lexer;
+mod map;
+mod parser;
+mod span;
+
+use proc_macro::TokenStream;
+use proc_macro2::{Ident, TokenStream as TokenStream2, Literal, Span};
+use proc_macro_hack::proc_macro_hack;
+use quote::quote;
+use syn::{DeriveInput, parse_macro_input};
+
+use alchemy_styles::cssparser::{Parser, ParserInput, RuleListParser};
+use alchemy_styles::styles_parser::{Rule, RuleParser};
+
+/// Implements the `rsx! {}` macro, which turns RSX tags into `RSX` node trees.
+#[proc_macro_hack]
+pub fn rsx(input: TokenStream) -> TokenStream {
+ let stream = lexer::unroll_stream(input.into(), false);
+ let result = rsx::expand_rsx(&stream);
+ TokenStream::from(match result {
+ Err(err) => error::parse_error(&stream, &err),
+ Ok((node, ty)) => match node.into_token_stream(&ty) {
+ Err(err) => err,
+ Ok(success) => success,
+ },
+ })
+}
+
+/// Implements the `styles! {}` macro, which turns CSS stylesheet strings into `Vec`.
+#[proc_macro_hack]
+pub fn styles(input: TokenStream) -> TokenStream {
+ let s = input.to_string().replace(" ", "");
+ let mut input = ParserInput::new(&s);
+ let mut parser = Parser::new(&mut input);
+
+ let parsed: Vec = RuleListParser::new_for_stylesheet(&mut parser, RuleParser {})
+ .collect::>()
+ .into_iter()
+ .filter_map(|rule| {
+ rule.ok()
+ })
+ .collect();
+
+ let mut body = TokenStream2::new();
+ for rule in parsed {
+ let mut stream = TokenStream2::new();
+ for style in rule.styles {
+ stream.extend(quote!(#style,));
+ }
+
+ let key = Literal::string(&rule.key);
+ body.extend(quote!(styles.insert(#key, vec![#stream]);))
+ }
+
+ quote!(alchemy::StyleSheet::new({
+ use alchemy::style_attributes::*;
+ use alchemy::Color;
+ let mut styles = std::collections::HashMap::new();
+ #body
+ styles
+ })).into()
+}
+
+/// Implements a derive macro for automating props setting and conversion.
+#[proc_macro_derive(Props)]
+pub fn writable_props_derive(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let name = &input.ident;
+ let name_props = Ident::new(&format!("{}Props", name), Span::call_site());
+ let generics = input.generics;
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ TokenStream::from(quote! {
+ impl #impl_generics #name #ty_generics #where_clause {
+ pub fn default_props() -> #name_props {
+ #name_props::default()
+ }
+ }
+
+ impl #impl_generics alchemy::ComponentProps for #name #ty_generics #where_clause {
+ fn set_props(&mut self, new_props: &mut std::any::Any) {
+ match new_props.downcast_ref::<#name_props>() {
+ Some(props) => { },
+ None => { panic!("Woah there, somehow the wrong props were being passed!"); }
+ }
+ }
+ }
+ })
+}
diff --git a/macros/src/map.rs b/macros/src/map.rs
new file mode 100644
index 0000000..da62a98
--- /dev/null
+++ b/macros/src/map.rs
@@ -0,0 +1,54 @@
+//! Implements StringyMap, originally written by Bodil Stokke
+//! over in [typed-html](https://github.com/bodil/typed-html).
+
+use std::collections::BTreeMap;
+
+#[derive(Clone)]
+pub struct StringyMap(BTreeMap);
+
+impl StringyMap
+where
+ K: ToString,
+{
+ pub fn new() -> Self {
+ StringyMap(BTreeMap::new())
+ }
+
+ pub fn insert(&mut self, k: K, v: V) -> Option {
+ let s = k.to_string();
+ self.0.insert(s, (k, v)).map(|(_, v)| v)
+ }
+
+ pub fn remove(&mut self, k: &K) -> Option {
+ let s = k.to_string();
+ self.0.remove(&s).map(|(_, v)| v)
+ }
+
+ pub fn iter(&self) -> impl Iterator- {
+ self.0.values()
+ }
+
+ pub fn keys(&self) -> impl Iterator
- {
+ self.0.values().map(|(k, _)| k)
+ }
+
+ #[allow(dead_code)]
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+}
+
+impl
From> for StringyMap
+where
+ OK: Into,
+ OV: Into,
+ K: ToString,
+{
+ fn from(vec: Vec<(OK, OV)>) -> Self {
+ let mut out = Self::new();
+ for (key, value) in vec {
+ out.insert(key.into(), value.into());
+ }
+ out
+ }
+}
diff --git a/macros/src/parser.rs b/macros/src/parser.rs
new file mode 100644
index 0000000..0d703a6
--- /dev/null
+++ b/macros/src/parser.rs
@@ -0,0 +1,6 @@
+//! Implements parsing, originally written by Bodil Stokke
+//! over in [typed-html](https://github.com/bodil/typed-html).
+
+use lalrpop_util::lalrpop_mod;
+
+lalrpop_mod!(pub grammar);
diff --git a/macros/src/rsx.rs b/macros/src/rsx.rs
new file mode 100644
index 0000000..9243b63
--- /dev/null
+++ b/macros/src/rsx.rs
@@ -0,0 +1,240 @@
+use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree};
+use quote::{quote, quote_spanned};
+
+use crate::error::ParseError;
+use crate::ident;
+use crate::lexer::{/*to_stream, */Lexer, Token};
+use crate::map::StringyMap;
+use crate::parser::grammar;
+
+use std::iter::FromIterator;
+
+#[derive(Clone)]
+pub enum Node {
+ Element(Element),
+ Text(Literal),
+ Block(Group),
+}
+
+impl Node {
+ pub fn into_token_stream(self, ty: &Option>) -> Result {
+ match self {
+ Node::Element(el) => el.into_token_stream(ty),
+ Node::Text(text) => {
+ let text = TokenTree::Literal(text);
+ Ok(quote!(alchemy::RSX::text(#text.to_string())))
+ }
+ Node::Block(group) => {
+ let span = group.span();
+ let error =
+ "you cannot use a block as a top level element or a required child element";
+ Err(quote_spanned! { span=>
+ compile_error! { #error }
+ })
+ }
+ }
+ }
+
+ fn into_child_stream(self, ty: &Option>) -> Result {
+ match self {
+ Node::Element(el) => {
+ let el = el.into_token_stream(ty)?;
+ Ok(quote!(
+ /*element.*/children.push(#el);
+ ))
+ }
+ tx @ Node::Text(_) => {
+ let tx = tx.into_token_stream(ty)?;
+ Ok(quote!(
+ /*element.*/children.push(#tx);
+ ))
+ }
+ Node::Block(group) => {
+ let group: TokenTree = group.into();
+ Ok(quote!(
+ for child in #group.into_iter() {
+ /*element.*/children.push(child);
+ }
+ ))
+ }
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Element {
+ pub name: Ident,
+ pub attributes: StringyMap,
+ pub children: Vec,
+}
+
+fn extract_event_handlers(
+ attrs: &mut StringyMap,
+) -> StringyMap {
+ let mut events = StringyMap::new();
+ let keys: Vec = attrs.keys().cloned().collect();
+ for key in keys {
+ let key_name = key.to_string();
+ let prefix = "on";
+ if key_name.starts_with(prefix) {
+ let event_name = &key_name[prefix.len()..];
+ let value = attrs.remove(&key).unwrap();
+ events.insert(ident::new_raw(event_name, key.span()), value);
+ }
+ }
+ events
+}
+
+fn process_value(value: &TokenTree) -> TokenStream {
+ match value {
+ TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {
+ let content = g.stream();
+ quote!( [ #content ] )
+ }
+ TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => {
+ let content = g.stream();
+ quote!( ( #content ) )
+ }
+ v => TokenStream::from_iter(vec![v.clone()]),
+ }
+}
+
+fn is_string_literal(literal: &Literal) -> bool {
+ // This is the worst API
+ literal.to_string().starts_with('"')
+}
+
+#[allow(dead_code)]
+fn stringify_ident(ident: &Ident) -> String {
+ let s = ident.to_string();
+ if s.starts_with("r#") {
+ s[2..].to_string()
+ } else {
+ s
+ }
+}
+
+impl Element {
+ fn into_token_stream(mut self, ty: &Option>) -> Result {
+ let name = self.name;
+ let name_str = name.to_string();
+ let typename: TokenTree = Ident::new(&name_str, name.span()).into();
+
+ let events = extract_event_handlers(&mut self.attributes);
+ let attrs = self.attributes.iter().map(|(key, value)| {
+ let name = key.to_string();
+ let token = TokenTree::Ident(ident::new_raw(&name, key.span()));
+ (name, token, value)
+ });
+
+ let mut attributes = TokenStream::new();
+ let mut styles = TokenStream::new();
+ styles.extend(quote!(alchemy::SpacedSet::new()));
+
+ for (attr_str, key, value) in attrs {
+ match value {
+ TokenTree::Literal(lit) if is_string_literal(lit) => {
+ let mut eprintln_msg = "ERROR: ".to_owned();
+ #[cfg(can_show_location_of_runtime_parse_error)]
+ {
+ let span = lit.span();
+ eprintln_msg += &format!(
+ "{}:{}:{}: ",
+ span.unstable()
+ .source_file()
+ .path()
+ .to_str()
+ .unwrap_or("unknown"),
+ span.unstable().start().line,
+ span.unstable().start().column
+ );
+ }
+ eprintln_msg += &format!(
+ "<{} {}={}> failed to parse attribute value: {{}}",
+ name_str, attr_str, lit,
+ );
+ #[cfg(not(can_show_location_of_runtime_parse_error))]
+ {
+ eprintln_msg += "\nERROR: rebuild with nightly to print source location";
+ }
+
+ attributes.extend(quote!(
+ props.#key = #lit.parse().unwrap_or_else(|err| {
+ eprintln!(#eprintln_msg, err);
+ panic!("Failed to parse string literal");
+ });
+ ));
+ },
+
+ value => {
+ let prop = key.to_string();
+ let value = process_value(value);
+
+ if prop == "r#styles" {
+ styles = quote!(std::convert::Into::into(#value));
+ continue;
+ }
+
+ if prop == "r#key" {
+ continue;
+ }
+
+ attributes.extend(quote!(
+ props.#key = std::convert::Into::into(#value);
+ ));
+ }
+ }
+ }
+
+ for (key, _value) in events.iter() {
+ if ty.is_none() {
+ let mut err = quote_spanned! { key.span() =>
+ compile_error! { "when using event handlers, you must declare the output type inside the rsx! macro" }
+ };
+ let hint = quote_spanned! { Span::call_site() =>
+ compile_error! { "for example: change rsx!(...
) to rsx!(...
: String)" }
+ };
+ err.extend(hint);
+ return Err(err);
+ }
+ //let key = TokenTree::Ident(key.clone());
+ //let value = process_value(value);
+ /*body.extend(quote!(
+ element.events.#key = Some(alchemy::dom::events::IntoEventHandler::into_event_handler(#value));
+ ));*/
+ }
+
+ /*let mut args = TokenStream::new();
+ let mut type_annotation = TokenStream::new();
+ if let Some(ty) = ty {
+ let type_var = to_stream(ty.clone());
+ type_annotation.extend(quote!(: #typename<#type_var>));
+ }*/
+
+ let mut children = TokenStream::new();
+ children.extend(self.children.into_iter().map(|node| {
+ node.into_child_stream(ty)
+ }).collect::, TokenStream>>()?);
+
+ let component_name = Literal::string(&typename.to_string());
+
+ Ok(quote! {
+ alchemy::RSX::node(#component_name, #styles, |key| {
+ Box::new(<#typename as alchemy::Component>::new(key))
+ }, {
+ let props = #typename::default_props();
+ #attributes
+ Box::new(props)
+ }, {
+ let mut children = vec![];
+ #children
+ children
+ })
+ })
+ }
+}
+
+// FIXME report a decent error when the macro contains multiple top level elements
+pub fn expand_rsx(input: &[Token]) -> Result<(Node, Option>), ParseError> {
+ grammar::NodeWithTypeParser::new().parse(Lexer::new(input))
+}
diff --git a/macros/src/span.rs b/macros/src/span.rs
new file mode 100644
index 0000000..4873598
--- /dev/null
+++ b/macros/src/span.rs
@@ -0,0 +1,13 @@
+//! Utility functions, originally written by Bodil Stokke
+//! over in [typed-html](https://github.com/bodil/typed-html).
+
+use proc_macro;
+use proc_macro2;
+
+pub fn from_unstable(span: proc_macro::Span) -> proc_macro2::Span {
+ let ident = proc_macro::Ident::new("_", span);
+ let tt = proc_macro::TokenTree::Ident(ident);
+ let tts = proc_macro::TokenStream::from(tt);
+ let tts2 = proc_macro2::TokenStream::from(tts);
+ tts2.into_iter().next().unwrap().span()
+}
diff --git a/robots.txt b/robots.txt
deleted file mode 100644
index 3e27a3d..0000000
--- a/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Sitemap: https://alchemy.rs/sitemap.xml
diff --git a/rust-toolchain b/rust-toolchain
new file mode 100644
index 0000000..2aeaa11
--- /dev/null
+++ b/rust-toolchain
@@ -0,0 +1 @@
+1.35.0
diff --git a/sitemap.xml b/sitemap.xml
deleted file mode 100644
index 961b750..0000000
--- a/sitemap.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- https://alchemy.rs/
-
-
-
-
diff --git a/src/build b/src/build
deleted file mode 100755
index c14963b..0000000
--- a/src/build
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-# Feel free to change this via pull request if you want a different shell. ;P
-
-
-rm -rf ../css ../images ../favicons
-zola build
-mv public/* ../
-rm -rf public
diff --git a/src/static/banner.png b/src/static/banner.png
deleted file mode 100644
index 7fa4370..0000000
Binary files a/src/static/banner.png and /dev/null differ
diff --git a/src/static/css/layout.css b/src/static/css/layout.css
deleted file mode 100644
index 06f2b54..0000000
--- a/src/static/css/layout.css
+++ /dev/null
@@ -1,20 +0,0 @@
-* { box-sizing: border-box; }
-html, body { padding 0; margin: 0; background: #131414; color: #e1edf1; font-family: Helvetica Neue,Helvetica,sans-serif; }
-body { max-width: 900px; margin: 0 auto; }
-
-a, a:visited { color: #ca1134; }
-a:hover { color: #d90b31; background: #f4f4f4; }
-
-pre { font-size: 1rem; line-height: 1.3rem; border-radius: 4px; overflow: auto; padding: 1.3rem; margin-bottom: 1.5rem; font-family: "Anonymous Pro", Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif; }
-pre span { font-style: normal !important; text-decoration: none !important; }
-
-#banner { width: 100%; }
-h1 { color: #f3f3f3; font-size: 2.1rem; line-height: 2.4rem; font-family: aleo; margin: .5rem 0 .6rem; }
-h2 { font-size: 1.3rem; line-height: 1.8rem; font-family: aleo, georgia, serif; margin: 0; padding: 2rem 1.3rem .45rem 0; color: #959e9d; }
-p { font-size: 1rem; line-height: 1.4rem; }
-#tempGetStarted { text-align: center; }
-#tempGetStarted h2 { padding-right: 0; }
-.getStartedBtn { display: inline-block; font: normal 1.4rem/2rem aleo, georgia, serif; padding: 1.4rem 4rem; background: #307ace; text-decoration: none; color: #e1edf1; border-radius: 8px; }
-.gh { margin-right: 1.4rem; }
-
-footer { text-align: center; margin-top: 4rem; margin-bottom: 2rem; font-size: .8rem; line-height: 1rem; color: #555; }
diff --git a/src/static/css/reset.css b/src/static/css/reset.css
deleted file mode 100644
index 9aa45be..0000000
--- a/src/static/css/reset.css
+++ /dev/null
@@ -1,12 +0,0 @@
-html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 62.5%; font: inherit; vertical-align: baseline; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body {line-height:1;} ol, ul {list-style:none;} blockquote, q {quotes:none;} blockquote:before, blockquote:after, q:before, q:after {content:'';content: none;} table {border-collapse:collapse;border-spacing:0;} .group:before, .group:after {content:"";display:table;} .group:after {clear:both;} .group {zoom:1;}
-
-@media screen and (min-width: 500px) {
-.col {} .row:after {content: ""; clear: both; display: table;} .col {float: left; width: 100%; box-sizing: border-box;}
-}
-
-@font-face {
- font-family: 'aleo';
- src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
- font-weight: normal;
- font-style: normal;
-}
diff --git a/src/static/favicon.ico b/src/static/favicon.ico
deleted file mode 100644
index bcf2320..0000000
Binary files a/src/static/favicon.ico and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-114x114.png b/src/static/favicons/apple-touch-icon-114x114.png
deleted file mode 100644
index d0a2558..0000000
Binary files a/src/static/favicons/apple-touch-icon-114x114.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-120x120.png b/src/static/favicons/apple-touch-icon-120x120.png
deleted file mode 100644
index 23b52a2..0000000
Binary files a/src/static/favicons/apple-touch-icon-120x120.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-144x144.png b/src/static/favicons/apple-touch-icon-144x144.png
deleted file mode 100644
index 44e7261..0000000
Binary files a/src/static/favicons/apple-touch-icon-144x144.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-152x152.png b/src/static/favicons/apple-touch-icon-152x152.png
deleted file mode 100644
index f1073b7..0000000
Binary files a/src/static/favicons/apple-touch-icon-152x152.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-57x57.png b/src/static/favicons/apple-touch-icon-57x57.png
deleted file mode 100644
index 130ae87..0000000
Binary files a/src/static/favicons/apple-touch-icon-57x57.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-60x60.png b/src/static/favicons/apple-touch-icon-60x60.png
deleted file mode 100644
index 73b7568..0000000
Binary files a/src/static/favicons/apple-touch-icon-60x60.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-72x72.png b/src/static/favicons/apple-touch-icon-72x72.png
deleted file mode 100644
index 9682b8a..0000000
Binary files a/src/static/favicons/apple-touch-icon-72x72.png and /dev/null differ
diff --git a/src/static/favicons/apple-touch-icon-76x76.png b/src/static/favicons/apple-touch-icon-76x76.png
deleted file mode 100644
index e480caa..0000000
Binary files a/src/static/favicons/apple-touch-icon-76x76.png and /dev/null differ
diff --git a/src/static/favicons/favicon-128.png b/src/static/favicons/favicon-128.png
deleted file mode 100644
index 511815f..0000000
Binary files a/src/static/favicons/favicon-128.png and /dev/null differ
diff --git a/src/static/favicons/favicon-16x16.png b/src/static/favicons/favicon-16x16.png
deleted file mode 100644
index 6ecef57..0000000
Binary files a/src/static/favicons/favicon-16x16.png and /dev/null differ
diff --git a/src/static/favicons/favicon-196x196.png b/src/static/favicons/favicon-196x196.png
deleted file mode 100644
index b79353f..0000000
Binary files a/src/static/favicons/favicon-196x196.png and /dev/null differ
diff --git a/src/static/favicons/favicon-32x32.png b/src/static/favicons/favicon-32x32.png
deleted file mode 100644
index c6efd9f..0000000
Binary files a/src/static/favicons/favicon-32x32.png and /dev/null differ
diff --git a/src/static/favicons/favicon-96x96.png b/src/static/favicons/favicon-96x96.png
deleted file mode 100644
index 0207801..0000000
Binary files a/src/static/favicons/favicon-96x96.png and /dev/null differ
diff --git a/src/static/favicons/mstile-144x144.png b/src/static/favicons/mstile-144x144.png
deleted file mode 100644
index 44e7261..0000000
Binary files a/src/static/favicons/mstile-144x144.png and /dev/null differ
diff --git a/src/static/favicons/mstile-150x150.png b/src/static/favicons/mstile-150x150.png
deleted file mode 100644
index cac97f1..0000000
Binary files a/src/static/favicons/mstile-150x150.png and /dev/null differ
diff --git a/src/static/favicons/mstile-310x150.png b/src/static/favicons/mstile-310x150.png
deleted file mode 100644
index 0d2d4cb..0000000
Binary files a/src/static/favicons/mstile-310x150.png and /dev/null differ
diff --git a/src/static/favicons/mstile-310x310.png b/src/static/favicons/mstile-310x310.png
deleted file mode 100644
index 2b531f6..0000000
Binary files a/src/static/favicons/mstile-310x310.png and /dev/null differ
diff --git a/src/static/favicons/mstile-70x70.png b/src/static/favicons/mstile-70x70.png
deleted file mode 100644
index 511815f..0000000
Binary files a/src/static/favicons/mstile-70x70.png and /dev/null differ
diff --git a/src/static/humans.txt b/src/static/humans.txt
deleted file mode 100755
index dad3d68..0000000
--- a/src/static/humans.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Site: https://rymc.io/
-Twitter: @ryanmcgrath
-GitHub: ryanmcgrath
-Dribbble: ryanmcgrath
diff --git a/src/static/images/banner.png b/src/static/images/banner.png
deleted file mode 100644
index 3160843..0000000
Binary files a/src/static/images/banner.png and /dev/null differ
diff --git a/styles/Cargo.toml b/styles/Cargo.toml
new file mode 100644
index 0000000..42d5716
--- /dev/null
+++ b/styles/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "alchemy-styles"
+description = "Style parsing and hoisting for Alchemy, the Rust cross-platform GUI framework."
+version = "0.1.0"
+edition = "2018"
+authors = ["Ryan McGrath "]
+license = "MPL-2.0+"
+repository = "https://github.com/ryanmcgrath/alchemy"
+categories = ["gui", "rendering::engine", "multimedia"]
+keywords = ["gui", "css", "styles", "layout", "ui"]
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[features]
+tokenize = ["proc-macro2", "quote"]
+parser = ["cssparser"]
+
+[dependencies]
+cssparser = { version = "0.25.5", optional = true }
+lazy_static = "1.3"
+proc-macro2 = { version = "0.4.24", optional = true }
+quote = { version = "0.6.10", optional = true }
+serde = { version = "1.0", features = ["derive"] }
+toml = "0.5"
diff --git a/styles/README.md b/styles/README.md
new file mode 100644
index 0000000..c8ef374
--- /dev/null
+++ b/styles/README.md
@@ -0,0 +1,5 @@
+# Alchemy-Styles
+This crate implements CSS parsing and Flexbox layout. CSS parsing relies on the [CSS Parser from Servo](https://github.com/servo/rust-cssparser). Flexbox is implemented with [Stretch](https://github.com/vislyhq/stretch), albeit currently [a fork by msilgreith](https://github.com/msiglreith/stretch/tree/index), cloned into here to serve a few small changes (a change for more thread safety, and to push appearance based styles that Flexbox doesn't concern itself with). Down the road, I could see this not including Stretch inline.
+
+## Questions, Comments?
+Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
diff --git a/styles/src/color.rs b/styles/src/color.rs
new file mode 100644
index 0000000..cf55bc4
--- /dev/null
+++ b/styles/src/color.rs
@@ -0,0 +1,667 @@
+//! Implements `Color`. Heavily based on the `Color` module in Servo's CSS parser, but tweaked
+//! for (what I believe) is a friendlier API, and to separate out the parsing into a separate
+//! module.
+
+#[cfg(feature="parser")]
+use std::{fmt, f32::consts::PI};
+
+#[cfg(feature="parser")]
+use cssparser::{BasicParseError, ParseError, Parser, ToCss, Token};
+
+/// A color with red, green, blue, and alpha components, in a byte each.
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub struct Color {
+ /// The red component.
+ pub red: u8,
+ /// The green component.
+ pub green: u8,
+ /// The blue component.
+ pub blue: u8,
+ /// The alpha component.
+ pub alpha: u8,
+}
+
+impl Default for Color {
+ fn default() -> Color {
+ Color { red: 0, green: 0, blue: 0, alpha: 0 }
+ }
+}
+
+impl Color {
+ /// Constructs a new Color value from float components. It expects the red,
+ /// green, blue and alpha channels in that order, and all values will be
+ /// clamped to the 0.0 ... 1.0 range.
+ #[inline]
+ pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
+ Self::new(
+ clamp_unit_f32(red),
+ clamp_unit_f32(green),
+ clamp_unit_f32(blue),
+ clamp_unit_f32(alpha),
+ )
+ }
+
+ /// Returns a transparent color.
+ #[inline]
+ pub fn transparent() -> Self {
+ Self::new(0, 0, 0, 0)
+ }
+
+ /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
+ #[inline]
+ pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
+ Color {
+ red: red,
+ green: green,
+ blue: blue,
+ alpha: alpha,
+ }
+ }
+
+ /// Returns the red channel in a floating point number form, from 0 to 1.
+ #[inline]
+ pub fn red_f32(&self) -> f32 {
+ self.red as f32 / 255.0
+ }
+
+ /// Returns the green channel in a floating point number form, from 0 to 1.
+ #[inline]
+ pub fn green_f32(&self) -> f32 {
+ self.green as f32 / 255.0
+ }
+
+ /// Returns the blue channel in a floating point number form, from 0 to 1.
+ #[inline]
+ pub fn blue_f32(&self) -> f32 {
+ self.blue as f32 / 255.0
+ }
+
+ /// Returns the alpha channel in a floating point number form, from 0 to 1.
+ #[inline]
+ pub fn alpha_f32(&self) -> f32 {
+ self.alpha as f32 / 255.0
+ }
+
+ /// Parse a value, per CSS Color Module Level 3.
+ ///
+ /// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
+ #[cfg(feature="parser")]
+ pub fn parse_with<'i, 't, ComponentParser>(
+ component_parser: &ComponentParser,
+ input: &mut Parser<'i, 't>,
+ ) -> Result>
+ where
+ ComponentParser: ColorComponentParser<'i>,
+ {
+ // FIXME: remove clone() when lifetimes are non-lexical
+ let location = input.current_source_location();
+ let token = input.next()?.clone();
+ match token {
+ Token::Hash(ref value) | Token::IDHash(ref value) => {
+ Color::parse_hash(value.as_bytes())
+ }
+ Token::Ident(ref value) => parse_color_keyword(&*value),
+ Token::Function(ref name) => {
+ return input.parse_nested_block(|arguments| {
+ parse_color_function(component_parser, &*name, arguments)
+ })
+ }
+ _ => Err(()),
+ }
+ .map_err(|()| location.new_unexpected_token_error(token))
+ }
+
+ /// Parse a value, per CSS Color Module Level 3.
+ #[cfg(feature="parser")]
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> {
+ let component_parser = DefaultComponentParser;
+ Self::parse_with(&component_parser, input).map_err(ParseError::basic)
+ }
+
+ /// Parse a color hash, without the leading '#' character.
+ #[cfg(feature="parser")]
+ #[inline]
+ pub fn parse_hash(value: &[u8]) -> Result {
+ match value.len() {
+ 8 => Ok(rgba(
+ from_hex(value[0])? * 16 + from_hex(value[1])?,
+ from_hex(value[2])? * 16 + from_hex(value[3])?,
+ from_hex(value[4])? * 16 + from_hex(value[5])?,
+ from_hex(value[6])? * 16 + from_hex(value[7])?,
+ )),
+ 6 => Ok(rgb(
+ from_hex(value[0])? * 16 + from_hex(value[1])?,
+ from_hex(value[2])? * 16 + from_hex(value[3])?,
+ from_hex(value[4])? * 16 + from_hex(value[5])?,
+ )),
+ 4 => Ok(rgba(
+ from_hex(value[0])? * 17,
+ from_hex(value[1])? * 17,
+ from_hex(value[2])? * 17,
+ from_hex(value[3])? * 17,
+ )),
+ 3 => Ok(rgb(
+ from_hex(value[0])? * 17,
+ from_hex(value[1])? * 17,
+ from_hex(value[2])? * 17,
+ )),
+ _ => Err(()),
+ }
+ }
+
+}
+
+#[cfg(feature="parser")]
+impl ToCss for Color {
+ fn to_css(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ let serialize_alpha = self.alpha != 255;
+
+ dest.write_str(if serialize_alpha { "rgba(" } else { "rgb(" })?;
+ self.red.to_css(dest)?;
+ dest.write_str(", ")?;
+ self.green.to_css(dest)?;
+ dest.write_str(", ")?;
+ self.blue.to_css(dest)?;
+ if serialize_alpha {
+ dest.write_str(", ")?;
+
+ // Try first with two decimal places, then with three.
+ let mut rounded_alpha = (self.alpha_f32() * 100.).round() / 100.;
+ if clamp_unit_f32(rounded_alpha) != self.alpha {
+ rounded_alpha = (self.alpha_f32() * 1000.).round() / 1000.;
+ }
+
+ rounded_alpha.to_css(dest)?;
+ }
+ dest.write_char(')')
+ }
+}
+
+/// Either a number or a percentage.
+#[cfg(feature="parser")]
+pub enum NumberOrPercentage {
+ /// ``.
+ Number {
+ /// The numeric value parsed, as a float.
+ value: f32,
+ },
+ /// ``
+ Percentage {
+ /// The value as a float, divided by 100 so that the nominal range is
+ /// 0.0 to 1.0.
+ unit_value: f32,
+ },
+}
+
+#[cfg(feature="parser")]
+impl NumberOrPercentage {
+ fn unit_value(&self) -> f32 {
+ match *self {
+ NumberOrPercentage::Number { value } => value,
+ NumberOrPercentage::Percentage { unit_value } => unit_value,
+ }
+ }
+}
+
+/// Either an angle or a number.
+#[cfg(feature="parser")]
+pub enum AngleOrNumber {
+ /// ``.
+ Number {
+ /// The numeric value parsed, as a float.
+ value: f32,
+ },
+ /// ``
+ Angle {
+ /// The value as a number of degrees.
+ degrees: f32,
+ },
+}
+
+#[cfg(feature="parser")]
+impl AngleOrNumber {
+ fn degrees(&self) -> f32 {
+ match *self {
+ AngleOrNumber::Number { value } => value,
+ AngleOrNumber::Angle { degrees } => degrees,
+ }
+ }
+}
+
+/// A trait that can be used to hook into how `cssparser` parses color
+/// components, with the intention of implementing more complicated behavior.
+///
+/// For example, this is used by Servo to support calc() in color.
+#[cfg(feature="parser")]
+pub trait ColorComponentParser<'i> {
+ /// A custom error type that can be returned from the parsing functions.
+ type Error: 'i;
+
+ /// Parse an `` or ``.
+ ///
+ /// Returns the result in degrees.
+ fn parse_angle_or_number<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result> {
+ let location = input.current_source_location();
+ Ok(match *input.next()? {
+ Token::Number { value, .. } => AngleOrNumber::Number { value },
+ Token::Dimension {
+ value: v, ref unit, ..
+ } => {
+ let degrees = match_ignore_ascii_case! { &*unit,
+ "deg" => v,
+ "grad" => v * 360. / 400.,
+ "rad" => v * 360. / (2. * PI),
+ "turn" => v * 360.,
+ _ => return Err(location.new_unexpected_token_error(Token::Ident(unit.clone()))),
+ };
+
+ AngleOrNumber::Angle { degrees }
+ }
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
+ }
+
+ /// Parse a `` value.
+ ///
+ /// Returns the result in a number from 0.0 to 1.0.
+ fn parse_percentage<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result> {
+ input.expect_percentage().map_err(From::from)
+ }
+
+ /// Parse a `` value.
+ fn parse_number<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result> {
+ input.expect_number().map_err(From::from)
+ }
+
+ /// Parse a `` value or a `` value.
+ fn parse_number_or_percentage<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result> {
+ let location = input.current_source_location();
+ Ok(match *input.next()? {
+ Token::Number { value, .. } => NumberOrPercentage::Number { value },
+ Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
+ }
+}
+
+#[cfg(feature="parser")]
+struct DefaultComponentParser;
+
+#[cfg(feature="parser")]
+impl<'i> ColorComponentParser<'i> for DefaultComponentParser {
+ type Error = ();
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn rgb(red: u8, green: u8, blue: u8) -> Color {
+ rgba(red, green, blue, 255)
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
+ Color::new(red, green, blue, alpha)
+}
+
+/// Return the named color with the given name.
+///
+/// Matching is case-insensitive in the ASCII range.
+/// CSS escaping (if relevant) should be resolved before calling this function.
+/// (For example, the value of an `Ident` token is fine.)
+#[cfg(feature="parser")]
+#[inline]
+pub fn parse_color_keyword(ident: &str) -> Result {
+ macro_rules! rgb {
+ ($red: expr, $green: expr, $blue: expr) => {
+ Color {
+ red: $red,
+ green: $green,
+ blue: $blue,
+ alpha: 255,
+ }
+ };
+ }
+
+ ascii_case_insensitive_phf_map! {
+ keyword -> Color = {
+ "black" => rgb!(0, 0, 0),
+ "silver" => rgb!(192, 192, 192),
+ "gray" => rgb!(128, 128, 128),
+ "white" => rgb!(255, 255, 255),
+ "maroon" => rgb!(128, 0, 0),
+ "red" => rgb!(255, 0, 0),
+ "purple" => rgb!(128, 0, 128),
+ "fuchsia" => rgb!(255, 0, 255),
+ "green" => rgb!(0, 128, 0),
+ "lime" => rgb!(0, 255, 0),
+ "olive" => rgb!(128, 128, 0),
+ "yellow" => rgb!(255, 255, 0),
+ "navy" => rgb!(0, 0, 128),
+ "blue" => rgb!(0, 0, 255),
+ "teal" => rgb!(0, 128, 128),
+ "aqua" => rgb!(0, 255, 255),
+
+ "aliceblue" => rgb!(240, 248, 255),
+ "antiquewhite" => rgb!(250, 235, 215),
+ "aquamarine" => rgb!(127, 255, 212),
+ "azure" => rgb!(240, 255, 255),
+ "beige" => rgb!(245, 245, 220),
+ "bisque" => rgb!(255, 228, 196),
+ "blanchedalmond" => rgb!(255, 235, 205),
+ "blueviolet" => rgb!(138, 43, 226),
+ "brown" => rgb!(165, 42, 42),
+ "burlywood" => rgb!(222, 184, 135),
+ "cadetblue" => rgb!(95, 158, 160),
+ "chartreuse" => rgb!(127, 255, 0),
+ "chocolate" => rgb!(210, 105, 30),
+ "coral" => rgb!(255, 127, 80),
+ "cornflowerblue" => rgb!(100, 149, 237),
+ "cornsilk" => rgb!(255, 248, 220),
+ "crimson" => rgb!(220, 20, 60),
+ "cyan" => rgb!(0, 255, 255),
+ "darkblue" => rgb!(0, 0, 139),
+ "darkcyan" => rgb!(0, 139, 139),
+ "darkgoldenrod" => rgb!(184, 134, 11),
+ "darkgray" => rgb!(169, 169, 169),
+ "darkgreen" => rgb!(0, 100, 0),
+ "darkgrey" => rgb!(169, 169, 169),
+ "darkkhaki" => rgb!(189, 183, 107),
+ "darkmagenta" => rgb!(139, 0, 139),
+ "darkolivegreen" => rgb!(85, 107, 47),
+ "darkorange" => rgb!(255, 140, 0),
+ "darkorchid" => rgb!(153, 50, 204),
+ "darkred" => rgb!(139, 0, 0),
+ "darksalmon" => rgb!(233, 150, 122),
+ "darkseagreen" => rgb!(143, 188, 143),
+ "darkslateblue" => rgb!(72, 61, 139),
+ "darkslategray" => rgb!(47, 79, 79),
+ "darkslategrey" => rgb!(47, 79, 79),
+ "darkturquoise" => rgb!(0, 206, 209),
+ "darkviolet" => rgb!(148, 0, 211),
+ "deeppink" => rgb!(255, 20, 147),
+ "deepskyblue" => rgb!(0, 191, 255),
+ "dimgray" => rgb!(105, 105, 105),
+ "dimgrey" => rgb!(105, 105, 105),
+ "dodgerblue" => rgb!(30, 144, 255),
+ "firebrick" => rgb!(178, 34, 34),
+ "floralwhite" => rgb!(255, 250, 240),
+ "forestgreen" => rgb!(34, 139, 34),
+ "gainsboro" => rgb!(220, 220, 220),
+ "ghostwhite" => rgb!(248, 248, 255),
+ "gold" => rgb!(255, 215, 0),
+ "goldenrod" => rgb!(218, 165, 32),
+ "greenyellow" => rgb!(173, 255, 47),
+ "grey" => rgb!(128, 128, 128),
+ "honeydew" => rgb!(240, 255, 240),
+ "hotpink" => rgb!(255, 105, 180),
+ "indianred" => rgb!(205, 92, 92),
+ "indigo" => rgb!(75, 0, 130),
+ "ivory" => rgb!(255, 255, 240),
+ "khaki" => rgb!(240, 230, 140),
+ "lavender" => rgb!(230, 230, 250),
+ "lavenderblush" => rgb!(255, 240, 245),
+ "lawngreen" => rgb!(124, 252, 0),
+ "lemonchiffon" => rgb!(255, 250, 205),
+ "lightblue" => rgb!(173, 216, 230),
+ "lightcoral" => rgb!(240, 128, 128),
+ "lightcyan" => rgb!(224, 255, 255),
+ "lightgoldenrodyellow" => rgb!(250, 250, 210),
+ "lightgray" => rgb!(211, 211, 211),
+ "lightgreen" => rgb!(144, 238, 144),
+ "lightgrey" => rgb!(211, 211, 211),
+ "lightpink" => rgb!(255, 182, 193),
+ "lightsalmon" => rgb!(255, 160, 122),
+ "lightseagreen" => rgb!(32, 178, 170),
+ "lightskyblue" => rgb!(135, 206, 250),
+ "lightslategray" => rgb!(119, 136, 153),
+ "lightslategrey" => rgb!(119, 136, 153),
+ "lightsteelblue" => rgb!(176, 196, 222),
+ "lightyellow" => rgb!(255, 255, 224),
+ "limegreen" => rgb!(50, 205, 50),
+ "linen" => rgb!(250, 240, 230),
+ "magenta" => rgb!(255, 0, 255),
+ "mediumaquamarine" => rgb!(102, 205, 170),
+ "mediumblue" => rgb!(0, 0, 205),
+ "mediumorchid" => rgb!(186, 85, 211),
+ "mediumpurple" => rgb!(147, 112, 219),
+ "mediumseagreen" => rgb!(60, 179, 113),
+ "mediumslateblue" => rgb!(123, 104, 238),
+ "mediumspringgreen" => rgb!(0, 250, 154),
+ "mediumturquoise" => rgb!(72, 209, 204),
+ "mediumvioletred" => rgb!(199, 21, 133),
+ "midnightblue" => rgb!(25, 25, 112),
+ "mintcream" => rgb!(245, 255, 250),
+ "mistyrose" => rgb!(255, 228, 225),
+ "moccasin" => rgb!(255, 228, 181),
+ "navajowhite" => rgb!(255, 222, 173),
+ "oldlace" => rgb!(253, 245, 230),
+ "olivedrab" => rgb!(107, 142, 35),
+ "orange" => rgb!(255, 165, 0),
+ "orangered" => rgb!(255, 69, 0),
+ "orchid" => rgb!(218, 112, 214),
+ "palegoldenrod" => rgb!(238, 232, 170),
+ "palegreen" => rgb!(152, 251, 152),
+ "paleturquoise" => rgb!(175, 238, 238),
+ "palevioletred" => rgb!(219, 112, 147),
+ "papayawhip" => rgb!(255, 239, 213),
+ "peachpuff" => rgb!(255, 218, 185),
+ "peru" => rgb!(205, 133, 63),
+ "pink" => rgb!(255, 192, 203),
+ "plum" => rgb!(221, 160, 221),
+ "powderblue" => rgb!(176, 224, 230),
+ "rebeccapurple" => rgb!(102, 51, 153),
+ "rosybrown" => rgb!(188, 143, 143),
+ "royalblue" => rgb!(65, 105, 225),
+ "saddlebrown" => rgb!(139, 69, 19),
+ "salmon" => rgb!(250, 128, 114),
+ "sandybrown" => rgb!(244, 164, 96),
+ "seagreen" => rgb!(46, 139, 87),
+ "seashell" => rgb!(255, 245, 238),
+ "sienna" => rgb!(160, 82, 45),
+ "skyblue" => rgb!(135, 206, 235),
+ "slateblue" => rgb!(106, 90, 205),
+ "slategray" => rgb!(112, 128, 144),
+ "slategrey" => rgb!(112, 128, 144),
+ "snow" => rgb!(255, 250, 250),
+ "springgreen" => rgb!(0, 255, 127),
+ "steelblue" => rgb!(70, 130, 180),
+ "tan" => rgb!(210, 180, 140),
+ "thistle" => rgb!(216, 191, 216),
+ "tomato" => rgb!(255, 99, 71),
+ "turquoise" => rgb!(64, 224, 208),
+ "violet" => rgb!(238, 130, 238),
+ "wheat" => rgb!(245, 222, 179),
+ "whitesmoke" => rgb!(245, 245, 245),
+ "yellowgreen" => rgb!(154, 205, 50),
+
+ "transparent" => Color { red: 0, green: 0, blue: 0, alpha: 0 }
+ }
+ }
+
+ keyword(ident).cloned().ok_or(())
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn from_hex(c: u8) -> Result {
+ match c {
+ b'0'...b'9' => Ok(c - b'0'),
+ b'a'...b'f' => Ok(c - b'a' + 10),
+ b'A'...b'F' => Ok(c - b'A' + 10),
+ _ => Err(()),
+ }
+}
+
+fn clamp_unit_f32(val: f32) -> u8 {
+ // Whilst scaling by 256 and flooring would provide
+ // an equal distribution of integers to percentage inputs,
+ // this is not what Gecko does so we instead multiply by 255
+ // and round (adding 0.5 and flooring is equivalent to rounding)
+ //
+ // Chrome does something similar for the alpha value, but not
+ // the rgb values.
+ //
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
+ //
+ // Clamping to 256 and rounding after would let 1.0 map to 256, and
+ // `256.0_f32 as u8` is undefined behavior:
+ //
+ // https://github.com/rust-lang/rust/issues/10184
+ clamp_floor_256_f32(val * 255.)
+}
+
+fn clamp_floor_256_f32(val: f32) -> u8 {
+ val.round().max(0.).min(255.) as u8
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn parse_color_function<'i, 't, ComponentParser>(
+ component_parser: &ComponentParser,
+ name: &str,
+ arguments: &mut Parser<'i, 't>,
+) -> Result>
+where
+ ComponentParser: ColorComponentParser<'i>,
+{
+ let (red, green, blue, uses_commas) = match_ignore_ascii_case! { name,
+ "rgb" | "rgba" => parse_rgb_components_rgb(component_parser, arguments)?,
+ "hsl" | "hsla" => parse_rgb_components_hsl(component_parser, arguments)?,
+ _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name.to_owned().into()))),
+ };
+
+ let alpha = if !arguments.is_exhausted() {
+ if uses_commas {
+ arguments.expect_comma()?;
+ } else {
+ arguments.expect_delim('/')?;
+ };
+ clamp_unit_f32(
+ component_parser
+ .parse_number_or_percentage(arguments)?
+ .unit_value(),
+ )
+ } else {
+ 255
+ };
+
+ arguments.expect_exhausted()?;
+ Ok(rgba(red, green, blue, alpha))
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn parse_rgb_components_rgb<'i, 't, ComponentParser>(
+ component_parser: &ComponentParser,
+ arguments: &mut Parser<'i, 't>,
+) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
+where
+ ComponentParser: ColorComponentParser<'i>,
+{
+ // Either integers or percentages, but all the same type.
+ // https://drafts.csswg.org/css-color/#rgb-functions
+ let (red, is_number) = match component_parser.parse_number_or_percentage(arguments)? {
+ NumberOrPercentage::Number { value } => (clamp_floor_256_f32(value), true),
+ NumberOrPercentage::Percentage { unit_value } => (clamp_unit_f32(unit_value), false),
+ };
+
+ let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
+
+ let green;
+ let blue;
+ if is_number {
+ green = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
+ if uses_commas {
+ arguments.expect_comma()?;
+ }
+ blue = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
+ } else {
+ green = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
+ if uses_commas {
+ arguments.expect_comma()?;
+ }
+ blue = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
+ }
+
+ Ok((red, green, blue, uses_commas))
+}
+
+#[cfg(feature="parser")]
+#[inline]
+fn parse_rgb_components_hsl<'i, 't, ComponentParser>(
+ component_parser: &ComponentParser,
+ arguments: &mut Parser<'i, 't>,
+) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
+where
+ ComponentParser: ColorComponentParser<'i>,
+{
+ // Hue given as an angle
+ // https://drafts.csswg.org/css-values/#angles
+ let hue_degrees = component_parser.parse_angle_or_number(arguments)?.degrees();
+
+ // Subtract an integer before rounding, to avoid some rounding errors:
+ let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
+ let hue = hue_normalized_degrees / 360.;
+
+ // Saturation and lightness are clamped to 0% ... 100%
+ // https://drafts.csswg.org/css-color/#the-hsl-notation
+ let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
+
+ let saturation = component_parser.parse_percentage(arguments)?;
+ let saturation = saturation.max(0.).min(1.);
+
+ if uses_commas {
+ arguments.expect_comma()?;
+ }
+
+ let lightness = component_parser.parse_percentage(arguments)?;
+ let lightness = lightness.max(0.).min(1.);
+
+ // https://drafts.csswg.org/css-color/#hsl-color
+ // except with h pre-multiplied by 3, to avoid some rounding errors.
+ fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
+ if h3 < 0. {
+ h3 += 3.
+ }
+ if h3 > 3. {
+ h3 -= 3.
+ }
+
+ if h3 * 2. < 1. {
+ m1 + (m2 - m1) * h3 * 2.
+ } else if h3 * 2. < 3. {
+ m2
+ } else if h3 < 2. {
+ m1 + (m2 - m1) * (2. - h3) * 2.
+ } else {
+ m1
+ }
+ }
+ let m2 = if lightness <= 0.5 {
+ lightness * (saturation + 1.)
+ } else {
+ lightness + saturation - lightness * saturation
+ };
+ let m1 = lightness * 2. - m2;
+ let hue_times_3 = hue * 3.;
+ let red = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
+ let green = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3));
+ let blue = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
+ return Ok((red, green, blue, uses_commas));
+}
diff --git a/styles/src/engine.rs b/styles/src/engine.rs
new file mode 100644
index 0000000..da41ee5
--- /dev/null
+++ b/styles/src/engine.rs
@@ -0,0 +1,131 @@
+//! 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::stretch::style::Style;
+
+use crate::StylesList;
+use crate::styles::Appearance;
+use crate::stylesheet::StyleSheet;
+
+static CONFIG_FILE_NAME: &str = "alchemy.toml";
+
+#[derive(Debug, Deserialize)]
+struct RawConfig<'d> {
+ #[serde(borrow)]
+ general: Option>,
+}
+
+#[derive(Debug, Deserialize)]
+struct General<'a> {
+ #[serde(borrow)]
+ dirs: Option>
+}
+
+/// The `ThemeEngine` controls loading themes and registering associated
+/// styles.
+#[derive(Debug)]
+pub struct ThemeEngine {
+ pub dirs: Vec,
+ pub themes: RwLock>
+}
+
+impl ThemeEngine {
+ /// Creates a new 'ThemeEngine` instance.
+ pub fn new() -> ThemeEngine {
+ // This env var is set by Cargo... so if this code breaks, there's
+ // bigger concerns, lol
+ let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+
+ let root = PathBuf::from(manifest_dir);
+ let default_dirs = vec![root.join("themes")];
+
+ let toml_contents = read_config_file();
+ let raw: RawConfig<'_> = toml::from_str(&toml_contents).expect(&format!("Invalid TOML in {}!", CONFIG_FILE_NAME));
+
+ let dirs = match raw.general {
+ Some(General { dirs }) => (
+ dirs.map_or(default_dirs, |v| {
+ v.into_iter().map(|dir| root.join(dir)).collect()
+ })
+ ),
+
+ None => default_dirs
+ };
+
+ ThemeEngine { dirs, themes: RwLock::new(HashMap::new()) }
+ }
+
+ /// Registers a stylesheet (typically created by the `styles! {}` macro) for a given
+ /// theme.
+ pub fn register_styles(&self, key: &str, stylesheet: StyleSheet) {
+ let mut themes = self.themes.write().unwrap();
+ if !themes.contains_key(key) {
+ themes.insert(key.to_string(), stylesheet);
+ return;
+ }
+
+ // if let Some(existing_stylesheet) = self.themes.get_mut(key) {
+ // *existing_stylesheet.merge(stylesheet);
+ //}
+ }
+
+ /// Given a theme key, style keys, and a style, configures the style for layout
+ /// and appearance.
+ pub fn configure_style_for_keys_in_theme(
+ &self,
+ theme: &str,
+ keys: &StylesList,
+ style: &mut Style,
+ appearance: &mut Appearance
+ ) {
+ let themes = self.themes.read().unwrap();
+
+ match themes.get(theme) {
+ Some(theme) => {
+ for key in &keys.0 {
+ theme.apply_styles(key, style, appearance);
+ }
+ },
+
+ 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_styles_for_keys(&self, keys: &StylesList, style: &mut Style, appearance: &mut Appearance) {
+ self.configure_style_for_keys_in_theme("default", keys, style, appearance)
+ }
+}
+
+/// Utility method for reading a config file from the `CARGO_MANIFEST_DIR`. Hat tip to
+/// [askama](https://github.com/djc/askama) for this!
+pub fn read_config_file() -> String {
+ let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+ let root = PathBuf::from(manifest_dir);
+ let filename = root.join(CONFIG_FILE_NAME);
+
+ if filename.exists() {
+ fs::read_to_string(&filename)
+ .expect(&format!("Unable to read {}", filename.to_str().unwrap()))
+ } else {
+ "".to_string()
+ }
+}
diff --git a/styles/src/lib.rs b/styles/src/lib.rs
new file mode 100644
index 0000000..d6e60b0
--- /dev/null
+++ b/styles/src/lib.rs
@@ -0,0 +1,42 @@
+//! This crate hoists various styles and layout parameters for implementing
+//! Flexbox in Alchemy. For all intents and purposes, you can essentially consider
+//! this to be the root crate for Alchemy, as just about everything ends up using it.
+
+// We hoist this for ease of use in other crates, since... well, pretty much
+// every other crate in the project imports this already.
+pub use lazy_static::lazy_static;
+
+#[cfg(feature="parser")]
+#[macro_use] pub extern crate cssparser;
+
+pub mod color;
+pub use color::Color;
+
+mod engine;
+use engine::ThemeEngine;
+
+mod spacedlist;
+pub use spacedlist::SpacedList;
+
+mod spacedset;
+pub use spacedset::SpacedSet;
+
+pub mod stretch;
+pub use stretch::result::Layout;
+
+mod style_keys;
+pub use style_keys::StyleKey;
+pub type StylesList = SpacedSet;
+
+pub mod styles;
+pub use styles::{Appearance, Styles, Style};
+
+pub mod stylesheet;
+pub use stylesheet::StyleSheet;
+
+#[cfg(feature="parser")]
+pub mod styles_parser;
+
+lazy_static! {
+ pub static ref THEME_ENGINE: ThemeEngine = ThemeEngine::new();
+}
diff --git a/styles/src/spacedlist.rs b/styles/src/spacedlist.rs
new file mode 100644
index 0000000..0deefeb
--- /dev/null
+++ b/styles/src/spacedlist.rs
@@ -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(Vec );
+
+impl SpacedList {
+ /// Construct an empty `SpacedList`.
+ pub fn new() -> Self {
+ SpacedList(Vec::new())
+ }
+}
+
+impl Default for SpacedList {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl FromIterator for SpacedList {
+ fn from_iter