Props, reconciler fixes, demo fixes
This commit is contained in:
parent
47163c64f9
commit
290b57d336
17 changed files with 284 additions and 190 deletions
|
|
@ -5,7 +5,9 @@
|
||||||
//! just allow returning arbitrary iterators.
|
//! just allow returning arbitrary iterators.
|
||||||
|
|
||||||
use alchemy_lifecycle::ComponentKey;
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::traits::Component;
|
use alchemy_lifecycle::traits::{Component, Props};
|
||||||
|
|
||||||
|
pub struct FragmentProps;
|
||||||
|
|
||||||
/// 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
|
||||||
/// want to render some views without requiring an intermediate view.
|
/// want to render some views without requiring an intermediate view.
|
||||||
|
|
@ -20,8 +22,18 @@ use alchemy_lifecycle::traits::Component;
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Fragment;
|
pub struct Fragment;
|
||||||
|
|
||||||
impl Component for Fragment {
|
impl Fragment {
|
||||||
fn constructor(_key: ComponentKey) -> Fragment {
|
fn default_props() -> FragmentProps {
|
||||||
Fragment { }
|
FragmentProps {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Props for Fragment {
|
||||||
|
fn set_props(&mut self, _: &mut std::any::Any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Fragment {
|
||||||
|
fn new(_: ComponentKey) -> Fragment {
|
||||||
|
Fragment {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ use alchemy_styles::styles::{Appearance, Layout};
|
||||||
|
|
||||||
use alchemy_lifecycle::ComponentKey;
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::error::Error;
|
use alchemy_lifecycle::error::Error;
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX};
|
use alchemy_lifecycle::rsx::RSX;
|
||||||
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType};
|
||||||
|
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
||||||
|
|
||||||
|
pub struct TextProps;
|
||||||
|
|
||||||
/// Text rendering is a complicated mess, and being able to defer to the
|
/// Text rendering is a complicated mess, and being able to defer to the
|
||||||
/// backing platform for this is amazing. This is a very common Component.
|
/// backing platform for this is amazing. This is a very common Component.
|
||||||
///
|
///
|
||||||
|
|
@ -23,59 +25,60 @@ use alchemy_cocoa::text::{Text as PlatformTextBridge};
|
||||||
/// ```
|
/// ```
|
||||||
/// <Text styles=["styleKey1", "styleKey2"] />
|
/// <Text styles=["styleKey1", "styleKey2"] />
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Text {
|
pub struct Text(Mutex<PlatformTextBridge>);
|
||||||
text: String,
|
|
||||||
bridge: Mutex<PlatformTextBridge>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
|
pub fn default_props() -> TextProps { TextProps {} }
|
||||||
// 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
|
||||||
// rendering anyway.
|
// rendering anyway.
|
||||||
fn compare_and_update_text(&mut self, props: &Props) {
|
//fn compare_and_update_text(&mut self, props: &Props) {
|
||||||
let text = props.children.iter().map(|child| match child {
|
/*let text = props.*/
|
||||||
RSX::VirtualText(s) => s.0.clone(),
|
//}
|
||||||
_ => String::new()
|
}
|
||||||
}).collect::<String>();
|
|
||||||
|
|
||||||
if self.text != text {
|
impl Props for Text {
|
||||||
let mut bridge = self.bridge.lock().unwrap();
|
fn set_props(&mut self, _: &mut std::any::Any) {}
|
||||||
bridge.set_text(&text);
|
|
||||||
self.text = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Text {
|
impl Component for Text {
|
||||||
fn constructor(_: ComponentKey) -> Text {
|
fn new(_: ComponentKey) -> Text {
|
||||||
Text {
|
Text(Mutex::new(PlatformTextBridge::new()))
|
||||||
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> {
|
||||||
let bridge = self.bridge.lock().unwrap();
|
let bridge = self.0.lock().unwrap();
|
||||||
Some(bridge.borrow_native_backing_node())
|
Some(bridge.borrow_native_backing_node())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shouldn't be allowed to have child <Text> elements... or, should it?
|
// Shouldn't be allowed to have child <Text> elements... or, should it?
|
||||||
// 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, appearance: &Appearance, layout: &Layout) {
|
fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
|
||||||
let mut bridge = self.bridge.lock().unwrap();
|
let mut bridge = self.0.lock().unwrap();
|
||||||
bridge.apply_styles(appearance, layout);
|
bridge.apply_styles(appearance, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_did_mount(&mut self, props: &Props) {
|
fn component_did_mount(&mut self) {
|
||||||
self.compare_and_update_text(props);
|
let mut bridge = self.0.lock().unwrap();
|
||||||
|
bridge.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _props: &Props) -> Result<RSX, Error> {
|
// This one is a bit tricky, due to the way we have to do props + children in Rust.
|
||||||
|
// Here, we set it as the new text on render(), and then ensure it gets rendered on
|
||||||
|
// `component_did_update()` and `component_did_mount()`.
|
||||||
|
fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> {
|
||||||
|
let text = children.iter().map(|child| match child {
|
||||||
|
RSX::VirtualText(s) => s.0.to_owned(),
|
||||||
|
_ => String::new()
|
||||||
|
}).collect::<String>();
|
||||||
|
|
||||||
|
let mut bridge = self.0.lock().unwrap();
|
||||||
|
bridge.set_text(text);
|
||||||
|
|
||||||
Ok(RSX::None)
|
Ok(RSX::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,16 @@ use alchemy_styles::{Appearance, Layout, StylesList};
|
||||||
|
|
||||||
use alchemy_lifecycle::ComponentKey;
|
use alchemy_lifecycle::ComponentKey;
|
||||||
use alchemy_lifecycle::error::Error;
|
use alchemy_lifecycle::error::Error;
|
||||||
use alchemy_lifecycle::rsx::{Props, RSX};
|
use alchemy_lifecycle::rsx::RSX;
|
||||||
use alchemy_lifecycle::traits::{Component, PlatformSpecificNodeType};
|
use alchemy_lifecycle::traits::{Component, Props, PlatformSpecificNodeType};
|
||||||
|
|
||||||
use crate::components::Fragment;
|
use crate::components::Fragment;
|
||||||
|
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
use alchemy_cocoa::view::{View as PlatformViewBridge};
|
use alchemy_cocoa::view::{View as PlatformViewBridge};
|
||||||
|
|
||||||
|
pub struct ViewProps;
|
||||||
|
|
||||||
/// Views are the most basic piece of the API. If you want to display something, you'll
|
/// Views are the most basic piece of the API. If you want to display something, you'll
|
||||||
/// probably be reaching for a View first and foremost.
|
/// probably be reaching for a View first and foremost.
|
||||||
///
|
///
|
||||||
|
|
@ -25,44 +27,53 @@ use alchemy_cocoa::view::{View as PlatformViewBridge};
|
||||||
/// ```
|
/// ```
|
||||||
/// <View styles=["styleKey1", "styleKey2"] />
|
/// <View styles=["styleKey1", "styleKey2"] />
|
||||||
/// ```
|
/// ```
|
||||||
pub struct View(Mutex<PlatformViewBridge>);
|
pub struct View {
|
||||||
|
bridge: Mutex<PlatformViewBridge>
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for View {
|
impl Default for View {
|
||||||
fn default() -> View {
|
fn default() -> View {
|
||||||
View(Mutex::new(PlatformViewBridge::new()))
|
View {
|
||||||
|
bridge: Mutex::new(PlatformViewBridge::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn default_props() -> ViewProps {
|
||||||
|
ViewProps {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Props for View {
|
||||||
|
fn set_props(&mut self, _: &mut std::any::Any) {}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for View {
|
impl Component for View {
|
||||||
fn constructor(_key: ComponentKey) -> View {
|
fn new(_: ComponentKey) -> View {
|
||||||
View(Mutex::new(PlatformViewBridge::new()))
|
View::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
let bridge = self.0.lock().unwrap();
|
let bridge = self.bridge.lock().unwrap();
|
||||||
Some(bridge.borrow_native_backing_node())
|
Some(bridge.borrow_native_backing_node())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_child_component(&self, component: &Component) {
|
fn append_child_node(&self, node: PlatformSpecificNodeType) {
|
||||||
if let Some(child) = component.borrow_native_backing_node() {
|
let mut bridge = self.bridge.lock().unwrap();
|
||||||
let mut bridge = self.0.lock().unwrap();
|
bridge.append_child(node);
|
||||||
bridge.append_child(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
|
fn apply_styles(&self, appearance: &Appearance, layout: &Layout) {
|
||||||
let mut bridge = self.0.lock().unwrap();
|
let mut bridge = self.bridge.lock().unwrap();
|
||||||
bridge.apply_styles(appearance, layout);
|
bridge.apply_styles(appearance, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> {
|
||||||
Ok(RSX::node("Fragment", |key| Box::new(Fragment::constructor(key)), Props {
|
Ok(RSX::node("Fragment", "".into(), |key| {
|
||||||
attributes: std::collections::HashMap::new(),
|
Box::new(<Fragment as Component>::new(key))
|
||||||
key: "".into(),
|
}, Box::new(ViewProps {}), children))
|
||||||
styles: StylesList::new(),
|
|
||||||
children: props.children.clone()
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ use proc_macro_hack::proc_macro_hack;
|
||||||
|
|
||||||
pub use alchemy_lifecycle::ComponentKey;
|
pub use alchemy_lifecycle::ComponentKey;
|
||||||
pub use alchemy_lifecycle::traits::{
|
pub use alchemy_lifecycle::traits::{
|
||||||
AppDelegate, Component, WindowDelegate
|
AppDelegate, Component, Props, WindowDelegate
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use alchemy_lifecycle::error::Error;
|
pub use alchemy_lifecycle::error::Error;
|
||||||
pub use alchemy_lifecycle::rsx::{
|
pub use alchemy_lifecycle::rsx::{
|
||||||
Props, RSX, VirtualNode, VirtualText
|
RSX, VirtualNode, VirtualText
|
||||||
};
|
};
|
||||||
|
|
||||||
#[proc_macro_hack(support_nested)]
|
#[proc_macro_hack(support_nested)]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE};
|
use alchemy_lifecycle::{ComponentKey, RENDER_ENGINE};
|
||||||
use alchemy_lifecycle::rsx::RSX;
|
use alchemy_lifecycle::rsx::RSX;
|
||||||
use alchemy_lifecycle::traits::WindowDelegate;
|
use alchemy_lifecycle::traits::{Component, WindowDelegate};
|
||||||
|
|
||||||
use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE};
|
use alchemy_styles::{Appearance, Style, StylesList, THEME_ENGINE};
|
||||||
|
|
||||||
|
|
@ -92,7 +92,11 @@ impl Window {
|
||||||
let window_id = SHARED_APP.windows.allocate_new_window_id();
|
let window_id = SHARED_APP.windows.allocate_new_window_id();
|
||||||
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, &view, shared_app_ptr);
|
|
||||||
|
// This unwrap() is fine, since we implement View ourselves in Alchemy
|
||||||
|
let backing_node = view.borrow_native_backing_node().unwrap();
|
||||||
|
let bridge = PlatformWindowBridge::new(window_id, backing_node, shared_app_ptr);
|
||||||
|
|
||||||
let key = match RENDER_ENGINE.register_root_component(view) {
|
let key = match RENDER_ENGINE.register_root_component(view) {
|
||||||
Ok(key) => key,
|
Ok(key) => key,
|
||||||
Err(_e) => { panic!("Uhhhh this really messed up"); }
|
Err(_e) => { panic!("Uhhhh this really messed up"); }
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ static ALCHEMY_DELEGATE: &str = "alchemyDelegate";
|
||||||
/// colors and so forth.
|
/// colors and so forth.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
|
text: String,
|
||||||
inner_mut: Id<Object>,
|
inner_mut: Id<Object>,
|
||||||
inner_share: ShareId<Object>,
|
inner_share: ShareId<Object>,
|
||||||
background_color: Id<Object>,
|
background_color: Id<Object>,
|
||||||
|
|
@ -49,6 +50,7 @@ impl Text {
|
||||||
};
|
};
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
text: "".into(),
|
||||||
inner_mut: inner_mut,
|
inner_mut: inner_mut,
|
||||||
inner_share: inner_share,
|
inner_share: inner_share,
|
||||||
background_color: Color::transparent().into_nscolor(),
|
background_color: Color::transparent().into_nscolor(),
|
||||||
|
|
@ -88,9 +90,13 @@ impl Text {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: &str) {
|
pub fn set_text(&mut self, text: String) {
|
||||||
|
self.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let string_value = NSString::alloc(nil).init_str(text);
|
let string_value = NSString::alloc(nil).init_str(&self.text);
|
||||||
msg_send![&*self.inner_mut, setStringValue:string_value];
|
msg_send![&*self.inner_mut, setStringValue:string_value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ impl Window {
|
||||||
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
|
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
|
||||||
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
|
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
|
||||||
/// pointers.
|
/// pointers.
|
||||||
pub fn new<T: AppDelegate>(window_id: usize, content_view: &Component, app_ptr: *const T) -> Window {
|
pub fn new<T: AppDelegate>(window_id: usize, content_view: ShareId<Object>, app_ptr: *const T) -> Window {
|
||||||
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
|
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
|
||||||
|
|
||||||
let style = NSWindowStyleMask::NSResizableWindowMask |
|
let style = NSWindowStyleMask::NSResizableWindowMask |
|
||||||
|
|
@ -53,9 +53,9 @@ impl Window {
|
||||||
// Objective-C runtime gets out of sync.
|
// Objective-C runtime gets out of sync.
|
||||||
msg_send![window, setReleasedWhenClosed:NO];
|
msg_send![window, setReleasedWhenClosed:NO];
|
||||||
|
|
||||||
if let Some(view_ptr) = content_view.borrow_native_backing_node() {
|
//if let Some(view_ptr) = content_view.borrow_native_backing_node() {
|
||||||
msg_send![window, setContentView:view_ptr];
|
msg_send![window, setContentView:content_view];
|
||||||
}
|
//}
|
||||||
|
|
||||||
ShareId::from_ptr(window)
|
ShareId::from_ptr(window)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,23 +24,26 @@ impl AppDelegate for AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/*#[derive(Default)]
|
||||||
pub struct Banner;
|
pub struct Banner;
|
||||||
|
|
||||||
impl Component for Banner {
|
impl Component for Banner {
|
||||||
|
type Props = Box<()>;
|
||||||
|
type State = Box<()>;
|
||||||
|
|
||||||
fn constructor(_key: ComponentKey) -> Banner {
|
fn constructor(_key: ComponentKey) -> Banner {
|
||||||
Banner {}
|
Banner {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, props: &Props) -> Result<RSX, Error> {
|
fn render(&self, props: &Self::Props) -> Result<RSX, Error> {
|
||||||
Ok(rsx! {
|
Ok(rsx! {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<View styles=["wut1"]></View>
|
<View styles=["wut1"]></View>
|
||||||
{props.children.clone()}
|
//{props.children.clone()}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
pub struct WindowState;
|
pub struct WindowState;
|
||||||
|
|
||||||
|
|
@ -51,7 +54,6 @@ impl WindowDelegate for WindowState {
|
||||||
|
|
||||||
fn render(&self) -> Result<RSX, Error> {
|
fn render(&self) -> Result<RSX, Error> {
|
||||||
let messages = vec!["LOL"]; //, "wut", "BERT"];
|
let messages = vec!["LOL"]; //, "wut", "BERT"];
|
||||||
|
|
||||||
Ok(rsx! {
|
Ok(rsx! {
|
||||||
<View styles={messages}>
|
<View styles={messages}>
|
||||||
<Text styles=["message"]>"Hello there, my name is Bert"</Text>
|
<Text styles=["message"]>"Hello there, my name is Bert"</Text>
|
||||||
|
|
@ -61,9 +63,9 @@ impl WindowDelegate for WindowState {
|
||||||
})}*/
|
})}*/
|
||||||
<View styles=["box1"]>
|
<View styles=["box1"]>
|
||||||
//<View styles=["box1"]></View>
|
//<View styles=["box1"]></View>
|
||||||
<Banner>
|
//<Banner>
|
||||||
<View styles=["innermostBox"] />
|
//<View styles=["innermostBox"] />
|
||||||
</Banner>
|
//</Banner>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
})
|
})
|
||||||
|
|
|
||||||
31
lifecycle/src/reconciler/generic_root_view_stub.rs
Normal file
31
lifecycle/src/reconciler/generic_root_view_stub.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
//! This is a generic view used for avoiding a circular dependency issue.
|
||||||
|
//! You should never need to touch this.
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
|
use crate::ComponentKey;
|
||||||
|
use crate::traits::{Component, Props};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GenericRootViewProps;
|
||||||
|
|
||||||
|
/// This is never actually created, and is here primarily to avoid a circular
|
||||||
|
/// depedency issue (we can't import the View from alchemy's core crate, since the core crate
|
||||||
|
/// depends on this crate).
|
||||||
|
pub struct GenericRootView;
|
||||||
|
|
||||||
|
impl GenericRootView {
|
||||||
|
fn get_default_props() -> GenericRootViewProps {
|
||||||
|
GenericRootViewProps {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Props for GenericRootView {
|
||||||
|
fn set_props(&mut self, _: &mut Any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for GenericRootView {
|
||||||
|
fn new(_: ComponentKey) -> GenericRootView {
|
||||||
|
GenericRootView {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,15 @@
|
||||||
//! Internal struct used for tracking component instances and their
|
//! Internal struct used for tracking component instances and their
|
||||||
//! associated metadata (layout, appearance, etc).
|
//! associated metadata (layout, appearance, etc).
|
||||||
|
|
||||||
use alchemy_styles::Appearance;
|
use alchemy_styles::{Appearance, StylesList};
|
||||||
use alchemy_styles::stretch::node::{Node as LayoutNode};
|
use alchemy_styles::stretch::node::{Node as LayoutNode};
|
||||||
|
|
||||||
use crate::rsx::Props;
|
|
||||||
use crate::traits::Component;
|
use crate::traits::Component;
|
||||||
|
|
||||||
pub(crate) struct Instance {
|
pub(crate) struct Instance {
|
||||||
pub(crate) tag: &'static str,
|
pub(crate) tag: &'static str,
|
||||||
pub(crate) component: Box<Component>,
|
pub(crate) style_keys: StylesList,
|
||||||
pub(crate) props: Props,
|
pub(crate) component: Box<Component + 'static>,
|
||||||
pub(crate) appearance: Appearance,
|
pub(crate) appearance: Appearance,
|
||||||
pub(crate) layout: Option<LayoutNode>
|
pub(crate) layout: Option<LayoutNode>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
|
||||||
pub(crate) fn new(
|
|
||||||
tag: &'static str,
|
|
||||||
component: Box<Component>,
|
|
||||||
props: Props,
|
|
||||||
layout: Option<LayoutNode>
|
|
||||||
) -> Instance {
|
|
||||||
Instance {
|
|
||||||
tag: tag,
|
|
||||||
component: component,
|
|
||||||
props: props,
|
|
||||||
appearance: Appearance::default(),
|
|
||||||
layout: layout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,11 @@ use std::error::Error;
|
||||||
|
|
||||||
use alchemy_styles::THEME_ENGINE;
|
use alchemy_styles::THEME_ENGINE;
|
||||||
use alchemy_styles::styles::{Appearance, Dimension, Number, Size, Style};
|
use alchemy_styles::styles::{Appearance, Dimension, Number, Size, Style};
|
||||||
|
|
||||||
use crate::traits::Component;
|
|
||||||
use crate::rsx::{Props, RSX, VirtualNode};
|
|
||||||
|
|
||||||
use alchemy_styles::stretch::node::{Node as LayoutNode, Stretch as LayoutStore};
|
use alchemy_styles::stretch::node::{Node as LayoutNode, Stretch as LayoutStore};
|
||||||
|
|
||||||
|
use crate::rsx::{RSX, VirtualNode};
|
||||||
|
use crate::traits::Component;
|
||||||
|
|
||||||
pub mod key;
|
pub mod key;
|
||||||
use key::ComponentKey;
|
use key::ComponentKey;
|
||||||
|
|
||||||
|
|
@ -26,16 +25,10 @@ use error::RenderEngineError;
|
||||||
mod instance;
|
mod instance;
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
|
|
||||||
/// This is never actually created, and is here primarily to avoid a circular
|
mod generic_root_view_stub;
|
||||||
/// depedency issue (we can't import the View from alchemy's core crate, since the core crate
|
use generic_root_view_stub::{GenericRootView, GenericRootViewProps};
|
||||||
/// depends on this crate).
|
|
||||||
|
|
||||||
pub struct GenericRootView;
|
struct GenericRootProps;
|
||||||
impl Component for GenericRootView {
|
|
||||||
fn constructor(key: ComponentKey) -> GenericRootView {
|
|
||||||
GenericRootView {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RenderEngine {
|
pub struct RenderEngine {
|
||||||
queued_state_updates: Mutex<Vec<i32>>,
|
queued_state_updates: Mutex<Vec<i32>>,
|
||||||
|
|
@ -60,25 +53,24 @@ impl RenderEngine {
|
||||||
/// 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) -> Result<ComponentKey, Box<Error>> {
|
pub fn register_root_component<C: Component + 'static>(&self, component: C) -> Result<ComponentKey, Box<Error>> {
|
||||||
// Conceivably, this doesn't NEED to be a thing... but for now it is. If you've stumbled
|
// Conceivably, this doesn't NEED to be a thing... but for now it is. If you've stumbled
|
||||||
// upon here, wayward traveler, in need of a non-native-root-component, please open an
|
// upon here, wayward traveler, in need of a non-native-root-component, please open an
|
||||||
// issue to discuss. :)
|
// issue to discuss. :)
|
||||||
if !instance.has_native_backing_node() {
|
if !component.has_native_backing_node() {
|
||||||
return Err(Box::new(RenderEngineError::InvalidRootComponent {}));
|
return Err(Box::new(RenderEngineError::InvalidRootComponent {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut component_store = self.components.lock().unwrap();
|
let mut component_store = self.components.lock().unwrap();
|
||||||
let component_key = component_store.new_key();
|
|
||||||
component_store.insert(component_key, Instance::new("root", Box::new(instance), {
|
|
||||||
let mut props = Props::default();
|
|
||||||
props.styles = "root".into();
|
|
||||||
props
|
|
||||||
}, {
|
|
||||||
let mut layouts_store = self.layouts.lock().unwrap();
|
let mut layouts_store = self.layouts.lock().unwrap();
|
||||||
let style = Style::default();
|
let component_key = component_store.new_key();
|
||||||
Some(layouts_store.new_node(style, vec![])?)
|
component_store.insert(component_key, Instance {
|
||||||
}))?;
|
tag: "root",
|
||||||
|
style_keys: "root".into(),
|
||||||
|
component: Box::new(component),
|
||||||
|
appearance: Appearance::default(),
|
||||||
|
layout: Some(layouts_store.new_node(Style::default(), vec![])?)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(component_key)
|
Ok(component_key)
|
||||||
}
|
}
|
||||||
|
|
@ -97,19 +89,19 @@ impl RenderEngine {
|
||||||
let mut component_store = self.components.lock().unwrap();
|
let mut component_store = self.components.lock().unwrap();
|
||||||
let mut layout_store = self.layouts.lock().unwrap();
|
let mut layout_store = self.layouts.lock().unwrap();
|
||||||
|
|
||||||
let new_root_node = RSX::node("root", |_| {
|
let new_root_node = RSX::node("root", "root".into(), |_| {
|
||||||
Box::new(GenericRootView {})
|
Box::new(GenericRootView {})
|
||||||
}, Props::root(match child {
|
}, Box::new(GenericRootViewProps {}), match child {
|
||||||
RSX::VirtualNode(node) => {
|
RSX::VirtualNode(node) => {
|
||||||
if node.tag == "Fragment" {
|
if node.tag == "Fragment" {
|
||||||
node.props.children
|
node.children
|
||||||
} else {
|
} else {
|
||||||
vec![RSX::VirtualNode(node)]
|
vec![RSX::VirtualNode(node)]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => vec![]
|
_ => vec![]
|
||||||
}));
|
});
|
||||||
|
|
||||||
recursively_diff_tree(key, new_root_node, &mut component_store, &mut layout_store)?;
|
recursively_diff_tree(key, new_root_node, &mut component_store, &mut layout_store)?;
|
||||||
|
|
||||||
|
|
@ -117,7 +109,7 @@ impl RenderEngine {
|
||||||
let mut root_instance = component_store.get_mut(key)?;
|
let mut root_instance = component_store.get_mut(key)?;
|
||||||
let layout = root_instance.layout.unwrap();
|
let layout = root_instance.layout.unwrap();
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
THEME_ENGINE.configure_styles_for_keys(&root_instance.props.styles, &mut style, &mut root_instance.appearance);
|
THEME_ENGINE.configure_styles_for_keys(&root_instance.style_keys, &mut style, &mut root_instance.appearance);
|
||||||
style.size = Size {
|
style.size = Size {
|
||||||
width: Dimension::Points(dimensions.0 as f32),
|
width: Dimension::Points(dimensions.0 as f32),
|
||||||
height: Dimension::Points(dimensions.1 as f32)
|
height: Dimension::Points(dimensions.1 as f32)
|
||||||
|
|
@ -179,7 +171,7 @@ fn recursively_diff_tree(
|
||||||
old_children.reverse();
|
old_children.reverse();
|
||||||
|
|
||||||
if let RSX::VirtualNode(mut child) = new_tree {
|
if let RSX::VirtualNode(mut child) = new_tree {
|
||||||
for new_child_tree in child.props.children {
|
for new_child_tree in child.children {
|
||||||
match old_children.pop() {
|
match old_children.pop() {
|
||||||
// If there's a key in the old children for this position, it's
|
// If there's a key in the old children for this position, it's
|
||||||
// something we need to update, so let's recurse right back into it.
|
// something we need to update, so let's recurse right back into it.
|
||||||
|
|
@ -238,14 +230,21 @@ fn mount_component_tree(
|
||||||
let is_native_backed = component.has_native_backing_node();
|
let is_native_backed = component.has_native_backing_node();
|
||||||
|
|
||||||
// let state = get_derived_state_from_props()
|
// let state = get_derived_state_from_props()
|
||||||
let mut instance = Instance::new(tree.tag, component, tree.props, None);
|
let mut instance = Instance {
|
||||||
|
tag: tree.tag,
|
||||||
|
style_keys: tree.styles,
|
||||||
|
component: component,
|
||||||
|
appearance: Appearance::default(),
|
||||||
|
layout: None
|
||||||
|
};
|
||||||
|
|
||||||
if is_native_backed {
|
if is_native_backed {
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
THEME_ENGINE.configure_styles_for_keys(&instance.props.styles, &mut style, &mut instance.appearance);
|
THEME_ENGINE.configure_styles_for_keys(&instance.style_keys, &mut style, &mut instance.appearance);
|
||||||
instance.layout = Some(layout_store.new_node(style, vec![])?);
|
instance.layout = Some(layout_store.new_node(style, vec![])?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rendered = instance.component.render(&instance.props);
|
let rendered = instance.component.render(tree.children);
|
||||||
// instance.get_snapshot_before_update()
|
// instance.get_snapshot_before_update()
|
||||||
component_store.insert(key, instance)?;
|
component_store.insert(key, instance)?;
|
||||||
|
|
||||||
|
|
@ -256,7 +255,7 @@ fn mount_component_tree(
|
||||||
// tag similar to what React does, which just hoists the children out of it and
|
// tag similar to what React does, which just hoists the children out of it and
|
||||||
// discards the rest.
|
// discards the rest.
|
||||||
if child.tag == "Fragment" {
|
if child.tag == "Fragment" {
|
||||||
for child_tree in child.props.children {
|
for child_tree in child.children {
|
||||||
if let RSX::VirtualNode(child_tree) = child_tree {
|
if let RSX::VirtualNode(child_tree) = child_tree {
|
||||||
let child_key = mount_component_tree(child_tree, component_store, layout_store)?;
|
let child_key = mount_component_tree(child_tree, component_store, layout_store)?;
|
||||||
|
|
||||||
|
|
@ -286,7 +285,7 @@ fn mount_component_tree(
|
||||||
}
|
}
|
||||||
|
|
||||||
let instance_lol = component_store.get_mut(key)?;
|
let instance_lol = component_store.get_mut(key)?;
|
||||||
instance_lol.component.component_did_mount(&instance_lol.props);
|
instance_lol.component.component_did_mount();
|
||||||
|
|
||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +300,7 @@ fn unmount_component_tree(
|
||||||
layout_store: &mut LayoutStore
|
layout_store: &mut LayoutStore
|
||||||
) -> Result<Vec<LayoutNode>, Box<Error>> {
|
) -> Result<Vec<LayoutNode>, Box<Error>> {
|
||||||
let mut instance = component_store.remove(key)?;
|
let mut instance = component_store.remove(key)?;
|
||||||
instance.component.component_will_unmount(&instance.props);
|
instance.component.component_will_unmount();
|
||||||
|
|
||||||
let mut layout_nodes = vec![];
|
let mut layout_nodes = vec![];
|
||||||
|
|
||||||
|
|
@ -342,7 +341,11 @@ fn link_layout_nodess(
|
||||||
if let (Ok(parent_instance), Ok(child_instance)) = (components.get(parent), components.get(child)) {
|
if let (Ok(parent_instance), Ok(child_instance)) = (components.get(parent), components.get(child)) {
|
||||||
if let (Some(parent_layout), Some(child_layout)) = (parent_instance.layout, child_instance.layout) {
|
if let (Some(parent_layout), Some(child_layout)) = (parent_instance.layout, child_instance.layout) {
|
||||||
layouts.add_child(parent_layout, child_layout)?;
|
layouts.add_child(parent_layout, child_layout)?;
|
||||||
parent_instance.component.append_child_component(&*child_instance.component);
|
|
||||||
|
if let Some(platform_node) = child_instance.component.borrow_native_backing_node() {
|
||||||
|
parent_instance.component.append_child_node(platform_node);
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,22 @@
|
||||||
//! 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::any::Any;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use alchemy_styles::StylesList;
|
||||||
|
|
||||||
mod virtual_node;
|
mod virtual_node;
|
||||||
pub use virtual_node::VirtualNode;
|
pub use virtual_node::VirtualNode;
|
||||||
|
|
||||||
mod virtual_text;
|
mod virtual_text;
|
||||||
pub use virtual_text::VirtualText;
|
pub use virtual_text::VirtualText;
|
||||||
|
|
||||||
mod props;
|
|
||||||
pub use props::Props;
|
|
||||||
|
|
||||||
use crate::reconciler::key::ComponentKey;
|
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
|
||||||
/// system can work with. `None`, `VirtualText`, or `VirtualNode`.
|
/// system can work with. `None`, `VirtualText`, or `VirtualNode`.
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum RSX {
|
pub enum RSX {
|
||||||
None,
|
None,
|
||||||
VirtualText(VirtualText),
|
VirtualText(VirtualText),
|
||||||
|
|
@ -29,15 +28,19 @@ pub enum RSX {
|
||||||
impl RSX {
|
impl RSX {
|
||||||
/// Shorthand method for creating a new `RSX::VirtualNode` instance. Rarely should you call
|
/// Shorthand method for creating a new `RSX::VirtualNode` instance. Rarely should you call
|
||||||
/// this yourself; the `rsx! {}` macro handles this for you.
|
/// this yourself; the `rsx! {}` macro handles this for you.
|
||||||
pub fn node(
|
pub fn node<P: Any + 'static>(
|
||||||
tag: &'static str,
|
tag: &'static str,
|
||||||
|
styles: StylesList,
|
||||||
create_fn: fn(key: ComponentKey) -> Box<Component>,
|
create_fn: fn(key: ComponentKey) -> Box<Component>,
|
||||||
props: Props
|
props: P,
|
||||||
|
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,
|
||||||
props: props
|
styles: styles,
|
||||||
|
props: Box::new(props),
|
||||||
|
children: children
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl<'a> From<&'a str> for AttributeType {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
pub attributes: HashMap<&'static str, AttributeType>,
|
pub attributes: HashMap<&'static str, AttributeType>,
|
||||||
pub children: Vec<RSX>,
|
//pub children: Vec<RSX>,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub styles: StylesList
|
pub styles: StylesList
|
||||||
}
|
}
|
||||||
|
|
@ -42,17 +42,17 @@ impl Props {
|
||||||
key: String,
|
key: String,
|
||||||
styles: StylesList,
|
styles: StylesList,
|
||||||
attributes: HashMap<&'static str, AttributeType>,
|
attributes: HashMap<&'static str, AttributeType>,
|
||||||
children: Vec<RSX>
|
//children: Vec<RSX>
|
||||||
) -> Props {
|
) -> Props {
|
||||||
Props {
|
Props {
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
children: children,
|
//children: children,
|
||||||
key: key,
|
key: key,
|
||||||
styles: styles
|
styles: styles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper method used for constructing root-level Properties.
|
/*/// A helper method used for constructing root-level Properties.
|
||||||
pub(crate) fn root(children: Vec<RSX>) -> Props {
|
pub(crate) fn root(children: Vec<RSX>) -> Props {
|
||||||
Props {
|
Props {
|
||||||
attributes: HashMap::new(),
|
attributes: HashMap::new(),
|
||||||
|
|
@ -65,7 +65,7 @@ impl Props {
|
||||||
/// 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()
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// Returns a Option<&AttributeType> from the `attributes` inner HashMap.
|
/// Returns a Option<&AttributeType> from the `attributes` inner HashMap.
|
||||||
pub fn get(&self, key: &str) -> Option<&AttributeType> {
|
pub fn get(&self, key: &str) -> Option<&AttributeType> {
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
//! 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::any::Any;
|
||||||
use std::fmt::{Display, Debug};
|
use std::fmt::{Display, Debug};
|
||||||
|
|
||||||
|
use alchemy_styles::StylesList;
|
||||||
|
|
||||||
use crate::reconciler::key::ComponentKey;
|
use crate::reconciler::key::ComponentKey;
|
||||||
use crate::rsx::{RSX, Props};
|
use crate::rsx::RSX;
|
||||||
use crate::traits::Component;
|
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, properties, children and so on.
|
/// for lazy `Component` instantiation, properties, children and so on.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VirtualNode {
|
pub struct VirtualNode {
|
||||||
/// Used in debugging/printing/etc.
|
/// Used in debugging/printing/etc.
|
||||||
pub tag: &'static str,
|
pub tag: &'static str,
|
||||||
|
|
||||||
|
/// Used for determining which CSS styles should be applied to this node.
|
||||||
|
/// This property is accessed often enough that it's separated out here.
|
||||||
|
pub styles: StylesList,
|
||||||
|
|
||||||
/// `Component` instances are created on-demand, if the reconciler deems it be so. This
|
/// `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(key: ComponentKey) -> Box<Component>,
|
pub create_component_fn: fn(key: ComponentKey) -> Box<Component>,
|
||||||
|
|
||||||
/// `Props`, which are to be passed to this `Component` at various lifecycle methods. Once
|
/// When some RSX is returned, we scoop up the props inside a special block, and then shove
|
||||||
/// the reconciler takes ownership of this VirtualNode, these props are moved to a different
|
/// them in here as an `Any` object. When you `derive(Props)` on a `Component` struct, it
|
||||||
/// location - thus, you shouldn't rely on them for anything unless you specifically keep
|
/// creates a setter that specifically handles downcasting and persisting props for you.
|
||||||
/// ownership of a VirtualNode.
|
pub props: Box<Any>,
|
||||||
///
|
|
||||||
/// This aspect of functionality may be pulled in a later release if it causes too many issues.
|
/// Child components for this node.
|
||||||
pub props: Props
|
pub children: Vec<RSX>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VirtualNode {
|
impl Display for VirtualNode {
|
||||||
|
|
@ -32,7 +38,7 @@ impl Display for VirtualNode {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
write!(f, "<{}>", self.tag)?;
|
write!(f, "<{}>", self.tag)?;
|
||||||
|
|
||||||
for child in &self.props.children {
|
for child in &self.children {
|
||||||
write!(f, "{:?}", child)?;
|
write!(f, "{:?}", child)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
//! 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::any::Any;
|
||||||
|
|
||||||
use alchemy_styles::styles::{Appearance, Layout};
|
use alchemy_styles::styles::{Appearance, Layout};
|
||||||
|
|
||||||
//use crate::RENDER_ENGINE;
|
//use crate::RENDER_ENGINE;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::reconciler::key::ComponentKey;
|
use crate::reconciler::key::ComponentKey;
|
||||||
use crate::rsx::{RSX, Props};
|
use crate::rsx::RSX;
|
||||||
|
|
||||||
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
/// A per-platform wrapped Pointer type, used for attaching views/widgets.
|
||||||
#[cfg(feature = "cocoa")]
|
#[cfg(feature = "cocoa")]
|
||||||
|
|
@ -70,7 +72,9 @@ pub trait WindowDelegate: Send + Sync {
|
||||||
fn render(&self) -> Result<RSX, Error> { Ok(RSX::None) }
|
fn render(&self) -> Result<RSX, Error> { Ok(RSX::None) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait State {}
|
pub trait Props {
|
||||||
|
fn set_props(&mut self, new_props: &mut Any);
|
||||||
|
}
|
||||||
|
|
||||||
/// The `Component` lifecycle, mostly inspired from React, with a few extra methods for views that
|
/// The `Component` lifecycle, mostly inspired from React, with a few extra methods for views that
|
||||||
/// need to have a backing native layer. A good breakdown of the React Component lifecycle can be
|
/// need to have a backing native layer. A good breakdown of the React Component lifecycle can be
|
||||||
|
|
@ -79,8 +83,8 @@ pub trait State {}
|
||||||
/// Alchemy does not currently implement Hooks, and at the moment has no plans to do so (the API
|
/// Alchemy does not currently implement Hooks, and at the moment has no plans to do so (the API
|
||||||
/// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting
|
/// 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: Props + Send + Sync {
|
||||||
fn constructor(key: ComponentKey) -> Self where Self: Sized;
|
fn new(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
|
||||||
|
|
@ -91,16 +95,16 @@ pub trait Component: Send + Sync {
|
||||||
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> { None }
|
fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> { None }
|
||||||
|
|
||||||
/// 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 append it to the tree at your point.
|
/// `node`, you need to instruct the system how to append it to the tree at your point.
|
||||||
fn append_child_component(&self, _component: &Component) {}
|
fn append_child_node(&self, _component: PlatformSpecificNodeType) {}
|
||||||
|
|
||||||
/// 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.
|
/// `node`, you need to instruct the system how to replace it in the tree at your point.
|
||||||
fn replace_child_component(&self, _component: &Component) {}
|
fn replace_child_node(&self, _component: PlatformSpecificNodeType) {}
|
||||||
|
|
||||||
/// 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.
|
/// `node`, you need to instruct the system how to remove it from the tree at your point.
|
||||||
fn remove_child_component(&self, _component: &Component) {}
|
fn remove_child_node(&self, _component: PlatformSpecificNodeType) {}
|
||||||
|
|
||||||
/// Given a configured 'appearance' and computed `layout`, this method should transform them
|
/// Given a configured 'appearance' and computed `layout`, this method should transform them
|
||||||
/// into appropriate calls to the backing native node.
|
/// into appropriate calls to the backing native node.
|
||||||
|
|
@ -109,7 +113,7 @@ pub trait Component: Send + Sync {
|
||||||
/// 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.
|
||||||
/// This method exists for rare use cases where the state depends on changes in props over time.
|
/// This method exists for rare use cases where the state depends on changes in props over time.
|
||||||
fn get_derived_state_from_props(&self, _props: Props) {}
|
fn get_derived_state_from_props(&self) {}
|
||||||
|
|
||||||
/// Invoked right before the most recently rendered output is committed to the backing layer tree.
|
/// Invoked right before the most recently rendered output is committed to the backing layer tree.
|
||||||
/// It enables your component to capture some information from the tree (e.g. scroll position) before it's
|
/// It enables your component to capture some information from the tree (e.g. scroll position) before it's
|
||||||
|
|
@ -118,18 +122,18 @@ pub trait Component: Send + Sync {
|
||||||
///
|
///
|
||||||
/// This use case is not common, but it may occur in UIs like a chat thread that need to handle scroll
|
/// This use case is not common, but it may occur in UIs like a chat thread that need to handle scroll
|
||||||
/// position in a special way. A snapshot value (or None) should be returned.
|
/// position in a special way. A snapshot value (or None) should be returned.
|
||||||
fn get_snapshot_before_update(&self, _props: Props) {}
|
fn get_snapshot_before_update(&self) {}
|
||||||
|
|
||||||
/// Invoked immediately after a component is mounted (inserted into the tree).
|
/// Invoked immediately after a component is mounted (inserted into the tree).
|
||||||
/// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
|
/// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
|
||||||
/// This method is also a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe
|
/// This method is also a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe
|
||||||
/// in component_will_unmount().
|
/// in component_will_unmount().
|
||||||
fn component_did_mount(&mut self, _props: &Props) {}
|
fn component_did_mount(&mut self) {}
|
||||||
|
|
||||||
/// Invoked immediately after updating occurs. This method is not called for the initial render.
|
/// Invoked immediately after updating occurs. This method is not called for the initial render.
|
||||||
/// This is also a good place to do network requests as long as you compare the current props to previous props
|
/// This is also a good place to do network requests as long as you compare the current props to previous props
|
||||||
/// (e.g. a network request may not be necessary if the props have not changed).
|
/// (e.g. a network request may not be necessary if the props have not changed).
|
||||||
fn component_did_update(&mut self, _props: &Props) {}
|
fn component_did_update(&mut self) {}
|
||||||
|
|
||||||
/// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this
|
/// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this
|
||||||
/// method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that
|
/// method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that
|
||||||
|
|
@ -137,12 +141,12 @@ pub trait Component: Send + Sync {
|
||||||
///
|
///
|
||||||
/// You should not call set state in this method because the component will never be re-rendered. Once a
|
/// You should not call set state in this method because the component will never be re-rendered. Once a
|
||||||
/// component instance is unmounted, it will never be mounted again.
|
/// component instance is unmounted, it will never be mounted again.
|
||||||
fn component_will_unmount(&mut self, _props: &Props) {}
|
fn component_will_unmount(&mut self) {}
|
||||||
|
|
||||||
/// Invoked after an error has been thrown by a descendant component. Called during the "commit" phase,
|
/// Invoked after an error has been thrown by a descendant component. Called during the "commit" phase,
|
||||||
/// so side-effects are permitted. It should be used for things like logging errors (e.g,
|
/// so side-effects are permitted. It should be used for things like logging errors (e.g,
|
||||||
/// Sentry).
|
/// Sentry).
|
||||||
fn component_did_catch(&mut self, _props: &Props/* error: */) {}
|
fn component_did_catch(&mut self /* error: */) {}
|
||||||
|
|
||||||
/// Use this to let Alchemy know if a component’s output is not affected by the current change in state
|
/// Use this to let Alchemy know if a component’s output is not affected by the current change in state
|
||||||
/// or props. The default behavior is to re-render on every state change, and in the vast majority of
|
/// or props. The default behavior is to re-render on every state change, and in the vast majority of
|
||||||
|
|
@ -161,11 +165,11 @@ pub trait Component: Send + Sync {
|
||||||
/// returns the same result each time it’s invoked, and it does not directly interact with the
|
/// returns the same result each time it’s invoked, and it does not directly interact with the
|
||||||
/// backing rendering framework.
|
/// backing rendering framework.
|
||||||
///
|
///
|
||||||
/// If you need to interact with the browser, perform your work in component_did_mount() or the other
|
/// If you need to interact with the native layer, perform your work in component_did_mount() or the other
|
||||||
/// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
|
/// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
|
||||||
///
|
///
|
||||||
/// This method is not called if should_component_update() returns `false`.
|
/// This method is not called if should_component_update() returns `false`.
|
||||||
fn render(&self, _props: &Props) -> Result<RSX, Error> { Ok(RSX::None) }
|
fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> { Ok(RSX::None) }
|
||||||
|
|
||||||
/// This lifecycle is invoked after an error has been thrown by a descendant component. It receives
|
/// This lifecycle is invoked after an error has been thrown by a descendant component. It receives
|
||||||
/// the error that was thrown as a parameter and should return a value to update state.
|
/// the error that was thrown as a parameter and should return a value to update state.
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ mod parser;
|
||||||
mod span;
|
mod span;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{TokenStream as TokenStream2, Literal};
|
use proc_macro2::{Ident, TokenStream as TokenStream2, Literal, Span};
|
||||||
use proc_macro_hack::proc_macro_hack;
|
use proc_macro_hack::proc_macro_hack;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
use alchemy_styles::cssparser::{Parser, ParserInput, RuleListParser};
|
use alchemy_styles::cssparser::{Parser, ParserInput, RuleListParser};
|
||||||
use alchemy_styles::styles_parser::{Rule, RuleParser};
|
use alchemy_styles::styles_parser::{Rule, RuleParser};
|
||||||
|
|
@ -77,3 +78,30 @@ pub fn styles(input: TokenStream) -> TokenStream {
|
||||||
styles
|
styles
|
||||||
})).into()
|
})).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements a derive macro for automating props setting and conversion.
|
||||||
|
#[proc_macro_derive(Props)]
|
||||||
|
pub fn writable_props_derive(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = &input.ident;
|
||||||
|
let name_props = Ident::new(&format!("{}Props", name), Span::call_site());
|
||||||
|
let generics = input.generics;
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
|
fn default_props() -> #name_props {
|
||||||
|
#name_props::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #impl_generics alchemy::ComponentProps for #name #ty_generics #where_clause {
|
||||||
|
fn set_props(&mut self, new_props: &mut Any) {
|
||||||
|
match new_props.downcast_ref::<#name_props>() {
|
||||||
|
Some(props) => { },
|
||||||
|
None => { panic!("Woah there, somehow the wrong props were being passed!"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,6 @@ impl Element {
|
||||||
(name, token, value)
|
(name, token, value)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let mut attributes = TokenStream::new();
|
let mut attributes = TokenStream::new();
|
||||||
let mut styles = TokenStream::new();
|
let mut styles = TokenStream::new();
|
||||||
styles.extend(quote!(alchemy::SpacedSet::new()));
|
styles.extend(quote!(alchemy::SpacedSet::new()));
|
||||||
|
|
@ -159,29 +158,29 @@ impl Element {
|
||||||
eprintln_msg += "\nERROR: rebuild with nightly to print source location";
|
eprintln_msg += "\nERROR: rebuild with nightly to print source location";
|
||||||
}
|
}
|
||||||
|
|
||||||
//body.extend(quote!(
|
attributes.extend(quote!(
|
||||||
/*element.attrs.#key = Some(#lit.parse().unwrap_or_else(|err| {
|
props.#key = #lit.parse().unwrap_or_else(|err| {
|
||||||
eprintln!(#eprintln_msg, err);
|
eprintln!(#eprintln_msg, err);
|
||||||
panic!("failed to parse string literal");
|
panic!("Failed to parse string literal");
|
||||||
}));*/
|
});
|
||||||
//));
|
));
|
||||||
},
|
},
|
||||||
|
|
||||||
value => {
|
value => {
|
||||||
let key = key.to_string();
|
let prop = key.to_string();
|
||||||
let value = process_value(value);
|
let value = process_value(value);
|
||||||
|
|
||||||
if key == "r#styles" {
|
if prop == "r#styles" {
|
||||||
styles = quote!(std::convert::Into::into(#value));
|
styles = quote!(std::convert::Into::into(#value));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "r#key" {
|
if prop == "r#key" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.extend(quote!(
|
attributes.extend(quote!(
|
||||||
attributes.insert(#key, std::convert::Into::into(#value));
|
props.#key = std::convert::Into::into(#value);
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -219,19 +218,19 @@ 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, |key| {
|
alchemy::RSX::node(#component_name, #styles, |key| {
|
||||||
Box::new(#typename::constructor(key))
|
Box::new(<#typename as alchemy::Component>::new(key))
|
||||||
}, alchemy::Props::new("".into(), #styles, {
|
}, {
|
||||||
let mut attributes = std::collections::HashMap::new();
|
let props = #typename::default_props();
|
||||||
#attributes
|
#attributes
|
||||||
attributes
|
Box::new(props)
|
||||||
}, {
|
}, {
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
#children
|
#children
|
||||||
children
|
children
|
||||||
}))
|
})
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Reference in a new issue