1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Traits that are used in Alchemy. Alchemy implements a React-based Component
//! lifecycle, coupled with a delegate pattern inspired by those found in AppKit/UIKit.

use std::any::Any;

use alchemy_styles::styles::{Appearance, Layout};

//use crate::RENDER_ENGINE;
use crate::error::Error;
use crate::reconciler::key::ComponentKey;
use crate::rsx::RSX;

/// A per-platform wrapped Pointer type, used for attaching views/widgets.
#[cfg(feature = "cocoa")]
pub type PlatformSpecificNodeType = objc_id::ShareId<objc::runtime::Object>;

/// A per-platform wrapped Pointer type, used for attaching views/widgets.
#[cfg(not(feature = "cocoa"))]
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.
/// Alchemy recognizes this and provides an `AppDelegate` that receives events at a system
/// level and allows the user to operate within the established framework per-system.
pub trait AppDelegate: Send + Sync {
    /// Fired when an Application is about to finish launching.
    fn will_finish_launching(&mut self) {}

    /// Fired when an Application has finished launching - this is a good place to, say, show your
    /// window.
    fn did_finish_launching(&mut self) {}

    /// Fired when an Application will become active.
    fn will_become_active(&mut self) {}

    /// Fired when an Application became active.
    fn did_become_active(&mut self) {}

    /// Fired when an Application will resign active. You can use this to, say, persist resources
    /// or state.
    fn will_resign_active(&mut self) {}

    /// Fired when an Application has resigned active.
    fn did_resign_active(&mut self) {} 

    /// Fired when an Application is going to terminate. You can use this to, say, instruct the
    /// system to "wait a minute, lemme finish".
    fn should_terminate(&self) -> bool { true }

    /// Fired when the Application has determined "no, you're done, stop the world".
    fn will_terminate(&mut self) {}

    /// A private trait method that you shouldn't call. This may change or disappear in later
    /// releases. Do not rely on this.
    fn _window_will_close(&self, _window_id: usize) {}
}

/// Each platform has their own `Window` API, which Alchemy attempts to pair down to one consistent
/// API. This also acts as the bootstrapping point for a `render` tree.
pub trait WindowDelegate: Send + Sync {
    /// Fired when this Window will close. You can use this to clean up or destroy resources,
    /// timers, and other things.
    fn will_close(&mut self) { }

    /// Called as the first step in the `render` tree. Every Window contains its own content view
    /// that is special, called the root. Widget trees are added to it as necessary, bootstrapped
    /// from here.
    fn render(&self) -> Result<RSX, Error> { Ok(RSX::None) }
}

pub trait Props {
    fn set_props(&mut self, new_props: &mut Any);
}

/// The `Component` lifecycle, mostly inspired from React, with a few extra methods for views that
/// need to have a backing native layer. A good breakdown of the React Component lifecycle can be 
/// found [in this tweet](https://twitter.com/dan_abramov/status/981712092611989509?lang=en).
///
/// Alchemy does not currently implement Hooks, and at the moment has no plans to do so (the API
/// doesn't feel comfortable in Rust, in any way I tried). If you think you have an interesting
/// proposal for this, feel free to open an issue!
pub trait Component: Props + Send + Sync {
    fn new(key: ComponentKey) -> Self where Self: Sized;

    /// Indicates whether a Component instance carries a native backing node. If you return `true`
    /// from this, the reconciler will opt-in to the native backing layer. Returns `false` by
    /// default.
    fn has_native_backing_node(&self) -> bool { false }

    /// Returns a wrapped-per-platform pointer type that the backing framework tree can use.
    fn borrow_native_backing_node(&self) -> Option<PlatformSpecificNodeType> { None }

    /// If you implement a Native-backed component, you'll need to implement this. Given a
    /// `node`, you need to instruct the system how to append it to the tree at your point.
    fn append_child_node(&self, _component: PlatformSpecificNodeType) {}

    /// If you implement a Native-backed component, you'll need to implement this. Given a
    /// `node`, you need to instruct the system how to replace it in the tree at your point.
    fn replace_child_node(&self, _component: PlatformSpecificNodeType) {}

    /// If you implement a Native-backed component, you'll need to implement this. Given a
    /// `node`, you need to instruct the system how to remove it from the tree at your point.
    fn remove_child_node(&self, _component: PlatformSpecificNodeType) {}

