|
@@ -2,20 +2,25 @@ use crossterm::event::{
|
|
|
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
|
|
|
};
|
|
|
use dioxus_core::*;
|
|
|
+use fxhash::{FxHashMap, FxHashSet};
|
|
|
|
|
|
+use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
|
|
|
+use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
|
|
+use dioxus_html::input_data::keyboard_types::Modifiers;
|
|
|
+use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
|
|
+use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
|
|
use dioxus_html::{on::*, KeyCode};
|
|
|
-use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
|
|
use std::{
|
|
|
any::Any,
|
|
|
cell::RefCell,
|
|
|
- collections::{HashMap, HashSet},
|
|
|
rc::Rc,
|
|
|
sync::Arc,
|
|
|
time::{Duration, Instant},
|
|
|
};
|
|
|
+use stretch2::geometry::{Point, Size};
|
|
|
use stretch2::{prelude::Layout, Stretch};
|
|
|
|
|
|
-use crate::TuiNode;
|
|
|
+use crate::{Dom, Node};
|
|
|
|
|
|
// a wrapper around the input state for easier access
|
|
|
// todo: fix loop
|
|
@@ -31,12 +36,12 @@ use crate::TuiNode;
|
|
|
|
|
|
// pub fn mouse(&self) -> Option<MouseData> {
|
|
|
// let data = (**self.0).borrow();
|
|
|
-// data.mouse.as_ref().map(|m| clone_mouse_data(m))
|
|
|
+// data.mouse.as_ref().map(|m| m.clone())
|
|
|
// }
|
|
|
|
|
|
// pub fn wheel(&self) -> Option<WheelData> {
|
|
|
// let data = (**self.0).borrow();
|
|
|
-// data.wheel.as_ref().map(|w| clone_wheel_data(w))
|
|
|
+// data.wheel.as_ref().map(|w| w.clone())
|
|
|
// }
|
|
|
|
|
|
// pub fn screen(&self) -> Option<(u16, u16)> {
|
|
@@ -48,7 +53,7 @@ use crate::TuiNode;
|
|
|
// let data = (**self.0).borrow();
|
|
|
// data.last_key_pressed
|
|
|
// .as_ref()
|
|
|
-// .map(|k| clone_keyboard_data(&k.0))
|
|
|
+// .map(|k| &k.0.clone())
|
|
|
// }
|
|
|
// }
|
|
|
|
|
@@ -75,7 +80,7 @@ impl EventData {
|
|
|
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
|
|
|
|
|
|
pub struct InnerInputState {
|
|
|
- mouse: Option<(MouseData, Vec<u16>)>,
|
|
|
+ mouse: Option<MouseData>,
|
|
|
wheel: Option<WheelData>,
|
|
|
last_key_pressed: Option<(KeyboardData, Instant)>,
|
|
|
screen: Option<(u16, u16)>,
|
|
@@ -97,55 +102,39 @@ impl InnerInputState {
|
|
|
fn apply_event(&mut self, evt: &mut EventCore) {
|
|
|
match evt.1 {
|
|
|
// limitations: only two buttons may be held at once
|
|
|
- EventData::Mouse(ref mut m) => match &mut self.mouse {
|
|
|
- Some(state) => {
|
|
|
- let mut buttons = state.0.buttons;
|
|
|
- state.0 = clone_mouse_data(m);
|
|
|
- match evt.0 {
|
|
|
- // this code only runs when there are no buttons down
|
|
|
- "mouseup" => {
|
|
|
- buttons = 0;
|
|
|
- state.1 = Vec::new();
|
|
|
- }
|
|
|
- "mousedown" => {
|
|
|
- if state.1.contains(&m.buttons) {
|
|
|
- // if we already pressed a button and there is another button released the button crossterm sends is the button remaining
|
|
|
- if state.1.len() > 1 {
|
|
|
- evt.0 = "mouseup";
|
|
|
- state.1 = vec![m.buttons];
|
|
|
- }
|
|
|
- // otherwise some other button was pressed. In testing it was consistantly this mapping
|
|
|
- else {
|
|
|
- match m.buttons {
|
|
|
- 0x01 => state.1.push(0x02),
|
|
|
- 0x02 => state.1.push(0x01),
|
|
|
- 0x04 => state.1.push(0x01),
|
|
|
- _ => (),
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- state.1.push(m.buttons);
|
|
|
- }
|
|
|
+ EventData::Mouse(ref mut m) => {
|
|
|
+ let mut held_buttons = match &self.mouse {
|
|
|
+ Some(previous_data) => previous_data.held_buttons(),
|
|
|
+ None => MouseButtonSet::empty(),
|
|
|
+ };
|
|
|
|
|
|
- buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap();
|
|
|
- }
|
|
|
- _ => (),
|
|
|
+ match evt.0 {
|
|
|
+ "mousedown" => {
|
|
|
+ held_buttons.insert(
|
|
|
+ m.trigger_button()
|
|
|
+ .expect("No trigger button for mousedown event"),
|
|
|
+ );
|
|
|
}
|
|
|
- state.0.buttons = buttons;
|
|
|
- m.buttons = buttons;
|
|
|
- }
|
|
|
- None => {
|
|
|
- self.mouse = Some((
|
|
|
- clone_mouse_data(m),
|
|
|
- if m.buttons == 0 {
|
|
|
- Vec::new()
|
|
|
- } else {
|
|
|
- vec![m.buttons]
|
|
|
- },
|
|
|
- ));
|
|
|
+ "mouseup" => {
|
|
|
+ held_buttons.remove(
|
|
|
+ m.trigger_button()
|
|
|
+ .expect("No trigger button for mouseup event"),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ _ => {}
|
|
|
}
|
|
|
- },
|
|
|
- EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
|
|
|
EventData::Screen(ref s) => self.screen = Some(*s),
|
|
|
EventData::Keyboard(ref mut k) => {
|
|
|
let repeat = self
|
|
@@ -154,195 +143,343 @@ impl InnerInputState {
|
|
|
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
|
|
|
.is_some();
|
|
|
k.repeat = repeat;
|
|
|
- let new = clone_keyboard_data(k);
|
|
|
+ let new = k.clone();
|
|
|
self.last_key_pressed = Some((new, Instant::now()));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fn update<'a>(
|
|
|
+ fn update(
|
|
|
&mut self,
|
|
|
- dom: &'a VirtualDom,
|
|
|
- evts: &mut Vec<EventCore>,
|
|
|
+ evts: &mut [EventCore],
|
|
|
resolved_events: &mut Vec<UserEvent>,
|
|
|
layout: &Stretch,
|
|
|
- layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
|
|
- node: &'a VNode<'a>,
|
|
|
+ dom: &mut Dom,
|
|
|
) {
|
|
|
- struct Data<'b> {
|
|
|
- new_pos: (i32, i32),
|
|
|
- old_pos: Option<(i32, i32)>,
|
|
|
- clicked: bool,
|
|
|
- released: bool,
|
|
|
- wheel_delta: f64,
|
|
|
- mouse_data: &'b MouseData,
|
|
|
- wheel_data: &'b Option<WheelData>,
|
|
|
+ let previous_mouse = self.mouse.clone();
|
|
|
+
|
|
|
+ self.wheel = None;
|
|
|
+
|
|
|
+ for e in evts.iter_mut() {
|
|
|
+ self.apply_event(e);
|
|
|
}
|
|
|
|
|
|
- fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
|
|
|
- layout.location.x as i32 <= point.0
|
|
|
- && layout.location.x as i32 + layout.size.width as i32 >= point.0
|
|
|
- && layout.location.y as i32 <= point.1
|
|
|
- && layout.location.y as i32 + layout.size.height as i32 >= point.1
|
|
|
+ self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
|
|
|
+
|
|
|
+ // for s in &self.subscribers {
|
|
|
+ // s();
|
|
|
+ // }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn resolve_mouse_events(
|
|
|
+ &self,
|
|
|
+ previous_mouse: Option<MouseData>,
|
|
|
+ resolved_events: &mut Vec<UserEvent>,
|
|
|
+ layout: &Stretch,
|
|
|
+ dom: &mut Dom,
|
|
|
+ ) {
|
|
|
+ fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
|
|
|
+ let Point { x, y } = layout.location;
|
|
|
+ let Size { width, height } = layout.size;
|
|
|
+
|
|
|
+ let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
|
|
|
+ layout_rect.contains(point.cast())
|
|
|
}
|
|
|
|
|
|
- fn get_mouse_events<'c, 'd>(
|
|
|
- dom: &'c VirtualDom,
|
|
|
+ fn try_create_event(
|
|
|
+ name: &'static str,
|
|
|
+ data: Arc<dyn Any + Send + Sync>,
|
|
|
+ will_bubble: &mut FxHashSet<ElementId>,
|
|
|
resolved_events: &mut Vec<UserEvent>,
|
|
|
- layout: &Stretch,
|
|
|
- layouts: &HashMap<ElementId, TuiNode<'c>>,
|
|
|
- node: &'c VNode<'c>,
|
|
|
- data: &'d Data<'d>,
|
|
|
- ) -> HashSet<&'static str> {
|
|
|
- match node {
|
|
|
- VNode::Fragment(f) => {
|
|
|
- let mut union = HashSet::new();
|
|
|
- for child in f.children {
|
|
|
- union = union
|
|
|
- .union(&get_mouse_events(
|
|
|
- dom,
|
|
|
+ node: &Node,
|
|
|
+ dom: &Dom,
|
|
|
+ ) {
|
|
|
+ // only trigger event if the event was not triggered already by a child
|
|
|
+ if will_bubble.insert(node.id) {
|
|
|
+ let mut parent = node.parent;
|
|
|
+ while let Some(parent_id) = parent {
|
|
|
+ will_bubble.insert(parent_id);
|
|
|
+ parent = dom[parent_id.0].parent;
|
|
|
+ }
|
|
|
+ resolved_events.push(UserEvent {
|
|
|
+ scope_id: None,
|
|
|
+ priority: EventPriority::Medium,
|
|
|
+ name,
|
|
|
+ element: Some(node.id),
|
|
|
+ data,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
|
|
|
+ let Point { x, y } = layout.location;
|
|
|
+ let node_origin = ClientPoint::new(x.into(), y.into());
|
|
|
+
|
|
|
+ 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(),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
|
|
|
+ let wheel_data = &self.wheel;
|
|
|
+
|
|
|
+ {
|
|
|
+ // mousemove
|
|
|
+ if old_pos != Some(new_pos) {
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mousemove") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let previously_contained = old_pos
|
|
|
+ .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
+ .is_some();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains && previously_contained {
|
|
|
+ try_create_event(
|
|
|
+ "mousemove",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
resolved_events,
|
|
|
- layout,
|
|
|
- layouts,
|
|
|
- child,
|
|
|
- data,
|
|
|
- ))
|
|
|
- .copied()
|
|
|
- .collect();
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
- return union;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- VNode::Component(vcomp) => {
|
|
|
- let idx = vcomp.scope.get().unwrap();
|
|
|
- let new_node = dom.get_scope(idx).unwrap().root_node();
|
|
|
- return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
|
|
|
+ {
|
|
|
+ // mouseenter
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mouseenter") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let previously_contained = old_pos
|
|
|
+ .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
+ .is_some();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains && !previously_contained {
|
|
|
+ try_create_event(
|
|
|
+ "mouseenter",
|
|
|
+ Arc::new(mouse_data.clone()),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- VNode::Placeholder(_) => return HashSet::new(),
|
|
|
-
|
|
|
- VNode::Element(_) | VNode::Text(_) => {}
|
|
|
}
|
|
|
|
|
|
- let id = node.try_mounted_id().unwrap();
|
|
|
- let node = layouts.get(&id).unwrap();
|
|
|
+ {
|
|
|
+ // mouseover
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mouseover") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let previously_contained = old_pos
|
|
|
+ .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
+ .is_some();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains && !previously_contained {
|
|
|
+ try_create_event(
|
|
|
+ "mouseover",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- let node_layout = layout.layout(node.layout).unwrap();
|
|
|
+ // mousedown
|
|
|
+ if was_pressed {
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mousedown") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
|
|
|
- let previously_contained = data
|
|
|
- .old_pos
|
|
|
- .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
- .is_some();
|
|
|
- let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
|
|
+ if currently_contains {
|
|
|
+ try_create_event(
|
|
|
+ "mousedown",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- match node.node {
|
|
|
- VNode::Element(el) => {
|
|
|
- let mut events = HashSet::new();
|
|
|
- if previously_contained || currently_contains {
|
|
|
- for c in el.children {
|
|
|
- events = events
|
|
|
- .union(&get_mouse_events(
|
|
|
- dom,
|
|
|
- resolved_events,
|
|
|
- layout,
|
|
|
- layouts,
|
|
|
- c,
|
|
|
- data,
|
|
|
- ))
|
|
|
- .copied()
|
|
|
- .collect();
|
|
|
+ {
|
|
|
+ // mouseup
|
|
|
+ if was_released {
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mouseup") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains {
|
|
|
+ try_create_event(
|
|
|
+ "mouseup",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
}
|
|
|
}
|
|
|
- let mut try_create_event = |name| {
|
|
|
- // only trigger event if the event was not triggered already by a child
|
|
|
- if events.insert(name) {
|
|
|
- resolved_events.push(UserEvent {
|
|
|
- scope_id: None,
|
|
|
- priority: EventPriority::Medium,
|
|
|
- name,
|
|
|
- element: Some(el.id.get().unwrap()),
|
|
|
- data: Arc::new(clone_mouse_data(data.mouse_data)),
|
|
|
- })
|
|
|
- }
|
|
|
- };
|
|
|
- if currently_contains {
|
|
|
- if !previously_contained {
|
|
|
- try_create_event("mouseenter");
|
|
|
- try_create_event("mouseover");
|
|
|
- }
|
|
|
- if data.clicked {
|
|
|
- try_create_event("mousedown");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ // 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") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains {
|
|
|
+ try_create_event(
|
|
|
+ "click",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
}
|
|
|
- if data.released {
|
|
|
- try_create_event("mouseup");
|
|
|
- match data.mouse_data.button {
|
|
|
- 0 => try_create_event("click"),
|
|
|
- 2 => try_create_event("contextmenu"),
|
|
|
- _ => (),
|
|
|
- }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ // 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") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains {
|
|
|
+ try_create_event(
|
|
|
+ "contextmenu",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
}
|
|
|
- if let Some(w) = data.wheel_data {
|
|
|
- if data.wheel_delta != 0.0 {
|
|
|
- resolved_events.push(UserEvent {
|
|
|
- scope_id: None,
|
|
|
- priority: EventPriority::Medium,
|
|
|
- name: "wheel",
|
|
|
- element: Some(el.id.get().unwrap()),
|
|
|
- data: Arc::new(clone_wheel_data(w)),
|
|
|
- })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ // wheel
|
|
|
+ if let Some(w) = wheel_data {
|
|
|
+ if wheel_delta != 0.0 {
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("wheel") {
|
|
|
+ let node_layout =
|
|
|
+ layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if currently_contains {
|
|
|
+ try_create_event(
|
|
|
+ "wheel",
|
|
|
+ Arc::new(w.clone()),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
}
|
|
|
}
|
|
|
- } else if previously_contained {
|
|
|
- try_create_event("mouseleave");
|
|
|
- try_create_event("mouseout");
|
|
|
}
|
|
|
- events
|
|
|
}
|
|
|
- VNode::Text(_) => HashSet::new(),
|
|
|
- _ => todo!(),
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- let previous_mouse = self
|
|
|
- .mouse
|
|
|
- .as_ref()
|
|
|
- .map(|m| (clone_mouse_data(&m.0), m.1.clone()));
|
|
|
- // println!("{previous_mouse:?}");
|
|
|
-
|
|
|
- self.wheel = None;
|
|
|
|
|
|
- for e in evts.iter_mut() {
|
|
|
- self.apply_event(e);
|
|
|
- }
|
|
|
+ {
|
|
|
+ // mouseleave
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mouseleave") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let previously_contained = old_pos
|
|
|
+ .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
+ .is_some();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if !currently_contains && previously_contained {
|
|
|
+ try_create_event(
|
|
|
+ "mouseleave",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // resolve hover events
|
|
|
- if let Some(mouse) = &self.mouse {
|
|
|
- let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
|
|
|
- let old_pos = previous_mouse
|
|
|
- .as_ref()
|
|
|
- .map(|m| (m.0.screen_x, m.0.screen_y));
|
|
|
- let clicked =
|
|
|
- (!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
|
|
- let released =
|
|
|
- (mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
|
|
- let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
|
|
|
- let mouse_data = &mouse.0;
|
|
|
- let wheel_data = &self.wheel;
|
|
|
- let data = Data {
|
|
|
- new_pos,
|
|
|
- old_pos,
|
|
|
- clicked,
|
|
|
- released,
|
|
|
- wheel_delta,
|
|
|
- mouse_data,
|
|
|
- wheel_data,
|
|
|
- };
|
|
|
- get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
|
|
|
+ {
|
|
|
+ // mouseout
|
|
|
+ let mut will_bubble = FxHashSet::default();
|
|
|
+ for node in dom.get_listening_sorted("mouseout") {
|
|
|
+ let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
|
|
|
+ let previously_contained = old_pos
|
|
|
+ .filter(|pos| layout_contains_point(node_layout, *pos))
|
|
|
+ .is_some();
|
|
|
+ let currently_contains = layout_contains_point(node_layout, new_pos);
|
|
|
+
|
|
|
+ if !currently_contains && previously_contained {
|
|
|
+ try_create_event(
|
|
|
+ "mouseout",
|
|
|
+ Arc::new(prepare_mouse_data(mouse_data, node_layout)),
|
|
|
+ &mut will_bubble,
|
|
|
+ resolved_events,
|
|
|
+ node,
|
|
|
+ dom,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- // for s in &self.subscribers {
|
|
|
- // s();
|
|
|
- // }
|
|
|
}
|
|
|
|
|
|
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
|
|
@@ -358,24 +495,21 @@ pub struct RinkInputHandler {
|
|
|
impl RinkInputHandler {
|
|
|
/// global context that handles events
|
|
|
/// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
|
|
|
- pub fn new(
|
|
|
- mut receiver: UnboundedReceiver<TermEvent>,
|
|
|
- cx: &ScopeState,
|
|
|
- ) -> (Self, Rc<RefCell<InnerInputState>>) {
|
|
|
+ pub fn new() -> (
|
|
|
+ Self,
|
|
|
+ Rc<RefCell<InnerInputState>>,
|
|
|
+ impl FnMut(crossterm::event::Event),
|
|
|
+ ) {
|
|
|
let queued_events = Rc::new(RefCell::new(Vec::new()));
|
|
|
- let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
|
|
|
-
|
|
|
- cx.push_future(async move {
|
|
|
- while let Some(evt) = receiver.next().await {
|
|
|
- if let Some(evt) = get_event(evt) {
|
|
|
- if let Some(v) = queued_events2.upgrade() {
|
|
|
- (*v).borrow_mut().push(evt);
|
|
|
- } else {
|
|
|
- break;
|
|
|
- }
|
|
|
+ let queued_events2 = Rc::downgrade(&queued_events);
|
|
|
+
|
|
|
+ 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);
|
|
|
}
|
|
|
}
|
|
|
- });
|
|
|
+ };
|
|
|
|
|
|
let state = Rc::new(RefCell::new(InnerInputState::new()));
|
|
|
|
|
@@ -385,73 +519,65 @@ impl RinkInputHandler {
|
|
|
queued_events,
|
|
|
},
|
|
|
state,
|
|
|
+ regester_event,
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- pub fn get_events<'a>(
|
|
|
- &self,
|
|
|
- dom: &'a VirtualDom,
|
|
|
- layout: &Stretch,
|
|
|
- layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
|
|
- node: &'a VNode<'a>,
|
|
|
- ) -> Vec<UserEvent> {
|
|
|
- // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
|
|
- fn inner(
|
|
|
- queue: &[(&'static str, Arc<dyn Any + Send + Sync>)],
|
|
|
- resolved: &mut Vec<UserEvent>,
|
|
|
- node: &VNode,
|
|
|
- ) {
|
|
|
- match node {
|
|
|
- VNode::Fragment(frag) => {
|
|
|
- for c in frag.children {
|
|
|
- inner(queue, resolved, c);
|
|
|
- }
|
|
|
- }
|
|
|
- VNode::Element(el) => {
|
|
|
- for l in el.listeners {
|
|
|
- for (name, data) in queue.iter() {
|
|
|
- if *name == l.event {
|
|
|
- if let Some(id) = el.id.get() {
|
|
|
- resolved.push(UserEvent {
|
|
|
- scope_id: None,
|
|
|
- priority: EventPriority::Medium,
|
|
|
- name: *name,
|
|
|
- element: Some(id),
|
|
|
- data: data.clone(),
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- for c in el.children {
|
|
|
- inner(queue, resolved, c);
|
|
|
- }
|
|
|
- }
|
|
|
- _ => (),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
|
|
|
let mut resolved_events = Vec::new();
|
|
|
|
|
|
(*self.state).borrow_mut().update(
|
|
|
- dom,
|
|
|
&mut (*self.queued_events).borrow_mut(),
|
|
|
&mut resolved_events,
|
|
|
layout,
|
|
|
- layouts,
|
|
|
- node,
|
|
|
+ dom,
|
|
|
);
|
|
|
|
|
|
- let events: Vec<_> = self
|
|
|
+ let events = self
|
|
|
.queued_events
|
|
|
.replace(Vec::new())
|
|
|
.into_iter()
|
|
|
// these events were added in the update stage
|
|
|
- .filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
|
|
|
- .map(|e| (e.0, e.1.into_any()))
|
|
|
- .collect();
|
|
|
+ .filter(|e| {
|
|
|
+ ![
|
|
|
+ "mouseenter",
|
|
|
+ "mouseover",
|
|
|
+ "mouseleave",
|
|
|
+ "mouseout",
|
|
|
+ "mousedown",
|
|
|
+ "mouseup",
|
|
|
+ "mousemove",
|
|
|
+ "drag",
|
|
|
+ "wheel",
|
|
|
+ "click",
|
|
|
+ "contextmenu",
|
|
|
+ ]
|
|
|
+ .contains(&e.0)
|
|
|
+ })
|
|
|
+ .map(|evt| (evt.0, evt.1.into_any()));
|
|
|
|
|
|
- inner(&events, &mut resolved_events, node);
|
|
|
+ // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
|
|
+ let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
|
|
|
+ for (event, data) in events {
|
|
|
+ if let Some(v) = hm.get_mut(event) {
|
|
|
+ v.push(data);
|
|
|
+ } else {
|
|
|
+ hm.insert(event, vec![data]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (event, datas) in hm {
|
|
|
+ for node in dom.get_listening_sorted(event) {
|
|
|
+ for data in &datas {
|
|
|
+ resolved_events.push(UserEvent {
|
|
|
+ scope_id: None,
|
|
|
+ priority: EventPriority::Medium,
|
|
|
+ name: event,
|
|
|
+ element: Some(node.id),
|
|
|
+ data: data.clone(),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
resolved_events
|
|
|
}
|
|
@@ -468,34 +594,47 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
|
|
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
|
|
|
let meta = false;
|
|
|
|
|
|
- let get_mouse_data = |b| {
|
|
|
- let buttons = match b {
|
|
|
- None => 0,
|
|
|
- Some(MouseButton::Left) => 1,
|
|
|
- Some(MouseButton::Right) => 2,
|
|
|
- Some(MouseButton::Middle) => 4,
|
|
|
- };
|
|
|
- let button_state = match b {
|
|
|
- None => 0,
|
|
|
- Some(MouseButton::Left) => 0,
|
|
|
- Some(MouseButton::Middle) => 1,
|
|
|
- Some(MouseButton::Right) => 2,
|
|
|
- };
|
|
|
+ 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,
|
|
|
+ });
|
|
|
+
|
|
|
// from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
|
|
|
- EventData::Mouse(MouseData {
|
|
|
- alt_key: alt,
|
|
|
- button: button_state,
|
|
|
- buttons,
|
|
|
- client_x: x,
|
|
|
- client_y: y,
|
|
|
- ctrl_key: ctrl,
|
|
|
- meta_key: meta,
|
|
|
- page_x: x,
|
|
|
- page_y: y,
|
|
|
- screen_x: x,
|
|
|
- screen_y: y,
|
|
|
- shift_key: shift,
|
|
|
- })
|
|
|
+
|
|
|
+ // 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),
|
|
|
+ // 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,
|
|
|
+ ))
|
|
|
};
|
|
|
|
|
|
let get_wheel_data = |up| {
|
|
@@ -525,6 +664,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
|
|
|
|
|
fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
|
|
let (code, key_str);
|
|
|
+ let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT);
|
|
|
if let TermKeyCode::Char(c) = event.code {
|
|
|
code = match c {
|
|
|
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
|
|
@@ -638,7 +778,11 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
|
|
12 => KeyCode::F12,
|
|
|
_ => return None,
|
|
|
},
|
|
|
- TermKeyCode::BackTab => return None,
|
|
|
+ // backtab is Shift + Tab
|
|
|
+ TermKeyCode::BackTab => {
|
|
|
+ shift_key = true;
|
|
|
+ KeyCode::Tab
|
|
|
+ }
|
|
|
TermKeyCode::Null => return None,
|
|
|
_ => return None,
|
|
|
};
|
|
@@ -651,57 +795,15 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
|
|
|
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
|
|
|
Some(EventData::Keyboard(KeyboardData {
|
|
|
char_code: code.raw_code(),
|
|
|
- key: key_str.to_string(),
|
|
|
+ key: key_str,
|
|
|
key_code: code,
|
|
|
alt_key: event.modifiers.contains(KeyModifiers::ALT),
|
|
|
ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),
|
|
|
meta_key: false,
|
|
|
- shift_key: event.modifiers.contains(KeyModifiers::SHIFT),
|
|
|
+ shift_key,
|
|
|
locale: Default::default(),
|
|
|
location: 0x00,
|
|
|
repeat: Default::default(),
|
|
|
which: Default::default(),
|
|
|
}))
|
|
|
}
|
|
|
-
|
|
|
-fn clone_mouse_data(m: &MouseData) -> MouseData {
|
|
|
- MouseData {
|
|
|
- client_x: m.client_x,
|
|
|
- client_y: m.client_y,
|
|
|
- page_x: m.page_x,
|
|
|
- page_y: m.page_y,
|
|
|
- screen_x: m.screen_x,
|
|
|
- screen_y: m.screen_y,
|
|
|
- alt_key: m.alt_key,
|
|
|
- ctrl_key: m.ctrl_key,
|
|
|
- meta_key: m.meta_key,
|
|
|
- shift_key: m.shift_key,
|
|
|
- button: m.button,
|
|
|
- buttons: m.buttons,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
|
|
|
- KeyboardData {
|
|
|
- char_code: k.char_code,
|
|
|
- key: k.key.clone(),
|
|
|
- key_code: k.key_code,
|
|
|
- alt_key: k.alt_key,
|
|
|
- ctrl_key: k.ctrl_key,
|
|
|
- meta_key: k.meta_key,
|
|
|
- shift_key: k.shift_key,
|
|
|
- locale: k.locale.clone(),
|
|
|
- location: k.location,
|
|
|
- repeat: k.repeat,
|
|
|
- which: k.which,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-fn clone_wheel_data(w: &WheelData) -> WheelData {
|
|
|
- WheelData {
|
|
|
- delta_mode: w.delta_mode,
|
|
|
- delta_x: w.delta_x,
|
|
|
- delta_y: w.delta_y,
|
|
|
- delta_z: w.delta_x,
|
|
|
- }
|
|
|
-}
|