Skip to content
Snippets Groups Projects
context.rs 40.9 KiB
Newer Older
StarToaster's avatar
StarToaster committed
use std::sync::{Arc, RwLock};

use bevy::{
    ecs::{event::ManualEventReader, system::CommandQueue},
    prelude::*,
    utils::{HashMap, HashSet},
NiseVoid's avatar
NiseVoid committed
    window::PrimaryWindow,
StarToaster's avatar
StarToaster committed
};
use morphorm::Hierarchy;

use crate::{
    calculate_nodes::{calculate_layout, calculate_nodes},
    children::KChildren,
    clone_component::{clone_state, clone_system, EntityCloneSystems, PreviousWidget},
StarToaster's avatar
StarToaster committed
    context_entities::ContextEntities,
StarToaster's avatar
StarToaster committed
    event_dispatcher::EventDispatcher,
    focus_tree::FocusTree,
StarToaster's avatar
StarToaster committed
    layout::{LayoutCache, Rect},
    layout_dispatcher::LayoutEventDispatcher,
NiseVoid's avatar
NiseVoid committed
    node::{DirtyNode, Node, WrappedIndex},
    prelude::KayakWidgetContext,
StarToaster's avatar
StarToaster committed
    render_primitive::RenderPrimitive,
        ComputedStyles, Corner, Edge, KCursorIcon, KPositionType, KStyle, LayoutType,
        RenderCommand, StyleProp, Units,
StarToaster's avatar
StarToaster committed
    tree::{Change, Tree},
    widget_state::WidgetState,
    Focusable, KayakUIPlugin, WindowSize,
StarToaster's avatar
StarToaster committed
};

/// A tag component representing when a widget has been mounted(added to the tree).
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
StarToaster's avatar
StarToaster committed
pub struct Mounted;

const UPDATE_DEPTH: u32 = 0;

