lumecs 0.0.1

Experimental GUI backed by Bevy ECS + usual suspects
use bevy_ecs::prelude::*;

use crate::keys::Key;
use crate::layout::RenderedLayout;
use crate::point::Point;
use crate::runtime::MousePosition;
use crate::widget::WidgetNode;

#[derive(Event, Debug)]
pub struct Click(pub MousePosition);

#[derive(Event, Debug)]
pub struct DoubleClick(pub MousePosition);

#[derive(Event, Debug)]
pub struct Scroll(pub MousePosition, pub Point);

#[derive(Event, Debug)]
pub struct KeyPress(pub Key);

#[derive(Event)]
pub struct EntityMessage<M>(pub M);

pub trait MessageTypeTrait: Send + Sync + 'static {
    fn position(&self) -> Option<MousePosition> {
        None
    }
}

impl MessageTypeTrait for Click {
    fn position(&self) -> Option<MousePosition> {
        Some(self.0)
    }
}

impl MessageTypeTrait for DoubleClick {
    fn position(&self) -> Option<MousePosition> {
        Some(self.0)
    }
}

impl MessageTypeTrait for Scroll {
    fn position(&self) -> Option<MousePosition> {
        Some(self.0)
    }
}

impl MessageTypeTrait for KeyPress {}

#[derive(Component)]
pub struct Message<E: MessageTypeTrait, M> {
    message_type: std::marker::PhantomData<E>,
    pub producer: Box<dyn Fn(&E) -> Option<M> + Send + Sync>,
}

impl<E: MessageTypeTrait, M: Clone + Send + Sync + 'static> Message<E, M> {
    pub fn new(content: M) -> Message<E, M> {
        Message {
            message_type: std::marker::PhantomData,
            producer: Box::new(move |_| Some(content.clone())),
        }
    }
}

impl<E: MessageTypeTrait, M: Send + Sync + 'static> Message<E, M> {
    pub fn from_event(content: impl Fn(&E) -> Option<M> + Send + Sync + 'static) -> Message<E, M> {
        Message {
            message_type: std::marker::PhantomData,
            producer: Box::new(content),
        }
    }
}

fn on_event<E: MessageTypeTrait + Event + std::fmt::Debug, M: Send + Sync + 'static>(
    event: On<E>,
    mut commands: Commands,
    q: Query<(Entity, &Message<E, M>, &RenderedLayout), With<WidgetNode>>,
) {
    for (_entity, message, layout) in &q {
        if let Some(mouse_position) = event.position()
            && !layout.0.contains_point(&mouse_position.0)
        {
            continue;
        }

        if let Some(content) = (message.producer)(&*event) {
            commands.trigger(EntityMessage(content));
        }
    }
}

pub fn message_setup<M: Clone + Send + Sync + 'static>(world: &mut World) {
    world.add_observer(on_event::<Click, M>);
    world.add_observer(on_event::<DoubleClick, M>);
    world.add_observer(on_event::<Scroll, M>);
    world.add_observer(on_event::<KeyPress, M>);
}