diff --git a/404.html b/404.html new file mode 100644 index 0000000..f7d50b1 --- /dev/null +++ b/404.html @@ -0,0 +1,10 @@ + + + + File Not Found: 404. + + +

Oops!

+

File Not Found: 404.

+ + diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 665d640..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[workspace] -members = [ - "styles", - "macros", - "lifecycle", - "alchemy", - "examples/layout" -] - -[profile.release] -lto = true -panic = "abort" diff --git a/README.md b/README.md index dbe75fd..e76ac28 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,24 @@ -Potion +# Alchemy Website +The source for `alchemy.rs`, which is hosted on GitHub Pages. -Alchemy - A Rust GUI Framework -========================================================== +## 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. -[![Crates.io](https://img.shields.io/crates/v/alchemy.svg)](https://crates.io/crates/alchemy) +If you make substantial changes, make sure the build command outputs to the proper directory: -[Homepage](https://alchemy.rs) • [API Documentation](https://docs.rs/alchemy/) +To build: -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 { - - }) - }); -} +``` +cd src +./build ``` -## 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. +To run: -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) - } -} +``` +cd src +zola serve ``` -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)). +## Questions, Comments? +Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/). diff --git a/alchemy/Cargo.toml b/alchemy/Cargo.toml deleted file mode 100644 index a376ab6..0000000 --- a/alchemy/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[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 deleted file mode 100644 index 80c44de..0000000 --- a/alchemy/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index f1230d2..0000000 --- a/alchemy/src/app.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! 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 deleted file mode 100644 index a3c940e..0000000 --- a/alchemy/src/components/fragment.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! 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 deleted file mode 100644 index 4015a88..0000000 --- a/alchemy/src/components/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! 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 deleted file mode 100644 index 105a537..0000000 --- a/alchemy/src/components/text.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! 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 deleted file mode 100644 index a8ccdaf..0000000 --- a/alchemy/src/components/view.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! 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 deleted file mode 100644 index 3bf0bfd..0000000 --- a/alchemy/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! 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 deleted file mode 100644 index 630c6e0..0000000 --- a/alchemy/src/window/manager.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! 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 deleted file mode 100644 index 5cc5848..0000000 --- a/alchemy/src/window/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! 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 deleted file mode 100644 index 9c4a84b..0000000 --- a/alchemy/src/window/window.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! 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 deleted file mode 100644 index 693af1b..0000000 --- a/assets/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 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 deleted file mode 100644 index 2fea384..0000000 Binary files a/assets/alchemy_logo_250x.png and /dev/null differ diff --git a/assets/social-preview-gh.png b/assets/social-preview-gh.png deleted file mode 100644 index 432fd31..0000000 Binary files a/assets/social-preview-gh.png and /dev/null differ diff --git a/website/static/banner.png b/banner.png similarity index 100% rename from website/static/banner.png rename to banner.png diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index 9fa08ad..0000000 --- a/clippy.toml +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 16d4ff9..0000000 --- a/cocoa/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[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 deleted file mode 100644 index 3ac6dce..0000000 --- a/cocoa/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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 deleted file mode 100644 index f2152ea..0000000 --- a/cocoa/src/app.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! 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 deleted file mode 100644 index aa285be..0000000 --- a/cocoa/src/color.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! 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 deleted file mode 100644 index 1900be3..0000000 --- a/cocoa/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! 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 deleted file mode 100644 index 2845955..0000000 --- a/cocoa/src/text.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! 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 deleted file mode 100644 index 5b123cb..0000000 --- a/cocoa/src/view.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! 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 deleted file mode 100644 index 64b4dd9..0000000 --- a/cocoa/src/window.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! 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/website/static/css/layout.css b/css/layout.css similarity index 100% rename from website/static/css/layout.css rename to css/layout.css diff --git a/website/static/css/reset.css b/css/reset.css similarity index 100% rename from website/static/css/reset.css rename to css/reset.css diff --git a/examples/layout/Cargo.toml b/examples/layout/Cargo.toml deleted file mode 100644 index c3cc60e..0000000 --- a/examples/layout/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[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 deleted file mode 100644 index 2ea858f..0000000 --- a/examples/layout/src/main.rs +++ /dev/null @@ -1,124 +0,0 @@ -#![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/website/static/favicon.ico b/favicon.ico similarity index 100% rename from website/static/favicon.ico rename to favicon.ico diff --git a/website/static/favicons/apple-touch-icon-114x114.png b/favicons/apple-touch-icon-114x114.png similarity index 100% rename from website/static/favicons/apple-touch-icon-114x114.png rename to favicons/apple-touch-icon-114x114.png diff --git a/website/static/favicons/apple-touch-icon-120x120.png b/favicons/apple-touch-icon-120x120.png similarity index 100% rename from website/static/favicons/apple-touch-icon-120x120.png rename to favicons/apple-touch-icon-120x120.png diff --git a/website/static/favicons/apple-touch-icon-144x144.png b/favicons/apple-touch-icon-144x144.png similarity index 100% rename from website/static/favicons/apple-touch-icon-144x144.png rename to favicons/apple-touch-icon-144x144.png diff --git a/website/static/favicons/apple-touch-icon-152x152.png b/favicons/apple-touch-icon-152x152.png similarity index 100% rename from website/static/favicons/apple-touch-icon-152x152.png rename to favicons/apple-touch-icon-152x152.png diff --git a/website/static/favicons/apple-touch-icon-57x57.png b/favicons/apple-touch-icon-57x57.png similarity index 100% rename from website/static/favicons/apple-touch-icon-57x57.png rename to favicons/apple-touch-icon-57x57.png diff --git a/website/static/favicons/apple-touch-icon-60x60.png b/favicons/apple-touch-icon-60x60.png similarity index 100% rename from website/static/favicons/apple-touch-icon-60x60.png rename to favicons/apple-touch-icon-60x60.png diff --git a/website/static/favicons/apple-touch-icon-72x72.png b/favicons/apple-touch-icon-72x72.png similarity index 100% rename from website/static/favicons/apple-touch-icon-72x72.png rename to favicons/apple-touch-icon-72x72.png diff --git a/website/static/favicons/apple-touch-icon-76x76.png b/favicons/apple-touch-icon-76x76.png similarity index 100% rename from website/static/favicons/apple-touch-icon-76x76.png rename to favicons/apple-touch-icon-76x76.png diff --git a/website/static/favicons/favicon-128.png b/favicons/favicon-128.png similarity index 100% rename from website/static/favicons/favicon-128.png rename to favicons/favicon-128.png diff --git a/website/static/favicons/favicon-16x16.png b/favicons/favicon-16x16.png similarity index 100% rename from website/static/favicons/favicon-16x16.png rename to favicons/favicon-16x16.png diff --git a/website/static/favicons/favicon-196x196.png b/favicons/favicon-196x196.png similarity index 100% rename from website/static/favicons/favicon-196x196.png rename to favicons/favicon-196x196.png diff --git a/website/static/favicons/favicon-32x32.png b/favicons/favicon-32x32.png similarity index 100% rename from website/static/favicons/favicon-32x32.png rename to favicons/favicon-32x32.png diff --git a/website/static/favicons/favicon-96x96.png b/favicons/favicon-96x96.png similarity index 100% rename from website/static/favicons/favicon-96x96.png rename to favicons/favicon-96x96.png diff --git a/website/static/favicons/mstile-144x144.png b/favicons/mstile-144x144.png similarity index 100% rename from website/static/favicons/mstile-144x144.png rename to favicons/mstile-144x144.png diff --git a/website/static/favicons/mstile-150x150.png b/favicons/mstile-150x150.png similarity index 100% rename from website/static/favicons/mstile-150x150.png rename to favicons/mstile-150x150.png diff --git a/website/static/favicons/mstile-310x150.png b/favicons/mstile-310x150.png similarity index 100% rename from website/static/favicons/mstile-310x150.png rename to favicons/mstile-310x150.png diff --git a/website/static/favicons/mstile-310x310.png b/favicons/mstile-310x310.png similarity index 100% rename from website/static/favicons/mstile-310x310.png rename to favicons/mstile-310x310.png diff --git a/website/static/favicons/mstile-70x70.png b/favicons/mstile-70x70.png similarity index 100% rename from website/static/favicons/mstile-70x70.png rename to favicons/mstile-70x70.png diff --git a/website/static/humans.txt b/humans.txt similarity index 100% rename from website/static/humans.txt rename to humans.txt diff --git a/website/static/images/banner.png b/images/banner.png similarity index 100% rename from website/static/images/banner.png rename to images/banner.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..941f9b1 --- /dev/null +++ b/index.html @@ -0,0 +1,169 @@ + + + + + + + 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 {
+        
+        })
+    });
+}
+
+

Get Started

