update input handling

This commit is contained in:
griffi-gh 2024-03-11 18:40:11 +01:00
parent 67b55ec3c1
commit 027bc2c429
6 changed files with 120 additions and 98 deletions

View file

@ -3,11 +3,12 @@
use std::any::Any; use std::any::Any;
use crate::{ use crate::{
draw::{atlas::ImageCtx, UiDrawCommandList}, draw::{atlas::ImageCtx, UiDrawCommandList},
input::InputCtx,
layout::LayoutInfo, layout::LayoutInfo,
measure::Response, measure::Response,
state::StateRepo, state::StateRepo,
text::{FontHandle, TextMeasure}, text::{FontHandle, TextMeasure},
UiInstance UiInstance,
}; };
mod builtin; mod builtin;
@ -20,6 +21,8 @@ pub struct MeasureContext<'a> {
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle, pub current_font: FontHandle,
pub images: ImageCtx<'a>, pub images: ImageCtx<'a>,
//XXX: should measure have a reference to input?
//pub input: InputCtx<'a>,
} }
/// Context for the `Element::process` function /// Context for the `Element::process` function
@ -31,6 +34,7 @@ pub struct ProcessContext<'a> {
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle, pub current_font: FontHandle,
pub images: ImageCtx<'a>, pub images: ImageCtx<'a>,
pub input: InputCtx<'a>,
} }
pub trait UiElement { pub trait UiElement {

View file

@ -452,6 +452,7 @@ impl UiElement for Container {
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images, images: ctx.images,
input: ctx.input,
}); });
//layout //layout

View file

@ -56,6 +56,7 @@ impl UiElement for Transformer {
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font, current_font: ctx.current_font,
images: ctx.images, images: ctx.images,
input: ctx.input,
}); });
ctx.draw.add(UiDrawCommand::PopTransform); ctx.draw.add(UiDrawCommand::PopTransform);
} }

View file