    /// Given a configured 'appearance' and computed `layout`, this method should transform them 
    /// into appropriate calls to the backing native node.
    fn apply_styles(&self, _appearance: &Appearance, _layout: &Layout) {}

    /// Invoked right before calling the render method, both on the initial mount and on subsequent updates.
    /// It should return an object to update the state, or null to update nothing.
    /// This method exists for rare use cases where the state depends on changes in props over time.
    fn get_derived_state_from_props(&self) {}
    
    /// Invoked right before the most recently rendered output is committed to the backing layer tree.
    /// It enables your component to capture some information from the tree (e.g. scroll position) before it's 
    /// potentially changed. Any value returned by this lifecycle will be passed as a parameter 
    /// to component_did_update().
    /// 
    /// This use case is not common, but it may occur in UIs like a chat thread that need to handle scroll 
    /// position in a special way. A snapshot value (or None) should be returned.
    fn get_snapshot_before_update(&self) {}

    /// Invoked immediately after a component is mounted (inserted into the tree).
    /// If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
    /// This method is also a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe 
    /// in component_will_unmount().
    fn component_did_mount(&mut self) {}

    /// Invoked immediately after updating occurs. This method is not called for the initial render.
    /// This is also a good place to do network requests as long as you compare the current props to previous props 
    /// (e.g. a network request may not be necessary if the props have not changed).
    fn component_did_update(&mut self) {}

    /// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this 
    /// method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that 
    /// were created in component_did_mount().
    /// 
    /// You should not call set state in this method because the component will never be re-rendered. Once a 
    /// component instance is unmounted, it will never be mounted again.
    fn component_will_unmount(&mut self) {}

    /// Invoked after an error has been thrown by a descendant component. Called during the "commit" phase, 
    /// so side-effects are permitted. It should be used for things like logging errors (e.g,
    /// Sentry).
    fn component_did_catch(&mut self /* error: */) {}

    /// Use this to let Alchemy know if a component’s output is not affected by the current change in state 
    /// or props. The default behavior is to re-render on every state change, and in the vast majority of 
    /// cases you should rely on the default behavior.
    ///
    /// This is invoked before rendering when new props or state are being received. Defaults to true. This 
    /// method is not called for the initial render or when force_update() is used. This method only exists 
    /// as a performance optimization. Do not rely on it to “prevent” a rendering, as this can lead to bugs.
    fn should_component_update(&self) -> bool { true }

    /// The only required method for a `Component`. Should return a Result of RSX nodes, or an
    /// Error (in very rare cases, such as trying to get a key from a strange HashMap or
    /// something). 
    ///
    /// The render() function should be pure, meaning that it does not modify component state, it 
    /// returns the same result each time it’s invoked, and it does not directly interact with the 
    /// backing rendering framework.
    ///
    /// If you need to interact with the native layer, perform your work in component_did_mount() or the other 
    /// lifecycle methods instead. Keeping `render()` pure makes components easier to think about.
    ///
    /// This method is not called if should_component_update() returns `false`.
    fn render(&self, children: Vec<RSX>) -> Result<RSX, Error> { Ok(RSX::None) }

    /// This lifecycle is invoked after an error has been thrown by a descendant component. It receives 
    /// the error that was thrown as a parameter and should return a value to update state.
    ///
    /// This is called during the "render" phase, so side-effects are not permitted. 
    /// For those use cases, use component_did_catch() instead.
    fn get_derived_state_from_error(&self, _error: ()) {}

    /// By default, when your component’s state or props change, your component will re-render. 
    /// If your `render()` method depends on some other data, you can tell Alchemy that the component 
    /// needs re-rendering by calling `force_update()`.
    ///
    /// Calling `force_update()` will cause `render()` to be called on the component, skipping 
    /// `should_component_update()`. This will trigger the normal lifecycle methods for child components, 
    /// including the `should_component_update()` method of each child. Alchemy will still only update the 
    /// backing widget tree if the markup changes.
    ///
    /// Normally, you should try to avoid all uses of `force_update()` and only read from `this.props` 
    /// and `this.state` in `render()`.
    fn force_update(&self) {}
}