#![warn(missing_docs)]
use crate::graphics::Point;
use crate::item_tree::{ItemVisitorResult, VisitChildrenResult};
use crate::items::{ItemRc, ItemRef, ItemWeak, PointerEventButton};
use crate::window::WindowRc;
use crate::Property;
use crate::{component::ComponentRc, SharedString};
use alloc::rc::Rc;
use alloc::vec::Vec;
use const_field_offset::FieldOffsets;
use core::pin::Pin;
use euclid::default::Vector2D;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub enum MouseEvent {
MousePressed { pos: Point, button: PointerEventButton },
MouseReleased { pos: Point, button: PointerEventButton },
MouseMoved { pos: Point },
MouseWheel { pos: Point, delta: Point },
MouseExit,
}
impl MouseEvent {
pub fn pos(&self) -> Option<Point> {
match self {
MouseEvent::MousePressed { pos, .. } => Some(*pos),
MouseEvent::MouseReleased { pos, .. } => Some(*pos),
MouseEvent::MouseMoved { pos } => Some(*pos),
MouseEvent::MouseWheel { pos, .. } => Some(*pos),
MouseEvent::MouseExit => None,
}
}
pub fn translate(&mut self, vec: Vector2D<f32>) {
let pos = match self {
MouseEvent::MousePressed { pos, .. } => Some(pos),
MouseEvent::MouseReleased { pos, .. } => Some(pos),
MouseEvent::MouseMoved { pos } => Some(pos),
MouseEvent::MouseWheel { pos, .. } => Some(pos),
MouseEvent::MouseExit => None,
};
if let Some(pos) = pos {
*pos += vec;
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum InputEventResult {
EventAccepted,
EventIgnored,
GrabMouse,
}
impl Default for InputEventResult {
fn default() -> Self {
Self::EventIgnored
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum InputEventFilterResult {
ForwardEvent,
ForwardAndIgnore,
ForwardAndInterceptGrab,
Intercept,
}
impl Default for InputEventFilterResult {
fn default() -> Self {
Self::ForwardEvent
}
}
#[allow(missing_docs, non_upper_case_globals)]
pub mod key_codes {
macro_rules! declare_consts_for_special_keys {
($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident)|* ;)*) => {
$(pub const $name : char = $char;)*
};
}
sixtyfps_common::for_each_special_keys!(declare_consts_for_special_keys);
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(C)]
pub struct KeyboardModifiers {
pub alt: bool,
pub control: bool,
pub meta: bool,
pub shift: bool,
}
#[derive(Debug, Clone, PartialEq, strum::EnumString, strum::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 modifiers: KeyboardModifiers,
pub text: SharedString,
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, InputEventFilterResult)>,
grabbed: bool,
}
fn handle_mouse_grab(
mouse_event: &MouseEvent,
window: &WindowRc,
mouse_input_state: &mut MouseInputState,
) -> bool {
if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
return false;
};
let mut event = *mouse_event;
let mut intercept = false;
let mut invalid = false;
mouse_input_state.item_stack.retain(|it| {
if invalid {
return false;
}
let item = if let Some(item) = it.0.upgrade() {
item
} else {
invalid = true;
return false;
};
if intercept {
item.borrow().as_ref().input_event(MouseEvent::MouseExit, window, &item);
return false;
}
let g = item.borrow().as_ref().geometry();
event.translate(-g.origin.to_vector());
if it.1 == InputEventFilterResult::ForwardAndInterceptGrab
&& item.borrow().as_ref().input_event_filter_before_children(event, window, &item)
== InputEventFilterResult::Intercept
{
intercept = true;
}
true
});
if invalid {
return false;
}
let grabber = mouse_input_state.item_stack.last().unwrap().0.upgrade().unwrap();
let input_result = grabber.borrow().as_ref().input_event(event, window, &grabber);
if input_result != InputEventResult::GrabMouse {
mouse_input_state.grabbed = false;
send_exit_events(mouse_input_state, mouse_event.pos(), window);
}
true
}
fn send_exit_events(
mouse_input_state: &MouseInputState,
mut pos: Option<Point>,
window: &WindowRc,
) {
for it in mouse_input_state.item_stack.iter() {
let item = if let Some(item) = it.0.upgrade() { item } else { break };
let g = item.borrow().as_ref().geometry();
let contains = pos.map_or(false, |p| g.contains(p));
if let Some(p) = pos.as_mut() {
*p -= g.origin.to_vector();
}
if !contains {
item.borrow().as_ref().input_event(MouseEvent::MouseExit, window, &item);
}
}
}
pub fn process_mouse_input(
component: ComponentRc,
mouse_event: MouseEvent,
window: &WindowRc,
mut mouse_input_state: MouseInputState,
) -> MouseInputState {
if handle_mouse_grab(&mouse_event, window, &mut mouse_input_state) {
return mouse_input_state;
}
send_exit_events(&mouse_input_state, mouse_event.pos(), window);
let mut result = MouseInputState::default();
type State = (Vector2D<f32>, Vec<(ItemWeak, InputEventFilterResult)>);
crate::item_tree::visit_items_with_post_visit(
&component,
crate::item_tree::TraversalOrder::FrontToBack,
|comp_rc: &ComponentRc,
item: core::pin::Pin<ItemRef>,
item_index: usize,
(offset, mouse_grabber_stack): &State| {
let item_rc = ItemRc::new(comp_rc.clone(), item_index);
let geom = item.as_ref().geometry();
let geom = geom.translate(*offset);
let mut mouse_grabber_stack = mouse_grabber_stack.clone();
let post_visit_state = if mouse_event.pos().map_or(false, |p| geom.contains(p))
|| crate::item_rendering::is_clipping_item(item)
{
let mut event2 = mouse_event;
event2.translate(-geom.origin.to_vector());
let filter_result =
item.as_ref().input_event_filter_before_children(event2, window, &item_rc);
mouse_grabber_stack.push((item_rc.downgrade(), filter_result));
match filter_result {
InputEventFilterResult::ForwardAndIgnore => None,
InputEventFilterResult::ForwardEvent => {
Some((event2, mouse_grabber_stack.clone(), item_rc, false))
}
InputEventFilterResult::ForwardAndInterceptGrab => {
Some((event2, mouse_grabber_stack.clone(), item_rc, false))
}
InputEventFilterResult::Intercept => {
return (
ItemVisitorResult::Abort,
Some((event2, mouse_grabber_stack, item_rc, true)),
)
}
}
} else {
mouse_grabber_stack
.push((item_rc.downgrade(), InputEventFilterResult::ForwardAndIgnore));
None
};
(
ItemVisitorResult::Continue((geom.origin.to_vector(), mouse_grabber_stack)),
post_visit_state,
)
},
|_, item, post_state, r| {
if let Some((event2, mouse_grabber_stack, item_rc, intercept)) = post_state {
if r.has_aborted() && !intercept {
return r;
}
match item.as_ref().input_event(event2, window, &item_rc) {
InputEventResult::EventAccepted => {
result.item_stack = mouse_grabber_stack;
result.grabbed = false;
return VisitChildrenResult::abort(item_rc.index(), 0);
}
InputEventResult::EventIgnored => {
return VisitChildrenResult::CONTINUE;
}
InputEventResult::GrabMouse => {
result.item_stack = mouse_grabber_stack;
result.item_stack.last_mut().unwrap().1 =
InputEventFilterResult::ForwardAndInterceptGrab;
result.grabbed = true;
return VisitChildrenResult::abort(item_rc.index(), 0);
}
}
}
r
},
(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,
core::time::Duration::from_millis(500),
toggle_cursor,
);
}
}
pub fn stop(&self) {
self.cursor_blink_timer.stop()
}
}