Skip to content
Snippets Groups Projects
event_dispatcher.rs 36.6 KiB
Newer Older
StarToaster's avatar
StarToaster committed
use bevy::{
    prelude::{Component, Entity, KeyCode, World},
StarToaster's avatar
StarToaster committed
    utils::{HashMap, HashSet},
};
MrGVSV's avatar
MrGVSV committed
use crate::{
    context::KayakRootContext,
StarToaster's avatar
StarToaster committed
    cursor::{CursorEvent, PointerEvents, ScrollEvent, ScrollUnit},
    event::{Event, EventType},
    focus_tree::FocusTree,
    input_event::{InputEvent, InputEventCategory},
    keyboard_event::{KeyboardEvent, KeyboardModifiers},
    layout::Rect,
    node::{Node, WrappedIndex},
StarToaster's avatar
StarToaster committed
    on_event::OnEvent,
    prelude::KayakWidgetContext,
    styles::{ComputedStyles, KStyle, RenderCommand},
StarToaster's avatar
StarToaster committed
    Focusable,
MrGVSV's avatar
MrGVSV committed
};
StarToaster's avatar
StarToaster committed
type EventMap = HashMap<WrappedIndex, HashSet<EventType>>;
type TreeNode = (
    // The node ID
StarToaster's avatar
StarToaster committed
    WrappedIndex,
    // The node depth
    isize,
);

#[derive(Debug, Clone)]
struct EventState {
    best_z_index: f32,
StarToaster's avatar
StarToaster committed
    best_match: Option<WrappedIndex>,
    best_depth: isize,
}

impl Default for EventState {
    fn default() -> Self {
        Self {
            best_z_index: f32::NEG_INFINITY,
            best_match: None,
            best_depth: -1,
        }
    }
}

#[derive(Component, Debug, Clone, Default)]
pub struct EventDispatcher {
    is_mouse_pressed: bool,
MrGVSV's avatar
MrGVSV committed
    next_mouse_pressed: bool,
    current_mouse_position: (f32, f32),
    next_mouse_position: (f32, f32),
    previous_events: EventMap,
    keyboard_modifiers: KeyboardModifiers,
StarToaster's avatar
StarToaster committed
    // pub last_clicked: Binding<WrappedIndex>,
    contains_cursor: Option<bool>,
    wants_cursor: Option<bool>,
StarToaster's avatar
StarToaster committed
    has_cursor: Option<WrappedIndex>,
    pub(crate) cursor_capture: Option<WrappedIndex>,
    pub(crate) hovered: Option<WrappedIndex>,
}

