ori-core 0.1.0-alpha.1

Core library for Ori, a declarative UI framework for Rust.
Documentation
use std::any::{self, Any};

use glam::Vec2;

use crate::{
    BoxConstraints, Callback, DrawContext, Event, EventContext, LayoutContext, RequestRedrawEvent,
    SendSync, SharedSignal, Style,
};

/// A [`View`] is a component that can be rendered to the screen.
#[allow(unused_variables)]
pub trait View: SendSync + 'static {
    /// The state of the view.
    type State: SendSync + 'static;

    /// Builds the state of the view.
    fn build(&self) -> Self::State;

    /// Returns the style of the view.
    fn style(&self) -> Style {
        Style::default()
    }

    /// Handles an event.
    fn event(&self, state: &mut Self::State, cx: &mut EventContext, event: &Event) {}

    /// Handle layout and returns the size of the view.
    ///
    /// This method should return a size that fits the [`BoxConstraints`].
    ///
    /// The default implementation returns the minimum size.
    fn layout(&self, state: &mut Self::State, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2 {
        bc.min
    }

    /// Draws the view.
    fn draw(&self, state: &mut Self::State, cx: &mut DrawContext) {}
}

/// A [`View`] that with an unknown state.
///
/// This is used to store a [`View`] in a [`Node`](crate::Node).
pub trait AnyView: SendSync {
    fn style(&self) -> Style;

    fn event(&self, state: &mut dyn Any, cx: &mut EventContext, event: &Event);

    fn layout(&self, state: &mut dyn Any, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2;

    fn draw(&self, state: &mut dyn Any, cx: &mut DrawContext);
}

impl<T: View> AnyView for T {
    fn style(&self) -> Style {
        self.style()
    }

    fn event(&self, state: &mut dyn Any, cx: &mut EventContext, event: &Event) {
        if let Some(state) = state.downcast_mut::<T::State>() {
            self.event(state, cx, event);
        } else {
            tracing::warn!("invalid state type on {}", any::type_name::<T>());
        }
    }

    fn layout(&self, state: &mut dyn Any, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2 {
        if let Some(state) = state.downcast_mut::<T::State>() {
            self.layout(state, cx, bc)
        } else {
            tracing::warn!("invalid state type on {}", any::type_name::<T>());
            bc.min
        }
    }

    fn draw(&self, state: &mut dyn Any, cx: &mut DrawContext) {
        if let Some(state) = state.downcast_mut::<T::State>() {
            self.draw(state, cx);
        } else {
            tracing::warn!("invalid state type on {}", any::type_name::<T>());
        }
    }
}

/// When a view is wrapped in a signal, the view will be redrawn when the signal
/// changes.
impl<V: View + SendSync> View for SharedSignal<V> {
    type State = (Callback<'static, ()>, V::State);

    fn build(&self) -> Self::State {
        (Callback::default(), self.get_untracked().build())
    }

    fn style(&self) -> Style {
        self.get_untracked().style()
    }

    fn event(&self, (_, state): &mut Self::State, cx: &mut EventContext, event: &Event) {
        self.get().event(state, cx, event);
    }

    fn layout(
        &self,
        (_, state): &mut Self::State,
        cx: &mut LayoutContext,
        bc: BoxConstraints,
    ) -> Vec2 {
        self.get().layout(state, cx, bc)
    }

    fn draw(&self, (callback, state): &mut Self::State, cx: &mut DrawContext) {
        // redraw when the signal changes
        let event_sink = cx.event_sink.clone();
        *callback = Callback::new(move |&()| event_sink.send(RequestRedrawEvent));

        self.emitter().subscribe_weak(callback.downgrade());
        self.get().draw(state, cx);
    }
}

impl View for () {
    type State = ();

    fn build(&self) -> Self::State {}

    fn event(&self, _state: &mut Self::State, _cx: &mut EventContext, _event: &Event) {}

    fn layout(
        &self,
        _state: &mut Self::State,
        _cx: &mut LayoutContext,
        bc: BoxConstraints,
    ) -> Vec2 {
        bc.min
    }

    fn draw(&self, _state: &mut Self::State, _cx: &mut DrawContext) {}
}