Skip to content
Snippets Groups Projects
widget.rs 6.24 KiB
Newer Older
Louis's avatar
Louis committed
use bevy::{
    ecs::system::SystemParam,
    prelude::{Changed, Component, Entity, In, Query, Res, With},
};

use crate::{
    children::KChildren,
    context::{Mounted, WidgetName},
    prelude::KayakWidgetContext,
    styles::{ComputedStyles, KStyle},
};

pub trait Widget: Send + Sync {
    fn get_name(&self) -> WidgetName {
        WidgetName(std::any::type_name::<Self>().into())
    }
}

#[derive(Component, Default, PartialEq, Eq, Clone)]
pub struct EmptyState;

pub fn widget_update<Props: PartialEq + Component + Clone, State: PartialEq + Component + Clone>(
    In((entity, previous_entity)): In<(Entity, Entity)>,
    widget_context: Res<KayakWidgetContext>,
    widget_param: WidgetParam<Props, State>,
) -> bool {
    widget_param.has_changed(&widget_context, entity, previous_entity)
}

pub fn widget_update_with_context<
    Props: PartialEq + Component + Clone,
    State: PartialEq + Component + Clone,
    Context: PartialEq + Component + Clone + Default,
>(
    In((entity, previous_entity)): In<(Entity, Entity)>,
    widget_context: Res<KayakWidgetContext>,
    widget_param: WidgetParam<Props, State>,
    context_query: Query<Entity, Changed<Context>>,
) -> bool {
    // Uses bevy state changes to see if context has changed.
    if let Some(context_entity) = widget_context.get_context_entity::<Context>(entity) {
        if context_query.contains(context_entity) {
            log::trace!(
                "Entity context: {} has changed! {}-{}",
                std::any::type_name::<Context>(),
                widget_param.widget_names.get(entity).unwrap().0,
                entity.index()
            );
            return true;
        }
    }

    widget_param.has_changed(&widget_context, entity, previous_entity)
}

#[derive(SystemParam)]
pub struct WidgetParam<'w, 's, Props: PartialEq + Component, State: PartialEq + Component> {
    pub props_query: Query<'w, 's, &'static Props>,
    pub old_props_query: Query<'w, 's, &'static Props>,
    pub mounted_query: Query<'w, 's, Entity, With<Mounted>>,
    pub style_query: Query<'w, 's, &'static KStyle>,
    pub computed_style_query: Query<'w, 's, &'static ComputedStyles>,
    pub children_query: Query<'w, 's, &'static KChildren>,
    pub state_query: Query<'w, 's, &'static State>,
    pub widget_names: Query<'w, 's, &'static WidgetName>,
}

impl<'w, 's, Props: PartialEq + Component, State: PartialEq + Component>
    WidgetParam<'w, 's, Props, State>
{
    pub fn has_changed(
        &self,
        widget_context: &KayakWidgetContext,
        current_entity: Entity,
        previous_entity: Entity,
    ) -> bool {
        if !self.mounted_query.is_empty() {
            log::trace!(
                "Entity was mounted! {}-{}",
                self.widget_names.get(current_entity).unwrap().0,
                current_entity.index()
            );
            return true;
        }

        // Compare the widget names. Sometimes our widget entity changes with the same ID but differing components.
        if let (Ok(name), Ok(old_name)) = (
            self.widget_names.get(current_entity),
            self.widget_names.get(previous_entity),
        ) {
            if name != old_name {
                return true;
            }
        }

        // Compare styles
        if let (Ok(style), Ok(old_style)) = (
            self.style_query.get(current_entity),
            self.style_query.get(previous_entity),
        ) {
            if style != old_style {
                log::trace!(
                    "Entity styles have changed! {}-{}",
                    self.widget_names.get(current_entity).unwrap().0,
                    current_entity.index()
                );
                return true;
            }
        }

        // Compare computed styles
        if let (Ok(style), Ok(old_style)) = (
            self.computed_style_query.get(current_entity),
            self.computed_style_query.get(previous_entity),
        ) {
            if style != old_style {
                log::trace!(
                    "Entity computed styles have changed! {}-{}",
                    self.widget_names.get(current_entity).unwrap().0,
                    current_entity.index()
                );
                return true;
            }
        }

        // Compare children
        // If children don't exist ignore as mount will add them!
        if let (Ok(children), Ok(old_children)) = (
            self.children_query.get(current_entity),
            self.children_query.get(previous_entity),
        ) {
            if children != old_children {
                log::trace!(
                    "Entity children have changed! {}-{}",
                    self.widget_names.get(current_entity).unwrap().0,
                    current_entity.index()
                );
                return true;
            }
        }

        // Check props
        if let (Ok(props), Ok(previous_props)) = (
            self.props_query.get(current_entity),
            self.old_props_query.get(previous_entity),
        ) {
            if previous_props != props {
                log::trace!(
                    "Entity props have changed! {}-{}",
                    self.widget_names.get(current_entity).unwrap().0,
                    current_entity.index()
                );
                return true;
            }
        }

        // Check state
        let previous_state_entity = widget_context.get_state(previous_entity);
        let current_state_entity = widget_context.get_state(current_entity);

        // Check if state was nothing but now is something
        if current_state_entity.is_some() && previous_state_entity.is_none() {
            return true;
        }

        // Check state
        if let (Some(current_state_entity), Some(previous_state_entity)) =
            (current_state_entity, previous_state_entity)
        {
            if let (Ok(state), Ok(previous_state)) = (
                self.state_query.get(current_state_entity),
                self.state_query.get(previous_state_entity),
            ) {
                if previous_state != state {
                    return true;
                }
            }
        }

        false
    }
}