mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 08:28:42 -06:00
update input handling
This commit is contained in:
parent
f25efdb3ca
commit
352691c228
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
175
hui/src/input.rs
175
hui/src/input.rs
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue