haalka 0.7.1

ergonomic reactive Bevy UI library powered by FRP signals
Documentation
use bevy_ecs::prelude::*;
use bevy_picking::prelude::*;
use bevy_ui::prelude::*;
use jonmo::signal::{Signal, SignalExt};

use super::{
    align::{Alignable, LayoutDirection},
    element::{BuilderPassThrough, BuilderWrapper, IntoOptionElement, Nameable, UiRootable},
    global_event_aware::GlobalEventAware,
    mouse_wheel_scrollable::MouseWheelScrollable,
    pointer_event_aware::{Cursorable, PointerEventAware},
    viewport_mutable::ViewportMutable,
};
use crate::{clone_semantics_doc, impl_element_clone};

// TODO: add the extra flag machinery that MoonZoon has to ensure that El's have exactly one child
// (or child signal)
/// Singleton [`Element`](super::element::Element) with exactly one child (not yet enforced).
///
/// While multiple children can still be declared with repeated calls to [`.child`](`El::child`) or
/// [`.child_signal`](`El::child_signal`), their relative alignment was arbitrarily chosen to match
/// [MoonZoon's implementation](https://github.com/MoonZoon/MoonZoon/blob/fc73b0d90bf39be72e70fdcab4f319ea5b8e6cfc/crates/zoon/src/element/el.rs#L41-L69) and should not be relied on.
///
/// Port of [MoonZoon](https://github.com/MoonZoon/MoonZoon)'s [`El`](https://github.com/MoonZoon/MoonZoon/blob/19c6cf6b4d07cd27bee7758977ef1ea4d5b9933d/crates/zoon/src/element/el.rs).
#[doc = clone_semantics_doc!("El")]
#[derive(Default)]
pub struct El<NodeType> {
    builder: jonmo::Builder,
    _node_type: std::marker::PhantomData<NodeType>,
}

impl_element_clone!("El", El<NodeType>, my_el, ".name(label)");

impl<NodeType: Bundle> From<jonmo::Builder> for El<NodeType> {
    fn from(builder: jonmo::Builder) -> Self {
        Self {
            builder: builder
                .with_component::<Node>(|mut node| {
                    node.display = Display::Flex;
                    node.flex_direction = FlexDirection::Column;
                })
                .insert((LayoutDirection::Column, Pickable::IGNORE)),
            _node_type: std::marker::PhantomData,
        }
    }
}

impl<NodeType: Bundle + Default> El<NodeType> {
    /// Construct a new [`El`] from a [`Bundle`] with a [`Default`] implementation.
    ///
    /// # Notes
    /// [`Bundle`]s without the [`Node`] component will not behave as expected.
    pub fn new() -> Self {
        Self::from(jonmo::Builder::from(NodeType::default()))
    }
}

impl<NodeType> BuilderWrapper for El<NodeType> {
    fn builder_mut(&mut self) -> &mut jonmo::Builder {
        &mut self.builder
    }
}

impl<NodeType: Bundle> Alignable for El<NodeType> {}
impl<NodeType: Bundle> Cursorable for El<NodeType> {}
impl<NodeType: Bundle> GlobalEventAware for El<NodeType> {}
impl<NodeType: Bundle> Nameable for El<NodeType> {}
impl<NodeType: Bundle> PointerEventAware for El<NodeType> {}
impl<NodeType: Bundle> MouseWheelScrollable for El<NodeType> {}
impl<NodeType: Bundle> UiRootable for El<NodeType> {}
impl<NodeType: Bundle> ViewportMutable for El<NodeType> {}

impl<NodeType: Bundle> BuilderPassThrough for El<NodeType> {}

impl<NodeType: Bundle> El<NodeType> {
    /// Declare a static child.
    pub fn child<IOE: IntoOptionElement>(self, child_option: IOE) -> Self {
        if let Some(child) = child_option.into_option_element() {
            self.with_builder(|builder| builder.child(child.into_builder()))
        } else {
            self
        }
    }

    /// Declare a reactive child. When the [`Signal`] outputs [`None`], the child is removed.
    pub fn child_signal<IOE, S>(self, child_option_signal_option: impl Into<Option<S>>) -> Self
    where
        IOE: IntoOptionElement + 'static,
        S: Signal<Item = IOE> + Send + Sync + 'static,
    {
        if let Some(child_option_signal) = child_option_signal_option.into() {
            self.with_builder(|builder| {
                builder.child_signal(
                    child_option_signal.map_in(move |child_option: IOE| {
                        child_option.into_option_element().map(|el| el.into_builder())
                    }),
                )
            })
        } else {
            self
        }
    }
}