Move towards a new reconciler model, undo a lot of the weird stretch integrations I had done, separate out Layout and Appearance concepts, introduce ComponentKey and constructor lifecycle for components
This commit is contained in:
parent
6fd3f79099
commit
91266cc841
31 changed files with 820 additions and 941 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
//! but as the language stabilizes even further I'd love to get rid of this and
|
//! but as the language stabilizes even further I'd love to get rid of this and
|
||||||
//! just allow returning arbitrary iterators.
|
//! just allow returning arbitrary iterators.
|
||||||
|
|
||||||
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::traits::Component;
|
use alchemy_lifecycle::traits::Component;
|
||||||
|
|
||||||
/// Fragments are special - you can do something like the following in cases where you
|
/// Fragments are special - you can do something like the following in cases where you
|
||||||
|
|
@ -19,4 +20,8 @@ use alchemy_lifecycle::traits::Component;
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Fragment;
|
pub struct Fragment;
|
||||||
|
|
||||||
impl Component for Fragment {}
|
impl Component for Fragment {
|
||||||
|
fn constructor(_key: ComponentKey) -> Fragment {
|
||||||
|
Fragment { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@
|
||||||
|
|
||||||
use std::sync::{Mutex};
|
use std::sync::{Mutex};
|
||||||
|
|
||||||
use alchemy_styles::styles::{Layout, Style};
|
use alchemy_styles::styles::{Appearance, Layout};
|
||||||
|
|
||||||
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::error::Error;
|
use alchemy_lifecycle::error::Error;
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX};
|
use alchemy_lifecycle::rsx::{Props, RSX};
|
||||||
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
||||||
|
|
@ -23,19 +24,11 @@ use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
||||||
/// <Text styles=["styleKey1", "styleKey2"] />
|
/// <Text styles=["styleKey1", "styleKey2"] />
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
|
key: ComponentKey,
|
||||||
text: String,
|
text: String,
|
||||||
bridge: Mutex<PlatformTextBridge>
|
bridge: Mutex<PlatformTextBridge>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Text {
|
|
||||||
fn default() -> Text {
|
|
||||||
Text {
|
|
||||||
text: "".into(),
|
|
||||||
bridge: Mutex::new(PlatformTextBridge::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
// This is very naive for now, but it's fine - we probably
|
// This is very naive for now, but it's fine - we probably
|
||||||
// want to do some fun stuff here later with stylized text
|
// want to do some fun stuff here later with stylized text
|
||||||
|
|
@ -55,6 +48,14 @@ impl Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Text {
|
impl Component for Text {
|
||||||
|
fn constructor(key: ComponentKey) -> Text {
|
||||||
|
Text {
|
||||||
|
key: key,
|
||||||
|
text: "".into(),
|
||||||
|
bridge: Mutex::new(PlatformTextBridge::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn has_native_backing_node(&self) -> bool { true }
|
fn has_native_backing_node(&self) -> bool { true }
|
||||||
|
|
||||||
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> {
|
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> {
|
||||||
|
|
@ -66,9 +67,9 @@ impl Component for Text {
|
||||||
// Panic might not be right here, but eh, should probably do something.
|
// Panic might not be right here, but eh, should probably do something.
|
||||||
fn append_child_component(&self, _component: &Component) {}
|
fn append_child_component(&self, _component: &Component) {}
|
||||||
|
|
||||||
fn apply_styles(&self, layout: &Layout, style: &Style) {
|
fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
|
||||||
let mut bridge = self.bridge.lock().unwrap();
|
let mut bridge = self.bridge.lock().unwrap();
|
||||||
bridge.apply_styles(layout, style);
|
bridge.apply_styles(appearance, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_did_mount(&mut self, props: &Props) {
|
fn component_did_mount(&mut self, props: &Props) {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@
|
||||||
//! hence why they're all (somewhat annoyingly, but lovingly) re-implemented
|
//! hence why they're all (somewhat annoyingly, but lovingly) re-implemented
|
||||||
//! as bridges.
|
//! as bridges.
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use alchemy_styles::{Layout, Style, StylesList};
|
use alchemy_styles::{Appearance, Layout, StylesList};
|
||||||
|
|
||||||
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::error::Error;
|
use alchemy_lifecycle::error::Error;
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX};
|
use alchemy_lifecycle::rsx::{Props, RSX};
|
||||||
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
||||||
|
|
@ -33,6 +34,10 @@ impl Default for View {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for View {
|
impl Component for View {
|
||||||
|
fn constructor(_key: ComponentKey) -> View {
|
||||||
|
View(Mutex::new(PlatformViewBridge::new()))
|
||||||
|
}
|
||||||
|
|
||||||
fn has_native_backing_node(&self) -> bool { true }
|
fn has_native_backing_node(&self) -> bool { true }
|
||||||
|
|
||||||
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> {
|
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> {
|
||||||
|
|
@ -47,17 +52,17 @@ impl Component for View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_styles(&self, layout: &Layout, style: &Style) {
|
fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
|
||||||
let mut bridge = self.0.lock().unwrap();
|
let mut bridge = self.0.lock().unwrap();
|
||||||
bridge.apply_styles(layout, style);
|
bridge.apply_styles(appearance, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
||||||
Ok(RSX::node("Fragment", || Arc::new(RwLock::new(Fragment::default())), Props {
|
Ok(RSX::node("Fragment", |key| Box::new(Fragment::constructor(key)), Props {
|
||||||
attributes: std::collections::HashMap::new(),
|
attributes: std::collections::HashMap::new(),
|
||||||
key: "".into(),
|
key: "".into(),
|
||||||
styles: StylesList::new(),
|
styles: StylesList::new(),
|
||||||
children: props.children.clone()
|
children: vec![]
|
||||||
}))
|
}, props.children.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use std::sync::Arc;
|
||||||
pub use lazy_static::lazy_static;
|
pub use lazy_static::lazy_static;
|
||||||
use proc_macro_hack::proc_macro_hack;
|
use proc_macro_hack::proc_macro_hack;
|
||||||
|
|
||||||
|
pub use alchemy_lifecycle::ComponentKey;
|
||||||
pub use alchemy_lifecycle::traits::{
|
pub use alchemy_lifecycle::traits::{
|
||||||
AppDelegate, Component, WindowDelegate
|
AppDelegate, Component, WindowDelegate
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
//! Implements the Window API. It attempts to provide a nice, common interface across
|
//! Implements the Window API. It attempts to provide a nice, common interface across
|
||||||
//! per-platform Window APIs.
|
//! per-platform Window APIs.
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use alchemy_lifecycle::{Uuid, RENDER_ENGINE};
|
use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE};
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX};
|
use alchemy_lifecycle::rsx::RSX;
|
||||||
use alchemy_lifecycle::traits::{Component, WindowDelegate};
|
use alchemy_lifecycle::traits::WindowDelegate;
|
||||||
|
|
||||||
use alchemy_styles::number::Number;
|
|
||||||
use alchemy_styles::geometry::Size;
|
|
||||||
use alchemy_styles::styles::{Style, Dimension};
|
|
||||||
|
|
||||||
use crate::{App, SHARED_APP};
|
use crate::{App, SHARED_APP};
|
||||||
use crate::components::View;
|
use crate::components::View;
|
||||||
|
|
@ -25,7 +21,7 @@ pub struct AppWindow {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub bridge: PlatformWindowBridge,
|
pub bridge: PlatformWindowBridge,
|
||||||
pub delegate: Box<WindowDelegate>,
|
pub delegate: Box<WindowDelegate>,
|
||||||
pub render_key: Uuid
|
pub render_key: ComponentKey
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWindow {
|
impl AppWindow {
|
||||||
|
|
@ -74,7 +70,10 @@ impl Window {
|
||||||
let view = View::default();
|
let view = View::default();
|
||||||
let shared_app_ptr: *const App = &**SHARED_APP;
|
let shared_app_ptr: *const App = &**SHARED_APP;
|
||||||
let bridge = PlatformWindowBridge::new(window_id, title, dimensions, &view, shared_app_ptr);
|
let bridge = PlatformWindowBridge::new(window_id, title, dimensions, &view, shared_app_ptr);
|
||||||
let key = RENDER_ENGINE.register_root_component(view);
|
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 {
|
Window(Arc::new(Mutex::new(AppWindow {
|
||||||
id: window_id,
|
id: window_id,
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString};
|
||||||
|
|
||||||
use crate::color::IntoNSColor;
|
use crate::color::IntoNSColor;
|
||||||
|
|
||||||
use alchemy_styles::color::Color;
|
use alchemy_styles::{Color, Layout, Appearance};
|
||||||
use alchemy_styles::styles::Style;
|
|
||||||
use alchemy_styles::result::Layout;
|
|
||||||
|
|
||||||
use alchemy_lifecycle::traits::PlatformSpecificNodeType;
|
use alchemy_lifecycle::traits::PlatformSpecificNodeType;
|
||||||
|
|
||||||
|
|
@ -74,15 +72,15 @@ impl Text {
|
||||||
/// Given a `&Style`, will set the frame, background color, borders and so forth. It then
|
/// 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
|
/// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this
|
||||||
/// view.
|
/// view.
|
||||||
pub fn apply_styles(&mut self, layout: &Layout, style: &Style) {
|
pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let rect = NSRect::new(
|
let rect = NSRect::new(
|
||||||
NSPoint::new(layout.location.x.into(), layout.location.y.into()),
|
NSPoint::new(layout.location.x.into(), layout.location.y.into()),
|
||||||
NSSize::new(layout.size.width.into(), layout.size.height.into())
|
NSSize::new(layout.size.width.into(), layout.size.height.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
self.background_color = style.background_color.into_nscolor();
|
self.background_color = appearance.background_color.into_nscolor();
|
||||||
self.text_color = style.text_color.into_nscolor();
|
self.text_color = appearance.text_color.into_nscolor();
|
||||||
|
|
||||||
msg_send![&*self.inner_mut, setFrame:rect];
|
msg_send![&*self.inner_mut, setFrame:rect];
|
||||||
msg_send![&*self.inner_mut, setBackgroundColor:&*self.background_color];
|
msg_send![&*self.inner_mut, setBackgroundColor:&*self.background_color];
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ use cocoa::foundation::{NSRect, NSPoint, NSSize};
|
||||||
|
|
||||||
use crate::color::IntoNSColor;
|
use crate::color::IntoNSColor;
|
||||||
|
|
||||||
use alchemy_styles::color::Color;
|
use alchemy_styles::{Appearance, Color, Layout};
|
||||||
use alchemy_styles::styles::Style;
|
|
||||||
use alchemy_styles::result::Layout;
|
|
||||||
|
|
||||||
use alchemy_lifecycle::traits::PlatformSpecificNodeType;
|
use alchemy_lifecycle::traits::PlatformSpecificNodeType;
|
||||||
|
|
||||||
|
|
@ -70,14 +68,14 @@ impl View {
|
||||||
/// Given a `&Style`, will set the frame, background color, borders and so forth. It then
|
/// 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
|
/// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this
|
||||||
/// view.
|
/// view.
|
||||||
pub fn apply_styles(&mut self, layout: &Layout, style: &Style) {
|
pub fn apply_styles(&mut self, appearance: &Appearance, layout: &Layout) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let rect = NSRect::new(
|
let rect = NSRect::new(
|
||||||
NSPoint::new(layout.location.x.into(), layout.location.y.into()),
|
NSPoint::new(layout.location.x.into(), layout.location.y.into()),
|
||||||
NSSize::new(layout.size.width.into(), layout.size.height.into())
|
NSSize::new(layout.size.width.into(), layout.size.height.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
self.background_color = style.background_color.into_nscolor();
|
self.background_color = appearance.background_color.into_nscolor();
|
||||||
self.inner_mut.set_ivar(BACKGROUND_COLOR, &*self.background_color);
|
self.inner_mut.set_ivar(BACKGROUND_COLOR, &*self.background_color);
|
||||||
|
|
||||||
msg_send![&*self.inner_mut, setFrame:rect];
|
msg_send![&*self.inner_mut, setFrame:rect];
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
/// @created March 26th, 2019
|
/// @created March 26th, 2019
|
||||||
|
|
||||||
use alchemy::{
|
use alchemy::{
|
||||||
AppDelegate, Component, Fragment, Props, Error, rsx, RSX, styles,
|
AppDelegate, Component, ComponentKey, Fragment, Props, Error, rsx, RSX, styles,
|
||||||
Text, View, Window, WindowDelegate
|
Text, View, Window, WindowDelegate
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -26,6 +26,10 @@ impl AppDelegate for AppState {
|
||||||
pub struct Banner;
|
pub struct Banner;
|
||||||
|
|
||||||
impl Component for Banner {
|
impl Component for Banner {
|
||||||
|
fn constructor(_key: ComponentKey) -> Banner {
|
||||||
|
Banner {}
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
||||||
Ok(rsx! {
|
Ok(rsx! {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,3 @@ alchemy-styles = { version = "0.1", path = "../styles" }
|
||||||
objc = { version = "0.2.6", optional = true }
|
objc = { version = "0.2.6", optional = true }
|
||||||
objc_id = { version = "0.1.1", optional = true }
|
objc_id = { version = "0.1.1", optional = true }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
uuid = { version = "0.7", features = ["v4"] }
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
//! This crate also includes the diffing and patching system for the widget tree -
|
//! 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.
|
//! it needs to live with the `Component` lifecycle to enable state updating.
|
||||||
|
|
||||||
pub use uuid::Uuid;
|
pub use std::sync::Arc;
|
||||||
|
|
||||||
use alchemy_styles::lazy_static;
|
use alchemy_styles::lazy_static;
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub mod traits;
|
||||||
|
|
||||||
mod reconciler;
|
mod reconciler;
|
||||||
use reconciler::RenderEngine;
|
use reconciler::RenderEngine;
|
||||||
|
pub use reconciler::key::ComponentKey;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new();
|
pub static ref RENDER_ENGINE: RenderEngine = RenderEngine::new();
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,33 @@
|
||||||
//! run. These are mostly internal to the rendering engine itself, but could potentially
|
//! run. These are mostly internal to the rendering engine itself, but could potentially
|
||||||
//! show up elsewhere.
|
//! show up elsewhere.
|
||||||
|
|
||||||
use std::fmt;
|
use core::any::Any;
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
|
use crate::reconciler::key::ComponentKey;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum RenderEngineError {
|
pub enum RenderEngineError {
|
||||||
InvalidKeyError
|
InvalidKey,
|
||||||
|
InvalidRootComponent,
|
||||||
|
InvalidComponentKey(ComponentKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for RenderEngineError {
|
impl std::fmt::Display for RenderEngineError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match *self {
|
||||||
RenderEngineError::InvalidKeyError => write!(f, "An invalid key was passed to the render engine.")
|
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 fmt::Debug for RenderEngineError {
|
impl std::error::Error for RenderEngineError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn description(&self) -> &str {
|
||||||
match self {
|
match *self {
|
||||||
RenderEngineError::InvalidKeyError => write!(f, "An invalid key was passed to the render engine: {{ file: {}, line: {} }}", file!(), line!())
|
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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for RenderEngineError {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
51
lifecycle/src/reconciler/key.rs
Normal file
51
lifecycle/src/reconciler/key.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
//! Implements an auto-incrementing ID for Component instances.
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use alchemy_styles::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Global stretch instance id allocator.
|
||||||
|
pub(crate) static ref INSTANCE_ALLOCATOR: Mutex<Allocator> = 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,71 +6,73 @@ use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::mem::{discriminant, swap};
|
use std::mem::{discriminant, swap};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use alchemy_styles::THEME_ENGINE;
|
||||||
|
use alchemy_styles::styles::{Appearance,Dimension, Number, Size, Style};
|
||||||
use alchemy_styles::{Stretch, THEME_ENGINE};
|
|
||||||
use alchemy_styles::styles::{Style, Dimension};
|
|
||||||
use alchemy_styles::number::Number;
|
|
||||||
use alchemy_styles::geometry::Size;
|
|
||||||
|
|
||||||
use crate::traits::Component;
|
use crate::traits::Component;
|
||||||
use crate::rsx::{Props, RSX, VirtualNode};
|
use crate::rsx::{Props, RSX, VirtualNode};
|
||||||
|
|
||||||
mod error;
|
use alchemy_styles::stretch::node::{Node as StyleNode, Stretch as LayoutStore};
|
||||||
|
|
||||||
|
pub mod key;
|
||||||
|
use key::ComponentKey;
|
||||||
|
|
||||||
|
pub mod storage;
|
||||||
|
use storage::ComponentStore;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
use error::RenderEngineError;
|
use error::RenderEngineError;
|
||||||
|
|
||||||
// This is never actually created, it's just to satisfy the fact that View
|
// This is never actually created, it's just to satisfy the fact that View
|
||||||
// is defined in the core crate, which we can't import here without creating a
|
// is defined in the core crate, which we can't import here without creating a
|
||||||
// circular dependency.
|
// circular dependency.
|
||||||
struct StubView;
|
struct StubView;
|
||||||
impl Component for StubView {}
|
impl Component for StubView {
|
||||||
|
fn constructor(key: ComponentKey) -> StubView {
|
||||||
|
StubView {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RenderEngine {
|
pub struct RenderEngine {
|
||||||
pending_state_updates: Mutex<Vec<i32>>,
|
queued_state_updates: Mutex<Vec<i32>>,
|
||||||
trees: Mutex<HashMap<Uuid, (RSX, Stretch)>>
|
components: Mutex<ComponentStore>,
|
||||||
|
layouts: Mutex<LayoutStore>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderEngine {
|
impl RenderEngine {
|
||||||
pub(crate) fn new() -> RenderEngine {
|
pub(crate) fn new() -> RenderEngine {
|
||||||
RenderEngine {
|
RenderEngine {
|
||||||
pending_state_updates: Mutex::new(vec![]),
|
queued_state_updates: Mutex::new(vec![]),
|
||||||
trees: Mutex::new(HashMap::new())
|
components: Mutex::new(ComponentStore::new()),
|
||||||
|
layouts: Mutex::new(LayoutStore::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn queue_update_for(&self, component_ptr: usize, updater: Box<Fn() -> Component + Send + Sync + 'static>) {
|
||||||
|
// }
|
||||||
|
|
||||||
/// `Window`'s (or anything "root" in nature) need to register with the
|
/// `Window`'s (or anything "root" in nature) need to register with the
|
||||||
/// reconciler for things like setState to work properly. When they do so,
|
/// 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`
|
/// 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
|
/// to re-render or update their tree, they pass that key and whatever the new tree
|
||||||
/// should be.
|
/// should be.
|
||||||
pub fn register_root_component<C: Component + 'static>(&self, instance: C) -> Uuid {
|
pub fn register_root_component<C: Component + 'static>(&self, instance: C) -> Result<ComponentKey, Box<Error>> {
|
||||||
let mut root_node = RSX::node("root", || {
|
// Conceivably, this doesn't NEED to be a thing... but for now it is. If you've stumbled
|
||||||
Arc::new(RwLock::new(StubView {}))
|
// upon here, wayward traveler, in need of a non-native-root-component, please open an
|
||||||
}, {
|
// issue to discuss. :)
|
||||||
let mut props = Props::default();
|
if !instance.has_native_backing_node() {
|
||||||
props.styles = "root".into();
|
return Err(Box::new(RenderEngineError::InvalidRootComponent {}));
|
||||||
props
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut stretch = Stretch::new();
|
|
||||||
if let RSX::VirtualNode(root) = &mut root_node {
|
|
||||||
let mut style = Style::default();
|
|
||||||
style.size = Size {
|
|
||||||
width: Dimension::Points(600.),
|
|
||||||
height: Dimension::Points(600.)
|
|
||||||
};
|
|
||||||
|
|
||||||
root.instance = Some(Arc::new(RwLock::new(instance)));
|
|
||||||
root.layout_node = match stretch.new_node(style, vec![]) {
|
|
||||||
Ok(node) => Some(node),
|
|
||||||
Err(e) => { None }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = Uuid::new_v4();
|
let layout_key = {
|
||||||
let mut trees = self.trees.lock().unwrap();
|
let style = Style::default();
|
||||||
trees.insert(key, (root_node, stretch));
|
let mut layouts = self.layouts.lock().unwrap();
|
||||||
key
|
Some(layouts.new_node(style, vec![])?)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut components = self.components.lock().unwrap();
|
||||||
|
let component_key = components.new_node(instance, layout_key, vec![])?;
|
||||||
|
Ok(component_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a key, and a new root tree, will diff the tree structure (position, components,
|
/// Given a key, and a new root tree, will diff the tree structure (position, components,
|
||||||
|
|
@ -80,36 +82,37 @@ impl RenderEngine {
|
||||||
/// the new tree before discarding the old tree.
|
/// the new tree before discarding the old tree.
|
||||||
///
|
///
|
||||||
/// This calls the necessary component lifecycles per-component.
|
/// This calls the necessary component lifecycles per-component.
|
||||||
pub fn diff_and_render_root(&self, key: &Uuid, child: RSX) -> Result<(), Box<Error>> {
|
pub fn diff_and_render_root(&self, key: &ComponentKey, child: RSX) -> Result<(), Box<Error>> {
|
||||||
|
/*
|
||||||
let mut new_root = RSX::node("root", || {
|
let mut new_root = RSX::node("root", || {
|
||||||
Arc::new(RwLock::new(StubView {}))
|
Box::new(StubView {})
|
||||||
}, {
|
}, {
|
||||||
let mut props = Props::default();
|
let mut props = Props::default();
|
||||||
props.styles = "root".into();
|
props.styles = "root".into();
|
||||||
props
|
props
|
||||||
|
}, match child {
|
||||||
|
RSX::VirtualNode(mut child) => {
|
||||||
|
let mut children = vec![];
|
||||||
|
|
||||||
|
if child.tag == "Fragment" {
|
||||||
|
children.append(&mut child.children);
|
||||||
|
} else {
|
||||||
|
children.push(RSX::VirtualNode(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
children
|
||||||
|
},
|
||||||
|
|
||||||
|
// If it's an RSX::None or RSX::VirtualText, we'll just do nothing, as... one
|
||||||
|
// requires nothing, and one isn't supported unless it's inside a <Text> tag, and
|
||||||
|
// we know the root element isn't a <Text> if we're here.
|
||||||
|
_ => vec![]
|
||||||
});
|
});
|
||||||
|
|
||||||
// If it's an RSX::None, or a RSX::VirtualText, we do nothing, as... one
|
|
||||||
// requires nothing, and one isn't supported unless it's inside a <Text> tag.
|
|
||||||
if let RSX::VirtualNode(mut child) = child {
|
|
||||||
if let RSX::VirtualNode(new_root_node) = &mut new_root {
|
|
||||||
if child.tag == "Fragment" {
|
|
||||||
new_root_node.children.append(&mut child.children);
|
|
||||||
} else {
|
|
||||||
new_root_node.children.push(RSX::VirtualNode(child));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut trees = self.trees.lock().unwrap();
|
let mut trees = self.trees.lock().unwrap();
|
||||||
let (old_root, mut stretch) = trees.remove(key).ok_or_else(|| RenderEngineError::InvalidKeyError {})?;
|
let (old_root, mut stretch) = trees.remove(key).ok_or_else(|| RenderEngineError::InvalidKey {})?;
|
||||||
let patched_new_root = diff_and_patch_trees(old_root, new_root, &mut stretch, 0)?;
|
let patched_new_root = diff_and_patch_trees(old_root, new_root, &mut stretch, 0)?;
|
||||||
|
|
||||||
/*let window_size = Size {
|
|
||||||
width: Number::Defined(600.),
|
|
||||||
height: Number::Defined(600.)
|
|
||||||
};*/
|
|
||||||
|
|
||||||
if let RSX::VirtualNode(node) = &patched_new_root {
|
if let RSX::VirtualNode(node) = &patched_new_root {
|
||||||
if let Some(layout_node) = &node.layout_node {
|
if let Some(layout_node) = &node.layout_node {
|
||||||
stretch.compute_layout(*layout_node, Size {
|
stretch.compute_layout(*layout_node, Size {
|
||||||
|
|
@ -120,375 +123,7 @@ impl RenderEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trees.insert(*key, (patched_new_root, stretch));
|
trees.insert(*key, (patched_new_root, stretch));*/
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given two node trees, will compare, diff, and apply changes in a recursive fashion.
|
|
||||||
pub fn diff_and_patch_trees(old: RSX, new: RSX, stretch: &mut Stretch, depth: usize) -> Result<RSX, Box<Error>> {
|
|
||||||
// Whether we replace or not depends on a few things. If we're working on two different node
|
|
||||||
// types (text vs node), if the node tags are different, or if the key (in some cases) is
|
|
||||||
// different.
|
|
||||||
let is_replace = match discriminant(&old) != discriminant(&new) {
|
|
||||||
true => true,
|
|
||||||
false => {
|
|
||||||
if let (RSX::VirtualNode(old_element), RSX::VirtualNode(new_element)) = (&old, &new) {
|
|
||||||
old_element.tag != new_element.tag
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match (old, new) {
|
|
||||||
(RSX::VirtualNode(mut old_element), RSX::VirtualNode(mut new_element)) => {
|
|
||||||
if is_replace {
|
|
||||||
// Do something different in here...
|
|
||||||
//let mut mounted = mount_component_tree(new_tree);
|
|
||||||
// unmount_component_tree(old_tree);
|
|
||||||
// Swap them in memory, copy any layout + etc as necessary
|
|
||||||
// append, link layout nodes, etc
|
|
||||||
return Ok(RSX::VirtualNode(new_element));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, it's an update to an existing element. This means a cached Component
|
|
||||||
// instance might exist, and we want to keep it around and reuse it if possible. Let's check
|
|
||||||
// and do some swapping action to handle it.
|
|
||||||
//
|
|
||||||
// These need to move to the new tree, since we always keep 'em. We also wanna cache a
|
|
||||||
// reference to our content view.
|
|
||||||
swap(&mut old_element.instance, &mut new_element.instance);
|
|
||||||
swap(&mut old_element.layout_node, &mut new_element.layout_node);
|
|
||||||
|
|
||||||
// For the root tag, which is usually the content view of the Window, we don't want to
|
|
||||||
// perform the whole render/component lifecycle routine. It's a special case element,
|
|
||||||
// where the Window (or other root element) patches in the output of a render method
|
|
||||||
// specific to that object. An easy way to handle this is the depth parameter - in
|
|
||||||
// fact, it's why it exists. Depth 0 should be considered special and skip the
|
|
||||||
// rendering phase.
|
|
||||||
if depth > 0 {
|
|
||||||
// diff props, set new props
|
|
||||||
// instance.get_derived_state_from_props()
|
|
||||||
|
|
||||||
if let Some(instance) = &mut new_element.instance {
|
|
||||||
// diff props, set new props
|
|
||||||
// instance.get_derived_state_from_props()
|
|
||||||
|
|
||||||
//if instance.should_component_update() {
|
|
||||||
// instance.render() { }
|
|
||||||
// instance.get_snapshot_before_update()
|
|
||||||
// apply changes
|
|
||||||
//instance.component_did_update();
|
|
||||||
//} else {
|
|
||||||
// If should_component_update() returns false, then we want to take the
|
|
||||||
// children from the old node, move them to the new node, and recurse into
|
|
||||||
// that tree instead.
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This None path should never be hit, we just need to use a rather verbose pattern
|
|
||||||
// here. It's unsightly, I know.
|
|
||||||
let is_native_backed = match &new_element.instance {
|
|
||||||
Some(instance) => {
|
|
||||||
let lock = instance.read().unwrap();
|
|
||||||
lock.has_native_backing_node()
|
|
||||||
},
|
|
||||||
None => false
|
|
||||||
};
|
|
||||||
|
|
||||||
// There is probably a nicer way to do this that doesn't allocate as much, and I'm open
|
|
||||||
// to revisiting it. Platforms outside of Rust allocate far more than this, though, and
|
|
||||||
// in general the whole "avoid allocations" thing is fear mongering IMO. Revisit later.
|
|
||||||
//
|
|
||||||
// tl;dr we allocate a new Vec<RSX> that's equal to the length of our new children, and
|
|
||||||
// then swap it on our (owned) node... it's safe, as we own it. This allows us to
|
|
||||||
// iterate and dodge the borrow checker.
|
|
||||||
let mut children: Vec<RSX> = Vec::with_capacity(new_element.children.len());
|
|
||||||
std::mem::swap(&mut children, &mut new_element.children);
|
|
||||||
|
|
||||||
old_element.children.reverse();
|
|
||||||
for new_child_tree in children {
|
|
||||||
match old_element.children.pop() {
|
|
||||||
// A matching child in the old tree means we can recurse right back into the
|
|
||||||
// update phase.
|
|
||||||
Some(old_child_tree) => {
|
|
||||||
let updated = diff_and_patch_trees(old_child_tree, new_child_tree, stretch, depth + 1)?;
|
|
||||||
new_element.children.push(updated);
|
|
||||||
},
|
|
||||||
|
|
||||||
// If there's no matching child in the old tree, this is a new Component and we
|
|
||||||
// can feel free to mount/connect it.
|
|
||||||
None => {
|
|
||||||
if let RSX::VirtualNode(new_el) = new_child_tree {
|
|
||||||
let mut mounted = mount_component_tree(new_el, stretch)?;
|
|
||||||
|
|
||||||
// Link the layout nodes, handle the appending, etc.
|
|
||||||
// This happens inside mount_component_tree, but that only handles that
|
|
||||||
// specific tree. Think of this step as joining two trees in the graph.
|
|
||||||
if is_native_backed {
|
|
||||||
find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_element.children.push(RSX::VirtualNode(mounted));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim the fat - more children in the old tree than the new one means we gonna be
|
|
||||||
// droppin'. We need to send unmount lifecycle calls to these, and break any links we
|
|
||||||
// have (e.g, layout, backing view tree, etc).
|
|
||||||
loop {
|
|
||||||
match old_element.children.pop() {
|
|
||||||
Some(child) => {
|
|
||||||
if let RSX::VirtualNode(mut old_child) = child {
|
|
||||||
unmount_component_tree(&mut old_child, stretch)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
None => { break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RSX::VirtualNode(new_element))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're comparing two text nodes. Realistically... this requires nothing from us, because
|
|
||||||
// the <Text> tag (or any other component instance, if it desires) should handle it.
|
|
||||||
(RSX::VirtualText(_), RSX::VirtualText(text)) => {
|
|
||||||
Ok(RSX::VirtualText(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are all edge cases that shouldn't get hit. In particular:
|
|
||||||
//
|
|
||||||
// - VirtualText being replaced by VirtualNode should be caught by the discriminant check
|
|
||||||
// in the beginning of this function, which registers as a replace/mount.
|
|
||||||
// - VirtualNode being replaced with VirtualText is the same scenario as above.
|
|
||||||
// - The (RSX::None, ...) checks are to shut the compiler up; we never store the RSX::None
|
|
||||||
// return value, as it's mostly a value in place for return signature usability. Thus,
|
|
||||||
// these should quite literally never register.
|
|
||||||
//
|
|
||||||
// This goes without saying, but: never ever store RSX::None lol
|
|
||||||
(RSX::VirtualText(_), RSX::VirtualNode(_)) | (RSX::VirtualNode(_), RSX::VirtualText(_)) |
|
|
||||||
(RSX::None, RSX::VirtualText(_)) | (RSX::None, RSX::VirtualNode(_)) | (RSX::None, RSX::None) |
|
|
||||||
(RSX::VirtualNode(_), RSX::None) | (RSX::VirtualText(_), RSX::None) => {
|
|
||||||
unreachable!("Unequal variant discriminants should already have been handled.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walks the tree and applies styles. This happens after a layout computation, typically.
|
|
||||||
pub(crate) fn walk_and_apply_styles(node: &VirtualNode, layout_manager: &mut Stretch) -> Result<(), Box<Error>> {
|
|
||||||
if let (Some(layout_node), Some(instance)) = (node.layout_node, &node.instance) {
|
|
||||||
let component = instance.write().unwrap();
|
|
||||||
component.apply_styles(
|
|
||||||
layout_manager.layout(layout_node)?,
|
|
||||||
layout_manager.style(layout_node)?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in &node.children {
|
|
||||||
if let RSX::VirtualNode(child_node) = child {
|
|
||||||
walk_and_apply_styles(child_node, layout_manager)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a tree, will walk the branches until it finds the next root nodes to connect.
|
|
||||||
/// While this sounds slow, in practice it rarely has to go far in any direction.
|
|
||||||
fn find_and_link_layout_nodes(parent_node: &mut VirtualNode, child_tree: &mut VirtualNode, stretch: &mut Stretch) -> Result<(), Box<Error>> {
|
|
||||||
if let (Some(parent_instance), Some(child_instance)) = (&mut parent_node.instance, &mut child_tree.instance) {
|
|
||||||
if let (Some(parent_layout_node), Some(child_layout_node)) = (&parent_node.layout_node, &child_tree.layout_node) {
|
|
||||||
stretch.add_child(*parent_layout_node, *child_layout_node)?;
|
|
||||||
|
|
||||||
let parent_component = parent_instance.write().unwrap();
|
|
||||||
let child_component = child_instance.read().unwrap();
|
|
||||||
parent_component.append_child_component(&*child_component);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in child_tree.children.iter_mut() {
|
|
||||||
if let RSX::VirtualNode(child_tree) = child {
|
|
||||||
find_and_link_layout_nodes(parent_node, child_tree, stretch)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively constructs a Component tree. This entails adding it to the backing
|
|
||||||
/// view tree, firing various lifecycle methods, and ensuring that nodes for layout
|
|
||||||
/// passes are configured.
|
|
||||||
///
|
|
||||||
/// In the future, this would ideally return patch-sets for the backing layer or something.
|
|
||||||
fn mount_component_tree(mut new_element: VirtualNode, stretch: &mut Stretch) -> Result<VirtualNode, Box<Error>> {
|
|
||||||
let instance = (new_element.create_component_fn)();
|
|
||||||
|
|
||||||
let mut is_native_backed = false;
|
|
||||||
|
|
||||||
let rendered = {
|
|
||||||
let component = instance.read().unwrap();
|
|
||||||
// instance.get_derived_state_from_props(props)
|
|
||||||
|
|
||||||
is_native_backed = component.has_native_backing_node();
|
|
||||||
|
|
||||||
if is_native_backed {
|
|
||||||
let mut style = Style::default();
|
|
||||||
THEME_ENGINE.configure_style_for_keys(&new_element.props.styles, &mut style);
|
|
||||||
|
|
||||||
let layout_node = stretch.new_node(style, vec![])?;
|
|
||||||
new_element.layout_node = Some(layout_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
component.render(&new_element.props)
|
|
||||||
};
|
|
||||||
|
|
||||||
// instance.get_snapshot_before_update()
|
|
||||||
|
|
||||||
new_element.instance = Some(instance);
|
|
||||||
|
|
||||||
let mut children = match rendered {
|
|
||||||
Ok(opt) => match opt {
|
|
||||||
RSX::VirtualNode(child) => {
|
|
||||||
let mut children = vec![];
|
|
||||||
|
|
||||||
// We want to support Components being able to return arbitrary iteratable
|
|
||||||
// elements, but... well, it's not quite that simple. Thus we'll offer a <Fragment>
|
|
||||||
// tag similar to what React does, which just hoists the children out of it and
|
|
||||||
// discards the rest.
|
|
||||||
if child.tag == "Fragment" {
|
|
||||||
for child_node in child.props.children {
|
|
||||||
if let RSX::VirtualNode(node) = child_node {
|
|
||||||
let mut mounted = mount_component_tree(node, stretch)?;
|
|
||||||
|
|
||||||
if is_native_backed {
|
|
||||||
find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(RSX::VirtualNode(mounted));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut mounted = mount_component_tree(child, stretch)?;
|
|
||||||
|
|
||||||
if is_native_backed {
|
|
||||||
find_and_link_layout_nodes(&mut new_element, &mut mounted, stretch)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(RSX::VirtualNode(mounted));
|
|
||||||
}
|
|
||||||
|
|
||||||
children
|
|
||||||
},
|
|
||||||
|
|
||||||
// If a Component renders nothing (or this is a Text string, which we do nothing with)
|
|
||||||
// that's totally fine.
|
|
||||||
_ => vec![]
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
// return an RSX::VirtualNode(ErrorComponentView) or something?
|
|
||||||
/* instance.get_derived_state_from_error(e) */
|
|
||||||
// render error state or something I guess?
|
|
||||||
/* instance.component_did_catch(e, info) */
|
|
||||||
eprintln!("Error rendering: {}", e);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
new_element.children.append(&mut children);
|
|
||||||
|
|
||||||
if let Some(instance) = &mut new_element.instance {
|
|
||||||
let mut component = instance.write().unwrap();
|
|
||||||
component.component_did_mount(&new_element.props);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(new_element)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk the tree and unmount Component instances. This means we fire the
|
|
||||||
/// `component_will_unmount` hook and remove the node(s) from their respective trees.
|
|
||||||
///
|
|
||||||
/// This fires the hooks from a recursive inward-out pattern; that is, the deepest nodes in the tree
|
|
||||||
/// are the first to go, ensuring that everything is properly cleaned up.
|
|
||||||
fn unmount_component_tree(old_element: &mut VirtualNode, stretch: &mut Stretch) -> Result<(), Box<Error>> {
|
|
||||||
// We only need to recurse on VirtualNodes. Text and so on will automagically drop
|
|
||||||
// because we don't support freeform text, it has to be inside a <Text> at all times.
|
|
||||||
for child in old_element.children.iter_mut() {
|
|
||||||
if let RSX::VirtualNode(child_element) = child {
|
|
||||||
unmount_component_tree(child_element, stretch)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire the appropriate lifecycle method and then remove the node from the underlying
|
|
||||||
// graph. Remember that a Component can actually not necessarily have a native backing
|
|
||||||
// node, hence our necessary check.
|
|
||||||
if let Some(old_component) = &mut old_element.instance {
|
|
||||||
let mut component = old_component.write().unwrap();
|
|
||||||
component.component_will_unmount(&old_element.props);
|
|
||||||
|
|
||||||
/*if let Some(view) = old_component.get_native_backing_node() {
|
|
||||||
if let Some(native_view) = replace_native_view {
|
|
||||||
//replace_view(&view, &native_view);
|
|
||||||
} else {
|
|
||||||
//remove_view(&view);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rather than try to keep track of parent/child stuff for removal... just obliterate it,
|
|
||||||
// the underlying library does a good job of killing the links anyway.
|
|
||||||
if let Some(layout_node) = &mut old_element.layout_node {
|
|
||||||
stretch.set_children(*layout_node, vec![])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/*let mut add_attributes: HashMap<&str, &str> = HashMap::new();
|
|
||||||
let mut remove_attributes: Vec<&str> = vec![];
|
|
||||||
|
|
||||||
// TODO: -> split out into func
|
|
||||||
for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
|
|
||||||
match old_element.attrs.get(new_attr_name) {
|
|
||||||
Some(ref old_attr_val) => {
|
|
||||||
if old_attr_val != &new_attr_val {
|
|
||||||
add_attributes.insert(new_attr_name, new_attr_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
add_attributes.insert(new_attr_name, new_attr_val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: -> split out into func
|
|
||||||
for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
|
|
||||||
if add_attributes.get(&old_attr_name[..]).is_some() {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match new_element.attrs.get(old_attr_name) {
|
|
||||||
Some(ref new_attr_val) => {
|
|
||||||
if new_attr_val != &old_attr_val {
|
|
||||||
remove_attributes.push(old_attr_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
remove_attributes.push(old_attr_name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if add_attributes.len() > 0 {
|
|
||||||
patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
|
|
||||||
}
|
|
||||||
if remove_attributes.len() > 0 {
|
|
||||||
patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
|
|
||||||
}*/
|
|
||||||
|
|
|
||||||
163
lifecycle/src/reconciler/storage.rs
Normal file
163
lifecycle/src/reconciler/storage.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
//! 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 alchemy_styles::stretch::node::{Node as LayoutNode};
|
||||||
|
|
||||||
|
use crate::reconciler::error::{RenderEngineError as Error};
|
||||||
|
use crate::reconciler::key::{Allocator, Id, INSTANCE_ALLOCATOR, ComponentKey};
|
||||||
|
use crate::traits::Component;
|
||||||
|
|
||||||
|
/// 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<T>(HashMap<ComponentKey, T>);
|
||||||
|
|
||||||
|
impl<T> Storage<T> {
|
||||||
|
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 insert(&mut self, key: ComponentKey, value: T) -> Option<T> {
|
||||||
|
self.0.insert(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Index<&ComponentKey> for Storage<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn index(&self, idx: &ComponentKey) -> &T {
|
||||||
|
&(self.0)[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Instance {
|
||||||
|
component: Box<Component>,
|
||||||
|
appearance: Appearance,
|
||||||
|
layout: Option<LayoutNode>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ComponentStore {
|
||||||
|
id: Id,
|
||||||
|
nodes: Allocator,
|
||||||
|
components: Storage<Instance>,
|
||||||
|
parents: Storage<Vec<ComponentKey>>,
|
||||||
|
children: Storage<Vec<ComponentKey>>
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_node(&mut self) -> ComponentKey {
|
||||||
|
let local = self.nodes.allocate();
|
||||||
|
ComponentKey { instance: self.id, local }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_node<C: Component + 'static>(&mut self, component: C, layout_key: Option<LayoutNode>, children: Vec<ComponentKey>) -> Result<ComponentKey, Error> {
|
||||||
|
let key = self.allocate_node();
|
||||||
|
|
||||||
|
for child in &children {
|
||||||
|
self.parents.get_mut(*child)?.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.components.insert(key, Instance {
|
||||||
|
component: Box::new(component),
|
||||||
|
appearance: Appearance::default(),
|
||||||
|
layout: layout_key
|
||||||
|
});
|
||||||
|
|
||||||
|
self.parents.insert(key, Vec::with_capacity(1));
|
||||||
|
self.children.insert(key, children);
|
||||||
|
|
||||||
|
Ok(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<ComponentKey>) -> 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<ComponentKey, Error> {
|
||||||
|
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<ComponentKey, Error> {
|
||||||
|
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<ComponentKey, Error> {
|
||||||
|
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<Vec<ComponentKey>, Error> {
|
||||||
|
self.children.get(key).map(Clone::clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child_count(&self, key: ComponentKey) -> Result<usize, Error> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
//! uses these to build and alter UI; they're typically returned from `render()`
|
//! uses these to build and alter UI; they're typically returned from `render()`
|
||||||
//! methods.
|
//! methods.
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
mod virtual_node;
|
mod virtual_node;
|
||||||
|
|
@ -15,6 +14,7 @@ pub use virtual_text::VirtualText;
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::Props;
|
pub use props::Props;
|
||||||
|
|
||||||
|
use crate::reconciler::key::ComponentKey;
|
||||||
use crate::traits::Component;
|
use crate::traits::Component;
|
||||||
|
|
||||||
/// An enum representing the types of nodes that the
|
/// An enum representing the types of nodes that the
|
||||||
|
|
@ -31,16 +31,15 @@ impl RSX {
|
||||||
/// this yourself; the `rsx! {}` macro handles this for you.
|
/// this yourself; the `rsx! {}` macro handles this for you.
|
||||||
pub fn node(
|
pub fn node(
|
||||||
tag: &'static str,
|
tag: &'static str,
|
||||||
create_fn: fn() -> Arc<RwLock<Component>>,
|
create_fn: fn(key: ComponentKey) -> Box<Component>,
|
||||||
props: Props
|
props: Props,
|
||||||
|
children: Vec<RSX>
|
||||||
) -> RSX {
|
) -> RSX {
|
||||||
RSX::VirtualNode(VirtualNode {
|
RSX::VirtualNode(VirtualNode {
|
||||||
tag: tag,
|
tag: tag,
|
||||||
create_component_fn: create_fn,
|
create_component_fn: create_fn,
|
||||||
instance: None,
|
props: Some(props),
|
||||||
layout_node: None,
|
children: children
|
||||||
props: props,
|
|
||||||
children: vec![]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,15 @@ pub struct Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Props {
|
impl Props {
|
||||||
|
pub fn new(key: String, styles: StylesList, attributes: HashMap<&'static str, AttributeType>) -> Props {
|
||||||
|
Props {
|
||||||
|
attributes: attributes,
|
||||||
|
children: vec![],
|
||||||
|
key: key,
|
||||||
|
styles: styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a Vec of RSX nodes, which are really just cloned pointers for the most part.
|
/// Returns a Vec of RSX nodes, which are really just cloned pointers for the most part.
|
||||||
pub fn children(&self) -> Vec<RSX> {
|
pub fn children(&self) -> Vec<RSX> {
|
||||||
self.children.clone()
|
self.children.clone()
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
//! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive
|
//! Implements the `RSX::VirtualNode` struct, which is a bit of a recursive
|
||||||
//! structure.
|
//! structure.
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::fmt::{Display, Debug};
|
use std::fmt::{Display, Debug};
|
||||||
|
|
||||||
use alchemy_styles::node::Node;
|
use crate::reconciler::key::ComponentKey;
|
||||||
|
|
||||||
use crate::traits::Component;
|
|
||||||
use crate::rsx::{RSX, Props};
|
use crate::rsx::{RSX, Props};
|
||||||
|
use crate::traits::Component;
|
||||||
|
|
||||||
/// A VirtualNode is akin to an `Element` in React terms. Here, we provide a way
|
/// A VirtualNode is akin to an `Element` in React terms. Here, we provide a way
|
||||||
/// for lazy `Component` instantiation, along with storage for things like layout nodes,
|
/// for lazy `Component` instantiation, properties, children and so on.
|
||||||
/// properties, children and so on.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VirtualNode {
|
pub struct VirtualNode {
|
||||||
/// Used in debugging/printing/etc.
|
/// Used in debugging/printing/etc.
|
||||||
|
|
@ -19,22 +16,17 @@ pub struct VirtualNode {
|
||||||
|
|
||||||
/// `Component` instances are created on-demand, if the reconciler deems it be so. This
|
/// `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.
|
/// is a closure that should return an instance of the correct type.
|
||||||
pub create_component_fn: fn() -> Arc<RwLock<Component>>,
|
pub create_component_fn: fn(key: ComponentKey) -> Box<Component>,
|
||||||
|
|
||||||
/// A cached component instance, which is transferred between trees. Since `Component`
|
/// `Props`, which are to be passed to this `Component` at various lifecycle methods. Once
|
||||||
/// instances are lazily created, this is an `Option`, and defaults to `None`.
|
/// the reconciler takes ownership of this VirtualNode, these props are moved to a different
|
||||||
pub instance: Option<Arc<RwLock<Component>>>,
|
/// location - thus, you shouldn't rely on them for anything unless you specifically keep
|
||||||
|
/// ownership of a VirtualNode.
|
||||||
/// A cached `Node` for computing `Layout` with `Stretch`. Some components may not have
|
|
||||||
/// a need for layout (e.g, if they don't have a backing node), and thus this is optional.
|
|
||||||
///
|
///
|
||||||
/// The reconciler will handle bridging tree structures as necessary.
|
/// This aspect of functionality may be pulled in a later release if it causes too many issues.
|
||||||
pub layout_node: Option<Node>,
|
pub props: Option<Props>,
|
||||||
|
|
||||||
/// `Props`, which are to be passed to this `Component` at various lifecycle methods.
|
///
|
||||||
pub props: Props,
|
|
||||||
|
|
||||||
/// Computed children get stored here.
|
|
||||||
pub children: Vec<RSX>
|
pub children: Vec<RSX>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
//! Traits that are used in Alchemy. Alchemy implements a React-based Component
|
//! 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.
|
//! lifecycle, coupled with a delegate pattern inspired by those found in AppKit/UIKit.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use alchemy_styles::styles::{Appearance, Layout};
|
||||||
|
|
||||||
use alchemy_styles::styles::{Layout, Style};
|
|
||||||
|
|
||||||
|
//use crate::RENDER_ENGINE;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::reconciler::key::ComponentKey;
|
||||||
use crate::rsx::{RSX, Props};
|
use crate::rsx::{RSX, Props};
|
||||||
|
|
||||||
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
||||||
|
|
@ -16,6 +16,11 @@ pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;
|
||||||
#[cfg(not(feature = "cocoa"))]
|
#[cfg(not(feature = "cocoa"))]
|
||||||
pub type PlatformSpecificNodeType = ();
|
pub type PlatformSpecificNodeType = ();
|
||||||
|
|
||||||
|
/*fn update<C: Component, F: Fn() -> Box<C> + 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.
|
/// 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
|
/// 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.
|
/// level and allows the user to operate within the established framework per-system.
|
||||||
|
|
@ -75,6 +80,8 @@ pub trait State {}
|
||||||
/// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting
|
/// 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!
|
/// proposal for this, feel free to open an issue!
|
||||||
pub trait Component: Send + Sync {
|
pub trait Component: Send + Sync {
|
||||||
|
fn constructor(key: ComponentKey) -> Self where Self: Sized;
|
||||||
|
|
||||||
/// Indicates whether a Component instance carries a native backing node. If you return `true`
|
/// 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
|
/// from this, the reconciler will opt-in to the native backing layer. Returns `false` by
|
||||||
/// default.
|
/// default.
|
||||||
|
|
@ -89,16 +96,15 @@ pub trait Component: Send + Sync {
|
||||||
|
|
||||||
/// If you implement a Native-backed component, you'll need to implement this. Given a
|
/// If you implement a Native-backed component, you'll need to implement this. Given a
|
||||||
/// `component`, you need to instruct the system how to replace it in the tree at your point.
|
/// `component`, you need to instruct the system how to replace it in the tree at your point.
|
||||||
fn replace_child_component(&self, _component: Arc<Component>) {}
|
fn replace_child_component(&self, _component: &Component) {}
|
||||||
|
|
||||||
/// If you implement a Native-backed component, you'll need to implement this. Given a
|
/// If you implement a Native-backed component, you'll need to implement this. Given a
|
||||||
/// `component`, you need to instruct the system how to remove it from the tree at your point.
|
/// `component`, you need to instruct the system how to remove it from the tree at your point.
|
||||||
fn remove_child_component(&self, _component: Arc<Component>) {}
|
fn remove_child_component(&self, _component: &Component) {}
|
||||||
|
|
||||||
/// Given a computed `layout`, and an accompanying `Style` (which holds appearance-based
|
/// Given a configured 'appearance' and computed `layout`, this method should transform them
|
||||||
/// styles, like colors), this method should transform them into appropriate calls to the
|
/// into appropriate calls to the backing native node.
|
||||||
/// backing native node.
|
fn apply_styles(&self, _appearance: &Appearance, _layout: &Layout) {}
|
||||||
fn apply_styles(&self, _layout: &Layout, _style: &Style) {}
|
|
||||||
|
|
||||||
/// Invoked right before calling the render method, both on the initial mount and on subsequent updates.
|
/// 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.
|
/// It should return an object to update the state, or null to update nothing.
|
||||||
|
|
|
||||||
|
|
@ -220,21 +220,16 @@ impl Element {
|
||||||
let component_name = Literal::string(&typename.to_string());
|
let component_name = Literal::string(&typename.to_string());
|
||||||
|
|
||||||
Ok(quote!(
|
Ok(quote!(
|
||||||
alchemy::RSX::node(#component_name, || {
|
alchemy::RSX::node(#component_name, |key| {
|
||||||
std::sync::Arc::new(std::sync::RwLock::new(#typename::default()))
|
Box::new(#typename::constructor(key))
|
||||||
}, alchemy::Props {
|
}, alchemy::Props::new("".into(), #styles, {
|
||||||
attributes: {
|
let mut attributes = std::collections::HashMap::new();
|
||||||
let mut attributes = std::collections::HashMap::new();
|
#attributes
|
||||||
#attributes
|
attributes
|
||||||
attributes
|
}), {
|
||||||
},
|
let mut children = vec![];
|
||||||
children: {
|
#children
|
||||||
let mut children = vec![];
|
children
|
||||||
#children
|
|
||||||
children
|
|
||||||
},
|
|
||||||
key: "".into(),
|
|
||||||
styles: #styles
|
|
||||||
})
|
})
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ use std::collections::HashMap;
|
||||||
use toml;
|
use toml;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::stretch::style::Style;
|
||||||
|
|
||||||
use crate::StylesList;
|
use crate::StylesList;
|
||||||
use crate::styles::Style;
|
use crate::styles::Appearance;
|
||||||
use crate::stylesheet::StyleSheet;
|
use crate::stylesheet::StyleSheet;
|
||||||
|
|
||||||
static CONFIG_FILE_NAME: &str = "alchemy.toml";
|
static CONFIG_FILE_NAME: &str = "alchemy.toml";
|
||||||
|
|
@ -85,13 +87,19 @@ impl ThemeEngine {
|
||||||
|
|
||||||
/// Given a theme key, style keys, and a style, configures the style for layout
|
/// Given a theme key, style keys, and a style, configures the style for layout
|
||||||
/// and appearance.
|
/// and appearance.
|
||||||
pub fn configure_style_for_keys_in_theme(&self, theme: &str, keys: &StylesList, style: &mut Style) {
|
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();
|
let themes = self.themes.read().unwrap();
|
||||||
|
|
||||||
match themes.get(theme) {
|
match themes.get(theme) {
|
||||||
Some(theme) => {
|
Some(theme) => {
|
||||||
for key in &keys.0 {
|
for key in &keys.0 {
|
||||||
theme.apply_styles(key, style);
|
theme.apply_styles(key, style, appearance);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -102,8 +110,8 @@ impl ThemeEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same logic as `configure_style_for_keys_in_theme`, but defaults to the default theme.
|
/// The same logic as `configure_style_for_keys_in_theme`, but defaults to the default theme.
|
||||||
pub fn configure_style_for_keys(&self, keys: &StylesList, style: &mut Style) {
|
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)
|
self.configure_style_for_keys_in_theme("default", keys, style, appearance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,6 @@ pub use lazy_static::lazy_static;
|
||||||
#[cfg(feature="parser")]
|
#[cfg(feature="parser")]
|
||||||
#[macro_use] pub extern crate cssparser;
|
#[macro_use] pub extern crate cssparser;
|
||||||
|
|
||||||
mod stretch;
|
|
||||||
pub use stretch::{geometry, node, number, result, Stretch, Error};
|
|
||||||
pub use stretch::result::Layout;
|
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
|
|
||||||
|
|
@ -25,12 +21,15 @@ pub use spacedlist::SpacedList;
|
||||||
mod spacedset;
|
mod spacedset;
|
||||||
pub use spacedset::SpacedSet;
|
pub use spacedset::SpacedSet;
|
||||||
|
|
||||||
|
pub mod stretch;
|
||||||
|
pub use stretch::result::Layout;
|
||||||
|
|
||||||
mod style_keys;
|
mod style_keys;
|
||||||
pub use style_keys::StyleKey;
|
pub use style_keys::StyleKey;
|
||||||
pub type StylesList = SpacedSet<StyleKey>;
|
pub type StylesList = SpacedSet<StyleKey>;
|
||||||
|
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
pub use styles::{Style, Styles};
|
pub use styles::{Appearance, Styles};
|
||||||
|
|
||||||
pub mod stylesheet;
|
pub mod stylesheet;
|
||||||
pub use stylesheet::StyleSheet;
|
pub use stylesheet::StyleSheet;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
|
//! 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::any::Any;
|
||||||
use core::f32;
|
use core::f32;
|
||||||
|
|
||||||
use crate::node::{Node, Storage, Stretch};
|
use crate::stretch::node::{Node, Storage, Stretch};
|
||||||
use crate::result;
|
use crate::stretch::result;
|
||||||
use crate::styles::*;
|
use crate::stretch::style::*;
|
||||||
|
use crate::stretch::number::Number::*;
|
||||||
use crate::number::Number::*;
|
use crate::stretch::number::*;
|
||||||
use crate::number::*;
|
use crate::stretch::geometry::{Point, Rect, Size};
|
||||||
|
|
||||||
use crate::geometry::{Point, Rect, Size};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ComputeResult {
|
pub struct ComputeResult {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
//! 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 core::ops::Add;
|
||||||
|
|
||||||
use crate::number::Number;
|
use crate::stretch::number::Number;
|
||||||
use crate::styles as style;
|
use crate::stretch::style;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct Rect<T> {
|
pub struct Rect<T> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
//! Identifier for a Node
|
///! 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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct Id {
|
pub(crate) struct Id {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
//! 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 geometry;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod number;
|
pub mod number;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
|
pub mod style;
|
||||||
|
|
||||||
mod algo;
|
mod algo;
|
||||||
mod id;
|
mod id;
|
||||||
|
|
||||||
pub use crate::node::Stretch;
|
|
||||||
|
|
||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! 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::any::Any;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -6,12 +9,12 @@ use std::sync::Mutex;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::geometry::Size;
|
use crate::stretch::geometry::Size;
|
||||||
use crate::stretch::id;
|
use crate::stretch::id;
|
||||||
use crate::number::Number;
|
use crate::stretch::number::Number;
|
||||||
use crate::result::{Cache, Layout};
|
use crate::stretch::result::{Cache, Layout};
|
||||||
use crate::styles::*;
|
use crate::stretch::style::*;
|
||||||
use crate::Error;
|
use crate::stretch::Error;
|
||||||
|
|
||||||
type MeasureFunc = Box<Fn(Size<Number>) -> Result<Size<f32>, Box<Any>> + Send + Sync + 'static>;
|
type MeasureFunc = Box<Fn(Size<Number>) -> Result<Size<f32>, Box<Any>> + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! 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;
|
use core::ops;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
//! 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 crate::stretch::algo::ComputeResult;
|
use crate::stretch::algo::ComputeResult;
|
||||||
use crate::geometry::{Point, Size};
|
use crate::stretch::geometry::{Point, Size};
|
||||||
use crate::number::Number;
|
use crate::stretch::number::Number;
|
||||||
|
|
||||||
#[derive(Copy, Debug, Clone)]
|
#[derive(Copy, Debug, Clone)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
|
|
|
||||||
335
styles/src/stretch/style.rs
Normal file
335
styles/src/stretch/style.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
//! 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 crate::stretch::geometry::{Rect, Size};
|
||||||
|
use crate::stretch::number::Number;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum AlignItems {
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
Baseline,
|
||||||
|
Stretch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AlignItems {
|
||||||
|
fn default() -> AlignItems {
|
||||||
|
AlignItems::Stretch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum AlignSelf {
|
||||||
|
Auto,
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
Baseline,
|
||||||
|
Stretch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AlignSelf {
|
||||||
|
fn default() -> AlignSelf {
|
||||||
|
AlignSelf::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum AlignContent {
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
Stretch,
|
||||||
|
SpaceBetween,
|
||||||
|
SpaceAround,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AlignContent {
|
||||||
|
fn default() -> AlignContent {
|
||||||
|
AlignContent::Stretch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Direction {
|
||||||
|
Inherit,
|
||||||
|
LTR,
|
||||||
|
RTL,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Direction {
|
||||||
|
fn default() -> Direction {
|
||||||
|
Direction::Inherit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Display {
|
||||||
|
Flex,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Display {
|
||||||
|
fn default() -> Display {
|
||||||
|
Display::Flex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum FlexDirection {
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
RowReverse,
|
||||||
|
ColumnReverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FlexDirection {
|
||||||
|
fn default() -> FlexDirection {
|
||||||
|
FlexDirection::Row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlexDirection {
|
||||||
|
pub(crate) fn is_row(self) -> bool {
|
||||||
|
self == FlexDirection::Row || self == FlexDirection::RowReverse
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_column(self) -> bool {
|
||||||
|
self == FlexDirection::Column || self == FlexDirection::ColumnReverse
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_reverse(self) -> bool {
|
||||||
|
self == FlexDirection::RowReverse || self == FlexDirection::ColumnReverse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum JustifyContent {
|
||||||
|
FlexStart,
|
||||||
|
FlexEnd,
|
||||||
|
Center,
|
||||||
|
SpaceBetween,
|
||||||
|
SpaceAround,
|
||||||
|
SpaceEvenly,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JustifyContent {
|
||||||
|
fn default() -> JustifyContent {
|
||||||
|
JustifyContent::FlexStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Overflow {
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
Scroll,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Overflow {
|
||||||
|
fn default() -> Overflow {
|
||||||
|
Overflow::Visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum PositionType {
|
||||||
|
Relative,
|
||||||
|
Absolute,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PositionType {
|
||||||
|
fn default() -> PositionType {
|
||||||
|
PositionType::Relative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum FlexWrap {
|
||||||
|
NoWrap,
|
||||||
|
Wrap,
|
||||||
|
WrapReverse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FlexWrap {
|
||||||
|
fn default() -> FlexWrap {
|
||||||
|
FlexWrap::NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Dimension {
|
||||||
|
Undefined,
|
||||||
|
Auto,
|
||||||
|
Points(f32),
|
||||||
|
Percent(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Dimension {
|
||||||
|
fn default() -> Dimension {
|
||||||
|
Dimension::Undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dimension {
|
||||||
|
pub(crate) fn resolve(self, parent_width: Number) -> Number {
|
||||||
|
match self {
|
||||||
|
Dimension::Points(points) => Number::Defined(points),
|
||||||
|
Dimension::Percent(percent) => parent_width * percent,
|
||||||
|
_ => Number::Undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_defined(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Dimension::Points(_) => true,
|
||||||
|
Dimension::Percent(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Rect<Dimension> {
|
||||||
|
fn default() -> Rect<Dimension> {
|
||||||
|
Rect { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Size<Dimension> {
|
||||||
|
fn default() -> Size<Dimension> {
|
||||||
|
Size { width: Dimension::Auto, height: Dimension::Auto }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Style {
|
||||||
|
pub display: Display,
|
||||||
|
pub position_type: PositionType,
|
||||||
|
pub direction: Direction,
|
||||||
|
pub flex_direction: FlexDirection,
|
||||||
|
pub flex_wrap: FlexWrap,
|
||||||
|
pub overflow: Overflow,
|
||||||
|
pub align_items: AlignItems,
|
||||||
|
pub align_self: AlignSelf,
|
||||||
|
pub align_content: AlignContent,
|
||||||
|
pub justify_content: JustifyContent,
|
||||||
|
pub position: Rect<Dimension>,
|
||||||
|
pub margin: Rect<Dimension>,
|
||||||
|
pub padding: Rect<Dimension>,
|
||||||
|
pub border: Rect<Dimension>,
|
||||||
|
pub flex_grow: f32,
|
||||||
|
pub flex_shrink: f32,
|
||||||
|
pub flex_basis: Dimension,
|
||||||
|
pub size: Size<Dimension>,
|
||||||
|
pub min_size: Size<Dimension>,
|
||||||
|
pub max_size: Size<Dimension>,
|
||||||
|
pub aspect_ratio: Number,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Style {
|
||||||
|
fn default() -> Style {
|
||||||
|
Style {
|
||||||
|
display: Default::default(),
|
||||||
|
position_type: Default::default(),
|
||||||
|
direction: Default::default(),
|
||||||
|
flex_direction: Default::default(),
|
||||||
|
flex_wrap: Default::default(),
|
||||||
|
overflow: Default::default(),
|
||||||
|
align_items: Default::default(),
|
||||||
|
align_self: Default::default(),
|
||||||
|
align_content: Default::default(),
|
||||||
|
justify_content: Default::default(),
|
||||||
|
position: Default::default(),
|
||||||
|
margin: Default::default(),
|
||||||
|
padding: Default::default(),
|
||||||
|
border: Default::default(),
|
||||||
|
flex_grow: 0.0,
|
||||||
|
flex_shrink: 1.0,
|
||||||
|
flex_basis: Dimension::Auto,
|
||||||
|
size: Default::default(),
|
||||||
|
min_size: Default::default(),
|
||||||
|
max_size: Default::default(),
|
||||||
|
aspect_ratio: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style {
|
||||||
|
pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.width,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.width,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.margin.start,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.margin.end,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.size.height,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.size.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.height,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.height,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.margin.top,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension {
|
||||||
|
match direction {
|
||||||
|
FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom,
|
||||||
|
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf {
|
||||||
|
if self.align_self == AlignSelf::Auto {
|
||||||
|
match parent.align_items {
|
||||||
|
AlignItems::FlexStart => AlignSelf::FlexStart,
|
||||||
|
AlignItems::FlexEnd => AlignSelf::FlexEnd,
|
||||||
|
AlignItems::Center => AlignSelf::Center,
|
||||||
|
AlignItems::Baseline => AlignSelf::Baseline,
|
||||||
|
AlignItems::Stretch => AlignSelf::Stretch,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.align_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,224 +7,17 @@ use proc_macro2::{TokenStream, Ident, Span};
|
||||||
#[cfg(feature="tokenize")]
|
#[cfg(feature="tokenize")]
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
pub use crate::geometry::{Rect, Size};
|
|
||||||
pub use crate::number::Number;
|
|
||||||
pub use crate::color::Color;
|
pub use crate::color::Color;
|
||||||
|
|
||||||
|
pub use crate::stretch::geometry::{Point, Rect, Size};
|
||||||
|
pub use crate::stretch::number::Number;
|
||||||
pub use crate::stretch::result::Layout;
|
pub use crate::stretch::result::Layout;
|
||||||
|
|
||||||
/// Describes how items should be aligned.
|
pub use crate::stretch::style::{
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
Style,
|
||||||
pub enum AlignItems {
|
AlignContent, AlignItems, AlignSelf, Dimension, Direction, Display,
|
||||||
FlexStart,
|
FlexDirection, JustifyContent, Overflow, PositionType, FlexWrap
|
||||||
FlexEnd,
|
};
|
||||||
Center,
|
|
||||||
Baseline,
|
|
||||||
Stretch,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AlignItems {
|
|
||||||
fn default() -> AlignItems {
|
|
||||||
AlignItems::Stretch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how this item should be aligned.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum AlignSelf {
|
|
||||||
Auto,
|
|
||||||
FlexStart,
|
|
||||||
FlexEnd,
|
|
||||||
Center,
|
|
||||||
Baseline,
|
|
||||||
Stretch,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AlignSelf {
|
|
||||||
fn default() -> AlignSelf {
|
|
||||||
AlignSelf::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how content should be aligned.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum AlignContent {
|
|
||||||
FlexStart,
|
|
||||||
FlexEnd,
|
|
||||||
Center,
|
|
||||||
Stretch,
|
|
||||||
SpaceBetween,
|
|
||||||
SpaceAround,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AlignContent {
|
|
||||||
fn default() -> AlignContent {
|
|
||||||
AlignContent::Stretch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how things should flow - particularly important for start/end positions.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum Direction {
|
|
||||||
Inherit,
|
|
||||||
LTR,
|
|
||||||
RTL,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Direction {
|
|
||||||
fn default() -> Direction {
|
|
||||||
Direction::Inherit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes whether an item is visible or not.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum Display {
|
|
||||||
Flex,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Display {
|
|
||||||
fn default() -> Display {
|
|
||||||
Display::Flex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how items should be aligned.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum FlexDirection {
|
|
||||||
Row,
|
|
||||||
Column,
|
|
||||||
RowReverse,
|
|
||||||
ColumnReverse,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FlexDirection {
|
|
||||||
fn default() -> FlexDirection {
|
|
||||||
FlexDirection::Row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlexDirection {
|
|
||||||
/// Checks if this is a row.
|
|
||||||
pub(crate) fn is_row(self) -> bool {
|
|
||||||
self == FlexDirection::Row || self == FlexDirection::RowReverse
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if this is a column.
|
|
||||||
pub(crate) fn is_column(self) -> bool {
|
|
||||||
self == FlexDirection::Column || self == FlexDirection::ColumnReverse
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if this is a reversed direction.
|
|
||||||
pub(crate) fn is_reverse(self) -> bool {
|
|
||||||
self == FlexDirection::RowReverse || self == FlexDirection::ColumnReverse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how content should be justified.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum JustifyContent {
|
|
||||||
FlexStart,
|
|
||||||
FlexEnd,
|
|
||||||
Center,
|
|
||||||
SpaceBetween,
|
|
||||||
SpaceAround,
|
|
||||||
SpaceEvenly,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for JustifyContent {
|
|
||||||
fn default() -> JustifyContent {
|
|
||||||
JustifyContent::FlexStart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how content should overflow.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum Overflow {
|
|
||||||
Visible,
|
|
||||||
Hidden,
|
|
||||||
Scroll,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Overflow {
|
|
||||||
fn default() -> Overflow {
|
|
||||||
Overflow::Visible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how content should be positioned.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum PositionType {
|
|
||||||
Relative,
|
|
||||||
Absolute,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PositionType {
|
|
||||||
fn default() -> PositionType {
|
|
||||||
PositionType::Relative
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes how content should wrap.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum FlexWrap {
|
|
||||||
NoWrap,
|
|
||||||
Wrap,
|
|
||||||
WrapReverse,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FlexWrap {
|
|
||||||
fn default() -> FlexWrap {
|
|
||||||
FlexWrap::NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a Dimension; automatic, undefined, or a value.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
|
||||||
pub enum Dimension {
|
|
||||||
Undefined,
|
|
||||||
Auto,
|
|
||||||
Points(f32),
|
|
||||||
Percent(f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Dimension {
|
|
||||||
fn default() -> Dimension {
|
|
||||||
Dimension::Undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dimension {
|
|
||||||
/// Internal method for Stretch.
|
|
||||||
pub(crate) fn resolve(self, parent_width: Number) -> Number {
|
|
||||||
match self {
|
|
||||||
Dimension::Points(points) => Number::Defined(points),
|
|
||||||
Dimension::Percent(percent) => parent_width * percent,
|
|
||||||
_ => Number::Undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this Dimension is defined by a value or not.
|
|
||||||
pub(crate) fn is_defined(self) -> bool {
|
|
||||||
match self {
|
|
||||||
Dimension::Points(_) => true,
|
|
||||||
Dimension::Percent(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Rect<Dimension> {
|
|
||||||
fn default() -> Rect<Dimension> {
|
|
||||||
Rect { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Size<Dimension> {
|
|
||||||
fn default() -> Size<Dimension> {
|
|
||||||
Size { width: Dimension::Auto, height: Dimension::Auto }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the backface-visibility for a view. This may be removed in a later release.
|
/// Describes the backface-visibility for a view. This may be removed in a later release.
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
|
@ -308,34 +101,10 @@ impl Default for FontFamily {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Style` is passed into the Stretch Flexbox rendering system to produce a computed
|
/// When applying layout to a backing view, you'll get two calls - one with a `Layout`,
|
||||||
/// `Layout`. This is also passed to native nodes, to transform into per-platform style
|
/// which contains the computed frame, and one with an `Appearance`, which contains things
|
||||||
/// commands.
|
/// like colors, fonts, and so on.
|
||||||
#[derive(Copy, Clone, Debug)]
|
pub struct Appearance {
|
||||||
pub struct Style {
|
|
||||||
pub display: Display,
|
|
||||||
pub position_type: PositionType,
|
|
||||||
pub direction: Direction,
|
|
||||||
pub flex_direction: FlexDirection,
|
|
||||||
pub flex_wrap: FlexWrap,
|
|
||||||
pub overflow: Overflow,
|
|
||||||
pub align_items: AlignItems,
|
|
||||||
pub align_self: AlignSelf,
|
|
||||||
pub align_content: AlignContent,
|
|
||||||
pub justify_content: JustifyContent,
|
|
||||||
pub position: Rect<Dimension>,
|
|
||||||
pub margin: Rect<Dimension>,
|
|
||||||
pub padding: Rect<Dimension>,
|
|
||||||
pub border: Rect<Dimension>,
|
|
||||||
pub flex_grow: f32,
|
|
||||||
pub flex_shrink: f32,
|
|
||||||
pub flex_basis: Dimension,
|
|
||||||
pub size: Size<Dimension>,
|
|
||||||
pub min_size: Size<Dimension>,
|
|
||||||
pub max_size: Size<Dimension>,
|
|
||||||
pub aspect_ratio: Number,
|
|
||||||
|
|
||||||
// Appearance-based styles
|
|
||||||
pub background_color: Color,
|
pub background_color: Color,
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub font_style: FontStyle,
|
pub font_style: FontStyle,
|
||||||
|
|
@ -348,35 +117,12 @@ pub struct Style {
|
||||||
pub tint_color: Color
|
pub tint_color: Color
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Style {
|
impl Default for Appearance {
|
||||||
fn default() -> Style {
|
fn default() -> Appearance {
|
||||||
Style {
|
Appearance {
|
||||||
display: Default::default(),
|
|
||||||
position_type: Default::default(),
|
|
||||||
direction: Default::default(),
|
|
||||||
flex_direction: Default::default(),
|
|
||||||
flex_wrap: Default::default(),
|
|
||||||
overflow: Default::default(),
|
|
||||||
align_items: Default::default(),
|
|
||||||
align_self: Default::default(),
|
|
||||||
align_content: Default::default(),
|
|
||||||
justify_content: Default::default(),
|
|
||||||
position: Default::default(),
|
|
||||||
margin: Default::default(),
|
|
||||||
padding: Default::default(),
|
|
||||||
border: Default::default(),
|
|
||||||
flex_grow: 0.0,
|
|
||||||
flex_shrink: 1.0,
|
|
||||||
flex_basis: Dimension::Auto,
|
|
||||||
size: Default::default(),
|
|
||||||
min_size: Default::default(),
|
|
||||||
max_size: Default::default(),
|
|
||||||
aspect_ratio: Default::default(),
|
|
||||||
background_color: Color::transparent(),
|
background_color: Color::transparent(),
|
||||||
|
|
||||||
// @TODO: We can definitely judge a default value better here.
|
// @TODO: We can definitely judge a default value better here.
|
||||||
font_size: 14.,
|
font_size: 14.,
|
||||||
|
|
||||||
font_style: FontStyle::default(),
|
font_style: FontStyle::default(),
|
||||||
font_weight: FontWeight::default(),
|
font_weight: FontWeight::default(),
|
||||||
opacity: 1.,
|
opacity: 1.,
|
||||||
|
|
@ -389,95 +135,6 @@ impl Default for Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
|
||||||
/// Determines the minimum main size, given flex direction.
|
|
||||||
pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.width,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the maximum main size, given flex direction.
|
|
||||||
pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.width,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the main margin start, given flex direction.
|
|
||||||
pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.margin.start,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.top,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the main margin end, given flex direction.
|
|
||||||
pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.margin.end,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.bottom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the cross size, given flex direction.
|
|
||||||
pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.size.height,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.size.width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the minimum cross size, given flex direction.
|
|
||||||
pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.min_size.height,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.min_size.width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the maximum cross size, given flex direction.
|
|
||||||
pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.max_size.height,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.max_size.width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the cross margin start, given flex direction.
|
|
||||||
pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.margin.top,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the cross margin end, given flex direction.
|
|
||||||
pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension {
|
|
||||||
match direction {
|
|
||||||
FlexDirection::Row | FlexDirection::RowReverse => self.margin.bottom,
|
|
||||||
FlexDirection::Column | FlexDirection::ColumnReverse => self.margin.end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines the inherited align_self style, given a parent `&Style`.
|
|
||||||
pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf {
|
|
||||||
if self.align_self == AlignSelf::Auto {
|
|
||||||
match parent.align_items {
|
|
||||||
AlignItems::FlexStart => AlignSelf::FlexStart,
|
|
||||||
AlignItems::FlexEnd => AlignSelf::FlexEnd,
|
|
||||||
AlignItems::Center => AlignSelf::Center,
|
|
||||||
AlignItems::Baseline => AlignSelf::Baseline,
|
|
||||||
AlignItems::Stretch => AlignSelf::Stretch,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.align_self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// These exist purely for use in the parser code.
|
/// These exist purely for use in the parser code.
|
||||||
///
|
///
|
||||||
/// A `Style` is what's used for a node; `Styles` are what's parsed and stored.
|
/// A `Style` is what's used for a node; `Styles` are what's parsed and stored.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::styles::{Dimension, Rect, Size, Style, Styles};
|
use crate::stretch::style::Style;
|
||||||
|
use crate::styles::{Appearance, Dimension, Rect, Size, Styles};
|
||||||
|
|
||||||
/// A `StyleSheet` contains selectors and parsed `Styles` attributes.
|
/// A `StyleSheet` contains selectors and parsed `Styles` attributes.
|
||||||
/// It also has some logic to apply styles for n keys to a given `Style` node.
|
/// It also has some logic to apply styles for n keys to a given `Style` node.
|
||||||
|
|
@ -20,9 +21,9 @@ impl StyleSheet {
|
||||||
StyleSheet(styles)
|
StyleSheet(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_styles(&self, key: &str, style: &mut Style) {
|
pub fn apply_styles(&self, key: &str, style: &mut Style, appearance: &mut Appearance) {
|
||||||
match self.0.get(key) {
|
match self.0.get(key) {
|
||||||
Some(styles) => { reduce_styles_into_style(styles, style); },
|
Some(styles) => { reduce_styles_into_style(styles, style, appearance); },
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -30,14 +31,14 @@ impl StyleSheet {
|
||||||
|
|
||||||
/// This takes a list of styles, and a mutable style object, and attempts to configure the
|
/// This takes a list of styles, and a mutable style object, and attempts to configure the
|
||||||
/// style object in a way that makes sense given n styles.
|
/// style object in a way that makes sense given n styles.
|
||||||
fn reduce_styles_into_style(styles: &Vec<Styles>, layout: &mut Style) {
|
fn reduce_styles_into_style(styles: &Vec<Styles>, layout: &mut Style, appearance: &mut Appearance) {
|
||||||
for style in styles { match style {
|
for style in styles { match style {
|
||||||
Styles::AlignContent(val) => { layout.align_content = *val; },
|
Styles::AlignContent(val) => { layout.align_content = *val; },
|
||||||
Styles::AlignItems(val) => { layout.align_items = *val; },
|
Styles::AlignItems(val) => { layout.align_items = *val; },
|
||||||
Styles::AlignSelf(val) => { layout.align_self = *val; },
|
Styles::AlignSelf(val) => { layout.align_self = *val; },
|
||||||
Styles::AspectRatio(val) => { layout.aspect_ratio = *val; },
|
Styles::AspectRatio(val) => { layout.aspect_ratio = *val; },
|
||||||
Styles::BackfaceVisibility(_val) => { },
|
Styles::BackfaceVisibility(_val) => { },
|
||||||
Styles::BackgroundColor(val) => { layout.background_color = *val; },
|
Styles::BackgroundColor(val) => { appearance.background_color = *val; },
|
||||||
|
|
||||||
Styles::BorderColor(_val) => { },
|
Styles::BorderColor(_val) => { },
|
||||||
Styles::BorderEndColor(_val) => { },
|
Styles::BorderEndColor(_val) => { },
|
||||||
|
|
@ -102,9 +103,9 @@ fn reduce_styles_into_style(styles: &Vec<Styles>, layout: &mut Style) {
|
||||||
|
|
||||||
Styles::FontFamily(_val) => { },
|
Styles::FontFamily(_val) => { },
|
||||||
Styles::FontLineHeight(_val) => { },
|
Styles::FontLineHeight(_val) => { },
|
||||||
Styles::FontSize(val) => { layout.font_size = *val; },
|
Styles::FontSize(val) => { appearance.font_size = *val; },
|
||||||
Styles::FontStyle(val) => { layout.font_style = *val; },
|
Styles::FontStyle(val) => { appearance.font_style = *val; },
|
||||||
Styles::FontWeight(val) => { layout.font_weight = *val; },
|
Styles::FontWeight(val) => { appearance.font_weight = *val; },
|
||||||
|
|
||||||
Styles::Height(val) => {
|
Styles::Height(val) => {
|
||||||
layout.size = Size {
|
layout.size = Size {
|
||||||
|
|
@ -206,7 +207,7 @@ fn reduce_styles_into_style(styles: &Vec<Styles>, layout: &mut Style) {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
Styles::Opacity(val) => { layout.opacity = *val; },
|
Styles::Opacity(val) => { appearance.opacity = *val; },
|
||||||
Styles::Overflow(val) => { layout.overflow = *val; },
|
Styles::Overflow(val) => { layout.overflow = *val; },
|
||||||
|
|
||||||
Styles::PaddingBottom(val) => {
|
Styles::PaddingBottom(val) => {
|
||||||
|
|
@ -283,11 +284,11 @@ fn reduce_styles_into_style(styles: &Vec<Styles>, layout: &mut Style) {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
Styles::TextAlignment(val) => { layout.text_alignment = *val; },
|
Styles::TextAlignment(val) => { appearance.text_alignment = *val; },
|
||||||
Styles::TextColor(val) => { layout.text_color = *val; },
|
Styles::TextColor(val) => { appearance.text_color = *val; },
|
||||||
Styles::TextDecorationColor(val) => { layout.text_decoration_color = *val; },
|
Styles::TextDecorationColor(val) => { appearance.text_decoration_color = *val; },
|
||||||
Styles::TextShadowColor(val) => { layout.text_shadow_color = *val; },
|
Styles::TextShadowColor(val) => { appearance.text_shadow_color = *val; },
|
||||||
Styles::TintColor(val) => { layout.tint_color = *val; },
|
Styles::TintColor(val) => { appearance.tint_color = *val; },
|
||||||
|
|
||||||
Styles::Top(val) => {
|
Styles::Top(val) => {
|
||||||
layout.position = Rect {
|
layout.position = Rect {
|
||||||
|
|
|
||||||
Reference in a new issue