impl EventDispatcher {
    pub(crate) fn new() -> Self {
StarToaster's avatar
StarToaster committed
            // last_clicked: Binding::new(WrappedIndex(Entity::from_raw(0))),
            is_mouse_pressed: Default::default(),
MrGVSV's avatar
MrGVSV committed
            next_mouse_pressed: Default::default(),
            current_mouse_position: Default::default(),
            next_mouse_position: Default::default(),
            previous_events: Default::default(),
            keyboard_modifiers: Default::default(),
            contains_cursor: None,
            wants_cursor: None,
            has_cursor: None,
MrGVSV's avatar
MrGVSV committed
            cursor_capture: None,
MrGVSV's avatar
MrGVSV committed
            hovered: None,
    /// Returns whether the mouse is currently pressed or not
    #[allow(dead_code)]
    pub fn is_mouse_pressed(&self) -> bool {
        self.is_mouse_pressed
    }

    /// Gets the current mouse position (since last mouse event)
    #[allow(dead_code)]
    pub fn current_mouse_position(&self) -> (f32, f32) {
        self.current_mouse_position
    }
MrGVSV's avatar
MrGVSV committed
    /// Captures all cursor events and instead makes the given index the target
StarToaster's avatar
StarToaster committed
    pub fn capture_cursor(&mut self, index: Entity) -> Option<WrappedIndex> {
MrGVSV's avatar
MrGVSV committed
        let old = self.cursor_capture;
StarToaster's avatar
StarToaster committed
        self.cursor_capture = Some(WrappedIndex(index));
MrGVSV's avatar
MrGVSV committed
        old
    }

    /// Releases the captured cursor
    ///
    /// Returns true if successful.
    ///
    /// This will only release the cursor if the given index matches the current captor. This
    /// prevents other widgets from accidentally releasing against the will of the original captor.
    ///
    /// This check can be side-stepped if necessary by calling [`force_release_cursor`](Self::force_release_cursor)
    /// instead (or by calling this method with the correct index).
StarToaster's avatar
StarToaster committed
    pub fn release_cursor(&mut self, index: Entity) -> bool {
        if self.cursor_capture == Some(WrappedIndex(index)) {
MrGVSV's avatar
MrGVSV committed
            self.force_release_cursor();
            true
        } else {
            false
        }
    }

    /// Releases the captured cursor
    ///
    /// Returns the index of the previous captor.
    ///
    /// This will force the release, regardless of which widget has called it. To safely release,
    /// use the standard [`release_cursor`](Self::release_cursor) method instead.
StarToaster's avatar
StarToaster committed
    pub fn force_release_cursor(&mut self) -> Option<WrappedIndex> {
MrGVSV's avatar
MrGVSV committed
        let old = self.cursor_capture;
        self.cursor_capture = None;
        old
    }

    /// Returns true if the cursor is currently over a valid widget
    ///
    /// For the purposes of this method, a valid widget is one which has the means to display a visual component on its own.
    /// This means widgets specified with [`RenderCommand::Empty`], [`RenderCommand::Layout`], or [`RenderCommand::Clip`]
    /// do not meet the requirements to "contain" the cursor.
    #[allow(dead_code)]
    pub fn contains_cursor(&self) -> bool {
        self.contains_cursor.unwrap_or_default()
    }

    /// Returns true if the cursor may be needed by a widget or it's already in use by one
    ///
    /// This is useful for checking if certain events (such as a click) would "matter" to the UI at all. Example widgets
    /// include buttons, sliders, and text boxes.
    #[allow(dead_code)]
    pub fn wants_cursor(&self) -> bool {
        self.wants_cursor.unwrap_or_default() || self.has_cursor.is_some()
    }

    /// Returns true if the cursor is currently in use by a widget
    ///
    /// This is most often useful for checking drag events as it will still return true even if the drag continues outside
    /// the widget bounds (as long as it started within it).
    #[allow(dead_code)]
    pub fn has_cursor(&self) -> bool {
        self.has_cursor.is_some()
    }

    /// The currently hovered node
    #[allow(dead_code)]
StarToaster's avatar
StarToaster committed
    pub fn hovered(&self) -> Option<WrappedIndex> {
        self.hovered
    }

    /// Process and dispatch an [InputEvent](crate::InputEvent)
StarToaster's avatar
StarToaster committed
    // #[allow(dead_code)]
    // pub fn process_event(
    //     &mut self,
    //     input_event: InputEvent,
    //     context: &mut Context,
    //     focusable_query: &Query<&Focusable>,
    //     style_query: &Query<&Style>,
    //     on_event_query: &Query<&OnEvent>,
    // ) {
    //     let events = self.build_event_stream(&[input_event], context, focusable_query, style_query);
    //     self.dispatch_events(events, context, on_event_query);
    // }

    /// Process and dispatch a set of [InputEvents](crate::InputEvent)
    pub(crate) fn process_events(
StarToaster's avatar
StarToaster committed
        &mut self,
        input_events: &Vec<InputEvent>,
        context: &mut KayakRootContext,
StarToaster's avatar
StarToaster committed
        world: &mut World,
    ) {
        let events = { self.build_event_stream(input_events, context, world) };
StarToaster's avatar
StarToaster committed
        self.dispatch_events(events, context, world);
    }

    /// Dispatch an [Event](crate::Event)
    #[allow(dead_code)]
    pub fn dispatch_event(
        &mut self,
        event: Event,
        context: &mut KayakRootContext,
        world: &mut World,
    ) {
StarToaster's avatar
StarToaster committed
        self.dispatch_events(vec![event], context, world);
    }

    /// Dispatch a set of [Events](crate::Event)
StarToaster's avatar
StarToaster committed
    pub fn dispatch_events(
        &mut self,
        events: Vec<Event>,
        context: &mut KayakRootContext,
StarToaster's avatar
StarToaster committed
        world: &mut World,
    ) {
        // === Dispatch Events === //
        let mut next_events = HashMap::default();
        for mut event in events {
StarToaster's avatar
StarToaster committed
            let mut current_target: Option<WrappedIndex> = Some(WrappedIndex(event.target));
            while let Some(index) = current_target {
                // Create a copy of the event, specific for this node
                // This is to make sure unauthorized changes to the event are not propagated
                // (e.g., changing the event type, removing the target, etc.)
                let mut node_event = Event {
StarToaster's avatar
StarToaster committed
                    current_target: index.0,
                    ..event.clone()
                };

                // --- Update State --- //
StarArawn's avatar
StarArawn committed
                Self::insert_event(&mut next_events, &index, node_event.event_type);

                // --- Call Event --- //
StarToaster's avatar
StarToaster committed
                if let Some(mut entity) = world.get_entity_mut(index.0) {
                    if let Some(mut on_event) = entity.remove::<OnEvent>() {
                        let mut event_dispatcher_context = EventDispatcherContext {
                            cursor_capture: self.cursor_capture,
                        };

                        (event_dispatcher_context, node_event) = on_event.try_call(
                            event_dispatcher_context,
                            context.widget_state.clone(),
                            index.0,
                            node_event,
                            world,
                        );
StarToaster's avatar
StarToaster committed
                        world.entity_mut(index.0).insert(on_event);
                        event_dispatcher_context.merge(self);

                        // Sometimes events will require systems to be called.
                        // IE OnChange
                        let widget_context = KayakWidgetContext::new(
StarToaster's avatar
StarToaster committed
                            context.tree.clone(),
                            context.context_entities.clone(),
                            context.layout_cache.clone(),
                            context.widget_state.clone(),
                            context.order_tree.clone(),
                            context.index.clone(),
StarToaster's avatar
StarToaster committed
                        );
                        node_event.run_on_change(world, widget_context);
                    }
                }
                event.default_prevented |= node_event.default_prevented;

                // --- Propagate Event --- //
                if node_event.should_propagate {
StarToaster's avatar
StarToaster committed
                    if let Ok(node_tree) = context.tree.try_read() {
                        current_target = node_tree.get_parent(index);
                    } else {
                        current_target = None;
                    }
                } else {
                    current_target = None;
                }
            }

            if !event.default_prevented {
StarToaster's avatar
StarToaster committed
                self.execute_default(event, context, world);
        }

        // === Maintain Events === //
        // Events that need to be maintained without re-firing between event updates should be managed here
        for (index, events) in &self.previous_events {
            // Mouse is currently pressed for this node
MrGVSV's avatar
MrGVSV committed
            if self.is_mouse_pressed && events.contains(&EventType::MouseDown(Default::default())) {
                // Make sure this event isn't removed while mouse is still held down
MrGVSV's avatar
MrGVSV committed
                Self::insert_event(
                    &mut next_events,
                    index,
                    EventType::MouseDown(Default::default()),
                );
            }

            // Mouse is currently within this node
MrGVSV's avatar
MrGVSV committed
            if events.contains(&EventType::MouseIn(Default::default()))
MrGVSV's avatar
MrGVSV committed
                && !Self::contains_event(
MrGVSV's avatar
MrGVSV committed
                    &next_events,
                    index,
                    &EventType::MouseOut(Default::default()),
                )
StarArawn's avatar
StarArawn committed
            {
                // Make sure this event isn't removed while mouse is still within node
MrGVSV's avatar
MrGVSV committed
                Self::insert_event(
                    &mut next_events,
                    index,
                    EventType::MouseIn(Default::default()),
                );
            }
        }

        // Replace the previous events with the next set
        self.previous_events = next_events;
    }

    /// Generates a stream of [Events](crate::Event) from a set of [InputEvents](crate::InputEvent)
StarArawn's avatar
StarArawn committed
    fn build_event_stream(
        &mut self,
        input_events: &[InputEvent],
        context: &mut KayakRootContext,
StarToaster's avatar
StarToaster committed
        world: &mut World,
StarArawn's avatar
StarArawn committed
    ) -> Vec<Event> {
        let mut event_stream = Vec::<Event>::new();
        let mut states: HashMap<EventType, EventState> = HashMap::new();
StarToaster's avatar
StarToaster committed
        if let Ok(node_tree) = context.tree.try_read() {
            let root = if let Some(root) = node_tree.root_node {
                root
            } else {
                return event_stream;
            };

            // === Setup Cursor States === //
            let old_hovered = self.hovered;
            let old_contains_cursor = self.contains_cursor;
            let old_wants_cursor = self.wants_cursor;
            self.hovered = None;
            self.contains_cursor = None;
            self.wants_cursor = None;
            self.next_mouse_position = self.current_mouse_position;
            self.next_mouse_pressed = self.is_mouse_pressed;

            // --- Pre-Process --- //
            // We pre-process some events so that we can provide accurate event data (such as if the mouse is pressed)
            // This is faster than resolving data after the fact since `input_events` is generally very small
            for input_event in input_events {
StarToaster's avatar
StarToaster committed
                if let InputEvent::MouseMoved(point) = input_event {
                    // Reset next global mouse position
                    self.next_mouse_position = *point;
                }

                if matches!(input_event, InputEvent::MouseLeftPress) {
                    // Reset next global mouse pressed
                    self.next_mouse_pressed = true;
                    break;
                } else if matches!(input_event, InputEvent::MouseLeftRelease) {
                    // Reset next global mouse pressed
                    self.next_mouse_pressed = false;
                    // Reset global cursor container
                    self.has_cursor = None;
                    break;
StarToaster's avatar
StarToaster committed
            // === Mouse Events === //
            if let Some(captor) = self.cursor_capture {
                // A widget has been set to capture pointer events -> it should be the only one receiving events
MrGVSV's avatar
MrGVSV committed
                for input_event in input_events {
                    // --- Process Event --- //
                    if matches!(input_event.category(), InputEventCategory::Mouse) {
                        // A widget's PointerEvents style will determine how it and its children are processed
StarToaster's avatar
StarToaster committed
                        let pointer_events = Self::resolve_pointer_events(captor, world);
MrGVSV's avatar
MrGVSV committed

                        match pointer_events {
                            PointerEvents::All | PointerEvents::SelfOnly => {
                                let events = self.process_pointer_events(
                                    input_event,
StarToaster's avatar
StarToaster committed
                                    (captor, 0),
MrGVSV's avatar
MrGVSV committed
                                    &mut states,
StarToaster's avatar
StarToaster committed
                                    world,
                                    context,
                                    true,
MrGVSV's avatar
MrGVSV committed
                                );
                                event_stream.extend(events);
                            }
StarToaster's avatar
StarToaster committed
                            _ => {}
StarToaster's avatar
StarToaster committed
            } else {
                // No capturing widget -> process cursor events as normal
                let mut stack: Vec<TreeNode> = vec![(root, 0)];
StarToaster's avatar
StarToaster committed
                    let (current, depth) = stack.pop().unwrap();
                    let mut enter_children = true;

                    if let Some(entity_ref) = world.get_entity(current.0) {
                        if entity_ref.contains::<OnEvent>() {
                            for input_event in input_events {
                                // --- Process Event --- //
                                if matches!(input_event.category(), InputEventCategory::Mouse) {
                                    // A widget's PointerEvents style will determine how it and its children are processed
John Mitchell's avatar
John Mitchell committed
                                    let pointer_events =
                                        Self::resolve_pointer_events(current, world);

                                    match pointer_events {
                                        PointerEvents::All | PointerEvents::SelfOnly => {
                                            let events = self.process_pointer_events(
                                                input_event,
                                                (current, depth),
                                                &mut states,
                                                world,
                                                context,
                                                false,
                                            );
                                            event_stream.extend(events);

                                            if matches!(pointer_events, PointerEvents::SelfOnly) {
                                                enter_children = false;
                                            }
                                        PointerEvents::None => enter_children = false,
                                        PointerEvents::ChildrenOnly => {}
StarToaster's avatar
StarToaster committed
                                    }
                                }
                            }
                        }
                    }
                    // --- Push Children to Stack --- //
                    if enter_children {
                        if let Some(children) = node_tree.children.get(&current) {
                            let mut stack_children = Vec::new();
StarToaster's avatar
StarToaster committed
                            for child in children {
                                let child_z = world
                                    .entity(child.0)
StarToaster's avatar
StarToaster committed
                                    .get::<Node>()
                                    .map(|node| node.z)
                                    .unwrap_or(0.0);
                                stack_children.push((child_z, (*child, depth + 1)));
StarToaster's avatar
StarToaster committed
                            }
                            stack_children.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
                            stack.extend(stack_children.iter().map(|c| c.1));
MrGVSV's avatar
MrGVSV committed
                    }
StarToaster's avatar
StarToaster committed
            if let Ok(mut focus_tree) = context.focus_tree.try_write() {
                // === Keyboard Events === //
                for input_event in input_events {
                    // Keyboard events only care about the currently focused widget so we don't need to run this over every node in the tree
                    let events =
                        self.process_keyboard_events(input_event, &mut states, &focus_tree);
                    event_stream.extend(events);
                }
StarToaster's avatar
StarToaster committed
                // === Additional Events === //
                let mut had_focus_event = false;

                // These events are ones that require a specific target and need the tree to be evaluated before selecting the best match
                for (event_type, state) in states {
                    if let Some(node) = state.best_match {
                        event_stream.push(Event::new(node.0, event_type));

                        match event_type {
                            EventType::Focus => {
                                had_focus_event = true;
                                if let Some(current_focus) = focus_tree.current() {
                                    if current_focus != node {
                                        event_stream
                                            .push(Event::new(current_focus.0, EventType::Blur));
                                    }
                                }
                                focus_tree.focus(node);
                            }
                            EventType::Hover(..) => {
                                self.hovered = Some(node);
StarToaster's avatar
StarToaster committed
                            _ => {}
StarToaster's avatar
StarToaster committed
                // --- Blur Event --- //
                if !had_focus_event && input_events.contains(&InputEvent::MouseLeftPress) {
                    // A mouse press didn't contain a focus event -> blur
                    if let Some(current_focus) = focus_tree.current() {
                        event_stream.push(Event::new(current_focus.0, EventType::Blur));
                        focus_tree.blur();
                    }
                }
StarToaster's avatar
StarToaster committed
                // === Process Cursor States === //
                self.current_mouse_position = self.next_mouse_position;
                self.is_mouse_pressed = self.next_mouse_pressed;
StarToaster's avatar
StarToaster committed
                if self.hovered.is_none() {
                    // No change -> revert
                    self.hovered = old_hovered;
                }
                if self.contains_cursor.is_none() {
                    // No change -> revert
                    self.contains_cursor = old_contains_cursor;
                }
                if self.wants_cursor.is_none() {
                    // No change -> revert
                    self.wants_cursor = old_wants_cursor;
                }
            }
MrGVSV's avatar
MrGVSV committed
    /// Process the pointer-related events of an input event
    ///
    /// # Arguments
    ///
    /// * `input_event`: The input event
    /// * `tree_node`: The current node to process
    /// * `states`: The map of events to their current state (for selecting best fit)
    /// * `widget_manager`: The widget manager
    /// * `ignore_layout`: Whether to ignore layout (useful for handling captured events)
    ///
    /// returns: Vec<Event>
StarArawn's avatar
StarArawn committed
    fn process_pointer_events(
        &mut self,
        input_event: &InputEvent,
        tree_node: TreeNode,
        states: &mut HashMap<EventType, EventState>,
StarToaster's avatar
StarToaster committed
        world: &mut World,
        context: &KayakRootContext,
MrGVSV's avatar
MrGVSV committed
        ignore_layout: bool,
StarArawn's avatar
StarArawn committed
    ) -> Vec<Event> {
        let mut event_stream = Vec::<Event>::new();
        let (node, depth) = tree_node;

        // let widget_name = world.entity(node.0).get::<WidgetName>();
        // dbg!(widget_name);

        match input_event {
            InputEvent::MouseMoved(point) => {
StarToaster's avatar
StarToaster committed
                if let Some(layout) = context.get_layout(&node) {
MrGVSV's avatar
MrGVSV committed
                    let cursor_event = self.get_cursor_event(*point);
                    let was_contained = layout.contains(&self.current_mouse_position);
                    let is_contained = layout.contains(point);
MrGVSV's avatar
MrGVSV committed
                    if !ignore_layout && was_contained != is_contained {
                        if was_contained {
                            // Mouse out should fire even when
StarToaster's avatar
StarToaster committed
                            event_stream
                                .push(Event::new(node.0, EventType::MouseOut(cursor_event)));
                            // Self::update_state(
                            //     states,
                            //     (node, depth),
                            //     &layout,
                            //     EventType::MouseOut(cursor_event),
                            // );
                            // event_stream.push(Event::new(node.0, EventType::MouseIn(cursor_event)));
                            Self::update_state(
                                states,
                                (node, depth),
                                &layout,
                                EventType::MouseIn(cursor_event),
                            );
                    if self.contains_cursor.is_none() || !self.contains_cursor.unwrap_or_default() {
                        if let Some(styles) = world.get::<ComputedStyles>(node.0) {
                            // Check if the cursor moved onto a widget that qualifies as one that can contain it
                            if ignore_layout || Self::can_contain_cursor(&styles.0) {
                                self.contains_cursor = Some(is_contained);
                            }
                        }
                    }

                    if self.wants_cursor.is_none() || !self.wants_cursor.unwrap_or_default() {
StarToaster's avatar
StarToaster committed
                        let focusable = world.get::<Focusable>(node.0).is_some();
                        // Check if the cursor moved onto a focusable widget (i.e. one that would want it)
StarToaster's avatar
StarToaster committed
                        if focusable {
                            self.wants_cursor = Some(is_contained);
                        }
                    }

                    // Check for hover eligibility
MrGVSV's avatar
MrGVSV committed
                    if ignore_layout || is_contained {
MrGVSV's avatar
MrGVSV committed
                        Self::update_state(
                            states,
                            (node, depth),
StarToaster's avatar
StarToaster committed
                            &layout,
MrGVSV's avatar
MrGVSV committed
                            EventType::Hover(cursor_event),
                        );
                    }
                }
            }
            InputEvent::MouseLeftPress => {
StarToaster's avatar
StarToaster committed
                if let Some(layout) = context.get_layout(&node) {
MrGVSV's avatar
MrGVSV committed
                    if ignore_layout || layout.contains(&self.current_mouse_position) {
                        let cursor_event = self.get_cursor_event(self.current_mouse_position);
                        // event_stream.push(Event::new(node.0, EventType::MouseDown(cursor_event)));
                        Self::update_state(
                            states,
                            (node, depth),
                            &layout,
                            EventType::MouseDown(cursor_event),
                        );
StarToaster's avatar
StarToaster committed
                        if world.get::<Focusable>(node.0).is_some() {
                            Self::update_state(states, (node, depth), &layout, EventType::Focus);

                        if self.has_cursor.is_none() {
                            if let Some(styles) = world.get::<ComputedStyles>(node.0) {
                                // Check if the cursor moved onto a widget that qualifies as one that can contain it
                                if Self::can_contain_cursor(&styles.0) {
                                    self.has_cursor = Some(node);
                                }
                            }
                        }
                    }
                }
            }
            InputEvent::MouseLeftRelease => {
StarToaster's avatar
StarToaster committed
                if let Some(layout) = context.get_layout(&node) {
MrGVSV's avatar
MrGVSV committed
                    if ignore_layout || layout.contains(&self.current_mouse_position) {
                        let cursor_event = self.get_cursor_event(self.current_mouse_position);
                        // event_stream.push(Event::new(node.0, EventType::MouseUp(cursor_event)));
                        Self::update_state(
                            states,
                            (node, depth),
                            &layout,
                            EventType::MouseUp(cursor_event),
                        );
StarToaster's avatar
StarToaster committed
                        // self.last_clicked.set(node);
MrGVSV's avatar
MrGVSV committed
                        if Self::contains_event(
                            &self.previous_events,
                            &node,
                            &EventType::MouseDown(cursor_event),
                        ) {
                            Self::update_state(
                                states,
                                (node, depth),
StarToaster's avatar
StarToaster committed
                                &layout,
MrGVSV's avatar
MrGVSV committed
                                EventType::Click(cursor_event),
                            );
MrGVSV's avatar
MrGVSV committed
            InputEvent::Scroll { dx, dy, is_line } => {
StarToaster's avatar
StarToaster committed
                if let Some(layout) = context.get_layout(&node) {
MrGVSV's avatar
MrGVSV committed
                    // Check for scroll eligibility
                    if ignore_layout || layout.contains(&self.current_mouse_position) {
                        Self::update_state(
                            states,
                            (node, depth),
StarToaster's avatar
StarToaster committed
                            &layout,
MrGVSV's avatar
MrGVSV committed
                            EventType::Scroll(ScrollEvent {
                                delta: if *is_line {
                                    ScrollUnit::Line { x: *dx, y: *dy }
                                } else {
                                    ScrollUnit::Pixel { x: *dx, y: *dy }
MrGVSV's avatar
MrGVSV committed
                                },
MrGVSV's avatar
MrGVSV committed
                            }),
                        );
                    }
                }
            }
StarToaster's avatar
StarToaster committed
    fn resolve_pointer_events(index: WrappedIndex, world: &mut World) -> PointerEvents {
MrGVSV's avatar
MrGVSV committed
        let mut pointer_events = PointerEvents::default();
        if let Some(styles) = world.get::<ComputedStyles>(index.0) {
            pointer_events = styles.0.pointer_events.resolve();
MrGVSV's avatar
MrGVSV committed
        }
        pointer_events
    }

    fn get_cursor_event(&self, position: (f32, f32)) -> CursorEvent {
        let change = self.next_mouse_pressed != self.is_mouse_pressed;
        let pressed = self.next_mouse_pressed;
        CursorEvent {
            position,
            pressed,
            just_pressed: change && pressed,
            just_released: change && !pressed,
        }
    }

StarArawn's avatar
StarArawn committed
    fn process_keyboard_events(
        &mut self,
        input_event: &InputEvent,
        _states: &mut HashMap<EventType, EventState>,
StarToaster's avatar
StarToaster committed
        focus_tree: &FocusTree,
StarArawn's avatar
StarArawn committed
    ) -> Vec<Event> {
        let mut event_stream = Vec::new();
StarToaster's avatar
StarToaster committed
        if let Some(current_focus) = focus_tree.current() {
            match input_event {
StarArawn's avatar
StarArawn committed
                InputEvent::CharEvent { c } => {
StarToaster's avatar
StarToaster committed
                    event_stream.push(Event::new(current_focus.0, EventType::CharInput { c: *c }))
StarArawn's avatar
StarArawn committed
                }
                InputEvent::Keyboard { key, is_pressed } => {
                    // === Modifers === //
                    match key {
StarArawn's avatar
StarArawn committed
                        KeyCode::LControl | KeyCode::RControl => {
                            self.keyboard_modifiers.is_ctrl_pressed = *is_pressed
                        }
                        KeyCode::LShift | KeyCode::RShift => {
                            self.keyboard_modifiers.is_shift_pressed = *is_pressed
                        }
                        KeyCode::LAlt | KeyCode::RAlt => {
                            self.keyboard_modifiers.is_alt_pressed = *is_pressed
                        }
                        KeyCode::LWin | KeyCode::RWin => {
                            self.keyboard_modifiers.is_meta_pressed = *is_pressed
                        }
                        _ => {}
                    }

                    // === Event === //
                    if *is_pressed {
StarArawn's avatar
StarArawn committed
                        event_stream.push(Event::new(
StarToaster's avatar
StarToaster committed
                            current_focus.0,
StarArawn's avatar
StarArawn committed
                            EventType::KeyDown(KeyboardEvent::new(*key, self.keyboard_modifiers)),
                        ))
StarArawn's avatar
StarArawn committed
                        event_stream.push(Event::new(
StarToaster's avatar
StarToaster committed
                            current_focus.0,
StarArawn's avatar
StarArawn committed
                            EventType::KeyUp(KeyboardEvent::new(*key, self.keyboard_modifiers)),
                        ))
                _ => {}
            }
        }

        event_stream
    }

    /// Updates the state data for the given event
StarArawn's avatar
StarArawn committed
    fn update_state(
        states: &mut HashMap<EventType, EventState>,
        tree_node: TreeNode,
        layout: &Rect,
        event_type: EventType,
    ) {
        let state = states.entry(event_type).or_default();

        let (node, depth) = tree_node;
        // Node is at or above best depth and is at or above best z-level
        let mut should_update = depth >= state.best_depth && layout.z_index >= state.best_z_index;
        // OR node is above best z-level
        should_update |= layout.z_index >= state.best_z_index;

        if should_update {
            // dbg!(node.0.id(), layout.z_index);
            state.best_match = Some(node);
            state.best_z_index = layout.z_index;
            state.best_depth = depth;
        }
    }

    /// Checks if the given event map contains a specific event for the given widget
StarToaster's avatar
StarToaster committed
    fn contains_event(events: &EventMap, widget_id: &WrappedIndex, event_type: &EventType) -> bool {
        if let Some(entry) = events.get(widget_id) {
            entry.contains(event_type)
        } else {
            false
        }
    }

    /// Insert an event for a widget in the given event map
StarToaster's avatar
StarToaster committed
    fn insert_event(
        events: &mut EventMap,
        widget_id: &WrappedIndex,
        event_type: EventType,
    ) -> bool {
        let entry = events.entry(*widget_id).or_default();
        entry.insert(event_type)
    }
    /// Checks if the given widget is eligible to "contain" the cursor (i.e. the cursor is considered contained when hovering over it)
    ///
    /// Currently a valid widget is defined as one where:
    /// * RenderCommands is neither `Empty` nor `Layout` nor `Clip`
StarToaster's avatar
StarToaster committed
    fn can_contain_cursor(widget_styles: &KStyle) -> bool {
        let cmds = widget_styles.render_command.resolve();
        !matches!(
            cmds,
            RenderCommand::Empty | RenderCommand::Layout | RenderCommand::Clip
        )
    /// Executes default actions for events
    fn execute_default(&mut self, event: Event, context: &mut KayakRootContext, world: &mut World) {
        match event.event_type {
MrGVSV's avatar
MrGVSV committed
            EventType::KeyDown(evt) => match evt.key() {
                KeyCode::Tab => {
StarToaster's avatar
StarToaster committed
                    let (index, current_focus) =
                        if let Ok(mut focus_tree) = context.focus_tree.try_write() {
                            let current_focus = focus_tree.current();

                            let index = if evt.is_shift_pressed() {
                                focus_tree.prev()
                            } else {
                                focus_tree.next()
                            };
                            (index, current_focus)
                        } else {
                            (None, None)
                        };
MrGVSV's avatar
MrGVSV committed

                    if let Some(index) = index {
StarToaster's avatar
StarToaster committed
                        let mut events = vec![Event::new(index.0, EventType::Focus)];
MrGVSV's avatar
MrGVSV committed
                        if let Some(current_focus) = current_focus {
                            if current_focus != index {
StarToaster's avatar
StarToaster committed
                                events.push(Event::new(current_focus.0, EventType::Blur));
StarToaster's avatar
StarToaster committed
                        if let Ok(mut focus_tree) = context.focus_tree.try_write() {
                            focus_tree.focus(index);
                        }
                        self.dispatch_events(events, context, world);
MrGVSV's avatar
MrGVSV committed
                _ => {}
            },
MrGVSV's avatar
MrGVSV committed

    /// Merge this `EventDispatcher` with another, taking only the internally mutated data.
    ///
StarToaster's avatar
StarToaster committed
    /// This is meant to solve the issue in `Context`, where [`EventDispatcher::process_events`] and
    /// similar methods require mutable access to `Context`, forcing `EventDispatcher` to be cloned
    /// before running the method. However, some data mutated through `Context` may be lost when
MrGVSV's avatar
MrGVSV committed
    /// re-claiming the `EventDispatcher`. This method ensures that data mutated in such a way will not be
    /// overwritten during the merge.
    ///
    /// # Arguments
    ///
    /// * `from`: The other `EventDispatcher` to merge from
    ///
    /// returns: ()
    pub fn merge(&mut self, from: EventDispatcher) {
StarToaster's avatar
StarToaster committed
        // Merge only what could be changed internally. External changes (i.e. from Context)
MrGVSV's avatar
MrGVSV committed
        // should not be touched
StarToaster's avatar
StarToaster committed
        // self.last_clicked = from.last_clicked;
MrGVSV's avatar
MrGVSV committed
        self.is_mouse_pressed = from.is_mouse_pressed;
        self.next_mouse_pressed = from.next_mouse_pressed;
        self.current_mouse_position = from.current_mouse_position;
        self.next_mouse_position = from.next_mouse_position;
        self.previous_events = from.previous_events;
        self.keyboard_modifiers = from.keyboard_modifiers;
        self.contains_cursor = from.contains_cursor;
        self.wants_cursor = from.wants_cursor;
        self.has_cursor = from.has_cursor;
        self.hovered = from.hovered;
MrGVSV's avatar
MrGVSV committed

        // Do not include:
        // self.cursor_capture = from.cursor_capture;
    }
StarToaster's avatar
StarToaster committed

pub struct EventDispatcherContext {
    cursor_capture: Option<WrappedIndex>,
}

impl EventDispatcherContext {
    /// Captures all cursor events and instead makes the given index the target
    pub fn capture_cursor(&mut self, index: Entity) -> Option<WrappedIndex> {
        let old = self.cursor_capture;
        self.cursor_capture = Some(WrappedIndex(index));
        old
    }

    /// Releases the captured cursor
    ///
    /// Returns true if successful.
    ///
    /// This will only release the cursor if the given index matches the current captor. This
    /// prevents other widgets from accidentally releasing against the will of the original captor.
    ///
    /// This check can be side-stepped if necessary by calling [`force_release_cursor`](Self::force_release_cursor)
    /// instead (or by calling this method with the correct index).
    pub fn release_cursor(&mut self, index: Entity) -> bool {
        if self.cursor_capture == Some(WrappedIndex(index)) {
            self.force_release_cursor();
            true
        } else {
            false
        }
    }

    /// Releases the captured cursor
    ///
    /// Returns the index of the previous captor.
    ///
    /// This will force the release, regardless of which widget has called it. To safely release,
    /// use the standard [`release_cursor`](Self::release_cursor) method instead.
    pub fn force_release_cursor(&mut self) -> Option<WrappedIndex> {
        let old = self.cursor_capture;
        self.cursor_capture = None;
        old
    }

    pub(crate) fn merge(self, event_dispatcher: &mut EventDispatcher) {
        event_dispatcher.cursor_capture = self.cursor_capture;
    }
}