#![warn(missing_docs)]
use crate::graphics::Point;
use crate::item_tree::ItemVisitorResult;
use crate::items::{ItemRc, ItemRef, ItemWeak};
use crate::Property;
use crate::{component::ComponentRc, SharedString};
use const_field_offset::FieldOffsets;
use euclid::default::Vector2D;
use std::pin::Pin;
use std::rc::Rc;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MouseEventType {
MousePressed,
MouseReleased,
MouseMoved,
MouseExit,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct MouseEvent {
pub pos: Point,
pub what: MouseEventType,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum InputEventResult {
EventAccepted,
EventIgnored,
GrabMouse,
ObserveHover,
}
impl Default for InputEventResult {
fn default() -> Self {
Self::EventIgnored
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum InternalKeyCode {
Left,
Right,
Home,
End,
Back,
Delete,
Return,
}
const LEFT_CODE: char = '\u{000E}'; const RIGHT_CODE: char = '\u{000F}'; const HOME_CODE: char = '\u{0002}'; const END_CODE: char = '\u{0003}'; const BACK_CODE: char = '\u{0007}'; const DELETE_CODE: char = '\u{007F}'; const RETURN_CODE: char = '\u{000A}';
impl InternalKeyCode {
pub fn encode_to_string(&self) -> SharedString {
match self {
InternalKeyCode::Left => LEFT_CODE,
InternalKeyCode::Right => RIGHT_CODE,
InternalKeyCode::Home => HOME_CODE,
InternalKeyCode::End => END_CODE,
InternalKeyCode::Back => BACK_CODE,
InternalKeyCode::Delete => DELETE_CODE,
InternalKeyCode::Return => RETURN_CODE,
}
.to_string()
.into()
}
pub fn try_decode_from_string(str: &SharedString) -> Option<Self> {
let mut chars = str.chars();
let ch = chars.next();
if ch.is_some() && chars.next().is_none() {
Some(match ch.unwrap() {
LEFT_CODE => Self::Left,
RIGHT_CODE => Self::Right,
HOME_CODE => Self::Home,
END_CODE => Self::End,
BACK_CODE => Self::Back,
DELETE_CODE => Self::Delete,
RETURN_CODE => Self::Return,
_ => return None,
})
} else {
None
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct KeyboardModifiers {
pub alt: bool,
pub control: bool,
pub shift: bool,
pub meta: bool,
}
#[derive(Debug, Clone, PartialEq, strum_macros::EnumString, strum_macros::Display)]
#[repr(C)]
pub enum KeyEventType {
KeyPressed,
KeyReleased,
}
impl Default for KeyEventType {
fn default() -> Self {
Self::KeyPressed
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[repr(C)]
pub struct KeyEvent {
pub text: SharedString,
pub modifiers: KeyboardModifiers,
pub event_type: KeyEventType,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum KeyEventResult {
EventAccepted,
EventIgnored,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub enum FocusEvent {
FocusIn,
FocusOut,
WindowReceivedFocus,
WindowLostFocus,
}
#[derive(Default)]
pub struct MouseInputState {
item_stack: Vec<ItemWeak>,
grabbed: bool,
}
pub fn process_mouse_input(
component: ComponentRc,
mouse_event: MouseEvent,
window: &crate::window::ComponentWindow,
mouse_input_state: MouseInputState,
) -> MouseInputState {
'grab: loop {
if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
break 'grab;
};
let mut event = mouse_event.clone();
for it in mouse_input_state.item_stack.iter() {
let item = if let Some(item) = it.upgrade() { item } else { break 'grab };
let g = item.borrow().as_ref().geometry();
event.pos -= g.origin.to_vector();
}
let grabber = mouse_input_state.item_stack.last().unwrap().upgrade().unwrap();
return match grabber.borrow().as_ref().input_event(event, window, &grabber) {
InputEventResult::GrabMouse => mouse_input_state,
_ => Default::default(),
};
}
let mut pos = mouse_event.pos;
for it in mouse_input_state.item_stack.iter() {
let item = if let Some(item) = it.upgrade() { item } else { break };
let g = item.borrow().as_ref().geometry();
pos -= g.origin.to_vector();
item.borrow().as_ref().input_event(
MouseEvent { pos, what: MouseEventType::MouseExit },
window,
&item,
);
}
let mut result = MouseInputState::default();
type State = (Vector2D<f32>, Vec<ItemWeak>);
crate::item_tree::visit_items(
&component,
crate::item_tree::TraversalOrder::FrontToBack,
|comp_rc: &ComponentRc,
item: core::pin::Pin<ItemRef>,
item_index: usize,
(offset, mouse_grabber_stack): &State|
-> ItemVisitorResult<State> {
let item_rc = ItemRc::new(comp_rc.clone(), item_index);
let geom = item.as_ref().geometry();
let geom = geom.translate(*offset);
if geom.contains(mouse_event.pos) {
let mut event2 = mouse_event.clone();
event2.pos -= geom.origin.to_vector();
match item.as_ref().input_event(event2, window, &item_rc) {
InputEventResult::EventAccepted => {
result.item_stack = mouse_grabber_stack.clone();
result.item_stack.push(item_rc.downgrade());
result.grabbed = false;
return ItemVisitorResult::Abort;
}
InputEventResult::EventIgnored => (),
InputEventResult::GrabMouse => {
result.item_stack = mouse_grabber_stack.clone();
result.item_stack.push(item_rc.downgrade());
result.grabbed = true;
return ItemVisitorResult::Abort;
}
InputEventResult::ObserveHover => {
result.item_stack = mouse_grabber_stack.clone();
result.item_stack.push(item_rc.downgrade());
result.grabbed = false;
}
};
}
let mut mouse_grabber_stack = mouse_grabber_stack.clone();
mouse_grabber_stack.push(item_rc.downgrade());
ItemVisitorResult::Continue((geom.origin.to_vector(), mouse_grabber_stack))
},
(Vector2D::new(0., 0.), Vec::new()),
);
result
}
#[derive(FieldOffsets)]
#[repr(C)]
#[pin]
pub(crate) struct TextCursorBlinker {
cursor_visible: Property<bool>,
cursor_blink_timer: crate::timers::Timer,
}
impl TextCursorBlinker {
pub fn new() -> Pin<Rc<Self>> {
Rc::pin(Self {
cursor_visible: Property::new(true),
cursor_blink_timer: Default::default(),
})
}
pub fn set_binding(instance: Pin<Rc<TextCursorBlinker>>, prop: &Property<bool>) {
instance.as_ref().cursor_visible.set(true);
Self::start(&instance);
prop.set_binding(move || {
TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
});
}
pub fn start(self: &Pin<Rc<Self>>) {
if self.cursor_blink_timer.running() {
self.cursor_blink_timer.restart();
} else {
let toggle_cursor = {
let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
move || {
if let Some(blinker) = weak_blinker.upgrade() {
let visible = TextCursorBlinker::FIELD_OFFSETS
.cursor_visible
.apply_pin(blinker.as_ref())
.get();
blinker.cursor_visible.set(!visible);
}
}
};
self.cursor_blink_timer.start(
crate::timers::TimerMode::Repeated,
std::time::Duration::from_millis(500),
toggle_cursor,
);
}
}
pub fn stop(&self) {
self.cursor_blink_timer.stop()
}
}