Newer
Older
prelude::{Component, Entity, KeyCode, World},
context::KayakRootContext,
cursor::{CursorEvent, PointerEvents, ScrollEvent, ScrollUnit},
event::{Event, EventType},
focus_tree::FocusTree,
input_event::{InputEvent, InputEventCategory},
keyboard_event::{KeyboardEvent, KeyboardModifiers},
layout::Rect,
node::{Node, WrappedIndex},
prelude::KayakWidgetContext,
styles::{ComputedStyles, KStyle, RenderCommand},
type EventMap = HashMap<WrappedIndex, HashSet<EventType>>;
// The node depth
isize,
);
#[derive(Debug, Clone)]
struct EventState {
best_z_index: f32,
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 {
next_mouse_position: (f32, f32),
previous_events: EventMap,
keyboard_modifiers: KeyboardModifiers,
contains_cursor: Option<bool>,
wants_cursor: Option<bool>,
pub(crate) cursor_capture: Option<WrappedIndex>,
pub(crate) hovered: Option<WrappedIndex>,
pub(crate) fn new() -> Self {
// last_clicked: Binding::new(WrappedIndex(Entity::from_raw(0))),
is_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,
/// 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
}
/// Captures all cursor events and instead makes the given index the target
pub fn capture_cursor(&mut self, index: Entity) -> Option<WrappedIndex> {
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
}
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/// 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)]
/// Process and dispatch an [InputEvent](crate::InputEvent)
// #[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(
input_events: &Vec<InputEvent>,
context: &mut KayakRootContext,
let events = { self.build_event_stream(input_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,
) {
}
/// Dispatch a set of [Events](crate::Event)
pub fn dispatch_events(
&mut self,
events: Vec<Event>,
context: &mut KayakRootContext,
// === Dispatch Events === //
let mut next_events = HashMap::default();
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 {
};
// --- Update State --- //
Self::insert_event(&mut next_events, &index, node_event.event_type);
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,
);
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(
context.tree.clone(),
context.context_entities.clone(),
context.layout_cache.clone(),
context.widget_state.clone(),
context.order_tree.clone(),
context.index.clone(),
);
node_event.run_on_change(world, widget_context);
}
}
event.default_prevented |= node_event.default_prevented;
// --- Propagate Event --- //
if node_event.should_propagate {
if let Ok(node_tree) = context.tree.try_read() {
current_target = node_tree.get_parent(index);
} else {
current_target = None;
}
} else {
current_target = None;
}
}
}
// === 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
if self.is_mouse_pressed && events.contains(&EventType::MouseDown(Default::default())) {
// Make sure this event isn't removed while mouse is still held down
Self::insert_event(
&mut next_events,
index,
EventType::MouseDown(Default::default()),
);
}
// Mouse is currently within this node
&next_events,
index,
&EventType::MouseOut(Default::default()),
)
// Make sure this event isn't removed while mouse is still within node
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)
fn build_event_stream(
&mut self,
input_events: &[InputEvent],
context: &mut KayakRootContext,
let mut event_stream = Vec::<Event>::new();
let mut states: HashMap<EventType, EventState> = HashMap::new();
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
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;
// === 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
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
let pointer_events = Self::resolve_pointer_events(captor, world);
match pointer_events {
PointerEvents::All | PointerEvents::SelfOnly => {
let events = self.process_pointer_events(
input_event,
} else {
// No capturing widget -> process cursor events as normal
let mut stack: Vec<TreeNode> = vec![(root, 0)];
while !stack.is_empty() {
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
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 => {}
}
}
}
}
}
// --- Push Children to Stack --- //
if enter_children {
if let Some(children) = node_tree.children.get(¤t) {
let mut stack_children = Vec::new();
let child_z = world
.entity(child.0)
.unwrap_or(0.0);
stack_children.push((child_z, (*child, depth + 1)));
stack_children.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
stack.extend(stack_children.iter().map(|c| c.1));
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);
}
// === 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);
// --- 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();
}
}
// === Process Cursor States === //
self.current_mouse_position = self.next_mouse_position;
self.is_mouse_pressed = self.next_mouse_pressed;
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;
}
}
/// 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>
fn process_pointer_events(
&mut self,
input_event: &InputEvent,
tree_node: TreeNode,
states: &mut HashMap<EventType, EventState>,
context: &KayakRootContext,
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) => {
let was_contained = layout.contains(&self.current_mouse_position);
let is_contained = layout.contains(point);
// Mouse out should fire even when
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() {
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)
self.wants_cursor = Some(is_contained);
}
}
}
}
}
InputEvent::MouseLeftPress => {
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),
);
if world.get::<Focusable>(node.0).is_some() {
Self::update_state(states, (node, depth), &layout, EventType::Focus);
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 => {
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),
);
if Self::contains_event(
&self.previous_events,
&node,
&EventType::MouseDown(cursor_event),
) {
Self::update_state(
states,
(node, depth),
// Check for scroll eligibility
if ignore_layout || layout.contains(&self.current_mouse_position) {
Self::update_state(
states,
(node, depth),
EventType::Scroll(ScrollEvent {
delta: if *is_line {
ScrollUnit::Line { x: *dx, y: *dy }
} else {
ScrollUnit::Pixel { x: *dx, y: *dy }
_ => {}
}
event_stream
}
fn resolve_pointer_events(index: WrappedIndex, world: &mut World) -> PointerEvents {
if let Some(styles) = world.get::<ComputedStyles>(index.0) {
pointer_events = styles.0.pointer_events.resolve();
}
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,
}
}
fn process_keyboard_events(
&mut self,
input_event: &InputEvent,
_states: &mut HashMap<EventType, EventState>,
if let Some(current_focus) = focus_tree.current() {
event_stream.push(Event::new(current_focus.0, EventType::CharInput { c: *c }))
InputEvent::Keyboard { key, is_pressed } => {
// === Modifers === //
match key {
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 {
EventType::KeyDown(KeyboardEvent::new(*key, self.keyboard_modifiers)),
))
EventType::KeyUp(KeyboardEvent::new(*key, self.keyboard_modifiers)),
))
_ => {}
}
}
event_stream
}
/// Updates the state data for the given event
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;
// 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
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
fn insert_event(
events: &mut EventMap,
widget_id: &WrappedIndex,
event_type: EventType,
) -> bool {
let entry = events.entry(*widget_id).or_default();
/// 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`
fn can_contain_cursor(widget_styles: &KStyle) -> bool {
let cmds = widget_styles.render_command.resolve();
!matches!(
cmds,
RenderCommand::Empty | RenderCommand::Layout | RenderCommand::Clip
)
fn execute_default(&mut self, event: Event, context: &mut KayakRootContext, world: &mut World) {
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)
};
let mut events = vec![Event::new(index.0, EventType::Focus)];
if let Some(current_focus) = current_focus {
if current_focus != index {
events.push(Event::new(current_focus.0, EventType::Blur));
if let Ok(mut focus_tree) = context.focus_tree.try_write() {
focus_tree.focus(index);
}
self.dispatch_events(events, context, world);
/// Merge this `EventDispatcher` with another, taking only the internally mutated data.
///
/// 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
/// 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) {
// Merge only what could be changed internally. External changes (i.e. from Context)
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;
// Do not include:
// self.cursor_capture = from.cursor_capture;
}
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
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;
}
}