@ -47,10 +47,25 @@ pub enum ButtonState {
Pressed = 1, Pressed = 1,
} }
impl From<bool> for ButtonState {
fn from(b: bool) -> Self {
if b { ButtonState::Pressed } else { ButtonState::Released }
}
}
impl From<ButtonState> for bool {
fn from(s: ButtonState) -> Self {
s.is_pressed()
}
}
impl ButtonState { impl ButtonState {
/// Returns `true` if the button is pressed
pub fn is_pressed(self) -> bool { pub fn is_pressed(self) -> bool {
self == ButtonState::Pressed self == ButtonState::Pressed
} }
/// Returns `true` if the button is released
pub fn is_released(self) -> bool { pub fn is_released(self) -> bool {
self == ButtonState::Released self == ButtonState::Released
} }
@ -117,140 +132,106 @@ impl_fits64_for_keyboard_key!(
Mute = 104, VolumeUp = 105, VolumeDown = 106, MediaPlay = 107, MediaStop = 108, MediaNext = 109, MediaPrevious = 110 Mute = 104, VolumeUp = 105, VolumeDown = 106, MediaPlay = 107, MediaStop = 108, MediaNext = 109, MediaPrevious = 110
); );
/// Information about the state of a mouse button /// Information about the current state of a pressed mouse button
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct MouseButtonState { pub struct MouseButtonMeta {
/// Whether the input is currently active (i.e. the button is currently held down)
pub state: ButtonState,
/// Position at which the input was initiated (last time it was pressed **down**) /// Position at which the input was initiated (last time it was pressed **down**)
pub start_position: Option<Vec2>, pub start_position: Option<Vec2>,
} }
#[derive(Default)] #[derive(Default)]
pub struct MousePointer { pub struct MousePointer {
/// Current position of the mouse pointer
pub current_position: Vec2, pub current_position: Vec2,
pub buttons: HashMap<MouseButton, MouseButtonState, BuildNoHashHasher<u16>>, /// Current state of each mouse button (if down)
pub buttons: HashMap<MouseButton, MouseButtonMeta, BuildNoHashHasher<u16>>,
} }
/// Unique identifier of a touch pointer (finger)
pub type TouchId = u32;
pub struct TouchFinger { pub struct TouchFinger {
/// Unique identifier of the pointer (finger) /// Unique identifier of the pointer/finger
pub id: u32, pub id: TouchId,
/// Current position of the pointer/finger
pub current_position: Vec2, pub current_position: Vec2,
pub start_position: Vec2, pub start_position: Vec2,
} }
pub type PointerId = u32;
/// Represents a pointer (mouse or touch)
pub enum Pointer {
MousePointer(MousePointer),
TouchFinger(TouchFinger),
}
impl Pointer {
pub fn current_position(&self) -> Vec2 {
match self {
Pointer::MousePointer(mouse) => mouse.current_position,
Pointer::TouchFinger(touch) => touch.current_position,
}
}
}
impl MouseButtonState {
/// Check if the pointer (mouse or touch) was just pressed\
/// (i.e. it was not pressed in the previous frame, but is pressed now)
///
/// You should avoid using this, as it's not very intuitive for touch input (use `just_pressed` instead, if possible)
pub fn just_pressed(&self) -> bool {
todo!()
}
/// Check if the pointer (mouse or touch) was just released\
/// (i.e. it was pressed in the previous frame, but is not pressed now)
///
/// This is the preferred "on click" event for elements like buttons
pub fn just_released(&self) -> bool {
todo!()
}
}
pub struct PointerQuery<'a> {
pointers: &'a HashMap<PointerId, Pointer, BuildNoHashHasher<PointerId>>,
/// Set of filtered pointer IDs
filtered: SetU32
}
impl<'a> PointerQuery<'a> {
fn new(pointers: &'a HashMap<PointerId, Pointer, BuildNoHashHasher<PointerId>>) -> Self {
Self {
pointers,
filtered: pointers.keys().copied().collect(),
}
}
/// Filter pointers that are *currently* located within the specified rectangle
pub fn within_rect(&mut self, rect: Rect) -> &mut Self {
for (&idx, pointer) in self.pointers {
if rect.contains_point(pointer.current_position()) {
self.filtered.insert(idx);
}
}
self
}
/// Check if any pointers matched the filter
pub fn any_matched(&self) -> bool {
!self.filtered.is_empty()
}
pub fn finish(&self) -> Vec<&'a Pointer> {
self.filtered.iter()
.map(|id| self.pointers.get(&id).unwrap())
.collect()
}
}
const MOUSE_POINTER_ID: u32 = u32::MAX;
pub(crate) struct UiInputState { pub(crate) struct UiInputState {
pointers: HashMap<u32, Pointer, BuildNoHashHasher<u32>>, // pointers: HashMap<u32, Pointer, BuildNoHashHasher<u32>>,
mouse_pointer: MousePointer,
keyboard_state: Set64<KeyboardKey>, keyboard_state: Set64<KeyboardKey>,
} }
impl UiInputState { impl UiInputState {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
pointers: HashMap::default(), // pointers: HashMap::default(),
mouse_pointer: MousePointer::default(),
keyboard_state: Set64::new(), keyboard_state: Set64::new(),
} }
} }
pub fn query_pointer(&self) -> PointerQuery {
PointerQuery::new(&self.pointers)
}
/// Drain the event queue and update the internal input state /// Drain the event queue and update the internal input state
pub fn update_state(&mut self, event_queue: &mut EventQueue) { pub fn update_state(&mut self, event_queue: &mut EventQueue) {
for event in event_queue.drain() { for event in event_queue.drain() {
#[allow(clippy::single_match)]
match event { match event {
UiEvent::MouseMove(pos) => { UiEvent::MouseMove(pos) => {
let Pointer::MousePointer(mouse) = self.pointers.entry(MOUSE_POINTER_ID) self.mouse_pointer.current_position = pos;
.or_insert(Pointer::MousePointer(MousePointer::default())) else { unreachable!() };
mouse.current_position = pos;
}, },
UiEvent::MouseButton { button, state } => { UiEvent::MouseButton { button, state } => {
let Pointer::MousePointer(mouse) = self.pointers.entry(MOUSE_POINTER_ID) match state {
.or_insert(Pointer::MousePointer(MousePointer::default())) else { unreachable!() }; ButtonState::Pressed => {
let button_state = mouse.buttons.entry(button) let button = self.mouse_pointer.buttons.entry(button)
.or_insert(MouseButtonState::default()); .or_insert(MouseButtonMeta::default());
button_state.state = state; button.start_position = state.is_pressed().then_some(self.mouse_pointer.current_position);
button_state.start_position = state.is_pressed().then_some(mouse.current_position); },
ButtonState::Released => {
self.mouse_pointer.buttons.remove(&button);
},
}
}, },
UiEvent::KeyboardButton { key, state } => { UiEvent::KeyboardButton { key, state } => {
todo!() match state {
ButtonState::Pressed => self.keyboard_state.insert(key),
ButtonState::Released => self.keyboard_state.remove(&key),
};
}, },
_ => (), //TODO: Handle other events //TODO touch, text input
_ => (),
} }
} }
} }
pub fn ctx(&self) -> InputCtx {
InputCtx(self)
}
}
#[derive(Clone, Copy)]
pub struct InputCtx<'a>(&'a UiInputState);
impl<'a> InputCtx<'a> {
/// Get the current position of the mouse pointer
pub fn mouse_position(&self) -> Vec2 {
self.0.mouse_pointer.current_position
}
/// Get the current position of the mouse pointer within a rectangle
pub fn mouse_position_in_rect(&self, rect: Rect) -> Option<Vec2> {
let pos = self.0.mouse_pointer.current_position;
rect.contains_point(pos).then_some(pos - rect.position)
}
/// Get the state of a mouse button
pub fn mouse_button_down(&self, button: MouseButton) -> ButtonState {
self.0.mouse_pointer.buttons.contains_key(&button).into()
}
/// Check if a rect can be considered "hovered"
pub fn is_hovered(&self, rect: Rect) -> bool {
rect.contains_point(self.0.mouse_pointer.current_position)
}
} }

