From fb80bcf8f5b49b8bf96da20f9311869a08b91a9c Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Sat, 10 Feb 2024 11:12:18 +0100 Subject: [PATCH] Add `RawMouseEvent` which allows negative positions (fixes a bug) Since the mouse position is relative to the origin of a widget, mouse positions can be negative, previously they were clamped to 0, which led to "always hovering" situations even if the mouse was above, or left to a widget. This should fix the issue. --- src/app.rs | 2 +- src/widget.rs | 3 +-- src/widget/core.rs | 27 +++------------------ src/widget/events.rs | 58 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/app.rs b/src/app.rs index c8efffc..5327b82 100644 --- a/src/app.rs +++ b/src/app.rs @@ -177,7 +177,7 @@ impl + 'static> App { code: KeyCode::Esc, .. })) => Event::Quit, Ok(CxEvent::Key(key_event)) => Event::Key(key_event), - Ok(CxEvent::Mouse(mouse_event)) => Event::Mouse(mouse_event), + Ok(CxEvent::Mouse(mouse_event)) => Event::Mouse(mouse_event.into()), Ok(CxEvent::FocusGained) => Event::FocusGained, Ok(CxEvent::FocusLost) => Event::FocusLost, // CxEvent::Paste(_) => todo!(), diff --git a/src/widget.rs b/src/widget.rs index cb2ec85..5813caa 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -15,8 +15,7 @@ mod text; mod weighted_linear_layout; pub use self::core::{ - AnyWidget, ChangeFlags, CxState, Event, EventCx, LayoutCx, LifeCycleCx, Message, PaintCx, Pod, - Widget, + AnyWidget, ChangeFlags, CxState, EventCx, LayoutCx, LifeCycleCx, Message, PaintCx, Pod, Widget, }; pub(crate) use self::core::{PodFlags, WidgetState}; pub(crate) use border::Border; diff --git a/src/widget/core.rs b/src/widget/core.rs index da5b558..c715276 100644 --- a/src/widget/core.rs +++ b/src/widget/core.rs @@ -1,8 +1,7 @@ -use super::{BoxConstraints, LifeCycle}; +use super::{BoxConstraints, Event, LifeCycle}; use crate::geometry::{Point, Rect, Size}; use bitflags::bitflags; -pub use crossterm::event::MouseEvent; -use crossterm::event::{KeyEvent, MouseEventKind}; +use crossterm::event::MouseEventKind; use ratatui::Terminal; use std::{any::Any, ops::DerefMut}; use xilem_core::{message, Id}; @@ -18,24 +17,6 @@ use std::io::Stdout; message!(Send); -#[derive(Debug, Clone)] -pub enum Event { - /// Only sent once at the start of the application - Start, - Quit, - /// Sent e.g. when a future requests waking up the application - Wake, - FocusLost, - FocusGained, - Resize { - width: u16, - height: u16, - }, - // TODO create a custom type... - Mouse(MouseEvent), - Key(KeyEvent), -} - /// Static state that is shared between most contexts. pub struct CxState<'a> { messages: &'a mut Vec, @@ -471,8 +452,8 @@ impl Pod { { let mut mouse_event = *mouse_event; let (x, y) = ( - self.state.origin.x.round() as u16, - self.state.origin.y.round() as u16, + self.state.origin.x.round() as i16, + self.state.origin.y.round() as i16, ); mouse_event.column = mouse_event.column.saturating_sub(x); mouse_event.row = mouse_event.row.saturating_sub(y); diff --git a/src/widget/events.rs b/src/widget/events.rs index 83c9439..24dfc2a 100644 --- a/src/widget/events.rs +++ b/src/widget/events.rs @@ -7,9 +7,26 @@ use ratatui::style::Style; use super::{ core::{IdPath, PaintCx}, - Event, EventCx, LayoutCx, Message, Pod, Widget, + EventCx, LayoutCx, Message, Pod, Widget, }; +#[derive(Debug, Clone)] +pub enum Event { + /// Only sent once at the start of the application + Start, + Quit, + /// Sent e.g. when a future requests waking up the application + Wake, + FocusLost, + FocusGained, + Resize { + width: u16, + height: u16, + }, + Mouse(RawMouseEvent), + Key(crossterm::event::KeyEvent), +} + #[derive(Debug)] pub enum LifeCycle { HotChanged(bool), @@ -24,6 +41,25 @@ pub struct ViewContext { pub mouse_position: Option, } +#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] +pub struct RawMouseEvent { + pub kind: MouseEventKind, + pub column: i16, + pub row: i16, + pub modifiers: crossterm::event::KeyModifiers, +} + +impl From for RawMouseEvent { + fn from(event: crossterm::event::MouseEvent) -> Self { + RawMouseEvent { + kind: event.kind, + column: event.column as i16, + row: event.row as i16, + modifiers: event.modifiers, + } + } +} + impl ViewContext { pub fn translate_to(&self, new_origin: Point) -> ViewContext { // TODO I think the clip calculation is buggy in xilem (width/height?) @@ -42,19 +78,21 @@ impl ViewContext { } } +// TODO separate the widgets etc. into its own module? + #[derive(Debug)] /// A message representing a mouse event. pub struct MouseEvent { pub over_element: bool, pub is_active: bool, pub kind: MouseEventKind, - pub column: u16, - pub row: u16, + pub column: i16, + pub row: i16, pub modifiers: crossterm::event::KeyModifiers, } impl MouseEvent { - fn new(event: crossterm::event::MouseEvent, over_element: bool, is_active: bool) -> Self { + fn new(event: RawMouseEvent, over_element: bool, is_active: bool) -> Self { MouseEvent { over_element, is_active, @@ -108,7 +146,7 @@ impl Widget for OnMouse { match event { Event::Mouse( - event @ crossterm::event::MouseEvent { + event @ RawMouseEvent { kind: MouseEventKind::Down(button), .. }, @@ -128,7 +166,7 @@ impl Widget for OnMouse { )); } } - Event::Mouse(event @ crossterm::event::MouseEvent { kind, .. }) => { + Event::Mouse(event @ RawMouseEvent { kind, .. }) => { let is_active = cx.is_active(); if matches!(kind, MouseEventKind::Up(_)) { cx.set_active(false); @@ -188,7 +226,7 @@ impl Widget for OnClick { fn event(&mut self, cx: &mut EventCx, event: &Event) { self.element.event(cx, event); - if let Event::Mouse(crossterm::event::MouseEvent { + if let Event::Mouse(RawMouseEvent { kind: MouseEventKind::Down(MouseButton::Left), .. }) = event @@ -197,7 +235,7 @@ impl Widget for OnClick { } // TODO handle other events like e.g. FocusLost - if let Event::Mouse(crossterm::event::MouseEvent { + if let Event::Mouse(RawMouseEvent { kind: MouseEventKind::Up(MouseButton::Left), .. }) = event @@ -374,14 +412,14 @@ impl Widget for StyleOnPressed { self.element.event(cx, event); match event { - Event::Mouse(crossterm::event::MouseEvent { + Event::Mouse(RawMouseEvent { kind: MouseEventKind::Down(MouseButton::Left), .. }) => { cx.request_paint(); cx.set_active(cx.is_hot()); } - Event::Mouse(crossterm::event::MouseEvent { + Event::Mouse(RawMouseEvent { kind: MouseEventKind::Up(MouseButton::Left) | MouseEventKind::Moved, .. })