Skip to content
Snippets Groups Projects
context.rs 59.7 KiB
Newer Older
John Mitchell's avatar
John Mitchell committed
                            }
                            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);
John Mitchell's avatar
John Mitchell committed
                                }
                            }
                        }

                        // Mark nodes left as dirty.
                        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,
                    unique_ids,
                    unique_ids_parents,
        } else {
            // In this case the entity we are trying to process no longer exists.
            // The approach taken here removes said entities from the tree.
John Mitchell's avatar
John Mitchell committed

            // If the child exists as a child of one of the children we do not need to remove it.
            // TODO: This is kinda of expensive we should think of a way of making this faster..
            let mut contained_in_children = false;
            if let Ok(tree) = tree.read() {
                if let Ok(order_tree) = order_tree.try_read() {
                    if let Some(parent) = order_tree.parent(*entity) {
                        'outside_loop: for sibling in order_tree.child_iter(parent) {
                            for child in tree.down_iter_at(sibling, true) {
                                if let Some(entity_ref) = world.get_entity(child.0) {
                                    if let Some(children) = entity_ref.get::<KChildren>() {
                                        if children.contains_entity(entity.0) {
                                            trace!("Caught an entity that was marked as deleted but wasn't! {:?}", entity.0);
                                            // Don't despawn changed entity because it exists as a child passed via props
                                            contained_in_children = true;
                                            break 'outside_loop;
                                        }
                                    }
John Mitchell's avatar
John Mitchell committed
            }
            if !contained_in_children {
                if let Ok(mut tree) = tree.write() {
                    if let Ok(mut order_tree) = order_tree.try_write() {
                        log::trace!("Removing dangling entity! {:?}", entity.0.index());
                        order_tree.remove(*entity);
                    if let Some(mut entity_mut) = world.get_entity_mut(entity.0) {
                        entity_mut.remove_parent();
                        entity_mut.remove::<bevy::prelude::Children>();
                    // Remove state entity
                    if let Some(state_entity) = widget_state.remove(entity.0) {
                        if let Some(mut entity_mut) = world.get_entity_mut(state_entity) {
                            entity_mut.remove_parent();
                            entity_mut.despawn_recursive();
                        }
                    }
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() {
                    focus_tree.add(*entity, &tree);
    systems: &mut WidgetSystems,
StarToaster's avatar
StarToaster committed
    tree: &Arc<RwLock<Tree>>,
    focus_tree: &FocusTree,
StarToaster's avatar
StarToaster committed
    world: &mut World,
    entity: WrappedIndex,
    widget_type: String,
    mut widget_context: KayakWidgetContext,
StarToaster's avatar
StarToaster committed
    previous_children: Vec<Entity>,
    clone_systems: &Arc<RwLock<EntityCloneSystems>>,
John Mitchell's avatar
John Mitchell committed
    cloned_widget_entities: &DashMap<Entity, Entity>,
    widget_state: &WidgetState,
StarToaster's avatar
StarToaster committed
    new_ticks: &mut HashMap<String, u32>,
John Mitchell's avatar
John Mitchell committed
    _order_tree: &Arc<RwLock<Tree>>,
John Mitchell's avatar
John Mitchell committed
    _unique_ids: &DashMap<Entity, DashMap<String, Entity>>,
    _unique_ids_parents: &DashMap<Entity, Entity>,
StarToaster's avatar
StarToaster committed
) -> (Tree, bool) {
    // Check if we should update this widget
    let should_rerender = {
John Mitchell's avatar
John Mitchell committed
        // TODO: Move the spawning to when we create the widget.
John Mitchell's avatar
John Mitchell committed
        let old_props_entity = {
            let old_parent_entity = if let Ok(tree) = tree.try_read() {
                if let Some(parent_entity) = tree.get_parent(entity) {
                    cloned_widget_entities
                        .get(&parent_entity.0)
                        .map(|v| *v.value())
John Mitchell's avatar
John Mitchell committed
                } else {
                    None
John Mitchell's avatar
John Mitchell committed
                }
            } else {
                None
            };
            if let Some(entity) = cloned_widget_entities.get(&entity.0).map(|v| *v.value()) {
                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();
                    if let Some(parent_id) = old_parent_entity {
John Mitchell's avatar
John Mitchell committed
                        world.entity_mut(parent_id).add_child(target);
John Mitchell's avatar
John Mitchell committed
                    cloned_widget_entities.insert(entity, target);
John Mitchell's avatar
John Mitchell committed
                let target = world.spawn_empty().insert(PreviousWidget).id();
                if let Some(parent_id) = old_parent_entity {
                    if let Some(mut entity_mut) = world.get_entity_mut(parent_id) {
                        entity_mut.add_child(target);
                    }
                }
                cloned_widget_entities.insert(entity.0, target);
                target
            }
        };
        let widget_update_system = &mut systems
            .get_mut(&widget_type)
John Mitchell's avatar
John Mitchell committed
            .unwrap_or_else(|| {
                panic!(
                    "Wasn't able to find render/update systems for widget: {}!",
                    widget_type
                )
            })
        let old_tick = widget_update_system.get_last_change_tick();
John Mitchell's avatar
John Mitchell committed

        // Insert context as a bevy resource.
        world.insert_resource(widget_context);
John Mitchell's avatar
John Mitchell committed
        let should_rerender = widget_update_system.run((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);
John Mitchell's avatar
John Mitchell committed

        // Extract context
        widget_context = world.remove_resource::<KayakWidgetContext>().unwrap();
John Mitchell's avatar
John Mitchell committed
            if let Some(target_entity) = cloned_widget_entities.get(&entity.0).map(|v| *v.value()) {
                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);
John Mitchell's avatar
John Mitchell committed
                        }
                        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);
John Mitchell's avatar
John Mitchell committed
                        }
                        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);
John Mitchell's avatar
John Mitchell committed
                        }
John Mitchell's avatar
John Mitchell committed
                        if let Some(widget_name) =
                            world.entity(entity.0).get::<WidgetName>().cloned()
                        {
                            if let Some(mut entity) = world.get_entity_mut(target_entity) {
                                entity.insert(widget_name);
                        }
                    }
                }
            }
        }

        should_rerender
    };

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

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();
John Mitchell's avatar
John Mitchell committed
            widget_context.index.insert(entity.0, 0);
            log::trace!(
                "Advancing children for: {:?} by: {:?}",
                entity.0.index(),
                child_count
            );
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();
        world.insert_resource(widget_context.clone());
        world.insert_resource(focus_tree.clone());
John Mitchell's avatar
John Mitchell committed
        should_update_children = widget_render_system.run(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);
        world.remove_resource::<KayakWidgetContext>();
        world.remove_resource::<FocusTree>();
John Mitchell's avatar
John Mitchell committed
        widget_context.index.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>();

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

    command_queue.apply(world);

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)
NiseVoid's avatar
NiseVoid committed
            .add_system(crate::input::process_events.in_base_set(CoreSet::Update))
            .add_system(update_widgets_sys.in_base_set(CoreSet::PostUpdate))
            .add_system(
                calculate_ui
                    .after(update_widgets_sys)
                    .in_base_set(CoreSet::PostUpdate),
            )
StarToaster's avatar
StarToaster committed
            .add_system(crate::window_size::update_window_size);

        // Register reflection types.
        // A bit annoying..
        app //.register_type::<Node>()
            .register_type::<ComputedStyles>()
            .register_type::<KStyle>()
            .register_type::<crate::layout::Rect>()
            .register_type::<crate::node::Node>()
            .register_type::<WidgetName>()
            .register_type::<StyleProp<Color>>()
            .register_type::<StyleProp<Corner<f32>>>()
            .register_type::<StyleProp<Edge<f32>>>()
            .register_type::<StyleProp<Units>>()
            .register_type::<StyleProp<KCursorIcon>>()
            .register_type::<StyleProp<String>>()
            .register_type::<StyleProp<f32>>()
            .register_type::<StyleProp<LayoutType>>()
            .register_type::<StyleProp<Edge<Units>>>()
            .register_type::<StyleProp<PointerEvents>>()
            .register_type::<StyleProp<KPositionType>>()
            .register_type::<StyleProp<RenderCommand>>()
            .register_type::<StyleProp<i32>>();
StarToaster's avatar
StarToaster committed
    }
}

fn calculate_ui(world: &mut World) {
    let mut context_data = Vec::new();
    query_world::<Query<(Entity, &mut EventDispatcher, &mut KayakRootContext)>, _, _>(
        |mut query| {
            for (entity, mut event_dispatcher, mut kayak_root_context) in query.iter_mut() {
                context_data.push((
                    entity,
                    std::mem::take(&mut *event_dispatcher),
                    std::mem::take(&mut *kayak_root_context),
                ));
            }
        },
        world,
    );
    for (entity, event_dispatcher, mut context) in context_data.drain(..) {
        let mut node_system = IntoSystem::into_system(calculate_nodes);
        node_system.initialize(world);
        let mut layout_system = IntoSystem::into_system(calculate_layout);
        layout_system.initialize(world);
John Mitchell's avatar
John Mitchell committed
        for _ in 0..2 {
            context = node_system.run(context, world);
            node_system.apply_buffers(world);

            context = layout_system.run(context, world);
            layout_system.apply_buffers(world);
            LayoutEventDispatcher::dispatch(&mut context, world);
        }
        if event_dispatcher.hovered.is_none() {
            context.current_cursor = CursorIcon::Default;
        } else {
StarToaster's avatar
StarToaster committed
            let hovered = event_dispatcher.hovered.unwrap();
            if let Some(entity) = world.get_entity(hovered.0) {
                if let Some(node) = entity.get::<crate::node::Node>() {
                    let icon = node.resolved_styles.cursor.resolve();
                    context.current_cursor = icon.0;
                }
            }

NiseVoid's avatar
NiseVoid committed
            if let Ok(mut window) = world
                .query_filtered::<&mut Window, With<PrimaryWindow>>()
                .get_single_mut(world)
            {
                window.cursor.icon = context.current_cursor;
        world.entity_mut(entity).insert((event_dispatcher, context));
    }
/// A simple component that stores the type name of a widget
/// This is used by Kayak in order to find out which systems to run.
#[derive(Component, Reflect, Debug, Clone, PartialEq, Eq)]
#[reflect(Component)]
StarToaster's avatar
StarToaster committed
pub struct WidgetName(pub String);

impl Default for WidgetName {
    fn default() -> Self {
        log::warn!("You did not specify a widget name for a widget!");
        Self("NO_NAME".to_string())
    }
}

StarToaster's avatar
StarToaster committed
impl From<String> for WidgetName {
    fn from(value: String) -> Self {
        WidgetName(value)
    }
}

John Mitchell's avatar
John Mitchell committed
impl From<WidgetName> for String {
    fn from(val: WidgetName) -> Self {
        val.0