View file

@ -124,6 +124,7 @@ impl UiInstance {
text_measure: self.text_renderer.to_measure(), text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(), current_font: self.text_renderer.current_font(),
images: self.atlas.context(), images: self.atlas.context(),
input: self.input.ctx(),
}); });
} }

View file

@ -12,7 +12,41 @@ impl Rect {
pub fn contains_point(&self, point: Vec2) -> bool { pub fn contains_point(&self, point: Vec2) -> bool {
point.cmpge(self.position).all() && point.cmple(self.position + self.size).all() point.cmpge(self.position).all() && point.cmple(self.position + self.size).all()
} }
//TODO: return intersect rect
pub fn intersects_rect(&self, other: Rect) -> bool {
self.position.x < other.position.x + other.size.x
&& self.position.x + self.size.x > other.position.x
&& self.position.y < other.position.y + other.size.y
&& self.position.y + self.size.y > other.position.y
}
pub fn width(&self) -> f32 {
self.size.x
}
pub fn height(&self) -> f32 {
self.size.y
}
pub fn x(&self) -> f32 {
self.position.x
}
pub fn y(&self) -> f32 {
self.position.y
}
pub fn corners(&self) -> Corners<Vec2> {
Corners {
top_left: self.position,
top_right: self.position + Vec2::new(self.size.x, 0.0),
bottom_left: self.position + Vec2::new(0.0, self.size.y),
bottom_right: self.position + self.size,
}
}
} }
/// Represents 4 sides of a rectangular shape. /// Represents 4 sides of a rectangular shape.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] #[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Sides<T> { pub struct Sides<T> {