alchemy-lifecycle 0.1.0

A crate containing traits used in Alchemy, the Rust cross-platform GUI framework.
Documentation
//! 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) {}
}