+GitHub +Docs +
+ + + +
Created by Ryan McGrath. A more complete site for this project will come later. :)
+ + + + + diff --git a/lifecycle/Cargo.toml b/lifecycle/Cargo.toml deleted file mode 100644 index a4694db..0000000 --- a/lifecycle/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[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 deleted file mode 100644 index 2cef573..0000000 --- a/lifecycle/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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 deleted file mode 100644 index f9c1d35..0000000 --- a/lifecycle/src/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! 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 deleted file mode 100644 index 17b44a5..0000000 --- a/lifecycle/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! 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 deleted file mode 100644 index c3cb923..0000000 --- a/lifecycle/src/reconciler/error.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! 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 deleted file mode 100644 index 4658c5f..0000000 --- a/lifecycle/src/reconciler/generic_root_view_stub.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! 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 deleted file mode 100644 index eca423c..0000000 --- a/lifecycle/src/reconciler/instance.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! 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 deleted file mode 100644 index d348ec8..0000000 --- a/lifecycle/src/reconciler/key.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! 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 deleted file mode 100644 index 093b9dc..0000000 --- a/lifecycle/src/reconciler/mod.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! 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 deleted file mode 100644 index 63f087b..0000000 --- a/lifecycle/src/reconciler/storage.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! 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 deleted file mode 100644 index 6278f36..0000000 --- a/lifecycle/src/rsx/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! 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 deleted file mode 100644 index b3a3ea0..0000000 --- a/lifecycle/src/rsx/props.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! 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 deleted file mode 100644 index aa15630..0000000 --- a/lifecycle/src/rsx/virtual_node.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! 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 deleted file mode 100644 index 1c7814d..0000000 --- a/lifecycle/src/rsx/virtual_text.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! 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 deleted file mode 100644 index 1a1d6d6..0000000 --- a/lifecycle/src/traits.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! 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 deleted file mode 100644 index 43be491..0000000 --- a/macros/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[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 deleted file mode 100644 index 6ba7efa..0000000 --- a/macros/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 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 deleted file mode 100644 index 78d2b19..0000000 --- a/macros/src/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e4a1df5..0000000 --- a/macros/src/error.rs +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index a8f0a2b..0000000 --- a/macros/src/grammar.lalrpop +++ /dev/null @@ -1,303 +0,0 @@ -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 deleted file mode 100644 index db92caa..0000000 --- a/macros/src/ident.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! 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 deleted file mode 100644 index 7838597..0000000 --- a/macros/src/lexer.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! 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 deleted file mode 100644 index 60e31e0..0000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,107 +0,0 @@ -#![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 deleted file mode 100644 index da62a98..0000000 --- a/macros/src/map.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! 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 deleted file mode 100644 index 0d703a6..0000000 --- a/macros/src/parser.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! 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 deleted file mode 100644 index 9243b63..0000000 --- a/macros/src/rsx.rs +++ /dev/null @@ -1,240 +0,0 @@ -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 deleted file mode 100644 index 4873598..0000000 --- a/macros/src/span.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! 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 new file mode 100644 index 0000000..3e27a3d --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: https://alchemy.rs/sitemap.xml diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 2aeaa11..0000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.35.0 diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..961b750 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://alchemy.rs/ + + + + diff --git a/src/build b/src/build new file mode 100755 index 0000000..c14963b --- /dev/null +++ b/src/build @@ -0,0 +1,8 @@ +#!/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/website/config.toml b/src/config.toml similarity index 94% rename from website/config.toml rename to src/config.toml index 2b01f80..f0747cb 100644 --- a/website/config.toml +++ b/src/config.toml @@ -10,7 +10,7 @@ highlight_code = true highlight_theme = "dracula" # Whether to build a search index to be used later on by a JavaScript library -build_search_index = true +build_search_index = false [extra] # Put all your custom variables here diff --git a/website/content/_index.md b/src/content/_index.md similarity index 100% rename from website/content/_index.md rename to src/content/_index.md diff --git a/src/static/banner.png b/src/static/banner.png new file mode 100644 index 0000000..7fa4370 Binary files /dev/null and b/src/static/banner.png differ diff --git a/src/static/css/layout.css b/src/static/css/layout.css new file mode 100644 index 0000000..06f2b54 --- /dev/null +++ b/src/static/css/layout.css @@ -0,0 +1,20 @@ +* { 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 new file mode 100644 index 0000000..9aa45be --- /dev/null +++ b/src/static/css/reset.css @@ -0,0 +1,12 @@ +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 new file mode 100644 index 0000000..bcf2320 Binary files /dev/null and b/src/static/favicon.ico differ diff --git a/src/static/favicons/apple-touch-icon-114x114.png b/src/static/favicons/apple-touch-icon-114x114.png new file mode 100644 index 0000000..d0a2558 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-114x114.png differ diff --git a/src/static/favicons/apple-touch-icon-120x120.png b/src/static/favicons/apple-touch-icon-120x120.png new file mode 100644 index 0000000..23b52a2 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-120x120.png differ diff --git a/src/static/favicons/apple-touch-icon-144x144.png b/src/static/favicons/apple-touch-icon-144x144.png new file mode 100644 index 0000000..44e7261 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-144x144.png differ diff --git a/src/static/favicons/apple-touch-icon-152x152.png b/src/static/favicons/apple-touch-icon-152x152.png new file mode 100644 index 0000000..f1073b7 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-152x152.png differ diff --git a/src/static/favicons/apple-touch-icon-57x57.png b/src/static/favicons/apple-touch-icon-57x57.png new file mode 100644 index 0000000..130ae87 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-57x57.png differ diff --git a/src/static/favicons/apple-touch-icon-60x60.png b/src/static/favicons/apple-touch-icon-60x60.png new file mode 100644 index 0000000..73b7568 Binary files /dev/null and b/src/static/favicons/apple-touch-icon-60x60.png differ diff --git a/src/static/favicons/apple-touch-icon-72x72.png b/src/static/favicons/apple-touch-icon-72x72.png new file mode 100644 index 0000000..9682b8a Binary files /dev/null and b/src/static/favicons/apple-touch-icon-72x72.png differ diff --git a/src/static/favicons/apple-touch-icon-76x76.png b/src/static/favicons/apple-touch-icon-76x76.png new file mode 100644 index 0000000..e480caa Binary files /dev/null and b/src/static/favicons/apple-touch-icon-76x76.png differ diff --git a/src/static/favicons/favicon-128.png b/src/static/favicons/favicon-128.png new file mode 100644 index 0000000..511815f Binary files /dev/null and b/src/static/favicons/favicon-128.png differ diff --git a/src/static/favicons/favicon-16x16.png b/src/static/favicons/favicon-16x16.png new file mode 100644 index 0000000..6ecef57 Binary files /dev/null and b/src/static/favicons/favicon-16x16.png differ diff --git a/src/static/favicons/favicon-196x196.png b/src/static/favicons/favicon-196x196.png new file mode 100644 index 0000000..b79353f Binary files /dev/null and b/src/static/favicons/favicon-196x196.png differ diff --git a/src/static/favicons/favicon-32x32.png b/src/static/favicons/favicon-32x32.png new file mode 100644 index 0000000..c6efd9f Binary files /dev/null and b/src/static/favicons/favicon-32x32.png differ diff --git a/src/static/favicons/favicon-96x96.png b/src/static/favicons/favicon-96x96.png new file mode 100644 index 0000000..0207801 Binary files /dev/null and b/src/static/favicons/favicon-96x96.png differ diff --git a/src/static/favicons/mstile-144x144.png b/src/static/favicons/mstile-144x144.png new file mode 100644 index 0000000..44e7261 Binary files /dev/null and b/src/static/favicons/mstile-144x144.png differ diff --git a/src/static/favicons/mstile-150x150.png b/src/static/favicons/mstile-150x150.png new file mode 100644 index 0000000..cac97f1 Binary files /dev/null and b/src/static/favicons/mstile-150x150.png differ diff --git a/src/static/favicons/mstile-310x150.png b/src/static/favicons/mstile-310x150.png new file mode 100644 index 0000000..0d2d4cb Binary files /dev/null and b/src/static/favicons/mstile-310x150.png differ diff --git a/src/static/favicons/mstile-310x310.png b/src/static/favicons/mstile-310x310.png new file mode 100644 index 0000000..2b531f6 Binary files /dev/null and b/src/static/favicons/mstile-310x310.png differ diff --git a/src/static/favicons/mstile-70x70.png b/src/static/favicons/mstile-70x70.png new file mode 100644 index 0000000..511815f Binary files /dev/null and b/src/static/favicons/mstile-70x70.png differ diff --git a/src/static/humans.txt b/src/static/humans.txt new file mode 100755 index 0000000..dad3d68 --- /dev/null +++ b/src/static/humans.txt @@ -0,0 +1,4 @@ +Site: https://rymc.io/ +Twitter: @ryanmcgrath +GitHub: ryanmcgrath +Dribbble: ryanmcgrath diff --git a/src/static/images/banner.png b/src/static/images/banner.png new file mode 100644 index 0000000..3160843 Binary files /dev/null and b/src/static/images/banner.png differ diff --git a/website/templates/index.html b/src/templates/index.html similarity index 100% rename from website/templates/index.html rename to src/templates/index.html diff --git a/website/templates/layout.html b/src/templates/layout.html similarity index 100% rename from website/templates/layout.html rename to src/templates/layout.html diff --git a/styles/Cargo.toml b/styles/Cargo.toml deleted file mode 100644 index 42d5716..0000000 --- a/styles/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[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 deleted file mode 100644 index c8ef374..0000000 --- a/styles/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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 deleted file mode 100644 index cf55bc4..0000000 --- a/styles/src/color.rs +++ /dev/null @@ -1,667 +0,0 @@ -//! 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 deleted file mode 100644 index da41ee5..0000000 --- a/styles/src/engine.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! 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 deleted file mode 100644 index d6e60b0..0000000 --- a/styles/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! 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 deleted file mode 100644 index 0deefeb..0000000 --- a/styles/src/spacedlist.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! A space separated list of values. -//! -//! This type represents a list of non-unique values represented as a string of -//! values separated by spaces in HTML attributes. This is rarely used; a -//! SpacedSet of unique values is much more common. - - -use std::fmt::{Debug, Display, Error, Formatter}; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -/// A space separated list of values. -/// -/// This type represents a list of non-unique values represented as a string of -/// values separated by spaces in HTML attributes. This is rarely used; a -/// SpacedSet of unique values is much more common. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SpacedList
(Vec); - -impl SpacedList { - /// Construct an empty `SpacedList`. - pub fn new() -> Self { - SpacedList(Vec::new()) - } -} - -impl Default for SpacedList { - fn default() -> Self { - Self::new() - } -} - -impl FromIterator for SpacedList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedList(iter.into_iter().collect()) - } -} - -impl<'a, A: 'a + Clone> FromIterator<&'a A> for SpacedList { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedList(iter.into_iter().cloned().collect()) - } -} - -impl<'a, A: FromStr> From<&'a str> for SpacedList -where - ::Err: Debug, -{ - fn from(s: &'a str) -> Self { - Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) - } -} - -impl Deref for SpacedList { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SpacedList { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for SpacedList { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let mut it = self.0.iter().peekable(); - while let Some(class) = it.next() { - Display::fmt(class, f)?; - if it.peek().is_some() { - Display::fmt(" ", f)?; - } - } - Ok(()) - } -} - -impl Debug for SpacedList { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - f.debug_list().entries(self.0.iter()).finish() - } -} - -impl<'a, 'b, A: FromStr> From<(&'a str, &'b str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list - } -} - -impl<'a, 'b, 'c, A: FromStr> From<(&'a str, &'b str, &'c str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, A: FromStr> From<(&'a str, &'b str, &'c str, &'d str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, A: FromStr> From<(&'a str, &'b str, &'c str, &'d str, &'e str)> - for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, A: FromStr> - From<(&'a str, &'b str, &'c str, &'d str, &'e str, &'f str)> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, A: FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - )> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list.push(FromStr::from_str(s.6).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, A: FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - &'h str, - )> for SpacedList -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.push(FromStr::from_str(s.0).unwrap()); - list.push(FromStr::from_str(s.1).unwrap()); - list.push(FromStr::from_str(s.2).unwrap()); - list.push(FromStr::from_str(s.3).unwrap()); - list.push(FromStr::from_str(s.4).unwrap()); - list.push(FromStr::from_str(s.5).unwrap()); - list.push(FromStr::from_str(s.6).unwrap()); - list.push(FromStr::from_str(s.7).unwrap()); - list - } -} - -macro_rules! spacedlist_from_array { - ($num:tt) => { - impl<'a, A: FromStr> From<[&'a str; $num]> for SpacedList - where - ::Err: Debug, - { - fn from(s: [&str; $num]) -> Self { - Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) - } - } - }; -} -spacedlist_from_array!(1); -spacedlist_from_array!(2); -spacedlist_from_array!(3); -spacedlist_from_array!(4); -spacedlist_from_array!(5); -spacedlist_from_array!(6); -spacedlist_from_array!(7); -spacedlist_from_array!(8); -spacedlist_from_array!(9); -spacedlist_from_array!(10); -spacedlist_from_array!(11); -spacedlist_from_array!(12); -spacedlist_from_array!(13); -spacedlist_from_array!(14); -spacedlist_from_array!(15); -spacedlist_from_array!(16); -spacedlist_from_array!(17); -spacedlist_from_array!(18); -spacedlist_from_array!(19); -spacedlist_from_array!(20); -spacedlist_from_array!(21); -spacedlist_from_array!(22); -spacedlist_from_array!(23); -spacedlist_from_array!(24); -spacedlist_from_array!(25); -spacedlist_from_array!(26); -spacedlist_from_array!(27); -spacedlist_from_array!(28); -spacedlist_from_array!(29); -spacedlist_from_array!(30); -spacedlist_from_array!(31); -spacedlist_from_array!(32); diff --git a/styles/src/spacedset.rs b/styles/src/spacedset.rs deleted file mode 100644 index 115f8ef..0000000 --- a/styles/src/spacedset.rs +++ /dev/null @@ -1,308 +0,0 @@ -//! A space separated set of unique values. -//! -//! This type represents a set of unique values represented as a string of -//! values separated by spaces in HTML attributes. - -use std::collections::BTreeSet; -use std::fmt::{Debug, Display, Error, Formatter}; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -/// A space separated set of unique values. -/// -/// This type represents a set of unique values represented as a string of -/// values separated by spaces in HTML attributes. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct SpacedSet(pub BTreeSet); - -impl SpacedSet { - /// Construct an empty `SpacedSet`. - pub fn new() -> Self { - SpacedSet(BTreeSet::new()) - } - - /// Add a value to the `SpacedSet`. - pub fn add>(&mut self, value: T) -> bool { - self.0.insert(value.into()) - } -} - -impl Default for SpacedSet { - fn default() -> Self { - Self::new() - } -} - -impl FromIterator for SpacedSet { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedSet(iter.into_iter().collect()) - } -} - -impl<'a, A: 'a + Ord + Clone> FromIterator<&'a A> for SpacedSet { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - SpacedSet(iter.into_iter().cloned().collect()) - } -} - -impl<'a, A: Ord + FromStr> FromStr for SpacedSet -where - ::Err: Debug, -{ - type Err = ::Err; - - fn from_str(s: &str) -> Result { - let result: Result, Self::Err> = - s.split_whitespace().map(|s| FromStr::from_str(s)).collect(); - result.map(Self::from_iter) - } -} - -impl<'a, A: Ord + FromStr> From<&'a str> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: &'a str) -> Self { - Self::from_iter(s.split_whitespace().map(|s| FromStr::from_str(s).unwrap())) - } -} - -impl Deref for SpacedSet { - type Target = BTreeSet; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SpacedSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for SpacedSet { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let mut it = self.0.iter().peekable(); - while let Some(class) = it.next() { - Display::fmt(class, f)?; - if it.peek().is_some() { - Display::fmt(" ", f)?; - } - } - Ok(()) - } -} - -impl Debug for SpacedSet { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - f.debug_list().entries(self.0.iter()).finish() - } -} - -impl<'a, A: Ord + FromStr> From> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: Vec<&'a str>) -> Self { - let mut list = Self::new(); - - for key in s { - list.insert(FromStr::from_str(key).unwrap()); - } - - list - } -} - -impl<'a, A: Ord + FromStr> From<&Vec<&'a str>> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: &Vec<&'a str>) -> Self { - let mut list = Self::new(); - - for key in s { - list.insert(FromStr::from_str(key).unwrap()); - } - - list - } -} - -impl<'a, 'b, A: Ord + FromStr> From<(&'a str, &'b str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list - } -} - -impl<'a, 'b, 'c, A: Ord + FromStr> From<(&'a str, &'b str, &'c str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, A: Ord + FromStr> From<(&'a str, &'b str, &'c str, &'d str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, A: Ord + FromStr> From<(&'a str, &'b str, &'c str, &'d str, &'e str)> - for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, A: Ord + FromStr> - From<(&'a str, &'b str, &'c str, &'d str, &'e str, &'f str)> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, A: Ord + FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - )> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list.insert(FromStr::from_str(s.6).unwrap()); - list - } -} - -impl<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, A: Ord + FromStr> - From<( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - &'h str, - )> for SpacedSet -where - ::Err: Debug, -{ - fn from(s: (&str, &str, &str, &str, &str, &str, &str, &str)) -> Self { - let mut list = Self::new(); - list.insert(FromStr::from_str(s.0).unwrap()); - list.insert(FromStr::from_str(s.1).unwrap()); - list.insert(FromStr::from_str(s.2).unwrap()); - list.insert(FromStr::from_str(s.3).unwrap()); - list.insert(FromStr::from_str(s.4).unwrap()); - list.insert(FromStr::from_str(s.5).unwrap()); - list.insert(FromStr::from_str(s.6).unwrap()); - list.insert(FromStr::from_str(s.7).unwrap()); - list - } -} - -macro_rules! spacedlist_from_array { - ($num:tt) => { - impl<'a, A: Ord + FromStr> From<[&'a str; $num]> for SpacedSet - where - ::Err: Debug, - { - fn from(s: [&str; $num]) -> Self { - Self::from_iter(s.into_iter().map(|s| FromStr::from_str(*s).unwrap())) - } - } - }; -} -spacedlist_from_array!(1); -spacedlist_from_array!(2); -spacedlist_from_array!(3); -spacedlist_from_array!(4); -spacedlist_from_array!(5); -spacedlist_from_array!(6); -spacedlist_from_array!(7); -spacedlist_from_array!(8); -spacedlist_from_array!(9); -spacedlist_from_array!(10); -spacedlist_from_array!(11); -spacedlist_from_array!(12); -spacedlist_from_array!(13); -spacedlist_from_array!(14); -spacedlist_from_array!(15); -spacedlist_from_array!(16); -spacedlist_from_array!(17); -spacedlist_from_array!(18); -spacedlist_from_array!(19); -spacedlist_from_array!(20); -spacedlist_from_array!(21); -spacedlist_from_array!(22); -spacedlist_from_array!(23); -spacedlist_from_array!(24); -spacedlist_from_array!(25); -spacedlist_from_array!(26); -spacedlist_from_array!(27); -spacedlist_from_array!(28); -spacedlist_from_array!(29); -spacedlist_from_array!(30); -spacedlist_from_array!(31); -spacedlist_from_array!(32); diff --git a/styles/src/stretch/algo.rs b/styles/src/stretch/algo.rs deleted file mode 100644 index 8ef6948..0000000 --- a/styles/src/stretch/algo.rs +++ /dev/null @@ -1,1364 +0,0 @@ -//! This module is included while awaiting an upstream merge in stretch proper. -//! You should not rely on it, and consider it an implementation detail. - -use core::any::Any; -use core::f32; - -use crate::stretch::node::{Node, Storage, Stretch}; -use crate::stretch::result; -use crate::stretch::style::*; -use crate::stretch::number::Number::*; -use crate::stretch::number::*; -use crate::stretch::geometry::{Point, Rect, Size}; - -#[derive(Debug, Clone)] -pub struct ComputeResult { - pub size: Size, -} - -struct FlexItem { - node: Node, - - size: Size, - min_size: Size, - max_size: Size, - - position: Rect, - margin: Rect, - padding: Rect, - border: Rect, - - flex_basis: f32, - inner_flex_basis: f32, - violation: f32, - frozen: bool, - - hypothetical_inner_size: Size, - hypothetical_outer_size: Size, - target_size: Size, - outer_target_size: Size, - - baseline: f32, - - // temporary values for holding offset in the main / cross direction. - // offset is the relative position from the item's natural flow position based on - // relative position values, alignment, and justification. Does not include margin/padding/border. - offset_main: f32, - offset_cross: f32, -} - -struct FlexLine { - items: Vec, - cross_size: f32, - offset_cross: f32, -} - -impl Stretch { - pub(crate) fn compute(&mut self, root: Node, size: Size) -> Result<(), Box> { - let style = self.style[&root]; - let has_root_min_max = style.min_size.width.is_defined() - || style.min_size.height.is_defined() - || style.max_size.width.is_defined() - || style.max_size.height.is_defined(); - - let result = if has_root_min_max { - let first_pass = self.compute_internal( - root, - Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) }, - size, - false, - )?; - - self.compute_internal( - root, - Size { - width: first_pass - .size - .width - .maybe_max(style.min_size.width.resolve(size.width)) - .maybe_min(style.max_size.width.resolve(size.width)) - .to_number(), - height: first_pass - .size - .height - .maybe_max(style.min_size.height.resolve(size.height)) - .maybe_min(style.max_size.height.resolve(size.height)) - .to_number(), - }, - size, - true, - )? - } else { - self.compute_internal( - root, - Size { width: style.size.width.resolve(size.width), height: style.size.height.resolve(size.height) }, - size, - true, - )? - }; - - *self.layout.get_mut(root).unwrap() = result::Layout { - order: 0, - size: Size { width: result.size.width, height: result.size.height }, - location: Point { x: 0.0, y: 0.0 }, - }; - - Self::round_layout(&mut self.layout, &self.children, root, 0.0, 0.0); - Ok(()) - } - - fn round_layout( - layouts: &mut Storage, - children: &Storage>, - root: Node, - abs_x: f32, - abs_y: f32, - ) { - let layout = layouts.get_mut(root).unwrap(); - let abs_x = abs_x + layout.location.x; - let abs_y = abs_y + layout.location.y; - - layout.location.x = layout.location.x.round(); - layout.location.y = layout.location.y.round(); - layout.size.width = (abs_x + layout.size.width).round() - abs_x.round(); - layout.size.height = (abs_y + layout.size.height).round() - abs_y.round(); - for child in &children[&root] { - Self::round_layout(layouts, children, *child, abs_x, abs_y); - } - } - - fn compute_internal( - &mut self, - node: Node, - node_size: Size, - parent_size: Size, - perform_layout: bool, - ) -> Result> { - *self.is_dirty.get_mut(node).unwrap() = false; - - // First we check if we have a result for the given input - if let Some(cache) = &self.layout_cache[&node] { - if cache.perform_layout || !perform_layout { - let width_compatible = if let Number::Defined(width) = node_size.width { - (width - cache.result.size.width).abs() < f32::EPSILON - } else { - cache.node_size.width.is_undefined() - }; - - let height_compatible = if let Number::Defined(height) = node_size.height { - (height - cache.result.size.height).abs() < f32::EPSILON - } else { - cache.node_size.height.is_undefined() - }; - - if width_compatible && height_compatible { - return Ok(cache.result.clone()); - } - - if cache.node_size == node_size && cache.parent_size == parent_size { - return Ok(cache.result.clone()); - } - } - } - - // Define some general constants we will need for the remainder - // of the algorithm. - - let dir = self.style[&node].flex_direction; - let is_row = dir.is_row(); - let is_column = dir.is_column(); - let is_wrap_reverse = self.style[&node].flex_wrap == FlexWrap::WrapReverse; - - let margin = self.style[&node].margin.map(|n| n.resolve(parent_size.width).or_else(0.0)); - let padding = self.style[&node].padding.map(|n| n.resolve(parent_size.width).or_else(0.0)); - let border = self.style[&node].border.map(|n| n.resolve(parent_size.width).or_else(0.0)); - - let padding_border = Rect { - start: padding.start + border.start, - end: padding.end + border.end, - top: padding.top + border.top, - bottom: padding.bottom + border.bottom, - }; - - let node_inner_size = Size { - width: node_size.width - padding_border.horizontal(), - height: node_size.height - padding_border.vertical(), - }; - - let mut container_size = Size { width: 0.0, height: 0.0 }; - let mut inner_container_size = Size { width: 0.0, height: 0.0 }; - - // If this is a leaf node we can skip a lot of this function in some cases - if self.children[&node].is_empty() { - if node_size.width.is_defined() && node_size.height.is_defined() { - return Ok(ComputeResult { size: node_size.map(|s| s.or_else(0.0)) }); - } - - if let Some(ref measure) = self.measure[&node] { - let result = ComputeResult { size: measure(node_size)? }; - *self.layout_cache.get_mut(node).unwrap() = - Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); - return Ok(result); - } - - return Ok(ComputeResult { - size: Size { - width: node_size.width.or_else(0.0) + padding_border.horizontal(), - height: node_size.height.or_else(0.0) + padding_border.vertical(), - }, - }); - } - - // 9.2. Line Length Determination - - // 1. Generate anonymous flex items as described in §4 Flex Items. - - // 2. Determine the available main and cross space for the flex items. - // For each dimension, if that dimension of the flex container’s content box - // is a definite size, use that; if that dimension of the flex container is - // being sized under a min or max-content constraint, the available space in - // that dimension is that constraint; otherwise, subtract the flex container’s - // margin, border, and padding from the space available to the flex container - // in that dimension and use that value. This might result in an infinite value. - - let available_space = Size { - width: node_size.width.or_else(parent_size.width - margin.horizontal()) - padding_border.horizontal(), - height: node_size.height.or_else(parent_size.height - margin.vertical()) - padding_border.vertical(), - }; - - let mut flex_items: Vec = self.children[&node] - .iter() - .map(|child| (child, self.style[&child])) - .filter(|(_, style)| style.position_type != PositionType::Absolute) - .filter(|(_, style)| style.display != Display::None) - .map(|(child, child_style)| FlexItem { - node: *child, - - size: Size { - width: child_style.size.width.resolve(node_inner_size.width), - height: child_style.size.height.resolve(node_inner_size.height), - }, - - min_size: Size { - width: child_style.min_size.width.resolve(node_inner_size.width), - height: child_style.min_size.height.resolve(node_inner_size.height), - }, - - max_size: Size { - width: child_style.max_size.width.resolve(node_inner_size.width), - height: child_style.max_size.height.resolve(node_inner_size.height), - }, - - position: child_style.position.map(|p| p.resolve(node_inner_size.width)), - margin: child_style.margin.map(|m| m.resolve(node_inner_size.width).or_else(0.0)), - padding: child_style.padding.map(|p| p.resolve(node_inner_size.width).or_else(0.0)), - border: child_style.border.map(|b| b.resolve(node_inner_size.width).or_else(0.0)), - - flex_basis: 0.0, - inner_flex_basis: 0.0, - violation: 0.0, - frozen: false, - - hypothetical_inner_size: Size { width: 0.0, height: 0.0 }, - hypothetical_outer_size: Size { width: 0.0, height: 0.0 }, - target_size: Size { width: 0.0, height: 0.0 }, - outer_target_size: Size { width: 0.0, height: 0.0 }, - - baseline: 0.0, - - offset_main: 0.0, - offset_cross: 0.0, - }) - .collect(); - - let has_baseline_child = flex_items.iter().fold(false, |result, child| { - result || self.style[&child.node].align_self(&self.style[&node]) == AlignSelf::Baseline - }); - - // TODO - this does not follow spec. See commented out code below - // 3. Determine the flex base size and hypothetical main size of each item: - flex_items.iter_mut().try_for_each(|child| -> Result<(), Box> { - let child_style = self.style[&child.node]; - - // A. If the item has a definite used flex basis, that’s the flex base size. - - let flex_basis = child_style.flex_basis.resolve(node_inner_size.main(dir)); - if flex_basis.is_defined() { - child.flex_basis = flex_basis.or_else(0.0); - return Ok(()); - }; - - // B. If the flex item has an intrinsic aspect ratio, - // a used flex basis of content, and a definite cross size, - // then the flex base size is calculated from its inner - // cross size and the flex item’s intrinsic aspect ratio. - - if let Defined(ratio) = child_style.aspect_ratio { - if let Defined(cross) = node_size.cross(dir) { - if child_style.flex_basis == Dimension::Auto { - child.flex_basis = cross * ratio; - return Ok(()); - } - } - } - - // C. If the used flex basis is content or depends on its available space, - // and the flex container is being sized under a min-content or max-content - // constraint (e.g. when performing automatic table layout [CSS21]), - // size the item under that constraint. The flex base size is the item’s - // resulting main size. - - // TODO - Probably need to cover this case in future - - // D. Otherwise, if the used flex basis is content or depends on its - // available space, the available main size is infinite, and the flex item’s - // inline axis is parallel to the main axis, lay the item out using the rules - // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size - // is the item’s max-content main size. - - // TODO - Probably need to cover this case in future - - // E. Otherwise, size the item into the available space using its used flex basis - // in place of its main size, treating a value of content as max-content. - // If a cross size is needed to determine the main size (e.g. when the - // flex item’s main size is in its block axis) and the flex item’s cross size - // is auto and not definite, in this calculation use fit-content as the - // flex item’s cross size. The flex base size is the item’s resulting main size. - - let width: Number = if !child.size.width.is_defined() - && child_style.align_self(&self.style[&node]) == AlignSelf::Stretch - && is_column - { - available_space.width - } else { - child.size.width - }; - - let height: Number = if !child.size.height.is_defined() - && child_style.align_self(&self.style[&node]) == AlignSelf::Stretch - && is_row - { - available_space.height - } else { - child.size.height - }; - - child.flex_basis = self - .compute_internal( - child.node, - Size { - width: width.maybe_max(child.min_size.width).maybe_min(child.max_size.width), - height: height.maybe_max(child.min_size.height).maybe_min(child.max_size.height), - }, - available_space, - false, - )? - .size - .main(dir) - .maybe_max(child.min_size.main(dir)) - .maybe_min(child.max_size.main(dir)); - - Ok(()) - })?; - - // The hypothetical main size is the item’s flex base size clamped according to its - // used min and max main sizes (and flooring the content box size at zero). - - flex_items.iter_mut().try_for_each(|child| -> Result<(), Box> { - child.inner_flex_basis = child.flex_basis - child.padding.main(dir) - child.border.main(dir); - - // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though. - // The following logic was developed not from the spec but by trail and error looking into how - // webkit handled various scenarios. Can probably be solved better by passing in - // min-content max-content constraints from the top - let min_main = self - .compute_internal(child.node, Size { width: Undefined, height: Undefined }, available_space, false)? - .size - .main(dir) - .maybe_max(child.min_size.main(dir)) - .maybe_min(child.size.main(dir)) - .to_number(); - - child - .hypothetical_inner_size - .set_main(dir, child.flex_basis.maybe_max(min_main).maybe_min(child.max_size.main(dir))); - - child - .hypothetical_outer_size - .set_main(dir, child.hypothetical_inner_size.main(dir) + child.margin.main(dir)); - - Ok(()) - })?; - - // 9.3. Main Size Determination - - // 5. Collect flex items into flex lines: - // - If the flex container is single-line, collect all the flex items into - // a single flex line. - // - Otherwise, starting from the first uncollected item, collect consecutive - // items one by one until the first time that the next collected item would - // not fit into the flex container’s inner main size (or until a forced break - // is encountered, see §10 Fragmenting Flex Layout). If the very first - // uncollected item wouldn’t fit, collect just it into the line. - // - // For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.) - // Repeat until all flex items have been collected into flex lines - // - // Note that the "collect as many" line will collect zero-sized flex items onto - // the end of the previous line even if the last non-zero item exactly "filled up" the line. - - let mut flex_lines = { - let mut lines: Vec = vec![]; - let mut line_length = 0.0; - - if self.style[&node].flex_wrap == FlexWrap::NoWrap { - lines.push(FlexLine { items: flex_items, cross_size: 0.0, offset_cross: 0.0 }); - } else { - let mut line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 }; - - for child in flex_items { - line_length += child.hypothetical_outer_size.main(dir); - - if let Defined(main) = available_space.main(dir) { - if line_length > main && !line.items.is_empty() { - line_length = child.hypothetical_outer_size.main(dir); - lines.push(line); - line = FlexLine { items: vec![], cross_size: 0.0, offset_cross: 0.0 }; - } - } - - line.items.push(child); - } - - lines.push(line); - } - - lines - }; - - // 6. Resolve the flexible lengths of all the flex items to find their used main size. - // See §9.7 Resolving Flexible Lengths. - // - // 9.7. Resolving Flexible Lengths - - flex_lines.iter_mut().try_for_each(|line| -> Result<(), Box> { - // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all - // items on the line. If the sum is less than the flex container’s inner main size, - // use the flex grow factor for the rest of this algorithm; otherwise, use the - // flex shrink factor. - - let used_flex_factor: f32 = line.items.iter().map(|child| child.hypothetical_outer_size.main(dir)).sum(); - let growing = used_flex_factor < node_inner_size.main(dir).or_else(0.0); - let shrinking = !growing; - - // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size - // - Any item that has a flex factor of zero - // - If using the flex grow factor: any item that has a flex base size - // greater than its hypothetical main size - // - If using the flex shrink factor: any item that has a flex base size - // smaller than its hypothetical main size - - line.items.iter_mut().try_for_each(|child| -> Result<(), Box> { - // TODO - This is not found by reading the spec. Maybe this can be done in some other place - // instead. This was found by trail and error fixing tests to align with webkit output. - if node_inner_size.main(dir).is_undefined() && is_row { - child.target_size.set_main( - dir, - self.compute_internal( - child.node, - Size { - width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width), - height: child - .size - .height - .maybe_max(child.min_size.height) - .maybe_min(child.max_size.height), - }, - available_space, - false, - )? - .size - .main(dir) - .maybe_max(child.min_size.main(dir)) - .maybe_min(child.max_size.main(dir)), - ); - } else { - child.target_size.set_main(dir, child.hypothetical_inner_size.main(dir)); - } - - // TODO this should really only be set inside the if-statement below but - // that causes the target_main_size to never be set for some items - - child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir)); - - let child_style = &self.style[&child.node]; - if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0) - || (growing && child.flex_basis > child.hypothetical_inner_size.main(dir)) - || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(dir)) - { - child.frozen = true; - } - - Ok(()) - })?; - - // 3. Calculate initial free space. Sum the outer sizes of all items on the line, - // and subtract this from the flex container’s inner main size. For frozen items, - // use their outer target main size; for other items, use their outer flex base size. - - let used_space: f32 = line - .items - .iter() - .map(|child| { - child.margin.main(dir) + if child.frozen { child.target_size.main(dir) } else { child.flex_basis } - }) - .sum(); - - let initial_free_space = (node_inner_size.main(dir) - used_space).or_else(0.0); - - // 4. Loop - - loop { - // a. Check for flexible items. If all the flex items on the line are frozen, - // free space has been distributed; exit this loop. - - let mut frozen: Vec<&mut FlexItem> = vec![]; - let mut unfrozen: Vec<&mut FlexItem> = vec![]; - - line.items.iter_mut().for_each(|child| { - if child.frozen { - frozen.push(child); - } else { - unfrozen.push(child); - } - }); - - if unfrozen.is_empty() { - break; - } - - // b. Calculate the remaining free space as for initial free space, above. - // If the sum of the unfrozen flex items’ flex factors is less than one, - // multiply the initial free space by this sum. If the magnitude of this - // value is less than the magnitude of the remaining free space, use this - // as the remaining free space. - - let used_space: f32 = Iterator::chain(frozen.iter(), unfrozen.iter()) - .map(|child| { - child.margin.main(dir) - + if child.frozen { child.target_size.main(dir) } else { child.flex_basis } - }) - .sum(); - - let (sum_flex_grow, sum_flex_shrink): (f32, f32) = - unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| { - let style = &self.style[&item.node]; - (flex_grow + style.flex_grow, flex_shrink + style.flex_shrink) - }); - - let free_space = if growing && sum_flex_grow < 1.0 { - (initial_free_space * sum_flex_grow).maybe_min(node_inner_size.main(dir) - used_space) - } else if shrinking && sum_flex_shrink < 1.0 { - (initial_free_space * sum_flex_shrink).maybe_max(node_inner_size.main(dir) - used_space) - } else { - (node_inner_size.main(dir) - used_space).or_else(0.0) - }; - - // c. Distribute free space proportional to the flex factors. - // - If the remaining free space is zero - // Do Nothing - // - If using the flex grow factor - // Find the ratio of the item’s flex grow factor to the sum of the - // flex grow factors of all unfrozen items on the line. Set the item’s - // target main size to its flex base size plus a fraction of the remaining - // free space proportional to the ratio. - // - If using the flex shrink factor - // For every unfrozen item on the line, multiply its flex shrink factor by - // its inner flex base size, and note this as its scaled flex shrink factor. - // Find the ratio of the item’s scaled flex shrink factor to the sum of the - // scaled flex shrink factors of all unfrozen items on the line. Set the item’s - // target main size to its flex base size minus a fraction of the absolute value - // of the remaining free space proportional to the ratio. Note this may result - // in a negative inner main size; it will be corrected in the next step. - // - Otherwise - // Do Nothing - - if free_space.is_normal() { - if growing && sum_flex_grow > 0.0 { - unfrozen.iter_mut().for_each(|child| { - child.target_size.set_main( - dir, - child.flex_basis + free_space * (self.style[&child.node].flex_grow / sum_flex_grow), - ); - }); - } else if shrinking && sum_flex_shrink > 0.0 { - let sum_scaled_shrink_factor: f32 = unfrozen - .iter() - .map(|child| child.inner_flex_basis * self.style[&child.node].flex_shrink) - .sum(); - - if sum_scaled_shrink_factor > 0.0 { - unfrozen.iter_mut().for_each(|child| { - let scaled_shrink_factor = child.inner_flex_basis * self.style[&child.node].flex_shrink; - child.target_size.set_main( - dir, - child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor), - ) - }); - } - } - } - - // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its - // used min and max main sizes and floor its content-box size at zero. If the - // item’s target main size was made smaller by this, it’s a max violation. - // If the item’s target main size was made larger by this, it’s a min violation. - - let total_violation = unfrozen.iter_mut().try_fold(0.0, |acc, child| -> Result> { - // TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though. - // The following logic was developed not from the spec but by trail and error looking into how - // webkit handled various scenarios. Can probably be solved better by passing in - // min-content max-content constraints from the top. Need to figure out correct thing to do here as - // just piling on more conditionals. - let min_main = if is_row && self.measure[&child.node].is_none() { - self.compute_internal( - child.node, - Size { width: Undefined, height: Undefined }, - available_space, - false, - )? - .size - .width - .maybe_min(child.size.width) - .maybe_max(child.min_size.width) - .to_number() - } else { - child.min_size.main(dir) - }; - - let max_main = child.max_size.main(dir); - let clamped = child.target_size.main(dir).maybe_min(max_main).maybe_max(min_main).max(0.0); - child.violation = clamped - child.target_size.main(dir); - child.target_size.set_main(dir, clamped); - child.outer_target_size.set_main(dir, child.target_size.main(dir) + child.margin.main(dir)); - - Ok(acc + child.violation) - })?; - - // e. Freeze over-flexed items. The total violation is the sum of the adjustments - // from the previous step ∑(clamped size - unclamped size). If the total violation is: - // - Zero - // Freeze all items. - // - Positive - // Freeze all the items with min violations. - // - Negative - // Freeze all the items with max violations. - - unfrozen.iter_mut().for_each(|child| match total_violation { - v if v > 0.0 => child.frozen = child.violation > 0.0, - v if v < 0.0 => child.frozen = child.violation < 0.0, - _ => child.frozen = true, - }) - - // f. Return to the start of this loop. - } - - Ok(()) - })?; - - // Not part of the spec from what i can see but seems correct - container_size.set_main( - dir, - node_size.main(dir).or_else({ - let longest_line = flex_lines.iter().fold(f32::MIN, |acc, line| { - let length: f32 = line.items.iter().map(|item| item.outer_target_size.main(dir)).sum(); - acc.max(length) - }); - - let size = longest_line + padding_border.main(dir); - match available_space.main(dir) { - Defined(val) if flex_lines.len() > 1 && size < val => val, - _ => size, - } - }), - ); - - inner_container_size.set_main(dir, container_size.main(dir) - padding_border.main(dir)); - - // 9.4. Cross Size Determination - - // 7. Determine the hypothetical cross size of each item by performing layout with the - // used main size and the available space, treating auto as fit-content. - - flex_lines.iter_mut().try_for_each(|line| { - line.items.iter_mut().try_for_each(|child| -> Result<(), Box> { - let child_cross = - child.size.cross(dir).maybe_max(child.min_size.cross(dir)).maybe_min(child.max_size.cross(dir)); - - child.hypothetical_inner_size.set_cross( - dir, - self.compute_internal( - child.node, - Size { - width: if is_row { child.target_size.width.to_number() } else { child_cross }, - height: if is_row { child_cross } else { child.target_size.height.to_number() }, - }, - Size { - width: if is_row { container_size.main(dir).to_number() } else { available_space.width }, - height: if is_row { available_space.height } else { container_size.main(dir).to_number() }, - }, - false, - )? - .size - .cross(dir) - .maybe_max(child.min_size.cross(dir)) - .maybe_min(child.max_size.cross(dir)), - ); - - child - .hypothetical_outer_size - .set_cross(dir, child.hypothetical_inner_size.cross(dir) + child.margin.cross(dir)); - - Ok(()) - }) - })?; - - // TODO - probably should move this somewhere else as it doesn't make a ton of sense here but we need it below - // TODO - This is expensive and should only be done if we really require a baseline. aka, make it lazy - - fn calc_baseline(db: &Stretch, node: Node, layout: &result::Layout) -> f32 { - if db.children[&node].is_empty() { - layout.size.height - } else { - let child = db.children[&node][0]; - calc_baseline(db, child, &db.layout[&child]) - } - }; - - if has_baseline_child { - flex_lines.iter_mut().try_for_each(|line| { - line.items.iter_mut().try_for_each(|child| -> Result<(), Box> { - let result = self.compute_internal( - child.node, - Size { - width: if is_row { - child.target_size.width.to_number() - } else { - child.hypothetical_inner_size.width.to_number() - }, - height: if is_row { - child.hypothetical_inner_size.height.to_number() - } else { - child.target_size.height.to_number() - }, - }, - Size { - width: if is_row { container_size.width.to_number() } else { node_size.width }, - height: if is_row { node_size.height } else { container_size.height.to_number() }, - }, - true, - )?; - - child.baseline = calc_baseline( - self, - child.node, - &result::Layout { - order: self.children[&node].iter().position(|n| *n == child.node).unwrap() as u32, - size: result.size, - location: Point { x: 0.0, y: 0.0 }, - }, - ); - - Ok(()) - }) - })?; - } - - // 8. Calculate the cross size of each flex line. - // If the flex container is single-line and has a definite cross size, the cross size - // of the flex line is the flex container’s inner cross size. Otherwise, for each flex line: - // - // If the flex container is single-line, then clamp the line’s cross-size to be within - // the container’s computed min and max cross sizes. Note that if CSS 2.1’s definition - // of min/max-width/height applied more generally, this behavior would fall out automatically. - - if flex_lines.len() == 1 && node_size.cross(dir).is_defined() { - flex_lines[0].cross_size = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0); - } else { - flex_lines.iter_mut().for_each(|line| { - // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose - // align-self is baseline, and whose cross-axis margins are both non-auto. Find the - // largest of the distances between each item’s baseline and its hypothetical outer - // cross-start edge, and the largest of the distances between each item’s baseline - // and its hypothetical outer cross-end edge, and sum these two values. - - // 2. Among all the items not collected by the previous step, find the largest - // outer hypothetical cross size. - - // 3. The used cross-size of the flex line is the largest of the numbers found in the - // previous two steps and zero. - - let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x)); - line.cross_size = line - .items - .iter() - .map(|child| { - let child_style = &self.style[&child.node]; - if child_style.align_self(&self.style[&node]) == AlignSelf::Baseline - && child_style.cross_margin_start(dir) != Dimension::Auto - && child_style.cross_margin_end(dir) != Dimension::Auto - && child_style.cross_size(dir) == Dimension::Auto - { - max_baseline - child.baseline + child.hypothetical_outer_size.cross(dir) - } else { - child.hypothetical_outer_size.cross(dir) - } - }) - .fold(0.0, |acc, x| acc.max(x)); - }); - } - - // 9. Handle 'align-content: stretch'. If the flex container has a definite cross size, - // align-content is stretch, and the sum of the flex lines' cross sizes is less than - // the flex container’s inner cross size, increase the cross size of each flex line - // by equal amounts such that the sum of their cross sizes exactly equals the - // flex container’s inner cross size. - - if self.style[&node].align_content == AlignContent::Stretch && node_size.cross(dir).is_defined() { - let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum(); - let inner_cross = (node_size.cross(dir) - padding_border.cross(dir)).or_else(0.0); - - if total_cross < inner_cross { - let remaining = inner_cross - total_cross; - let addition = remaining / flex_lines.len() as f32; - flex_lines.iter_mut().for_each(|line| line.cross_size += addition); - } - } - - // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse, - // note the cross size of the line they’re in as the item’s strut size, and restart - // layout from the beginning. - // - // In this second layout round, when collecting items into lines, treat the collapsed - // items as having zero main size. For the rest of the algorithm following that step, - // ignore the collapsed items entirely (as if they were display:none) except that after - // calculating the cross size of the lines, if any line’s cross size is less than the - // largest strut size among all the collapsed items in the line, set its cross size to - // that strut size. - // - // Skip this step in the second layout round. - - // TODO implement once (if ever) we support visibility:collapse - - // 11. Determine the used cross size of each flex item. If a flex item has align-self: stretch, - // its computed cross size property is auto, and neither of its cross-axis margins are auto, - // the used outer cross size is the used cross size of its flex line, clamped according to - // the item’s used min and max cross sizes. Otherwise, the used cross size is the item’s - // hypothetical cross size. - // - // If the flex item has align-self: stretch, redo layout for its contents, treating this - // used size as its definite cross size so that percentage-sized children can be resolved. - // - // Note that this step does not affect the main size of the flex item, even if it has an - // intrinsic aspect ratio. - - flex_lines.iter_mut().for_each(|line| { - let line_cross_size = line.cross_size; - - line.items.iter_mut().for_each(|child| { - let child_style = &self.style[&child.node]; - child.target_size.set_cross( - dir, - if child_style.align_self(&self.style[&node]) == AlignSelf::Stretch - && child_style.cross_margin_start(dir) != Dimension::Auto - && child_style.cross_margin_end(dir) != Dimension::Auto - && child_style.cross_size(dir) == Dimension::Auto - { - (line_cross_size - child.margin.cross(dir)) - .maybe_max(child.min_size.cross(dir)) - .maybe_min(child.max_size.cross(dir)) - } else { - child.hypothetical_inner_size.cross(dir) - }, - ); - - child.outer_target_size.set_cross(dir, child.target_size.cross(dir) + child.margin.cross(dir)); - }); - }); - - // 9.5. Main-Axis Alignment - - // 12. Distribute any remaining free space. For each flex line: - // 1. If the remaining free space is positive and at least one main-axis margin on this - // line is auto, distribute the free space equally among these margins. Otherwise, - // set all auto margins to zero. - // 2. Align the items along the main-axis per justify-content. - - flex_lines.iter_mut().for_each(|line| { - let used_space: f32 = line.items.iter().map(|child| child.outer_target_size.main(dir)).sum(); - let free_space = inner_container_size.main(dir) - used_space; - let mut num_auto_margins = 0; - - line.items.iter_mut().for_each(|child| { - let child_style = &self.style[&child.node]; - if child_style.main_margin_start(dir) == Dimension::Auto { - num_auto_margins += 1; - } - if child_style.main_margin_end(dir) == Dimension::Auto { - num_auto_margins += 1; - } - }); - - if free_space > 0.0 && num_auto_margins > 0 { - let margin = free_space / num_auto_margins as f32; - - line.items.iter_mut().for_each(|child| { - let child_style = &self.style[&child.node]; - if child_style.main_margin_start(dir) == Dimension::Auto { - if is_row { - child.margin.start = margin; - } else { - child.margin.top = margin; - } - } - if child_style.main_margin_end(dir) == Dimension::Auto { - if is_row { - child.margin.end = margin; - } else { - child.margin.bottom = margin; - } - } - }); - } else { - let num_items = line.items.len(); - let layout_reverse = dir.is_reverse(); - - let justify_item = |(i, child): (usize, &mut FlexItem)| { - let is_first = i == 0; - - child.offset_main = match self.style[&node].justify_content { - JustifyContent::FlexStart => { - if layout_reverse && is_first { - free_space - } else { - 0.0 - } - } - JustifyContent::Center => { - if is_first { - free_space / 2.0 - } else { - 0.0 - } - } - JustifyContent::FlexEnd => { - if is_first && !layout_reverse { - free_space - } else { - 0.0 - } - } - JustifyContent::SpaceBetween => { - if is_first { - 0.0 - } else { - free_space / (num_items - 1) as f32 - } - } - JustifyContent::SpaceAround => { - if is_first { - (free_space / num_items as f32) / 2.0 - } else { - free_space / num_items as f32 - } - } - JustifyContent::SpaceEvenly => free_space / (num_items + 1) as f32, - }; - }; - - if layout_reverse { - line.items.iter_mut().rev().enumerate().for_each(justify_item); - } else { - line.items.iter_mut().enumerate().for_each(justify_item); - } - } - }); - - // 9.6. Cross-Axis Alignment - - // 13. Resolve cross-axis auto margins. If a flex item has auto cross-axis margins: - // - If its outer cross size (treating those auto margins as zero) is less than the - // cross size of its flex line, distribute the difference in those sizes equally - // to the auto margins. - // - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) - // is auto, set it to zero. Set the opposite margin so that the outer cross size of the - // item equals the cross size of its flex line. - - flex_lines.iter_mut().for_each(|line| { - let line_cross_size = line.cross_size; - let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x)); - - line.items.iter_mut().for_each(|child| { - let free_space = line_cross_size - child.outer_target_size.cross(dir); - let child_style = &self.style[&child.node]; - - if child_style.cross_margin_start(dir) == Dimension::Auto - && child_style.cross_margin_end(dir) == Dimension::Auto - { - if is_row { - child.margin.top = free_space / 2.0; - child.margin.bottom = free_space / 2.0; - } else { - child.margin.start = free_space / 2.0; - child.margin.end = free_space / 2.0; - } - } else if child_style.cross_margin_start(dir) == Dimension::Auto { - if is_row { - child.margin.top = free_space; - } else { - child.margin.start = free_space; - } - } else if child_style.cross_margin_end(dir) == Dimension::Auto { - if is_row { - child.margin.bottom = free_space; - } else { - child.margin.end = free_space; - } - } else { - // 14. Align all flex items along the cross-axis per align-self, if neither of the item’s - // cross-axis margins are auto. - - child.offset_cross = match child_style.align_self(&self.style[&node]) { - AlignSelf::Auto => 0.0, // Should never happen - AlignSelf::FlexStart => { - if is_wrap_reverse { - free_space - } else { - 0.0 - } - } - AlignSelf::FlexEnd => { - if is_wrap_reverse { - 0.0 - } else { - free_space - } - } - AlignSelf::Center => free_space / 2.0, - AlignSelf::Baseline => { - if is_row { - max_baseline - child.baseline - } else { - // baseline alignment only makes sense if the direction is row - // we treat it as flex-start alignment in columns. - if is_wrap_reverse { - free_space - } else { - 0.0 - } - } - } - AlignSelf::Stretch => { - if is_wrap_reverse { - free_space - } else { - 0.0 - } - } - }; - } - }); - }); - - // 15. Determine the flex container’s used cross size: - // - If the cross size property is a definite size, use that, clamped by the used - // min and max cross sizes of the flex container. - // - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used - // min and max cross sizes of the flex container. - - let total_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum(); - container_size.set_cross(dir, node_size.cross(dir).or_else(total_cross_size + padding_border.cross(dir))); - inner_container_size.set_cross(dir, container_size.cross(dir) - padding_border.cross(dir)); - - // We have the container size. If our caller does not care about performing - // layout we are done now. - if !perform_layout { - let result = ComputeResult { size: container_size }; - *self.layout_cache.get_mut(node).unwrap() = - Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); - return Ok(result); - } - - // 16. Align all flex lines per align-content. - - let free_space = inner_container_size.cross(dir) - total_cross_size; - let num_lines = flex_lines.len(); - - let align_line = |(i, line): (usize, &mut FlexLine)| { - let is_first = i == 0; - - line.offset_cross = match self.style[&node].align_content { - AlignContent::FlexStart => { - if is_first && is_wrap_reverse { - free_space - } else { - 0.0 - } - } - AlignContent::FlexEnd => { - if is_first && !is_wrap_reverse { - free_space - } else { - 0.0 - } - } - AlignContent::Center => { - if is_first { - free_space / 2.0 - } else { - 0.0 - } - } - AlignContent::Stretch => 0.0, - AlignContent::SpaceBetween => { - if is_first { - 0.0 - } else { - free_space / (num_lines - 1) as f32 - } - } - AlignContent::SpaceAround => { - if is_first { - (free_space / num_lines as f32) / 2.0 - } else { - free_space / num_lines as f32 - } - } - }; - }; - - if is_wrap_reverse { - flex_lines.iter_mut().rev().enumerate().for_each(align_line); - } else { - flex_lines.iter_mut().enumerate().for_each(align_line); - } - - // Do a final layout pass and gather the resulting layouts - { - let mut lines: Vec> = vec![]; - let mut total_offset_cross = padding_border.cross_start(dir); - - let layout_line = |line: &mut FlexLine| -> Result<(), Box> { - let mut children: Vec = vec![]; - let mut total_offset_main = padding_border.main_start(dir); - let line_offset_cross = line.offset_cross; - - let layout_item = |child: &mut FlexItem| -> Result<(), Box> { - let result = self.compute_internal( - child.node, - child.target_size.map(|s| s.to_number()), - container_size.map(|s| s.to_number()), - true, - )?; - - let offset_main = total_offset_main - + child.offset_main - + child.margin.main_start(dir) - + (child.position.main_start(dir).or_else(0.0) - child.position.main_end(dir).or_else(0.0)); - - let offset_cross = total_offset_cross - + child.offset_cross - + line_offset_cross - + child.margin.cross_start(dir) - + (child.position.cross_start(dir).or_else(0.0) - child.position.cross_end(dir).or_else(0.0)); - - *self.layout.get_mut(child.node).unwrap() = result::Layout { - order: self.children[&node].iter().position(|n| *n == child.node).unwrap() as u32, - size: result.size, - location: Point { - x: if is_row { offset_main } else { offset_cross }, - y: if is_column { offset_main } else { offset_cross }, - }, - }; - - total_offset_main += child.offset_main + child.margin.main(dir) + result.size.main(dir); - - Ok(()) - }; - - if dir.is_reverse() { - line.items.iter_mut().rev().try_for_each(layout_item)?; - } else { - line.items.iter_mut().try_for_each(layout_item)?; - } - - total_offset_cross += line_offset_cross + line.cross_size; - - if dir.is_reverse() { - children.reverse(); - } - - lines.push(children); - - Ok(()) - }; - - if is_wrap_reverse { - flex_lines.iter_mut().rev().try_for_each(layout_line)?; - } else { - flex_lines.iter_mut().try_for_each(layout_line)?; - } - } - - // Before returning we perform absolute layout on all absolutely positioned children - { - // TODO: remove number of Vec<_> generated - let candidates = self.children[&node] - .iter() - .cloned() - .enumerate() - .filter(|(_, child)| self.style[&child].position_type == PositionType::Absolute) - .collect::>(); - - for (order, child) in candidates { - let container_width = container_size.width.to_number(); - let container_height = container_size.height.to_number(); - - let child_style = self.style[&child]; - - let start = child_style.position.start.resolve(container_width) - + child_style.margin.start.resolve(container_width); - let end = - child_style.position.end.resolve(container_width) + child_style.margin.end.resolve(container_width); - let top = child_style.position.top.resolve(container_height) - + child_style.margin.top.resolve(container_height); - let bottom = child_style.position.bottom.resolve(container_height) - + child_style.margin.bottom.resolve(container_height); - - let (start_main, end_main) = if is_row { (start, end) } else { (top, bottom) }; - let (start_cross, end_cross) = if is_row { (top, bottom) } else { (start, end) }; - - let width = child_style - .size - .width - .resolve(container_width) - .maybe_max(child_style.min_size.width.resolve(container_width)) - .maybe_min(child_style.max_size.width.resolve(container_width)) - .or_else(if start.is_defined() && end.is_defined() { - container_width - start - end - } else { - Undefined - }); - - let height = child_style - .size - .height - .resolve(container_height) - .maybe_max(child_style.min_size.height.resolve(container_height)) - .maybe_min(child_style.max_size.height.resolve(container_height)) - .or_else(if top.is_defined() && bottom.is_defined() { - container_height - top - bottom - } else { - Undefined - }); - - let result = self.compute_internal( - child, - Size { width, height }, - Size { width: container_width, height: container_height }, - true, - )?; - - let free_main_space = container_size.main(dir) - - result - .size - .main(dir) - .maybe_max(child_style.min_main_size(dir).resolve(node_inner_size.main(dir))) - .maybe_min(child_style.max_main_size(dir).resolve(node_inner_size.main(dir))); - - let free_cross_space = container_size.cross(dir) - - result - .size - .cross(dir) - .maybe_max(child_style.min_cross_size(dir).resolve(node_inner_size.cross(dir))) - .maybe_min(child_style.max_cross_size(dir).resolve(node_inner_size.cross(dir))); - - let offset_main = if start_main.is_defined() { - start_main.or_else(0.0) + border.main_start(dir) - } else if end_main.is_defined() { - free_main_space - end_main.or_else(0.0) - border.main_end(dir) - } else { - match self.style[&node].justify_content { - JustifyContent::SpaceBetween | JustifyContent::FlexStart => padding_border.main_start(dir), - JustifyContent::FlexEnd => free_main_space - padding_border.main_end(dir), - JustifyContent::SpaceEvenly | JustifyContent::SpaceAround | JustifyContent::Center => { - free_main_space / 2.0 - } - } - }; - - let offset_cross = if start_cross.is_defined() { - start_cross.or_else(0.0) + border.cross_start(dir) - } else if end_cross.is_defined() { - free_cross_space - end_cross.or_else(0.0) - border.cross_end(dir) - } else { - match child_style.align_self(&self.style[&node]) { - AlignSelf::Auto => 0.0, // Should never happen - AlignSelf::FlexStart => { - if is_wrap_reverse { - free_cross_space - padding_border.cross_end(dir) - } else { - padding_border.cross_start(dir) - } - } - AlignSelf::FlexEnd => { - if is_wrap_reverse { - padding_border.cross_start(dir) - } else { - free_cross_space - padding_border.cross_end(dir) - } - } - AlignSelf::Center => free_cross_space / 2.0, - AlignSelf::Baseline => free_cross_space / 2.0, // Treat as center for now until we have baseline support - AlignSelf::Stretch => { - if is_wrap_reverse { - free_cross_space - padding_border.cross_end(dir) - } else { - padding_border.cross_start(dir) - } - } - } - }; - - *self.layout.get_mut(child).unwrap() = result::Layout { - order: order as u32, - size: result.size, - location: Point { - x: if is_row { offset_main } else { offset_cross }, - y: if is_column { offset_main } else { offset_cross }, - }, - }; - } - } - - fn hidden_layout(layout: &mut Storage, children: &Storage>, node: Node, order: u32) { - *layout.get_mut(node).unwrap() = - result::Layout { order, size: Size { width: 0.0, height: 0.0 }, location: Point { x: 0.0, y: 0.0 } }; - - for (order, child) in children[&node].iter().enumerate() { - hidden_layout(layout, children, *child, order as _); - } - } - - for (order, child) in self.children[&node].iter().enumerate() { - if self.style[&child].display == Display::None { - hidden_layout(&mut self.layout, &self.children, *child, order as _); - } - } - - let result = ComputeResult { size: container_size }; - *self.layout_cache.get_mut(node).unwrap() = - Some(result::Cache { node_size, parent_size, perform_layout, result: result.clone() }); - Ok(result) - } -} diff --git a/styles/src/stretch/geometry.rs b/styles/src/stretch/geometry.rs deleted file mode 100644 index 9984bbc..0000000 --- a/styles/src/stretch/geometry.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! This module is included while awaiting an upstream merge in stretch proper. -//! You should not rely on it, and consider it an implementation detail. - -use core::ops::Add; - -use crate::stretch::number::Number; -use crate::stretch::style; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Rect { - pub start: T, - pub end: T, - pub top: T, - pub bottom: T, -} - -impl Rect { - pub(crate) fn map(self, f: F) -> Rect - where - F: Fn(T) -> R, - { - Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) } - } -} - -impl Rect -where - T: Add + Copy + Clone, -{ - pub(crate) fn horizontal(&self) -> T { - self.start + self.end - } - - pub(crate) fn vertical(&self) -> T { - self.top + self.bottom - } - - pub(crate) fn main(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.start + self.end, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.top + self.bottom, - } - } - - pub(crate) fn cross(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.top + self.bottom, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.start + self.end, - } - } -} - -impl Rect -where - T: Copy + Clone, -{ - pub(crate) fn main_start(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.start, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.top, - } - } - - pub(crate) fn main_end(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.end, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.bottom, - } - } - - pub(crate) fn cross_start(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.top, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.start, - } - } - - pub(crate) fn cross_end(&self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.bottom, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.end, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Size { - pub width: T, - pub height: T, -} - -impl Size<()> { - pub fn undefined() -> Size { - Size { width: Number::Undefined, height: Number::Undefined } - } -} - -impl Size { - pub(crate) fn map(self, f: F) -> Size - where - F: Fn(T) -> R, - { - Size { width: f(self.width), height: f(self.height) } - } - - pub(crate) fn set_main(&mut self, direction: style::FlexDirection, value: T) { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.width = value, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.height = value, - } - } - - pub(crate) fn set_cross(&mut self, direction: style::FlexDirection, value: T) { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.height = value, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.width = value, - } - } - - pub(crate) fn main(self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.width, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.height, - } - } - - pub(crate) fn cross(self, direction: style::FlexDirection) -> T { - match direction { - style::FlexDirection::Row | style::FlexDirection::RowReverse => self.height, - style::FlexDirection::Column | style::FlexDirection::ColumnReverse => self.width, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Point { - pub x: T, - pub y: T, -} diff --git a/styles/src/stretch/id.rs b/styles/src/stretch/id.rs deleted file mode 100644 index 2f5af6f..0000000 --- a/styles/src/stretch/id.rs +++ /dev/null @@ -1,35 +0,0 @@ -///! This module is included while awaiting an upstream merge in stretch proper. -///! You should not rely on it, and consider it an implementation detail. - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) struct Id { - id: u32, - generation: u32, -} - -pub(crate) struct Allocator { - new_id: u32, - free_ids: Vec, -} - -impl Allocator { - pub fn new() -> Self { - Allocator { new_id: 0, free_ids: Vec::new() } - } - - pub fn allocate(&mut self) -> Id { - // TODO: better balancing - match self.free_ids.pop() { - Some(id) => Id { id: id.id, generation: id.generation + 1 }, - None => { - let id = self.new_id; - self.new_id += 1; - Id { id, generation: 0 } - } - } - } - - pub fn free(&mut self, ids: &[Id]) { - self.free_ids.extend(ids); - } -} diff --git a/styles/src/stretch/mod.rs b/styles/src/stretch/mod.rs deleted file mode 100644 index 4eedcf3..0000000 --- a/styles/src/stretch/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! This module is included while awaiting an upstream merge in stretch proper. -//! You should not rely on it, and consider it an implementation detail. - -pub mod geometry; -pub mod node; -pub mod number; -pub mod result; -pub mod style; - -mod algo; -mod id; - -use core::any::Any; - -#[derive(Debug)] -pub enum Error { - InvalidNode(node::Node), - Measure(Box), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - Error::InvalidNode(ref node) => write!(f, "Invalid node {:?}", node), - Error::Measure(_) => write!(f, "Error during measurement"), - } - } -} - -impl std::error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::InvalidNode(_) => "The node is not part of the stretch instance", - Error::Measure(_) => "Error occurred inside a measurement function", - } - } -} diff --git a/styles/src/stretch/node.rs b/styles/src/stretch/node.rs deleted file mode 100644 index 52b9e46..0000000 --- a/styles/src/stretch/node.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! This module is included while awaiting an upstream merge in stretch proper. -//! You should not rely on it, and consider it an implementation detail. - -use core::any::Any; - -use std::collections::HashMap; -use std::ops::Drop; -use std::sync::Mutex; - -use lazy_static::lazy_static; - -use crate::stretch::geometry::Size; -use crate::stretch::id; -use crate::stretch::number::Number; -use crate::stretch::result::{Cache, Layout}; -use crate::stretch::style::*; -use crate::stretch::Error; - -type MeasureFunc = Box) -> Result, Box> + Send + Sync + 'static>; - -lazy_static! { - /// Global stretch instance id allocator. - static ref INSTANCE_ALLOCATOR: Mutex = Mutex::new(id::Allocator::new()); -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct Node { - instance: id::Id, - local: id::Id, -} - -pub(crate) struct Storage(HashMap); - -impl Storage { - pub fn new() -> Self { - Storage(HashMap::new()) - } - - pub fn get(&self, node: Node) -> Result<&T, Error> { - match self.0.get(&node) { - Some(v) => Ok(v), - None => Err(Error::InvalidNode(node)), - } - } - - pub fn get_mut(&mut self, node: Node) -> Result<&mut T, Error> { - match self.0.get_mut(&node) { - Some(v) => Ok(v), - None => Err(Error::InvalidNode(node)), - } - } - - pub fn insert(&mut self, node: Node, value: T) -> Option { - self.0.insert(node, value) - } -} - -impl std::ops::Index<&Node> for Storage { - type Output = T; - - fn index(&self, idx: &Node) -> &T { - &(self.0)[idx] - } -} - -pub struct Stretch { - id: id::Id, - nodes: id::Allocator, - pub(crate) style: Storage