|
@@ -4,6 +4,11 @@ use crossterm::event::{
|
|
|
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 std::{
|
|
|
any::Any,
|
|
@@ -12,6 +17,7 @@ use std::{
|
|
|
sync::Arc,
|
|
|
time::{Duration, Instant},
|
|
|
};
|
|
|
+use stretch2::geometry::{Point, Size};
|
|
|
use stretch2::{prelude::Layout, Stretch};
|
|
|
|
|
|
use crate::{Dom, Node};
|
|
@@ -74,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)>,
|
|
@@ -96,54 +102,38 @@ 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 = m.clone();
|
|
|
- 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((
|
|
|
- m.clone(),
|
|
|
- if m.buttons == 0 {
|
|
|
- Vec::new()
|
|
|
- } else {
|
|
|
- vec![m.buttons]
|
|
|
- },
|
|
|
- ));
|
|
|
+ "mouseup" => {
|
|
|
+ held_buttons.remove(
|
|
|
+ m.trigger_button()
|
|
|
+ .expect("No trigger button for mouseup event"),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ _ => {}
|
|
|
}
|
|
|
- },
|
|
|
+
|
|
|
+ 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) => {
|
|
@@ -166,7 +156,7 @@ impl InnerInputState {
|
|
|
layout: &Stretch,
|
|
|
dom: &mut Dom,
|
|
|
) {
|
|
|
- let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone()));
|
|
|
+ let previous_mouse = self.mouse.clone();
|
|
|
|
|
|
self.wheel = None;
|
|
|
|
|
@@ -183,16 +173,17 @@ impl InnerInputState {
|
|
|
|
|
|
fn resolve_mouse_events(
|
|
|
&self,
|
|
|
- previous_mouse: Option<(MouseData, Vec<u16>)>,
|
|
|
+ previous_mouse: Option<MouseData>,
|
|
|
resolved_events: &mut Vec<UserEvent>,
|
|
|
layout: &Stretch,
|
|
|
dom: &mut Dom,
|
|
|
) {
|
|
|
- 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
|
|
|
+ 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 try_create_event(
|
|
@@ -221,25 +212,43 @@ impl InnerInputState {
|
|
|
}
|
|
|
|
|
|
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
|
|
|
- let mut data = mouse_data.clone();
|
|
|
- data.offset_x = data.client_x - layout.location.x as i32;
|
|
|
- data.offset_y = data.client_y - layout.location.y as i32;
|
|
|
- data
|
|
|
+ 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) = &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));
|
|
|
- // the a mouse button is pressed if a button was not down and is now down
|
|
|
- let pressed =
|
|
|
- (mouse.0.buttons & !previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
|
|
- // the a mouse button is pressed if a button was down and is now not down
|
|
|
- let released =
|
|
|
- (!mouse.0.buttons & previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
|
|
|
+ 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 mouse_data = &mouse.0;
|
|
|
let wheel_data = &self.wheel;
|
|
|
|
|
|
{
|
|
@@ -314,7 +323,7 @@ impl InnerInputState {
|
|
|
}
|
|
|
|
|
|
// mousedown
|
|
|
- if pressed {
|
|
|
+ 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();
|
|
@@ -335,7 +344,7 @@ impl InnerInputState {
|
|
|
|
|
|
{
|
|
|
// mouseup
|
|
|
- if released {
|
|
|
+ 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();
|
|
@@ -357,7 +366,7 @@ impl InnerInputState {
|
|
|
|
|
|
{
|
|
|
// click
|
|
|
- if mouse_data.button == 0 && released {
|
|
|
+ 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();
|
|
@@ -379,7 +388,8 @@ impl InnerInputState {
|
|
|
|
|
|
{
|
|
|
// contextmenu
|
|
|
- if mouse_data.button == 2 && released {
|
|
|
+ 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();
|
|
@@ -584,41 +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
|
|
|
|
|
|
// 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.
|
|
|
- EventData::Mouse(MouseData {
|
|
|
- alt_key: alt,
|
|
|
- button: button_state,
|
|
|
- buttons,
|
|
|
- client_x: x,
|
|
|
- client_y: y,
|
|
|
- ctrl_key: ctrl,
|
|
|
- meta_key: meta,
|
|
|
+ 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
|
|
|
- offset_x: 0,
|
|
|
- offset_y: 0,
|
|
|
- page_x: x,
|
|
|
- page_y: y,
|
|
|
- screen_x: x,
|
|
|
- screen_y: y,
|
|
|
- shift_key: shift,
|
|
|
- })
|
|
|
+ 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| {
|