ori-core 0.1.0-alpha.1

Core library for Ori, a declarative UI framework for Rust.
Documentation
use glam::Vec2;
use ori_macro::Build;

use crate::{
    Axis, BoxConstraints, Children, Context, DrawContext, Event, EventContext, EventSignal,
    FlexLayout, LayoutContext, Node, Parent, PointerEvent, Scope, Sendable, Style, View,
};

#[derive(Default, Build)]
pub struct Div {
    #[event]
    pub on_event: Option<EventSignal<Event>>,
    #[event]
    pub on_press: Option<EventSignal<PointerEvent>>,
    #[event]
    pub on_release: Option<EventSignal<PointerEvent>>,
    pub children: Children,
}

impl Div {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn child(mut self, child: impl View) -> Self {
        self.add_child(child);
        self
    }

    pub fn on_event<'a>(
        mut self,
        cx: Scope<'a>,
        callback: impl FnMut(&Event) + Sendable + 'a,
    ) -> Self {
        self.on_event
            .get_or_insert_with(|| EventSignal::new())
            .subscribe(cx, callback);

        self
    }

    pub fn on_press<'a>(
        mut self,
        cx: Scope<'a>,
        callback: impl FnMut(&PointerEvent) + Sendable + 'a,
    ) -> Self {
        self.on_press
            .get_or_insert_with(|| EventSignal::new())
            .subscribe(cx, callback);

        self
    }

    pub fn on_release<'a>(
        mut self,
        cx: Scope<'a>,
        callback: impl FnMut(&PointerEvent) + Sendable + 'a,
    ) -> Self {
        self.on_release
            .get_or_insert_with(|| EventSignal::new())
            .subscribe(cx, callback);

        self
    }

    fn handle_pointer_event(&self, cx: &mut EventContext, event: &PointerEvent) -> bool {
        if event.is_press() && cx.hovered() {
            if let Some(on_press) = &self.on_press {
                cx.activate();
                on_press.emit(event.clone());
            }
        } else if event.is_release() && cx.state.active {
            cx.deactivate();

            if let Some(on_release) = &self.on_release {
                on_release.emit(event.clone());
            }
        } else {
            return false;
        }

        true
    }
}

impl Parent for Div {
    fn add_child(&mut self, child: impl View) {
        self.children.push(Node::new(child));
    }
}

impl View for Div {
    type State = ();

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

    fn style(&self) -> Style {
        Style::new("div")
    }

    fn event(&self, _state: &mut Self::State, cx: &mut EventContext, event: &Event) {
        for child in &self.children {
            child.event(cx, event);
        }

        if let Some(on_event) = &self.on_event {
            on_event.emit(event.clone());
        }

        if event.is_handled() {
            return;
        }

        if let Some(pointer_event) = event.get::<PointerEvent>() {
            if self.handle_pointer_event(cx, pointer_event) {
                event.handle();
            }
        }
    }

    fn layout(&self, _state: &mut Self::State, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2 {
        let bc = cx.style_constraints(bc);

        let axis = cx.style::<Axis>("direction");
        let padding = cx.style_range("padding", 0.0..bc.max.min_element() / 2.0);
        let gap = cx.style_range("gap", 0.0..axis.major(bc.max));

        let justify_content = cx.style("justify-content");
        let align_items = cx.style("align-items");

        let flex = FlexLayout {
            axis,
            justify_content,
            align_items,
            gap,
            offset: Vec2::splat(padding),
        };

        let content_bc = bc.shrink(Vec2::splat(padding * 2.0));
        let size = self.children.flex_layout(cx, content_bc, flex);

        size + Vec2::splat(padding * 2.0)
    }

    fn draw(&self, _state: &mut Self::State, cx: &mut DrawContext) {
        cx.draw_quad();

        cx.draw_layer(|cx| {
            for child in &self.children {
                child.draw(cx);
            }
        });
    }
}