alchemy_lifecycle/
traits.rs

1//! Traits that are used in Alchemy. Alchemy implements a React-based Component
2//! lifecycle, coupled with a delegate pattern inspired by those found in AppKit/UIKit.
3
4use std::any::Any;
5
6use alchemy_styles::styles::{Appearance, Layout};
7
8//use crate::RENDER_ENGINE;
9use crate::error::Error;
10use crate::reconciler::key::ComponentKey;
11use crate::rsx::RSX;
12
13/// A per-platform wrapped Pointer type, used for attaching views/widgets.
14#[cfg(feature = "cocoa")]
15pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;
16
17/// A per-platform wrapped Pointer type, used for attaching views/widgets.
18#[cfg(not(feature = "cocoa"))]
19pub type PlatformSpecificNodeType = ();
20
21/*fn update<C: Component, F: Fn() -> Box<C> + Send + Sync + 'static>(component: &Component, updater: F) {
22    let component_ptr = component as *const C as usize;
23    RENDER_ENGINE.queue_update_for(component_ptr, Box::new(updater));
24}*/
25
26/// Each platform tends to have their own startup routine, their own runloop, and so on.
27/// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system
28/// level and allows the user to operate within the established framework per-system.
29pub trait AppDelegate: Send + Sync {
30    /// Fired when an Application is about to finish launching.
31    fn will_finish_launching(&mut self) {}
32
33    /// Fired when an Application has finished launching - this is a good place to, say, show your
34    /// window.
35    fn did_finish_launching(&mut self) {}
36
37    /// Fired when an Application will become active.
38    fn will_become_active(&mut self) {}
39
40    /// Fired when an Application became active.
41    fn did_become_active(&mut self) {}
42
43    /// Fired when an Application will resign active. You can use this to, say, persist resources
44    /// or state.
45    fn will_resign_active(&mut self) {}
46
47    /// Fired when an Application has resigned active.
48    fn did_resign_active(&mut self) {} 
49
50    /// Fired when an Application is going to terminate. You can use this to, say, instruct the
51    /// system to "wait a minute, lemme finish".
52    fn should_terminate(&self) -> bool { true }
53
54    /// Fired when the Application has determined "no, you're done, stop the world".
55    fn will_terminate(&mut self) {}
56
57    /// A private trait method that you shouldn't call. This may change or disappear in later
58    /// releases. Do not rely on this.
59    fn _window_will_close(&self, _window_id: usize) {}
60}
61
62/// Each platform has their own `Window` API, which Alchemy attempts to pair down to one consistent
63/// API. This also acts as the bootstrapping point for a `render` tree.
64pub trait WindowDelegate: Send + Sync {
65    /// Fired when this Window will close. You can use this to clean up or destroy resources,
66    /// timers, and other things.
67    fn will_close(&mut self) { }
68
69    /// Called as the first step in the `render` tree. Every Window contains its own content view
70    /// that is special, called the root. Widget trees are added to it as necessary, bootstrapped
71    /// from here.
72    fn render(&self) -> Result<RSX, Error> { Ok(RSX::None) }
73}
74
75pub trait Props {
76    fn set_props(&mut self, new_props: &mut Any);
77}
78
79/// The `Component` lifecycle, mostly inspired from React, with a few extra methods for views that
80/// need to have a backing native layer. A good breakdown of the React Component lifecycle can be 
81/// found [in this tweet](https://twitter.com/dan_abramov/status/981712092611989509?lang=en).
82///
83/// Alchemy does not currently implement Hooks, and at the moment has no plans to do so (the API
84/// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting
85/// proposal for this, feel free to open an issue!
86pub trait Component: Props + Send + Sync {
87    fn new(key: ComponentKey) -> Self where Self: Sized;
88
89    /// Indicates whether a Component instance carries a native backing node. If you return `true`
90    /// from this, the reconciler will opt-in to the native backing layer. Returns `false` by
91    /// default.
92    fn has_native_backing_node(&self) -> bool { false }
93
94    /// Returns a wrapped-per-platform pointer type that the backing framework tree can use.
95    fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> { None }
96
97    /// If you implement a Native-backed component, you'll need to implement this. Given a
98    /// `node`, you need to instruct the system how to append it to the tree at your point.
99    fn append_child_node(&self, _component: PlatformSpecificNodeType) {}
100
101    /// If you implement a Native-backed component, you'll need to implement this. Given a
102    /// `node`, you need to instruct the system how to replace it in the tree at your point.
103    fn replace_child_node(&self, _component: PlatformSpecificNodeType) {}
104
105    /// If you implement a Native-backed component, you'll need to implement this. Given a
106    /// `node`, you need to instruct the system how to remove it from the tree at your point.
107    fn remove_child_node(&self, _component: PlatformSpecificNodeType) {}
108
109    /// Given a configured 'appearance' and computed `layout`, this method should transform them 
110    /// into appropriate calls to the backing native node.
111    fn apply_styles(&self, _appearance: &Appearance, _layout: &Layout) {}
112
113    /// Invoked right before calling the render method, both on the initial mount and on subsequent updates.
114    /// It should return an object to update the state, or null to update nothing.
115    /// This method exists for rare use cases where the state depends on changes in props over time.
116    fn get_derived_state_from_props(&self) {}
117    
118    /// Invoked right before the most recently rendered output is committed to the backing layer tree.
119    /// It enables your component to capture some information from the tree (e.g. scroll position) before it's 
120    /// potentially changed. Any value returned by this lifecycle will be passed as a parameter 
121    /// to component_did_update().
122    /// 
123    /// This use case is not common, but it may occur in UIs like a chat thread that need to handle scroll 
124    /// position in a special way. A snapshot value (or None) should be returned.
125    fn get_snapshot_before_update(&self) {}
126
127    /// Invoked immediately after a component is mounted (inserted into the tree).
128    /// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
129    /// This method is also a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe 
130    /// in component_will_unmount().
131    fn component_did_mount(&mut self) {}
132
133    /// Invoked immediately after updating occurs. This method is not called for the initial render.
134    /// This is also a good place to do network requests as long as you compare the current props to previous props 
135    /// (e.g. a network request may not be necessary if the props have not changed).
136    fn component_did_update(&mut self) {}
137
138    /// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this 
139    /// method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that 
140    /// were created in component_did_mount().
141    /// 
142    /// You should not call set state in this method because the component will never be re-rendered. Once a 
143    /// component instance is unmounted, it will never be mounted again.
144    fn component_will_unmount(&mut self) {}
145
146    /// Invoked after an error has been thrown by a descendant component. Called during the "commit" phase, 
147    /// so side-effects are permitted. It should be used for things like logging errors (e.g,
148    /// Sentry).
149    fn component_did_catch(&mut self /* error: */) {}
150
151    /// Use this to let Alchemy know if a component’s output is not affected by the current change in state 
152    /// or props. The default behavior is to re-render on every state change, and in the vast majority of 
153    /// cases you should rely on the default behavior.
154    ///
155    /// This is invoked before rendering when new props or state are being received. Defaults to true. This 
156    /// method is not called for the initial render or when force_update() is used. This method only exists 
157    /// as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.
158    fn should_component_update(&self) -> bool { true }
159
160    /// The only required method for a `Component`. Should return a Result of RSX nodes, or an
161    /// Error (in very rare cases, such as trying to get a key from a strange HashMap or
162    /// something). 
163    ///
164    /// The render() function should be pure, meaning that it does not modify component state, it 
165    /// returns the same result each time it’s invoked, and it does not directly interact with the 
166    /// backing rendering framework.
167    ///
168    /// If you need to interact with the native layer, perform your work in component_did_mount() or the other 
169    /// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
170    ///
171    /// This method is not called if should_component_update() returns `false`.
172    fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> { Ok(RSX::None) }
173
174    /// This lifecycle is invoked after an error has been thrown by a descendant component. It receives 
175    /// the error that was thrown as a parameter and should return a value to update state.
176    ///
177    /// This is called during the "render" phase, so side-effects are not permitted. 
178    /// For those use cases, use component_did_catch() instead.
179    fn get_derived_state_from_error(&self, _error: ()) {}
180
181    /// By default, when your component’s state or props change, your component will re-render. 
182    /// If your `render()` method depends on some other data, you can tell Alchemy that the component 
183    /// needs re-rendering by calling `force_update()`.
184    ///
185    /// Calling `force_update()` will cause `render()` to be called on the component, skipping 
186    /// `should_component_update()`. This will trigger the normal lifecycle methods for child components, 
187    /// including the `should_component_update()` method of each child. Alchemy will still only update the 
188    /// backing widget tree if the markup changes.
189    ///
190    /// Normally, you should try to avoid all uses of `force_update()` and only read from `this.props` 
191    /// and `this.state` in `render()`.
192    fn force_update(&self) {}
193}