type WidgetSystems = HashMap<
    String,
    (
        Box<dyn System<In = (KayakWidgetContext, Entity, Entity), Out = bool>>,
        Box<dyn System<In = (KayakWidgetContext, Entity), Out = bool>>,
///
/// Kayak Context
///
/// This bevy resource keeps track of all of the necessary UI state. This includes the widgets, tree, input, layout, and other important data.
/// The Context provides some connivent helper functions for creating and using widgets, state, and context.
///
/// Usage:
/// ```rust
/// use bevy::prelude::*;
/// use kayak_ui::prelude::{widgets::*, *};
///
/// // Bevy setup function
/// fn setup(mut commands: Commands) {
///     let mut widget_context = Context::new();
///     let app_entity = commands.spawn(KayakAppBundle {
///         ..Default::default()
///     }).id();
///     // Stores the kayak app widget in the widget context's tree.
///     widget_context.add_widget(None, app_entity);
///     commands.spawn((widget_context, EventDispatcher::default()));
/// }
///
/// fn main() {
///     App::new()
///     .add_plugins(DefaultPlugins)
///     .add_plugin(ContextPlugin)
///     .add_plugin(KayakWidgets)
///     .add_startup_system(setup);
/// }
/// ```
pub struct KayakRootContext {
StarToaster's avatar
StarToaster committed
    pub tree: Arc<RwLock<Tree>>,
    pub(crate) layout_cache: Arc<RwLock<LayoutCache>>,
    pub(crate) focus_tree: Arc<RwLock<FocusTree>>,
    systems: WidgetSystems,
StarToaster's avatar
StarToaster committed
    pub(crate) current_z: f32,
    pub(crate) context_entities: ContextEntities,
    pub(crate) current_cursor: CursorIcon,
    pub(crate) clone_systems: Arc<RwLock<EntityCloneSystems>>,
    pub(crate) cloned_widget_entities: Arc<RwLock<HashMap<Entity, Entity>>>,
    pub(crate) widget_state: WidgetState,
    pub(crate) order_tree: Arc<RwLock<Tree>>,
    pub(crate) index: Arc<RwLock<HashMap<Entity, usize>>>,
    pub(crate) uninitilized_systems: HashSet<String>,
    pub camera_entity: Entity,
impl Default for KayakRootContext {
    fn default() -> Self {
        Self::new(Entity::from_raw(0))
impl KayakRootContext {
    /// Creates a new widget context.
    pub fn new(camera_entity: Entity) -> Self {
StarToaster's avatar
StarToaster committed
        Self {
            tree: Arc::new(RwLock::new(Tree::default())),
            layout_cache: Arc::new(RwLock::new(LayoutCache::default())),
            focus_tree: Default::default(),
            systems: HashMap::default(),
            current_z: 0.0,
            context_entities: ContextEntities::new(),
            current_cursor: CursorIcon::Default,
            clone_systems: Default::default(),
            cloned_widget_entities: Default::default(),
            widget_state: Default::default(),
            index: Default::default(),
            order_tree: Default::default(),
            uninitilized_systems: Default::default(),
    /// Adds a kayak plugin and runs the build function on the context.
    pub fn add_plugin(&mut self, plugin: impl KayakUIPlugin) {
        plugin.build(self)
    }

    /// Retreives the current entity that has focus or None if nothing is focused.
    pub fn get_current_focus(&self) -> Option<Entity> {
        if let Ok(tree) = self.focus_tree.try_read() {
            return tree.current().and_then(|a| Some(a.0));
        }
        None
    }

    /// Get's the layout for th given widget index.
StarToaster's avatar
StarToaster committed
    pub(crate) fn get_layout(&self, id: &WrappedIndex) -> Option<Rect> {
        if let Ok(cache) = self.layout_cache.try_read() {
            cache.rect.get(id).cloned()
        } else {
            None
        }
    }

    pub(crate) fn get_geometry_changed(&self, id: &WrappedIndex) -> bool {
        if let Ok(cache) = self.layout_cache.try_read() {
            if let Some(geometry_changed) = cache.geometry_changed.get(id) {
                !geometry_changed.is_empty()
            } else {
                false
            }
        } else {
            false
        }
    }

    /// Adds a new set of systems for a widget type.
    /// Update systems are ran every frame and return true or false depending on if the widget has "changed".
    /// Render systems are ran only if the widget has changed and are meant to re-render children and handle
    /// tree changes.
    pub fn add_widget_system<Params, Params2>(
StarToaster's avatar
StarToaster committed
        &mut self,
        type_name: impl Into<String>,
        update: impl IntoSystem<(KayakWidgetContext, Entity, Entity), bool, Params>,
        render: impl IntoSystem<(KayakWidgetContext, Entity), bool, Params2>,
StarToaster's avatar
StarToaster committed
    ) {
        let type_name = type_name.into();
        let update_system = Box::new(IntoSystem::into_system(update));
        let render_system = Box::new(IntoSystem::into_system(render));
        self.systems
            .insert(type_name.clone(), (update_system, render_system));
        self.uninitilized_systems.insert(type_name);
    /// Let's the widget context know what data types are used for a given widget.
    /// This is useful as it allows Kayak to keep track of previous values for diffing.
    /// When the default update widget system is called it checks the props and state of
    /// the current widget with it's values from the previous frame.
    /// This allows Kayak to diff data. Alternatively a custom widget update system can
    /// be used and listen for events, resources, or any other bevy ECS data.
    pub fn add_widget_data<
        Props: Component + Clone + PartialEq,
        State: Component + Clone + PartialEq,
    >(
        &mut self,
    ) {
        if let Ok(mut clone_systems) = self.clone_systems.try_write() {
            clone_systems
                .0
                .push((clone_system::<Props>, clone_state::<State>));
        }
    /// Adds a widget to the tree.
    /// Widgets are created using entities and components.
    /// Once created their id's need to be added to the widget tree
    /// so that the correct ordering is preserved for updates and rendering.
StarToaster's avatar
StarToaster committed
    pub fn add_widget(&mut self, parent: Option<Entity>, entity: Entity) {
        if let Ok(mut tree) = self.tree.write() {
            tree.add(WrappedIndex(entity), parent.map(WrappedIndex));
StarToaster's avatar
StarToaster committed
            if let Ok(mut cache) = self.layout_cache.try_write() {
                cache.add(WrappedIndex(entity));
            }
        }
    }

    /// Creates a new context using the context entity for the given type_id + parent id.
    /// Context can be considered state that changes across multiple components.
    /// Alternatively you can use bevy's resources.
StarToaster's avatar
StarToaster committed
    pub fn set_context_entity<T: Default + 'static>(
        &self,
        parent_id: Option<Entity>,
        context_entity: Entity,
    ) {
        if let Some(parent_id) = parent_id {
            self.context_entities
                .add_context_entity::<T>(parent_id, context_entity);
        }
    }

    /// Returns a new/existing widget entity.
    /// Because a re-render can potentially spawn new entities it's advised to use this
    /// to avoid creating a new entity.
    ///
    /// Usage:
    /// fn setup() {
    ///     let mut widget_context = WidgetContext::new();
    ///     // Root tree node, no parent node.
    ///     let root_entity =  widget_context.spawn_widget(&mut commands, None);
    ///     commands.entity(root_entity).insert(KayakAppBundle::default());
    ///     widget_context.add_widget(None, root_entity);
    /// }
    ///```
    pub fn spawn_widget(&self, commands: &mut Commands, parent_id: Option<Entity>) -> Entity {
        let mut entity = None;
        if let Some(parent_entity) = parent_id {
            let children = self.get_children_ordered(parent_entity);
            let child = children.get(self.get_and_add_index(parent_entity)).cloned();
            if let Some(child) = child {
StarToaster's avatar
StarToaster committed
                log::trace!("Reusing widget entity {:?}!", child.index());
                entity = Some(commands.get_or_spawn(child).id());

        // If we have no entity spawn it!
        if entity.is_none() {
            entity = Some(commands.spawn_empty().id());
            log::trace!(
                "Spawning new widget with entity {:?}!",
StarToaster's avatar
StarToaster committed
                entity.unwrap().index()
            );
            // We need to add it to the ordered tree
            if let Ok(mut tree) = self.order_tree.try_write() {
                tree.add(WrappedIndex(entity.unwrap()), parent_id.map(WrappedIndex))
            }
        }
        entity.unwrap()
    }

    fn get_children_ordered(&self, entity: Entity) -> Vec<Entity> {
        let mut children = vec![];
        if let Ok(tree) = self.order_tree.read() {
            let iterator = tree.child_iter(WrappedIndex(entity));

            children = iterator.map(|index| index.0).collect::<Vec<_>>();
        }

        children
    fn get_and_add_index(&self, parent: Entity) -> usize {
        if let Ok(mut hash_map) = self.index.try_write() {
            if hash_map.contains_key(&parent) {
                let index = hash_map.get_mut(&parent).unwrap();
                *index += 1;
                return current_index;
            } else {
                hash_map.insert(parent, 1);
                return 0;
            }
        }

        0
    }

    /// Generates a flat list of widget render commands sorted by tree order.
    /// There is no need to call this unless you are implementing your own custom renderer.
StarToaster's avatar
StarToaster committed
    pub fn build_render_primitives(
        &self,
        nodes: &Query<&crate::node::Node>,
        widget_names: &Query<&WidgetName>,
StarToaster's avatar
StarToaster committed
    ) -> Vec<RenderPrimitive> {
        let node_tree = self.tree.try_read();
        if node_tree.is_err() {
            return vec![];
        }

        let node_tree = node_tree.unwrap();

        if node_tree.root_node.is_none() {
            return vec![];
        }

        let render_primitives = if let Ok(mut layout_cache) = self.layout_cache.try_write() {
            recurse_node_tree_to_build_primitives(
                &mut layout_cache,
                nodes,
                widget_names,
                node_tree.root_node.unwrap(),
                0.0,
                RenderPrimitive::Empty,
            )
        } else {
            vec![]
        };
        // render_primitives.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());

        // render_primitives.iter().enumerate().for_each(|(index, p)| {
        //     log::info!("Name: {:?}, Z: {:?}", p.to_string(), index);
        // });

        // dbg!(&render_primitives
        //     .iter()
        //     .map(|a| (a.1.to_string(), a.0))
        //     .collect::<Vec<_>>());

        render_primitives.into_iter().collect()
StarToaster's avatar
StarToaster committed
    }
}

fn recurse_node_tree_to_build_primitives(
    node_tree: &Tree,
    layout_cache: &mut LayoutCache,
StarToaster's avatar
StarToaster committed
    nodes: &Query<&crate::node::Node>,
    widget_names: &Query<&WidgetName>,
StarToaster's avatar
StarToaster committed
    current_node: WrappedIndex,
    main_z_index: f32,
StarToaster's avatar
StarToaster committed
    mut prev_clip: RenderPrimitive,
) -> Vec<RenderPrimitive> {
    let mut render_primitives = Vec::new();
    if let Ok(node) = nodes.get(current_node.0) {
        let mut render_primitive = node.primitive.clone();
John Mitchell's avatar
John Mitchell committed
        let layout = if let Some(layout) = layout_cache.rect.get_mut(&current_node) {
            log::trace!(
                "z_index is {} and node.z is {} for: {}-{}",
                new_z_index,
                node.z,
                widget_names.get(current_node.0).unwrap().0,
                current_node.0.index(),
            );

            new_z_index += if node.z <= 0.0 { 0.0 } else { node.z };

            layout.z_index = new_z_index;
            render_primitive.set_layout(*layout);
            *layout
        } else {
            log::warn!(
                "No layout for node: {}-{}",
                widget_names.get(current_node.0).unwrap().0,
StarToaster's avatar
StarToaster committed
                current_node.0.index()
        match &render_primitive {
            RenderPrimitive::Text {
                content, layout, ..
            } => {
                    "Text node: {}-{} is equal to: {}, {:?}",
                    widget_names.get(current_node.0).unwrap().0,
StarToaster's avatar
StarToaster committed
                    current_node.0.index(),
                    layout,
                );
            }
            RenderPrimitive::Clip { layout } => {
                log::trace!(
                    "Clip node: {}-{} is equal to: {:?}",
                    widget_names.get(current_node.0).unwrap().0,
                    current_node.0.index(),
                    layout,
                );
            }
            RenderPrimitive::Empty => {
                log::trace!(
John Mitchell's avatar
John Mitchell committed
                    "Empty node: {}-{} is equal to: {:?}",
                    widget_names.get(current_node.0).unwrap().0,
StarToaster's avatar
StarToaster committed
                    current_node.0.index(),
John Mitchell's avatar
John Mitchell committed
                    layout
        render_primitives.push(render_primitive.clone());
        let new_prev_clip = if matches!(render_primitive, RenderPrimitive::Clip { .. }) {
            render_primitive.clone()
        } else {
            prev_clip
        };

        prev_clip = new_prev_clip.clone();
        if node_tree.children.contains_key(&current_node) {
            let z = 1.0f32;
            let mut children_primitives = Vec::new();
            for child in node_tree.children.get(&current_node).unwrap() {
                // main_z_index += 1.0;
                let mut children_p = recurse_node_tree_to_build_primitives(
                    node_tree,
                    layout_cache,
                    nodes,
                    widget_names,
                    *child,
                    main_z_index + if node.z < 0.0 { 0.0 } else { node.z } + z,
                    new_prev_clip.clone(),

                // Between each child node we need to reset the clip.
                if matches!(prev_clip, RenderPrimitive::Clip { .. }) {
                    children_p.push(prev_clip.clone());
                }

                if let Ok(node) = nodes.get(child.0) {
                    let zz = if node.z < 0.0 { z } else { z + node.z };
                    children_primitives.push((zz, children_p));
                }
StarToaster's avatar
StarToaster committed
            }

            // Sort and add
            children_primitives.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
            for cp in children_primitives.drain(..) {
                render_primitives.extend(cp.1);
            }
        } else {
            log::trace!(
                "No children for node: {}-{}",
                widget_names.get(current_node.0).unwrap().0,
StarToaster's avatar
StarToaster committed
                current_node.0.index()
StarToaster's avatar
StarToaster committed
        }
    } else {
        log::error!(
            "No render node: {}-{} > {}-{}",
            node_tree
                .get_parent(current_node)
                .unwrap_or(-1),
            widget_names
                .get(
                    node_tree
                        .get_parent(current_node)
                        .unwrap_or(Entity::from_raw(0))
                )
                .map(|v| v.0.clone())
                .unwrap_or_else(|_| "None".into()),
            widget_names
                .get(current_node.0)
                .map(|v| v.0.clone())
                .unwrap_or_else(|_| "None".into()),
StarToaster's avatar
StarToaster committed
            current_node.0.index()
StarToaster's avatar
StarToaster committed
    }

    render_primitives
}

fn update_widgets_sys(world: &mut World) {
    let mut context_data = Vec::new();
    query_world::<Query<(Entity, &mut KayakRootContext)>, _, _>(
        |mut query| {
            for (entity, mut kayak_root_context) in query.iter_mut() {
                context_data.push((entity, std::mem::take(&mut *kayak_root_context)));
StarToaster's avatar
StarToaster committed
        world,
    );

    for (entity, mut context) in context_data.drain(..) {
        for system_id in context.uninitilized_systems.drain() {
            if let Some(system) = context.systems.get_mut(&system_id) {
                system.0.initialize(world);
                system.1.initialize(world);
        let tree_iterator = if let Ok(tree) = context.tree.read() {
            tree.down_iter().collect::<Vec<_>>()
        } else {
            panic!("Failed to acquire read lock.");
        };

        // let change_tick = world.increment_change_tick();
        let old_focus = if let Ok(mut focus_tree) = context.focus_tree.try_write() {
            let current = focus_tree.current();
            focus_tree.clear();
            if let Ok(tree) = context.tree.read() {
                if let Some(root_node) = tree.root_node {
                    focus_tree.add(root_node, &tree);
                }
            }
            current
StarToaster's avatar
StarToaster committed
        } else {
            None
        };

        let mut new_ticks = HashMap::new();

        // dbg!("Updating widgets!");
        update_widgets(
            context.camera_entity,
            world,
            &context.tree,
            &context.layout_cache,
            &mut context.systems,
            tree_iterator,
            &context.context_entities,
            &context.focus_tree,
            &context.clone_systems,
            &context.cloned_widget_entities,
            &context.widget_state,
            &mut new_ticks,
            &context.order_tree,
            &context.index,
        );

        if let Some(old_focus) = old_focus {
            if let Ok(mut focus_tree) = context.focus_tree.try_write() {
                if focus_tree.contains(old_focus) {
                    focus_tree.focus(old_focus);
                }
            }
        // dbg!("Finished updating widgets!");
        let tick = world.read_change_tick();
        for (key, system) in context.systems.iter_mut() {
            if let Some(new_tick) = new_ticks.get(key) {
                system.0.set_last_change_tick(*new_tick);
                system.1.set_last_change_tick(*new_tick);
            } else {
                system.0.set_last_change_tick(tick);
                system.1.set_last_change_tick(tick);
            }
            // system.apply_buffers(world);
        }

        // Clear out indices
        if let Ok(mut indices) = context.index.try_write() {
            // for (entity, value) in indices.iter_mut() {
            //     if tree.root_node.unwrap().0.id() != entity.id() {
            //         *value = 0;
            //     }
            // }
            indices.clear();
        }
        world.entity_mut(entity).insert(context);
StarToaster's avatar
StarToaster committed
}

fn update_widgets(
    camera_entity: Entity,
StarToaster's avatar
StarToaster committed
    world: &mut World,
    tree: &Arc<RwLock<Tree>>,
    layout_cache: &Arc<RwLock<LayoutCache>>,
    systems: &mut WidgetSystems,
StarToaster's avatar
StarToaster committed
    widgets: Vec<WrappedIndex>,
    context_entities: &ContextEntities,
    focus_tree: &Arc<RwLock<FocusTree>>,
    clone_systems: &Arc<RwLock<EntityCloneSystems>>,
    cloned_widget_entities: &Arc<RwLock<HashMap<Entity, Entity>>>,
    widget_state: &WidgetState,
StarToaster's avatar
StarToaster committed
    new_ticks: &mut HashMap<String, u32>,
    order_tree: &Arc<RwLock<Tree>>,
    index: &Arc<RwLock<HashMap<Entity, usize>>>,
StarToaster's avatar
StarToaster committed
) {
    for entity in widgets.iter() {
        // A small hack to add parents to widgets
        // let mut command_queue = CommandQueue::default();
        // {
        //     let mut commands = Commands::new(&mut command_queue, &world);
        //     if let Some(mut entity_commands) = commands.get_entity(entity.0) {
        //         entity_commands.set_parent(camera_entity);
        //     }
        // }
        // command_queue.apply(world);
StarToaster's avatar
StarToaster committed
        if let Some(entity_ref) = world.get_entity(entity.0) {
            if let Some(widget_type) = entity_ref.get::<WidgetName>() {
                let widget_context = KayakWidgetContext::new(
StarToaster's avatar
StarToaster committed
                    tree.clone(),
                    context_entities.clone(),
                    layout_cache.clone(),
                    widget_state.clone(),
                    order_tree.clone(),
                    index.clone(),
                    Some(camera_entity),
StarToaster's avatar
StarToaster committed
                );
                widget_context.copy_from_point(tree, *entity);
StarToaster's avatar
StarToaster committed
                let children_before = widget_context.get_children(entity.0);
                let widget_name = widget_type.0.clone();
StarToaster's avatar
StarToaster committed
                let (widget_context, should_update_children) = update_widget(
                    systems,
                    tree,
                    world,
                    *entity,
                    widget_type.0.clone(),
                    widget_context,
                    children_before,
                    clone_systems,
                    cloned_widget_entities,
                    widget_state,
StarToaster's avatar
StarToaster committed
                    new_ticks,
                );

                if should_update_children {
                    if let Ok(mut tree) = tree.write() {
                        // let mut had_removal = false;
StarToaster's avatar
StarToaster committed
                        let diff = tree.diff_children(&widget_context, *entity, UPDATE_DEPTH);
                        for (_index, child, _parent, changes) in diff.changes.iter() {
                            if changes
                                .iter()
                                .any(|change| matches!(change, Change::Inserted))
                            {
                                if let Ok(mut cache) = layout_cache.try_write() {
                                    cache.add(*child);
                                }
                            }

                            if changes
                                .iter()
                                .any(|change| matches!(change, Change::Deleted))
                            {
                                // Children of this node need to be despawned.
                                let mut despawn_list = Vec::default();
                                for child in tree.down_iter_at(*child, true) {
                                    let children = if let Some(parent) = tree.get_parent(child) {
                                        world.entity(parent.0).get::<KChildren>()
                                    } else {
                                        None
                                    };

                                    if let Some(children) = children {
                                        if !children.iter().any(|c| *c == child.0) {
                                            despawn_list.push(child.0);
                                        }
                                    } else {
                                        despawn_list.push(child.0);
                                    }
                                    if let Ok(mut order_tree) = order_tree.try_write() {
                                        // had_removal = true;
                                        log::trace!(
                                            "Removing entity! {:?} inside of: {}-{:?}",
StarToaster's avatar
StarToaster committed
                                            child.0.index(),
StarToaster's avatar
StarToaster committed
                                            entity.0.index()
                                        );
                                        order_tree.remove(child);
                                    }
                                }
                                for entity in despawn_list.drain(..) {
                                    if let Some(entity_mut) = world.get_entity_mut(entity) {
                                        entity_mut.despawn();

                        // if had_removal {
                        //     tree.dump();
                        //     dbg!(&diff);
                        // }
StarToaster's avatar
StarToaster committed

                        tree.merge(&widget_context, *entity, diff, UPDATE_DEPTH);
                        // }

                        for child in widget_context.child_iter(*entity) {
                            if let Some(mut entity_commands) = world.get_entity_mut(child.0) {
                                entity_commands.insert(DirtyNode);
                            }
                        }
StarToaster's avatar
StarToaster committed
                    }
                }

                // if should_update_children {
                let children = if let Ok(tree) = tree.read() {
                    tree.child_iter(*entity).collect::<Vec<_>>()
                } else {
                    vec![]
                };
StarToaster's avatar
StarToaster committed
                update_widgets(
                    camera_entity,
StarToaster's avatar
StarToaster committed
                    world,
                    tree,
                    layout_cache,
                    systems,
                    children,
                    context_entities,
                    focus_tree,
                    clone_systems,
                    cloned_widget_entities,
                    widget_state,
StarToaster's avatar
StarToaster committed
                    new_ticks,
StarToaster's avatar
StarToaster committed
                );
                // }
            }
        } else {
            // In this case the entity we are trying to process no longer exists.
            // The approach taken here removes said entities from the tree.
            let mut despawn_list = Vec::default();
            if let Ok(mut tree) = tree.write() {
                for child in tree.down_iter_at(*entity, true) {
                    despawn_list.push(child.0);
                    if let Ok(mut order_tree) = order_tree.try_write() {
                        // had_removal = true;
                        log::trace!(
                            "Removing entity! {:?} inside of: {:?}",
                            child.0.index(),
                            entity.0.index()
                        );
                        order_tree.remove(child);
                    }
                }
John Mitchell's avatar
John Mitchell committed

                for entity in despawn_list.drain(..) {
                    tree.remove(WrappedIndex(entity));
                    if let Some(entity_mut) = world.get_entity_mut(entity) {
                        entity_mut.despawn();
                    }
                }
John Mitchell's avatar
John Mitchell committed
            }
StarToaster's avatar
StarToaster committed
        }

        if let Some(entity_ref) = world.get_entity(entity.0) {
            if entity_ref.contains::<Focusable>() {
                if let Ok(tree) = tree.try_read() {
                    if let Ok(mut focus_tree) = focus_tree.try_write() {
                        focus_tree.add(*entity, &tree);
                    }
                }
            }
        }
    }
}

fn update_widget(
    systems: &mut WidgetSystems,
StarToaster's avatar
StarToaster committed
    tree: &Arc<RwLock<Tree>>,
    world: &mut World,
    entity: WrappedIndex,
    widget_type: String,
    widget_context: KayakWidgetContext,
StarToaster's avatar
StarToaster committed
    previous_children: Vec<Entity>,
    clone_systems: &Arc<RwLock<EntityCloneSystems>>,
    cloned_widget_entities: &Arc<RwLock<HashMap<Entity, Entity>>>,
    widget_state: &WidgetState,
StarToaster's avatar
StarToaster committed
    new_ticks: &mut HashMap<String, u32>,
) -> (Tree, bool) {
    // Check if we should update this widget

    let should_rerender = {
        let old_props_entity =
            if let Ok(mut cloned_widget_entities) = cloned_widget_entities.try_write() {
                if let Some(entity) = cloned_widget_entities.get(&entity.0).cloned() {
                    if let Some(possible_entity) = world.get_entity(entity) {
                        let target = possible_entity.id();
                        cloned_widget_entities.insert(entity, target);
                        target
                    } else {
                        let target = world.spawn_empty().insert(PreviousWidget).id();
                        cloned_widget_entities.insert(entity, target);
                        target
                    }
                } else {
                    let target = world.spawn_empty().insert(PreviousWidget).id();
                    cloned_widget_entities.insert(entity.0, target);
                    target
                }
            } else {
                panic!("Couldn't get write lock!")
            };

        let widget_update_system = &mut systems
            .get_mut(&widget_type)
            .expect(&format!(
                "Wasn't able to find render/update systems for widget: {}!",
                widget_type
            ))
            .0;
        let old_tick = widget_update_system.get_last_change_tick();
        let should_rerender =
            widget_update_system.run((widget_context.clone(), entity.0, old_props_entity), world);
        let new_tick = widget_update_system.get_last_change_tick();
        new_ticks.insert(widget_type.clone(), new_tick);
        widget_update_system.set_last_change_tick(old_tick);
        widget_update_system.apply_buffers(world);

        if should_rerender {
            if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() {
                if let Some(target_entity) = cloned_widget_entities.get(&entity.0) {
                    if let Ok(clone_systems) = clone_systems.try_read() {
                        for s in clone_systems.0.iter() {
                            s.0(world, *target_entity, entity.0);
                            s.1(world, *target_entity, entity.0, widget_state);
                            if let Some(styles) = world.entity(entity.0).get::<KStyle>().cloned() {
                                if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                                    entity.insert(styles);
                                }
                            }
                            if let Some(styles) =
                                world.entity(entity.0).get::<ComputedStyles>().cloned()
                            {
                                if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                                    entity.insert(styles);
                                }
                            }
                            if let Some(children) =
                                world.entity(entity.0).get::<KChildren>().cloned()
                            {
                                if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                                    entity.insert(children);
                                }
                            }
                        }
                    }
                }
            }
        }

        should_rerender
    };

    if !should_rerender {
        return (widget_context.take(), false);
    }

    // if let Ok(tree) = tree.try_read() {
    //     if tree.root_node.unwrap() != entity {

    // }

StarToaster's avatar
StarToaster committed
    let should_update_children;
    if let Ok(tree) = tree.try_read() {
        log::trace!(
            "Re-rendering: {:?} {:?}, parent: {:?}",
            &widget_type,
StarToaster's avatar
StarToaster committed
            entity.0.index(),
            tree.parent(entity)
                .unwrap_or(WrappedIndex(Entity::from_raw(99999)))
                .0
StarToaster's avatar
StarToaster committed
                .index()
StarToaster's avatar
StarToaster committed
    {
        // Before rendering widget we need to advance the indices correctly..
        if let Some(children) = world.get::<KChildren>(entity.0) {
            let child_count = children.len();
            if let Ok(mut indices) = widget_context.index.try_write() {
                indices.insert(entity.0, 0);
                log::trace!(
                    "Advancing children for: {:?} by: {:?}",
StarToaster's avatar
StarToaster committed
                    entity.0.index(),
StarToaster's avatar
StarToaster committed
        // Remove children from previous render.
        widget_context.remove_children(previous_children);
        let widget_render_system = &mut systems.get_mut(&widget_type).unwrap().1;
        let old_tick = widget_render_system.get_last_change_tick();
        should_update_children =
            widget_render_system.run((widget_context.clone(), entity.0), world);
        let new_tick = widget_render_system.get_last_change_tick();
StarToaster's avatar
StarToaster committed
        new_ticks.insert(widget_type.clone(), new_tick);
        widget_render_system.set_last_change_tick(old_tick);
        widget_render_system.apply_buffers(world);

        if let Ok(mut indices) = widget_context.index.try_write() {
            indices.insert(entity.0, 0);
        }
StarToaster's avatar
StarToaster committed
    }
    let widget_context = widget_context.take();
    let mut command_queue = CommandQueue::default();
    let mut commands = Commands::new(&mut command_queue, world);

    commands.entity(entity.0).remove::<Mounted>();

    let diff = if let Ok(tree) = tree.read() {
        tree.diff_children(&widget_context, entity, UPDATE_DEPTH)
    } else {
        panic!("Failed to acquire read lock.");
    };

    log::trace!("Diff: {:?}", &diff);

    // Always mark widget dirty if it's re-rendered.
    // Mark node as needing a recalculation of rendering/layout.
    commands.entity(entity.0).insert(DirtyNode);

    for (_index, changed_entity, parent, changes) in diff.changes.iter() {
        if changes.iter().any(|change| *change == Change::Deleted) {
            // commands.entity(changed_entity.0).despawn();
            // commands.entity(changed_entity.0).remove::<DirtyNode>();
            // commands.entity(changed_entity.0).despawn_recursive();
        if changes.iter().any(|change| *change == Change::Inserted) {
            if let Some(mut entity_commands) = commands.get_entity(changed_entity.0) {
                entity_commands.insert(Mounted);
                entity_commands.set_parent(parent.0);
            }
            if let Some(mut parent_commands) = commands.get_entity(parent.0) {
                parent_commands.add_child(changed_entity.0);
StarToaster's avatar
StarToaster committed
    command_queue.apply(world);

    if should_update_children {
        if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() {
            if let Some(target_entity) = cloned_widget_entities.get(&entity.0) {
                if let Some(styles) = world.entity(entity.0).get::<KStyle>().cloned() {
                    if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                        entity.insert(styles);
                    }
                }
                if let Some(styles) = world.entity(entity.0).get::<ComputedStyles>().cloned() {
                    if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                        entity.insert(styles);
                    }
                }
                if let Some(children) = world.entity(entity.0).get::<KChildren>().cloned() {
                    if let Some(mut entity) = world.get_entity_mut(*target_entity) {
                        entity.insert(children);
                    }
                }
            }
        }
        for (_, child_entity, _, changes) in diff.changes.iter() {
            // Clone to entity.
            if changes.iter().any(|change| *change == Change::Deleted) {
                if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() {
                    if let Some(entity) = cloned_widget_entities.get(&child_entity.0) {
                        world.despawn(*entity);
                    }
                }
            }
        }
    }

StarToaster's avatar
StarToaster committed
    (widget_context, should_update_children)
}

/// The default Kayak Context plugin
/// Creates systems and resources for kayak.
pub struct KayakContextPlugin;
StarToaster's avatar
StarToaster committed

#[derive(Resource)]
pub struct CustomEventReader<T: bevy::ecs::event::Event>(pub ManualEventReader<T>);

impl Plugin for KayakContextPlugin {
StarToaster's avatar
StarToaster committed
    fn build(&self, app: &mut App) {
        app.insert_resource(WindowSize::default())
            .insert_resource(CustomEventReader(ManualEventReader::<
                bevy::window::CursorMoved,
            >::default()))
            .insert_resource(CustomEventReader(ManualEventReader::<
                bevy::input::mouse::MouseButtonInput,
            >::default()))
            .insert_resource(CustomEventReader(ManualEventReader::<
                bevy::input::mouse::MouseWheel,
            >::default()))
            .insert_resource(CustomEventReader(ManualEventReader::<
                bevy::window::ReceivedCharacter,
            >::default()))
            .insert_resource(CustomEventReader(ManualEventReader::<
                bevy::input::keyboard::KeyboardInput,
            >::default()))
            .add_plugin(crate::camera::KayakUICameraPlugin)
            .add_plugin(crate::render::BevyKayakUIRenderPlugin)
            .register_type::<Node>()