dioxus/packages/rink/src/hooks.rs

956 lines
35 KiB
Rust
Raw Normal View History

2022-02-05 22:28:19 +00:00
use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
2023-03-13 22:49:37 +00:00
use dioxus_native_core::prelude::*;
2023-01-28 20:51:05 +00:00
use dioxus_native_core::real_dom::NodeImmutable;
use rustc_hash::{FxHashMap, FxHashSet};
2022-02-05 22:28:19 +00:00
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
use dioxus_html::geometry::{
ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta,
};
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
2023-02-07 03:28:48 +00:00
use dioxus_html::{event_bubbles, EventData, FocusData, KeyboardData, MouseData, WheelData};
2022-01-12 14:40:36 +00:00
use std::{
cell::{RefCell, RefMut},
2022-01-12 14:40:36 +00:00
rc::Rc,
2022-02-05 22:28:19 +00:00
time::{Duration, Instant},
2022-01-12 14:40:36 +00:00
};
use taffy::geometry::{Point, Size};
2022-06-10 22:49:04 +00:00
use taffy::{prelude::Layout, Taffy};
2022-02-07 11:57:57 +00:00
2023-01-28 20:51:05 +00:00
use crate::focus::{Focus, Focused};
use crate::layout::TaffyLayout;
use crate::{layout_to_screen_space, FocusState};
pub(crate) struct Event {
2023-02-07 03:28:48 +00:00
pub id: NodeId,
pub name: &'static str,
2023-02-07 03:28:48 +00:00
pub data: Rc<EventData>,
pub bubbles: bool,
}
2022-01-12 14:40:36 +00:00
2022-02-05 22:28:19 +00:00
type EventCore = (&'static str, EventData);
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
2022-02-04 20:52:01 +00:00
2022-02-05 22:28:19 +00:00
pub struct InnerInputState {
mouse: Option<MouseData>,
2022-02-05 22:28:19 +00:00
wheel: Option<WheelData>,
last_key_pressed: Option<(KeyboardData, Instant)>,
pub(crate) focus_state: FocusState,
2022-02-05 22:28:19 +00:00
// subscribers: Vec<Rc<dyn Fn() + 'static>>,
}
2022-02-04 20:52:01 +00:00
2022-02-05 22:28:19 +00:00
impl InnerInputState {
2023-02-07 03:28:48 +00:00
fn create(rdom: &mut RealDom) -> Self {
2022-01-12 14:40:36 +00:00
Self {
2022-02-05 22:28:19 +00:00
mouse: None,
wheel: None,
last_key_pressed: None,
// subscribers: Vec::new(),
2023-02-07 03:28:48 +00:00
focus_state: FocusState::create(rdom),
2022-01-12 14:40:36 +00:00
}
}
2022-02-04 20:52:01 +00:00
2022-02-05 22:28:19 +00:00
// stores current input state and transforms events based on that state
fn apply_event(&mut self, evt: &mut EventCore) {
match evt.1 {
2022-02-06 13:08:15 +00:00
// limitations: only two buttons may be held at once
EventData::Mouse(ref mut m) => {
let mut held_buttons = match &self.mouse {
Some(previous_data) => previous_data.held_buttons(),
None => MouseButtonSet::empty(),
};
match evt.0 {
"mousedown" => {
held_buttons.insert(
m.trigger_button()
.expect("No trigger button for mousedown event"),
);
2022-02-06 13:08:15 +00:00
}
"mouseup" => {
held_buttons.remove(
m.trigger_button()
.expect("No trigger button for mouseup event"),
);
}
_ => {}
2022-02-05 22:28:19 +00:00
}
let new_mouse_data = MouseData::new(
m.coordinates(),
m.trigger_button(),
held_buttons,
m.modifiers(),
);
self.mouse = Some(new_mouse_data.clone());
*m = new_mouse_data;
}
2022-04-17 12:29:35 +00:00
EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
2022-02-05 22:28:19 +00:00
EventData::Keyboard(ref mut k) => {
2022-05-12 11:36:52 +00:00
let is_repeating = self
2022-02-05 22:28:19 +00:00
.last_key_pressed
.as_ref()
2022-05-12 11:36:52 +00:00
// heuristic for guessing which presses are auto-repeating. not necessarily accurate
2022-06-29 04:44:19 +00:00
.filter(|(last_data, last_instant)| {
last_data.key() == k.key() && last_instant.elapsed() < MAX_REPEAT_TIME
})
2022-02-05 22:28:19 +00:00
.is_some();
2022-05-12 11:36:52 +00:00
2022-06-29 04:44:19 +00:00
if is_repeating {
2022-05-12 11:36:52 +00:00
*k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers());
}
2022-06-29 04:44:19 +00:00
self.last_key_pressed = Some((k.clone(), Instant::now()));
2022-02-05 22:28:19 +00:00
}
2023-02-07 03:28:48 +00:00
_ => {}
2022-02-05 22:28:19 +00:00
}
2022-02-04 20:52:01 +00:00
}
2022-04-02 21:46:46 +00:00
fn update(
2022-02-07 11:57:57 +00:00
&mut self,
2022-05-03 21:44:53 +00:00
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<Event>,
2022-06-10 22:23:30 +00:00
layout: &Taffy,
2023-01-28 20:51:05 +00:00
dom: &mut RealDom,
2022-03-23 19:18:17 +00:00
) {
let previous_mouse = self.mouse.clone();
2022-03-23 19:18:17 +00:00
self.wheel = None;
2022-05-05 12:17:33 +00:00
let old_focus = self.focus_state.last_focused_id;
2022-05-03 21:44:53 +00:00
evts.retain(|e| match &e.1 {
2022-06-28 20:10:44 +00:00
EventData::Keyboard(k) => match k.code() {
Code::Tab => !self
.focus_state
.progress(dom, !k.modifiers().contains(Modifiers::SHIFT)),
2022-05-03 21:44:53 +00:00
_ => true,
},
_ => true,
});
2022-03-23 19:18:17 +00:00
for e in evts.iter_mut() {
self.apply_event(e);
}
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
2022-03-23 19:18:17 +00:00
2022-05-05 12:17:33 +00:00
if old_focus != self.focus_state.last_focused_id {
// elements with listeners will always have a element id
if let Some(id) = self.focus_state.last_focused_id {
2023-02-07 03:28:48 +00:00
resolved_events.push(Event {
name: "focus",
id,
data: Rc::new(EventData::Focus(FocusData {})),
bubbles: event_bubbles("focus"),
});
resolved_events.push(Event {
name: "focusin",
id,
data: Rc::new(EventData::Focus(FocusData {})),
bubbles: event_bubbles("focusin"),
});
2022-05-07 13:39:55 +00:00
}
if let Some(id) = old_focus {
2023-02-07 03:28:48 +00:00
resolved_events.push(Event {
name: "focusout",
id,
data: Rc::new(EventData::Focus(FocusData {})),
bubbles: event_bubbles("focusout"),
});
2022-05-05 12:17:33 +00:00
}
}
2022-03-23 19:18:17 +00:00
// for s in &self.subscribers {
// s();
// }
}
fn resolve_mouse_events(
2022-05-13 01:52:06 +00:00
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<Event>,
2022-06-10 22:23:30 +00:00
layout: &Taffy,
2023-01-28 20:51:05 +00:00
dom: &mut RealDom,
2022-02-07 11:57:57 +00:00
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
let Point { x, y } = layout.location;
let (x, y) = (
layout_to_screen_space(x).round(),
layout_to_screen_space(y).round(),
);
let Size { width, height } = layout.size;
let (width, height) = (
layout_to_screen_space(width).round(),
layout_to_screen_space(height).round(),
);
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
layout_rect.contains(point.cast())
2022-02-04 20:52:01 +00:00
}
2022-02-07 11:57:57 +00:00
2022-03-23 19:18:17 +00:00
fn try_create_event(
name: &'static str,
2023-02-07 03:28:48 +00:00
data: Rc<EventData>,
will_bubble: &mut FxHashSet<NodeId>,
resolved_events: &mut Vec<Event>,
2023-01-28 20:51:05 +00:00
node: NodeRef,
dom: &RealDom,
2022-03-23 19:18:17 +00:00
) {
// only trigger event if the event was not triggered already by a child
2023-02-07 03:28:48 +00:00
let id = node.id();
if will_bubble.insert(id) {
2023-01-28 20:51:05 +00:00
let mut parent = Some(node);
while let Some(current_parent) = parent {
2023-02-07 03:28:48 +00:00
let parent_id = current_parent.id();
2022-03-23 19:18:17 +00:00
will_bubble.insert(parent_id);
2023-01-28 20:51:05 +00:00
parent = current_parent.parent_id().and_then(|id| dom.get(id));
}
2023-02-07 03:28:48 +00:00
resolved_events.push(Event {
name,
id,
data,
bubbles: event_bubbles(name),
})
2022-02-07 11:57:57 +00:00
}
}
2022-05-04 18:58:48 +00:00
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
2022-05-07 12:32:19 +00:00
let Point { x, y } = layout.location;
2022-12-08 12:40:00 +00:00
let node_origin = ClientPoint::new(
layout_to_screen_space(x).into(),
layout_to_screen_space(y).into(),
);
2022-05-07 12:32:19 +00:00
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
.to_point()
.cast_unit();
let coordinates = Coordinates::new(
mouse_data.screen_coordinates(),
mouse_data.client_coordinates(),
new_client_coordinates,
mouse_data.page_coordinates(),
);
MouseData::new(
coordinates,
mouse_data.trigger_button(),
mouse_data.held_buttons(),
mouse_data.modifiers(),
)
2022-05-04 18:58:48 +00:00
}
if let Some(mouse_data) = &self.mouse {
let new_pos = mouse_data.screen_coordinates();
let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates());
// a mouse button is pressed if a button was not down and is now down
let previous_buttons = previous_mouse
.map_or(MouseButtonSet::empty(), |previous_data| {
previous_data.held_buttons()
});
let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty();
// a mouse button is released if a button was down and is now not down
let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
let was_scrolled = self
.wheel
.as_ref()
.map_or(false, |data| !data.delta().is_zero());
2022-02-07 11:57:57 +00:00
let wheel_data = &self.wheel;
2022-03-23 19:18:17 +00:00
{
// mousemove
if old_pos != Some(new_pos) {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousemove") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let previously_contained = old_pos
2022-06-14 00:42:03 +00:00
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
2022-06-14 00:42:03 +00:00
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
if currently_contains && previously_contained {
try_create_event(
"mousemove",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
2022-03-23 19:18:17 +00:00
}
}
}
{
// mouseenter
2022-03-27 01:10:15 +00:00
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseenter") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
2022-05-04 18:58:48 +00:00
let previously_contained = old_pos
2022-06-14 00:42:03 +00:00
.filter(|pos| layout_contains_point(&node_layout, *pos))
2022-03-23 19:18:17 +00:00
.is_some();
2022-06-14 00:42:03 +00:00
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
2022-04-02 21:46:46 +00:00
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
2023-02-07 03:28:48 +00:00
Rc::new(dioxus_html::EventData::Mouse(mouse_data.clone())),
2022-04-02 21:46:46 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-04-02 21:46:46 +00:00
);
2022-03-23 19:18:17 +00:00
}
}
}
{
// mouseover
2022-03-27 01:10:15 +00:00
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseover") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
2022-05-04 18:58:48 +00:00
let previously_contained = old_pos
2022-06-14 00:42:03 +00:00
.filter(|pos| layout_contains_point(&node_layout, *pos))
2022-03-23 19:18:17 +00:00
.is_some();
2022-06-14 00:42:03 +00:00
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
2022-04-02 21:46:46 +00:00
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
2022-04-02 21:46:46 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-04-02 21:46:46 +00:00
);
2022-03-23 19:18:17 +00:00
}
}
}
2022-05-07 00:35:55 +00:00
// mousedown
if was_pressed {
2022-03-27 01:10:15 +00:00
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousedown") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
2022-05-07 00:48:57 +00:00
if currently_contains {
2022-04-02 21:46:46 +00:00
try_create_event(
"mousedown",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
2022-04-02 21:46:46 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-04-02 21:46:46 +00:00
);
2022-03-23 19:18:17 +00:00
}
}
}
{
// mouseup
if was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseup") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
if currently_contains {
try_create_event(
"mouseup",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
2022-03-23 19:18:17 +00:00
}
}
}
{
// click
if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("click") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
2022-05-07 14:16:21 +00:00
if currently_contains {
try_create_event(
"click",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
2022-03-23 19:18:17 +00:00
}
}
}
{
// contextmenu
if mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) && was_released
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("contextmenu") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
2022-05-07 14:16:21 +00:00
if currently_contains {
2022-04-02 21:46:46 +00:00
try_create_event(
"contextmenu",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
2022-04-02 21:46:46 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-04-02 21:46:46 +00:00
);
2022-03-23 19:18:17 +00:00
}
}
}
}
{
// wheel
if let Some(w) = wheel_data {
if was_scrolled {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("wheel") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"wheel",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Wheel(w.clone())),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
}
2022-03-23 19:18:17 +00:00
{
// mouseleave
2022-03-27 01:10:15 +00:00
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseleave") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
2022-05-04 18:58:48 +00:00
let previously_contained = old_pos
2022-06-14 00:42:03 +00:00
.filter(|pos| layout_contains_point(&node_layout, *pos))
2022-03-23 19:18:17 +00:00
.is_some();
2022-06-14 00:42:03 +00:00
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
2022-03-23 19:18:17 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-03-23 19:18:17 +00:00
);
}
}
}
{
// mouseout
2022-03-27 01:10:15 +00:00
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseout") {
2022-06-14 00:42:03 +00:00
let node_layout = get_abs_layout(node, dom, layout);
2022-05-04 18:58:48 +00:00
let previously_contained = old_pos
2022-06-14 00:42:03 +00:00
.filter(|pos| layout_contains_point(&node_layout, *pos))
2022-03-23 19:18:17 +00:00
.is_some();
2022-06-14 00:42:03 +00:00
let currently_contains = layout_contains_point(&node_layout, new_pos);
2022-03-23 19:18:17 +00:00
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
2023-02-07 03:28:48 +00:00
Rc::new(EventData::Mouse(prepare_mouse_data(
mouse_data,
&node_layout,
))),
2022-03-23 19:18:17 +00:00
&mut will_bubble,
resolved_events,
node,
dom,
2022-03-23 19:18:17 +00:00
);
}
}
}
// update focus
2022-05-13 01:52:06 +00:00
if was_released {
let mut focus_id = None;
dom.traverse_depth_first(|node| {
2023-01-28 20:51:05 +00:00
let node_layout = layout
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
.unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
2023-01-28 20:51:05 +00:00
if currently_contains && node.get::<Focus>().unwrap().level.focusable() {
2023-02-07 03:28:48 +00:00
focus_id = Some(node.id());
}
});
if let Some(id) = focus_id {
self.focus_state.set_focus(dom, id);
}
}
2022-03-23 19:18:17 +00:00
}
2022-01-12 14:40:36 +00:00
}
2022-02-05 22:28:19 +00:00
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
// self.subscribers.push(f)
// }
}
2022-01-12 14:40:36 +00:00
2023-01-28 20:51:05 +00:00
fn get_abs_layout(node: NodeRef, dom: &RealDom, taffy: &Taffy) -> Layout {
let mut node_layout = *taffy
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
.unwrap();
2022-06-14 00:42:03 +00:00
let mut current = node;
2023-01-28 20:51:05 +00:00
while let Some(parent) = current.parent_id() {
let parent = dom.get(parent).unwrap();
2022-06-14 00:42:03 +00:00
current = parent;
2023-01-28 20:51:05 +00:00
let parent_layout = taffy
.layout(parent.get::<TaffyLayout>().unwrap().node.unwrap())
.unwrap();
2022-06-14 00:42:03 +00:00
node_layout.location.x += parent_layout.location.x;
node_layout.location.y += parent_layout.location.y;
}
node_layout
}
2022-02-05 22:28:19 +00:00
pub struct RinkInputHandler {
state: Rc<RefCell<InnerInputState>>,
queued_events: Rc<RefCell<Vec<EventCore>>>,
2022-01-12 14:40:36 +00:00
}
2022-02-05 22:28:19 +00:00
impl RinkInputHandler {
/// global context that handles events
2022-02-06 13:08:15 +00:00
/// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
2023-02-07 20:51:30 +00:00
pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) {
2022-02-05 22:28:19 +00:00
let queued_events = Rc::new(RefCell::new(Vec::new()));
2022-03-23 19:18:17 +00:00
let queued_events2 = Rc::downgrade(&queued_events);
2022-02-05 22:28:19 +00:00
2022-04-02 21:46:46 +00:00
let regester_event = move |evt: crossterm::event::Event| {
if let Some(evt) = get_event(evt) {
if let Some(v) = queued_events2.upgrade() {
(*v).borrow_mut().push(evt);
2022-02-04 20:52:01 +00:00
}
2022-02-05 22:28:19 +00:00
}
2022-04-02 21:46:46 +00:00
};
2022-02-05 22:28:19 +00:00
2023-02-07 03:28:48 +00:00
let state = Rc::new(RefCell::new(InnerInputState::create(rdom)));
2022-02-05 22:28:19 +00:00
(
Self {
2023-02-07 20:51:30 +00:00
state,
2022-02-05 22:28:19 +00:00
queued_events,
},
2022-04-02 21:46:46 +00:00
regester_event,
2022-02-05 22:28:19 +00:00
)
}
2023-01-28 20:51:05 +00:00
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut RealDom) -> Vec<Event> {
2022-02-05 22:28:19 +00:00
let mut resolved_events = Vec::new();
2022-02-07 11:57:57 +00:00
(*self.state).borrow_mut().update(
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
dom,
2022-02-07 11:57:57 +00:00
);
2022-02-05 22:28:19 +00:00
2022-03-23 19:18:17 +00:00
let events = self
2022-02-05 22:28:19 +00:00
.queued_events
.replace(Vec::new())
.into_iter()
2022-02-07 11:57:57 +00:00
// these events were added in the update stage
2022-03-23 19:18:17 +00:00
.filter(|e| {
![
"mouseenter",
"mouseover",
"mouseleave",
"mouseout",
"mousedown",
"mouseup",
"mousemove",
"drag",
"wheel",
"click",
"contextmenu",
]
.contains(&e.0)
})
2023-02-07 03:28:48 +00:00
.map(|evt| (evt.0, evt.1));
2022-02-05 22:28:19 +00:00
2023-02-07 03:28:48 +00:00
let mut hm: FxHashMap<&'static str, Vec<Rc<EventData>>> = FxHashMap::default();
2022-03-23 19:18:17 +00:00
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
2023-02-07 03:28:48 +00:00
v.push(Rc::new(data));
2022-03-23 19:18:17 +00:00
} else {
2023-02-07 03:28:48 +00:00
hm.insert(event, vec![Rc::new(data)]);
2022-03-23 19:18:17 +00:00
}
}
for (event, datas) in hm {
for node in dom.get_listening_sorted(event) {
2022-03-23 19:18:17 +00:00
for data in &datas {
2023-02-01 20:36:56 +00:00
let focused = node.get::<Focused>();
if focused.is_some() && focused.unwrap().0 {
2023-02-07 03:28:48 +00:00
resolved_events.push(Event {
name: event,
id: node.id(),
data: data.clone(),
bubbles: event_bubbles(event),
});
2022-05-03 16:02:35 +00:00
}
2022-03-23 19:18:17 +00:00
}
}
}
2022-02-05 22:28:19 +00:00
2022-02-07 11:57:57 +00:00
resolved_events
2022-02-05 22:28:19 +00:00
}
pub(crate) fn state(&self) -> RefMut<InnerInputState> {
self.state.borrow_mut()
2022-02-05 22:28:19 +00:00
}
}
2022-02-07 11:57:57 +00:00
// translate crossterm events into dioxus events
2022-02-05 22:28:19 +00:00
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
let (name, data): (&str, EventData) = match evt {
2022-03-10 03:06:45 +00:00
TermEvent::Key(k) => ("keydown", translate_key_event(k)?),
2022-02-05 22:28:19 +00:00
TermEvent::Mouse(m) => {
let (x, y) = (m.column.into(), m.row.into());
let alt = m.modifiers.contains(KeyModifiers::ALT);
let shift = m.modifiers.contains(KeyModifiers::SHIFT);
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
let meta = false;
let get_mouse_data = |crossterm_button: Option<MouseButton>| {
let button = crossterm_button.map(|b| match b {
MouseButton::Left => DioxusMouseButton::Primary,
MouseButton::Right => DioxusMouseButton::Secondary,
MouseButton::Middle => DioxusMouseButton::Auxiliary,
});
2022-02-05 22:28:19 +00:00
// from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
2022-05-04 18:58:48 +00:00
// The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
// todo?
// But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
let coordinates = Coordinates::new(
ScreenPoint::new(x, y),
ClientPoint::new(x, y),
2022-05-04 18:58:48 +00:00
// offset x/y are set when the origin of the event is assigned to an element
ElementPoint::new(0., 0.),
PagePoint::new(x, y),
);
let mut modifiers = Modifiers::empty();
if shift {
modifiers.insert(Modifiers::SHIFT);
}
if ctrl {
modifiers.insert(Modifiers::CONTROL);
}
if meta {
modifiers.insert(Modifiers::META);
}
if alt {
modifiers.insert(Modifiers::ALT);
}
// held mouse buttons get set later by maintaining state, as crossterm does not provide them
EventData::Mouse(MouseData::new(
coordinates,
button,
DioxusMouseButtons::empty(),
modifiers,
))
2022-02-05 22:28:19 +00:00
};
let get_wheel_data = |up| {
let y = if up { -1.0 } else { 1.0 };
EventData::Wheel(WheelData::new(WheelDelta::lines(0., y, 0.)))
2022-02-05 22:28:19 +00:00
};
match m.kind {
MouseEventKind::Down(b) => ("mousedown", get_mouse_data(Some(b))),
MouseEventKind::Up(b) => ("mouseup", get_mouse_data(Some(b))),
MouseEventKind::Drag(b) => ("drag", get_mouse_data(Some(b))),
MouseEventKind::Moved => ("mousemove", get_mouse_data(None)),
2022-02-05 22:45:40 +00:00
MouseEventKind::ScrollDown => ("wheel", get_wheel_data(false)),
MouseEventKind::ScrollUp => ("wheel", get_wheel_data(true)),
2022-01-12 14:40:36 +00:00
}
}
2023-02-07 03:28:48 +00:00
_ => return None,
2022-02-05 22:28:19 +00:00
};
Some((name, data))
}
2022-03-10 03:06:45 +00:00
fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
let key = key_from_crossterm_key_code(event.code);
// crossterm does not provide code. we make a guess as to which key might have been pressed
// this is probably garbage if the user has a custom keyboard layout
let code = guess_code_from_crossterm_key_code(event.code)?;
let modifiers = modifiers_from_crossterm_modifiers(event.modifiers);
Some(EventData::Keyboard(KeyboardData::new(
key,
code,
Location::Standard,
false,
modifiers,
)))
}
/// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues
///
/// Exceptions:
/// BackTab is converted to Key::Tab, and Null is converted to Key::Unidentified
fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key {
match key_code {
TermKeyCode::Backspace => Key::Backspace,
TermKeyCode::Enter => Key::Enter,
TermKeyCode::Left => Key::ArrowLeft,
TermKeyCode::Right => Key::ArrowRight,
TermKeyCode::Up => Key::ArrowUp,
TermKeyCode::Down => Key::ArrowDown,
TermKeyCode::Home => Key::Home,
TermKeyCode::End => Key::End,
TermKeyCode::PageUp => Key::PageUp,
TermKeyCode::PageDown => Key::PageDown,
TermKeyCode::Tab => Key::Tab,
// ? no corresponding Key
TermKeyCode::BackTab => Key::Tab,
TermKeyCode::Delete => Key::Delete,
TermKeyCode::Insert => Key::Insert,
TermKeyCode::F(1) => Key::F1,
TermKeyCode::F(2) => Key::F2,
TermKeyCode::F(3) => Key::F3,
TermKeyCode::F(4) => Key::F4,
TermKeyCode::F(5) => Key::F5,
TermKeyCode::F(6) => Key::F6,
TermKeyCode::F(7) => Key::F7,
TermKeyCode::F(8) => Key::F8,
TermKeyCode::F(9) => Key::F9,
TermKeyCode::F(10) => Key::F10,
TermKeyCode::F(11) => Key::F11,
TermKeyCode::F(12) => Key::F12,
TermKeyCode::F(13) => Key::F13,
TermKeyCode::F(14) => Key::F14,
TermKeyCode::F(15) => Key::F15,
TermKeyCode::F(16) => Key::F16,
TermKeyCode::F(17) => Key::F17,
TermKeyCode::F(18) => Key::F18,
TermKeyCode::F(19) => Key::F19,
TermKeyCode::F(20) => Key::F20,
TermKeyCode::F(21) => Key::F21,
TermKeyCode::F(22) => Key::F22,
TermKeyCode::F(23) => Key::F23,
TermKeyCode::F(24) => Key::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
TermKeyCode::Char(c) => Key::Character(c.to_string()),
TermKeyCode::Null => Key::Unidentified,
TermKeyCode::Esc => Key::Escape,
}
}
// Crossterm does not provide a way to get the `code` (physical key on keyboard)
// So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard
// It may look fine, but it's a horrible hack. But there's nothing better we can do.
fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option<Code> {
let code = match key_code {
TermKeyCode::Backspace => Code::Backspace,
TermKeyCode::Enter => Code::Enter,
TermKeyCode::Left => Code::ArrowLeft,
TermKeyCode::Right => Code::ArrowRight,
TermKeyCode::Up => Code::ArrowUp,
TermKeyCode::Down => Code::ArrowDown,
TermKeyCode::Home => Code::Home,
TermKeyCode::End => Code::End,
TermKeyCode::PageUp => Code::PageUp,
TermKeyCode::PageDown => Code::PageDown,
TermKeyCode::Tab => Code::Tab,
// ? Apparently you get BackTab by pressing Tab
TermKeyCode::BackTab => Code::Tab,
TermKeyCode::Delete => Code::Delete,
TermKeyCode::Insert => Code::Insert,
TermKeyCode::F(1) => Code::F1,
TermKeyCode::F(2) => Code::F2,
TermKeyCode::F(3) => Code::F3,
TermKeyCode::F(4) => Code::F4,
TermKeyCode::F(5) => Code::F5,
TermKeyCode::F(6) => Code::F6,
TermKeyCode::F(7) => Code::F7,
TermKeyCode::F(8) => Code::F8,
TermKeyCode::F(9) => Code::F9,
TermKeyCode::F(10) => Code::F10,
TermKeyCode::F(11) => Code::F11,
TermKeyCode::F(12) => Code::F12,
TermKeyCode::F(13) => Code::F13,
TermKeyCode::F(14) => Code::F14,
TermKeyCode::F(15) => Code::F15,
TermKeyCode::F(16) => Code::F16,
TermKeyCode::F(17) => Code::F17,
TermKeyCode::F(18) => Code::F18,
TermKeyCode::F(19) => Code::F19,
TermKeyCode::F(20) => Code::F20,
TermKeyCode::F(21) => Code::F21,
TermKeyCode::F(22) => Code::F22,
TermKeyCode::F(23) => Code::F23,
TermKeyCode::F(24) => Code::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
// this is a horrible way for crossterm to represent keys but we have to deal with it
TermKeyCode::Char(c) => match c {
2022-03-10 03:06:45 +00:00
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
'A' => Code::KeyA,
'B' => Code::KeyB,
'C' => Code::KeyC,
'D' => Code::KeyD,
'E' => Code::KeyE,
'F' => Code::KeyF,
'G' => Code::KeyG,
'H' => Code::KeyH,
'I' => Code::KeyI,
'J' => Code::KeyJ,
'K' => Code::KeyK,
'L' => Code::KeyL,
'M' => Code::KeyM,
'N' => Code::KeyN,
'O' => Code::KeyO,
'P' => Code::KeyP,
'Q' => Code::KeyQ,
'R' => Code::KeyR,
'S' => Code::KeyS,
'T' => Code::KeyT,
'U' => Code::KeyU,
'V' => Code::KeyV,
'W' => Code::KeyW,
'X' => Code::KeyX,
'Y' => Code::KeyY,
'Z' => Code::KeyZ,
_ => unreachable!("Exhaustively checked all characters in range A..Z"),
2022-03-10 03:06:45 +00:00
},
' ' => Code::Space,
'[' | '{' => Code::BracketLeft,
']' | '}' => Code::BracketRight,
';' => Code::Semicolon,
':' => Code::Semicolon,
',' => Code::Comma,
'<' => Code::Comma,
'.' => Code::Period,
'>' => Code::Period,
'1' => Code::Digit1,
'2' => Code::Digit2,
'3' => Code::Digit3,
'4' => Code::Digit4,
'5' => Code::Digit5,
'6' => Code::Digit6,
'7' => Code::Digit7,
'8' => Code::Digit8,
'9' => Code::Digit9,
'0' => Code::Digit0,
'!' => Code::Digit1,
'@' => Code::Digit2,
'#' => Code::Digit3,
'$' => Code::Digit4,
'%' => Code::Digit5,
'^' => Code::Digit6,
'&' => Code::Digit7,
'*' => Code::Digit8,
'(' => Code::Digit9,
')' => Code::Digit0,
// numpad characters are ambiguous; we don't know which key was really pressed
// it could be also:
// '*' => Code::Multiply,
// '/' => Code::Divide,
// '-' => Code::Subtract,
// '+' => Code::Add,
'+' => Code::Equal,
'-' | '_' => Code::Minus,
'\'' => Code::Quote,
'"' => Code::Quote,
'\\' => Code::Backslash,
'|' => Code::Backslash,
'/' => Code::Slash,
'?' => Code::Slash,
'=' => Code::Equal,
'`' => Code::Backquote,
'~' => Code::Backquote,
2022-03-10 03:06:45 +00:00
_ => return None,
},
TermKeyCode::Null => return None,
TermKeyCode::Esc => Code::Escape,
2022-03-10 03:06:45 +00:00
};
Some(code)
}
fn modifiers_from_crossterm_modifiers(src: KeyModifiers) -> Modifiers {
let mut modifiers = Modifiers::empty();
if src.contains(KeyModifiers::SHIFT) {
modifiers.insert(Modifiers::SHIFT);
}
if src.contains(KeyModifiers::ALT) {
modifiers.insert(Modifiers::ALT);
}
if src.contains(KeyModifiers::CONTROL) {
modifiers.insert(Modifiers::CONTROL);
}
modifiers
2022-02-05 22:28:19 +00:00
}