#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use alloc::{
boxed::Box,
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
vec::Vec,
};
use azul_css::AzString;
use crate::{
callbacks::Update,
dom::{DomId, DomNodeId, On},
geom::{LogicalPosition, LogicalRect},
hit_test::{FullHitTest, HitTestItem},
id::NodeId,
styled_dom::{ChangedCssProperty, NodeHierarchyItemId},
task::Instant,
OrderedMap,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EasingFunction {
Linear,
EaseInOut,
EaseOut,
Spring,
}
pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutWords = BTreeMap<NodeId, AzString>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FocusChange {
pub old: Option<DomNodeId>,
pub new: Option<DomNodeId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CallbackToCall {
pub node_id: NodeId,
pub hit_test_item: Option<HitTestItem>,
pub event_filter: EventFilter,
}
impl CallbackToCall {
#[must_use]
pub const fn new(
node_id: NodeId,
hit_test_item: Option<HitTestItem>,
event_filter: EventFilter,
) -> Self {
Self {
node_id,
hit_test_item,
event_filter,
}
}
#[must_use]
pub fn from_hit_test(
hit_test: &FullHitTest,
dom_id: DomId,
event_filter: EventFilter,
) -> Vec<Self> {
let Some(hit) = hit_test.hovered_nodes.get(&dom_id) else {
return Vec::new();
};
hit.regular_hit_test_nodes
.iter()
.map(|(node_id, item)| Self {
node_id: *node_id,
hit_test_item: Some(*item),
event_filter,
})
.collect()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[must_use = "ProcessEventResult must be used to determine if relayout/repaint is needed"]
pub enum ProcessEventResult {
DoNothing = 0,
ShouldReRenderCurrentWindow = 1,
ShouldUpdateDisplayListCurrentWindow = 2,
UpdateHitTesterAndProcessAgain = 3,
ShouldIncrementalRelayout = 4,
ShouldRegenerateDomCurrentWindow = 5,
ShouldRegenerateDomAllWindows = 6,
}
impl ProcessEventResult {
#[must_use]
pub const fn order(&self) -> usize {
use self::ProcessEventResult::{
DoNothing, ShouldIncrementalRelayout, ShouldReRenderCurrentWindow,
ShouldRegenerateDomAllWindows, ShouldRegenerateDomCurrentWindow,
ShouldUpdateDisplayListCurrentWindow, UpdateHitTesterAndProcessAgain,
};
match self {
DoNothing => 0,
ShouldReRenderCurrentWindow => 1,
ShouldUpdateDisplayListCurrentWindow => 2,
UpdateHitTesterAndProcessAgain => 3,
ShouldIncrementalRelayout => 4,
ShouldRegenerateDomCurrentWindow => 5,
ShouldRegenerateDomAllWindows => 6,
}
}
}
impl PartialOrd for ProcessEventResult {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.order().partial_cmp(&other.order())
}
}
impl Ord for ProcessEventResult {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.order().cmp(&other.order())
}
}
impl ProcessEventResult {
pub fn max_self(self, other: Self) -> Self {
self.max(other)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum EventSource {
User,
Programmatic,
Synthetic,
Lifecycle,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
#[derive(Default)]
pub enum EventPhase {
Capture,
Target,
#[default]
Bubble,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum MouseButton {
Left,
Middle,
Right,
Other(u8),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum ScrollDeltaMode {
Pixel,
Line,
Page,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum ScrollDirection {
Up,
Down,
Left,
Right,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ScrollIntoViewOptions {
pub block: ScrollLogicalPosition,
pub inline_axis: ScrollLogicalPosition,
pub behavior: ScrollIntoViewBehavior,
}
impl ScrollIntoViewOptions {
#[must_use]
pub const fn nearest() -> Self {
Self {
block: ScrollLogicalPosition::Nearest,
inline_axis: ScrollLogicalPosition::Nearest,
behavior: ScrollIntoViewBehavior::Auto,
}
}
#[must_use]
pub const fn center() -> Self {
Self {
block: ScrollLogicalPosition::Center,
inline_axis: ScrollLogicalPosition::Center,
behavior: ScrollIntoViewBehavior::Auto,
}
}
#[must_use]
pub const fn start() -> Self {
Self {
block: ScrollLogicalPosition::Start,
inline_axis: ScrollLogicalPosition::Start,
behavior: ScrollIntoViewBehavior::Auto,
}
}
#[must_use]
pub const fn end() -> Self {
Self {
block: ScrollLogicalPosition::End,
inline_axis: ScrollLogicalPosition::End,
behavior: ScrollIntoViewBehavior::Auto,
}
}
#[must_use]
pub const fn with_instant(mut self) -> Self {
self.behavior = ScrollIntoViewBehavior::Instant;
self
}
#[must_use]
pub const fn with_smooth(mut self) -> Self {
self.behavior = ScrollIntoViewBehavior::Smooth;
self
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum ScrollLogicalPosition {
Start,
Center,
End,
#[default]
Nearest,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum ScrollIntoViewBehavior {
#[default]
Auto,
Instant,
Smooth,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum LifecycleReason {
InitialMount,
Remount,
Resize,
Update,
Unmount,
Dismiss,
TearOff,
Dock,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
#[repr(C)]
pub struct KeyModifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub meta: bool,
}
impl KeyModifiers {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_shift(mut self) -> Self {
self.shift = true;
self
}
#[must_use]
pub const fn with_ctrl(mut self) -> Self {
self.ctrl = true;
self
}
#[must_use]
pub const fn with_alt(mut self) -> Self {
self.alt = true;
self
}
#[must_use]
pub const fn with_meta(mut self) -> Self {
self.meta = true;
self
}
#[must_use]
pub const fn is_empty(&self) -> bool {
!self.shift && !self.ctrl && !self.alt && !self.meta
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum PointerSource {
Unknown,
Mouse,
Touchpad,
Trackball,
Trackpoint,
Touchscreen,
Pen,
Eraser,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct MouseEventData {
pub position: LogicalPosition,
pub button: MouseButton,
pub buttons: u8,
pub modifiers: KeyModifiers,
pub source: PointerSource,
pub device_id: u64,
pub seat_id: u64,
}
impl Default for MouseEventData {
fn default() -> Self {
Self {
position: LogicalPosition { x: 0.0, y: 0.0 },
button: MouseButton::Left,
buttons: 0,
modifiers: KeyModifiers::default(),
source: PointerSource::Unknown,
device_id: 0,
seat_id: crate::window::PRIMARY_POINTER_SEAT,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyboardEventData {
pub key_code: u32,
pub char_code: Option<char>,
pub modifiers: KeyModifiers,
pub repeat: bool,
pub device_id: u64,
pub seat_id: u64,
}
impl Default for KeyboardEventData {
fn default() -> Self {
Self {
key_code: 0,
char_code: None,
modifiers: KeyModifiers::default(),
repeat: false,
device_id: 0,
seat_id: crate::window::PRIMARY_POINTER_SEAT,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScrollEventData {
pub delta: LogicalPosition,
pub delta_mode: ScrollDeltaMode,
pub seat_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TouchEventData {
pub id: u64,
pub position: LogicalPosition,
pub force: f32,
pub seat_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClipboardEventData {
pub content: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LifecycleEventData {
pub reason: LifecycleReason,
pub previous_bounds: Option<LogicalRect>,
pub current_bounds: LogicalRect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WindowEventData {
pub size: Option<LogicalRect>,
pub position: Option<LogicalPosition>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(C)]
pub struct CompositionCursor {
pub begin: usize,
pub end: usize,
}
azul_css::impl_option!(
CompositionCursor,
OptionCompositionCursor,
[Debug, Copy, Clone, PartialEq, Eq]
);
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct RawMotionEventData {
pub dx: f64,
pub dy: f64,
pub device_id: u64,
}
azul_css::impl_option!(
RawMotionEventData,
OptionRawMotionEventData,
[Debug, Copy, Clone, PartialEq]
);
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct MediaControlEventData {
pub position_us: i64,
pub kind: crate::media_session::MediaControlKind,
pub volume: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct SystemAudioEventData {
pub change: crate::media_session::SystemAudioChange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompositionEventData {
pub data: String,
pub cursor_begin: usize,
pub cursor_end: usize,
pub seat_id: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextInputEventData {
pub inserted_text: String,
pub old_text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DocumentEditEventData {
pub changeset_id: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventData {
Mouse(MouseEventData),
Keyboard(KeyboardEventData),
Scroll(ScrollEventData),
Touch(TouchEventData),
Clipboard(ClipboardEventData),
TextInput(TextInputEventData),
DocumentEdit(DocumentEditEventData),
Lifecycle(LifecycleEventData),
Window(WindowEventData),
None,
Composition(CompositionEventData),
RawMotion(RawMotionEventData),
MediaControl(MediaControlEventData),
SystemAudio(SystemAudioEventData),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum EventType {
MouseOver,
MouseEnter,
MouseLeave,
MouseOut,
MouseDown,
MouseUp,
Click,
DoubleClick,
ContextMenu,
KeyDown,
KeyUp,
KeyPress,
CompositionStart,
CompositionUpdate,
CompositionEnd,
Focus,
Blur,
FocusIn,
FocusOut,
Input,
Change,
Submit,
Reset,
Invalid,
Scroll,
ScrollStart,
ScrollEnd,
DragStart,
Drag,
DragEnd,
DragEnter,
DragOver,
DragLeave,
Drop,
TouchStart,
TouchMove,
TouchEnd,
TouchCancel,
PenDown,
PenMove,
PenUp,
PenEnter,
PenLeave,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
Copy,
Cut,
Paste,
Play,
Pause,
Ended,
TimeUpdate,
VolumeChange,
MediaError,
Mount,
Unmount,
Update,
Resize,
Dismiss,
TearOff,
Dock,
WindowResize,
WindowMove,
WindowClose,
WindowFrameChanged,
WindowFocusIn,
WindowFocusOut,
ThemeChange,
WindowDpiChanged,
WindowMonitorChanged,
MonitorConnected,
MonitorDisconnected,
FileHover,
FileDrop,
FileHoverCancel,
SensorChanged,
GamepadInput,
GeolocationFix,
GeolocationError,
PermissionChanged,
BiometricResult,
ScreenColorPicked,
KeyringResult,
DocumentEdit,
TextChanged,
DeviceConnected,
DeviceDisconnected,
PenSqueeze,
PenDoubleTap,
PenHover,
DefaultAction,
Selected,
HidReport,
ModifiersChanged,
RawMouseMotion,
DialRotate,
DialClick,
MouseMove,
MediaControl,
PointerLockChange,
SystemAudioChange,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::struct_excessive_bools)] pub struct SyntheticEvent {
pub event_type: EventType,
pub source: EventSource,
pub phase: EventPhase,
pub target: DomNodeId,
pub current_target: DomNodeId,
pub timestamp: Instant,
pub data: EventData,
pub stopped: bool,
pub stopped_immediate: bool,
pub prevented_default: bool,
pub at_target_only: bool,
}
impl SyntheticEvent {
#[must_use]
pub const fn new(
event_type: EventType,
source: EventSource,
target: DomNodeId,
timestamp: Instant,
data: EventData,
) -> Self {
Self {
event_type,
source,
phase: EventPhase::Target,
target,
current_target: target,
timestamp,
data,
stopped: false,
stopped_immediate: false,
prevented_default: false,
at_target_only: false,
}
}
#[must_use]
pub const fn at_target_only(mut self) -> Self {
self.at_target_only = true;
self
}
pub const fn stop_propagation(&mut self) {
self.stopped = true;
}
pub const fn stop_immediate_propagation(&mut self) {
self.stopped_immediate = true;
self.stopped = true;
}
pub const fn prevent_default(&mut self) {
self.prevented_default = true;
}
#[must_use]
pub const fn is_propagation_stopped(&self) -> bool {
self.stopped
}
#[must_use]
pub const fn is_immediate_propagation_stopped(&self) -> bool {
self.stopped_immediate
}
#[must_use]
pub const fn is_default_prevented(&self) -> bool {
self.prevented_default
}
}
#[derive(Debug, Clone, Default)]
pub struct PropagationResult {
pub callbacks_to_invoke: Vec<(NodeId, EventFilter)>,
pub default_prevented: bool,
}
#[must_use]
pub fn get_dom_path(
node_hierarchy: &crate::id::NodeHierarchy,
target_node: NodeHierarchyItemId,
) -> Vec<NodeId> {
let mut path = Vec::new();
let Some(target_node_id) = target_node.into_crate_internal() else {
return path;
};
let hier_ref = node_hierarchy.as_ref();
let node_count = hier_ref.len();
let mut visited: BTreeSet<NodeId> = BTreeSet::new();
let mut current = Some(target_node_id);
while let Some(node_id) = current {
if path.len() > node_count || !visited.insert(node_id) {
break;
}
path.push(node_id);
current = hier_ref.get(node_id).and_then(|node| node.parent);
}
path.reverse();
path
}
pub fn propagate_event(
event: &mut SyntheticEvent,
node_hierarchy: &crate::id::NodeHierarchy,
callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
) -> PropagationResult {
let path = get_dom_path(node_hierarchy, event.target.node);
if path.is_empty() {
return PropagationResult::default();
}
let ancestors = &path[..path.len().saturating_sub(1)];
let target_node_id = *path.last().unwrap();
let mut result = PropagationResult::default();
if event.at_target_only {
propagate_target_phase(event, target_node_id, callbacks, &mut result);
result.default_prevented = event.prevented_default;
return result;
}
propagate_phase(
event,
ancestors.iter().copied(),
EventPhase::Capture,
callbacks,
&mut result,
);
if !event.stopped {
propagate_target_phase(event, target_node_id, callbacks, &mut result);
}
if !event.stopped && event.event_type.bubbles() {
propagate_phase(
event,
ancestors.iter().rev().copied(),
EventPhase::Bubble,
callbacks,
&mut result,
);
}
result.default_prevented = event.prevented_default;
result
}
impl EventType {
#[must_use]
pub const fn bubbles(self) -> bool {
!matches!(
self,
Self::MouseEnter | Self::MouseLeave | Self::PenEnter | Self::PenLeave
)
}
}
fn propagate_phase(
event: &mut SyntheticEvent,
nodes: impl Iterator<Item = NodeId>,
phase: EventPhase,
callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
result: &mut PropagationResult,
) {
event.phase = phase;
for node_id in nodes {
if event.stopped_immediate || event.stopped {
return;
}
event.current_target = DomNodeId {
dom: event.target.dom,
node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
};
collect_matching_callbacks(event, node_id, phase, callbacks, result);
}
}
fn propagate_target_phase(
event: &mut SyntheticEvent,
target_node_id: NodeId,
callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
result: &mut PropagationResult,
) {
event.phase = EventPhase::Target;
event.current_target = event.target;
collect_matching_callbacks(event, target_node_id, EventPhase::Target, callbacks, result);
}
fn collect_matching_callbacks(
event: &SyntheticEvent,
node_id: NodeId,
phase: EventPhase,
callbacks: &BTreeMap<NodeId, Vec<EventFilter>>,
result: &mut PropagationResult,
) {
let Some(node_callbacks) = callbacks.get(&node_id) else {
return;
};
let matching = node_callbacks
.iter()
.take_while(|_| !event.stopped_immediate)
.filter(|filter| matches_filter_phase(**filter, event, phase))
.map(|filter| (node_id, *filter));
result.callbacks_to_invoke.extend(matching);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C, u8)]
pub enum DefaultAction {
FocusNext,
FocusPrevious,
FocusFirst,
FocusLast,
FocusUp,
FocusDown,
FocusLeft,
FocusRight,
ClearFocus,
ActivateFocusedElement { target: DomNodeId },
SubmitForm { form_node: DomNodeId },
CloseModal { modal_node: DomNodeId },
ScrollFocusedContainer {
direction: ScrollDirection,
amount: ScrollAmount,
},
SelectAllText,
SplitBlockAtCursor { target: DomNodeId },
MergeWithPrevious { target: DomNodeId },
MergeWithNext { target: DomNodeId },
None,
InsertLineBreakAtCursor { target: DomNodeId },
ResetForm { form_node: DomNodeId },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum ScrollAmount {
Line,
Page,
Document,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct DefaultActionResult {
pub action: DefaultAction,
pub prevented: bool,
}
impl Default for DefaultActionResult {
fn default() -> Self {
Self {
action: DefaultAction::None,
prevented: false,
}
}
}
impl DefaultActionResult {
#[must_use]
pub const fn new(action: DefaultAction) -> Self {
Self {
action,
prevented: false,
}
}
#[must_use]
pub const fn prevented() -> Self {
Self {
action: DefaultAction::None,
prevented: true,
}
}
#[must_use]
pub const fn has_action(&self) -> bool {
!self.prevented && !matches!(self.action, DefaultAction::None)
}
}
pub trait ActivationBehavior {
fn has_activation_behavior(&self) -> bool;
fn is_activatable(&self) -> bool;
}
pub trait Focusable {
fn get_tabindex(&self) -> Option<i32>;
fn is_focusable(&self) -> bool;
fn is_in_tab_order(&self) -> bool {
self.get_tabindex()
.map_or_else(|| self.is_naturally_focusable(), |i| i >= 0)
}
fn is_naturally_focusable(&self) -> bool;
}
fn matches_filter_phase(
filter: EventFilter,
event: &SyntheticEvent,
current_phase: EventPhase,
) -> bool {
if matches!(current_phase, EventPhase::Capture) {
return false;
}
match filter {
EventFilter::Hover(hover_filter) => {
matches_hover_filter(hover_filter, event, current_phase)
}
EventFilter::Focus(focus_filter) => {
matches_focus_filter(focus_filter, event, current_phase)
}
EventFilter::Window(window_filter) => {
matches_window_filter(window_filter, event, current_phase)
}
EventFilter::Component(component_filter) => {
matches_component_filter(component_filter, event, current_phase)
}
EventFilter::Application(application_filter) => {
matches_application_filter(application_filter, event, current_phase)
}
EventFilter::External(external_filter) => {
matches_external_filter(external_filter, event, current_phase)
}
}
}
const fn matches_component_filter(
filter: ComponentEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
matches!(
(filter, &event.event_type),
(ComponentEventFilter::AfterMount, EventType::Mount)
| (ComponentEventFilter::BeforeUnmount, EventType::Unmount)
| (ComponentEventFilter::Updated, EventType::Update)
| (ComponentEventFilter::NodeResized, EventType::Resize)
| (ComponentEventFilter::Dismissed, EventType::Dismiss)
| (ComponentEventFilter::TornOff, EventType::TearOff)
| (ComponentEventFilter::Docked, EventType::Dock)
| (ComponentEventFilter::DefaultAction, EventType::DefaultAction)
| (ComponentEventFilter::Selected, EventType::Selected)
)
}
pub const MOUSE_BUTTON_BACK: u8 = 3;
pub const MOUSE_BUTTON_FORWARD: u8 = 4;
pub const MOUSE_OTHER_MASK_BACK: u8 = 1 << 0;
pub const MOUSE_OTHER_MASK_FORWARD: u8 = 1 << 1;
fn check_mouse_button(data: &EventData, expected: MouseButton) -> bool {
if let EventData::Mouse(mouse_data) = data {
mouse_data.button == expected
} else {
false
}
}
#[allow(clippy::match_same_arms)]
fn matches_hover_filter(
filter: HoverEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use HoverEventFilter::{
BiometricResult, DoubleClick, Drag, DragEnd, DragEnter, DragLeave, DragOver, DragStart,
Drop, DroppedFile, GamepadInput, GeolocationError, GeolocationFix, HoveredFile,
HoveredFileCancelled, KeyringResult, LeftMouseDown, LeftMouseUp, MiddleMouseDown,
MiddleMouseUp, MouseDown, MouseEnter, MouseLeave, MouseMove, MouseOver, MouseUp,
PenDoubleTap, PenDown,
PenEnter, PenHover, PenLeave, PenMove, PenSqueeze, PenUp, PermissionChanged, RightMouseDown,
RightMouseUp,
ScreenColorPicked, Scroll, ScrollEnd, ScrollStart, SensorChanged, TextInput, TouchCancel,
TouchEnd, TouchMove, TouchStart, VirtualKeyDown, VirtualKeyUp,
};
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseMove, EventType::MouseMove) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Right)
}
(MiddleMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Middle)
}
(MouseUp, EventType::MouseUp) => true,
(HoverEventFilter::Click, EventType::Click) => true,
(LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
(MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => true,
(TextInput, EventType::KeyPress) => true,
(TextInput, EventType::Change) => true,
(RightMouseDown, EventType::ContextMenu) => true,
(VirtualKeyDown, EventType::KeyDown) => true,
(VirtualKeyUp, EventType::KeyUp) => true,
(HoveredFile, EventType::FileHover) => true,
(DroppedFile, EventType::FileDrop) => true,
(HoveredFileCancelled, EventType::FileHoverCancel) => true,
(TouchStart, EventType::TouchStart) => true,
(TouchMove, EventType::TouchMove) => true,
(TouchEnd, EventType::TouchEnd) => true,
(TouchCancel, EventType::TouchCancel) => true,
(PenDown, EventType::PenDown) => true,
(PenMove, EventType::PenMove) => true,
(PenUp, EventType::PenUp) => true,
(PenEnter, EventType::PenEnter) => true,
(PenLeave, EventType::PenLeave) => true,
(DragStart, EventType::DragStart) => true,
(Drag, EventType::Drag) => true,
(DragEnd, EventType::DragEnd) => true,
(DragEnter, EventType::DragEnter) => true,
(DragOver, EventType::DragOver) => true,
(DragLeave, EventType::DragLeave) => true,
(Drop, EventType::Drop) => true,
(DoubleClick, EventType::DoubleClick) => true,
(SensorChanged, EventType::SensorChanged) => true,
(GamepadInput, EventType::GamepadInput) => true,
(GeolocationFix, EventType::GeolocationFix) => true,
(GeolocationError, EventType::GeolocationError) => true,
(PermissionChanged, EventType::PermissionChanged) => true,
(BiometricResult, EventType::BiometricResult) => true,
(ScreenColorPicked, EventType::ScreenColorPicked) => true,
(KeyringResult, EventType::KeyringResult) => true,
(HoverEventFilter::LongPress, EventType::LongPress) => true,
(HoverEventFilter::SwipeLeft, EventType::SwipeLeft) => true,
(HoverEventFilter::SwipeRight, EventType::SwipeRight) => true,
(HoverEventFilter::SwipeUp, EventType::SwipeUp) => true,
(HoverEventFilter::SwipeDown, EventType::SwipeDown) => true,
(HoverEventFilter::PinchIn, EventType::PinchIn) => true,
(HoverEventFilter::PinchOut, EventType::PinchOut) => true,
(HoverEventFilter::RotateClockwise, EventType::RotateClockwise) => true,
(HoverEventFilter::RotateCounterClockwise, EventType::RotateCounterClockwise) => true,
(HoverEventFilter::MouseOut, EventType::MouseOut) => true,
(HoverEventFilter::FocusIn, EventType::FocusIn) => true,
(HoverEventFilter::FocusOut, EventType::FocusOut) => true,
(HoverEventFilter::CompositionStart, EventType::CompositionStart) => true,
(HoverEventFilter::CompositionUpdate, EventType::CompositionUpdate) => true,
(HoverEventFilter::CompositionEnd, EventType::CompositionEnd) => true,
(PenSqueeze, EventType::PenSqueeze) => true,
(PenDoubleTap, EventType::PenDoubleTap) => true,
(PenHover, EventType::PenHover) => true,
(HoverEventFilter::BackMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(HoverEventFilter::BackMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(HoverEventFilter::ForwardMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(HoverEventFilter::ForwardMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(HoverEventFilter::Submit, EventType::Submit) => true,
(HoverEventFilter::Change, EventType::Change) => true,
(HoverEventFilter::Reset, EventType::Reset) => true,
(HoverEventFilter::Invalid, EventType::Invalid) => true,
(HoverEventFilter::DialRotate, EventType::DialRotate) => true,
(HoverEventFilter::DialClick, EventType::DialClick) => true,
_ => false,
}
}
#[allow(clippy::match_same_arms)]
fn matches_focus_filter(
filter: FocusEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use FocusEventFilter::{
Drag, DragEnd, DragEnter, DragLeave, DragOver, DragStart, Drop, FocusLost, FocusReceived,
LeftMouseDown, LeftMouseUp, MiddleMouseDown, MiddleMouseUp, MouseDown, MouseEnter,
MouseLeave, MouseMove, MouseOver, MouseUp, RightMouseDown, RightMouseUp, Scroll, ScrollEnd,
ScrollStart, TextInput, VirtualKeyDown, VirtualKeyUp,
};
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseMove, EventType::MouseMove) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Right)
}
(MiddleMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Middle)
}
(MouseUp, EventType::MouseUp) => true,
(LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
(MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => true,
(FocusEventFilter::DocumentEdit, EventType::DocumentEdit) => true,
(FocusEventFilter::TextChanged, EventType::TextChanged) => true,
(VirtualKeyDown, EventType::KeyDown) => true,
(VirtualKeyUp, EventType::KeyUp) => true,
(FocusReceived, EventType::Focus) => true,
(FocusLost, EventType::Blur) => true,
(DragStart, EventType::DragStart) => true,
(Drag, EventType::Drag) => true,
(DragEnd, EventType::DragEnd) => true,
(DragEnter, EventType::DragEnter) => true,
(DragOver, EventType::DragOver) => true,
(DragLeave, EventType::DragLeave) => true,
(Drop, EventType::Drop) => true,
(FocusEventFilter::Copy, EventType::Copy) => true,
(FocusEventFilter::Cut, EventType::Cut) => true,
(FocusEventFilter::Paste, EventType::Paste) => true,
(FocusEventFilter::LongPress, EventType::LongPress) => true,
(FocusEventFilter::SwipeLeft, EventType::SwipeLeft) => true,
(FocusEventFilter::SwipeRight, EventType::SwipeRight) => true,
(FocusEventFilter::SwipeUp, EventType::SwipeUp) => true,
(FocusEventFilter::SwipeDown, EventType::SwipeDown) => true,
(FocusEventFilter::PinchIn, EventType::PinchIn) => true,
(FocusEventFilter::PinchOut, EventType::PinchOut) => true,
(FocusEventFilter::RotateClockwise, EventType::RotateClockwise) => true,
(FocusEventFilter::RotateCounterClockwise, EventType::RotateCounterClockwise) => true,
(FocusEventFilter::PenDown, EventType::PenDown) => true,
(FocusEventFilter::PenMove, EventType::PenMove) => true,
(FocusEventFilter::PenUp, EventType::PenUp) => true,
(FocusEventFilter::BackMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(FocusEventFilter::BackMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(FocusEventFilter::ForwardMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(FocusEventFilter::ForwardMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(FocusEventFilter::Submit, EventType::Submit) => true,
(FocusEventFilter::Change, EventType::Change) => true,
(FocusEventFilter::Reset, EventType::Reset) => true,
(FocusEventFilter::Invalid, EventType::Invalid) => true,
_ => false,
}
}
#[allow(clippy::match_same_arms)]
fn matches_application_filter(
filter: ApplicationEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use ApplicationEventFilter::{
DeviceConnected, DeviceDisconnected, MediaControl, MonitorConnected, MonitorDisconnected,
SystemAudioChange,
};
match (filter, &event.event_type) {
(MediaControl, EventType::MediaControl) => true,
(SystemAudioChange, EventType::SystemAudioChange) => true,
(DeviceConnected, EventType::DeviceConnected) => true,
(DeviceDisconnected, EventType::DeviceDisconnected) => true,
(MonitorConnected, EventType::MonitorConnected) => true,
(MonitorDisconnected, EventType::MonitorDisconnected) => true,
_ => false,
}
}
#[allow(clippy::match_same_arms)]
const fn matches_external_filter(
filter: ExternalEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
matches!(
(filter, &event.event_type),
(ExternalEventFilter::Play, EventType::Play)
| (ExternalEventFilter::Pause, EventType::Pause)
| (ExternalEventFilter::Ended, EventType::Ended)
| (ExternalEventFilter::TimeUpdate, EventType::TimeUpdate)
| (ExternalEventFilter::VolumeChange, EventType::VolumeChange)
| (ExternalEventFilter::MediaError, EventType::MediaError)
)
}
#[allow(clippy::match_same_arms)]
fn matches_window_filter(
filter: WindowEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use WindowEventFilter::{
BiometricResult, CloseRequested, Drag, DragEnd, DragEnter, DragLeave, DragOver, DragStart,
Drop, DroppedFile, FocusLost, FocusReceived, FrameChanged, GamepadInput, GeolocationError,
GeolocationFix, HoveredFile, HoveredFileCancelled, KeyringResult, LeftMouseDown,
LeftMouseUp, MiddleMouseDown, MiddleMouseUp, MouseDown, MouseEnter, MouseLeave, MouseMove,
MouseOver,
MouseUp, Moved, PenDoubleTap, PenDown, PenEnter, PenHover, PenLeave, PenMove, PenSqueeze,
ModifiersChanged, PenUp, PermissionChanged, PointerLockChange, RawMouseMotion, Resized,
RightMouseDown, RightMouseUp, ScreenColorPicked, Scroll, ScrollEnd, ScrollStart,
SensorChanged, TextInput, ThemeChanged, TouchCancel, TouchEnd, TouchMove, TouchStart,
VirtualKeyDown, VirtualKeyUp, WindowFocusLost, WindowFocusReceived,
};
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseMove, EventType::MouseMove) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Right)
}
(MiddleMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Middle)
}
(MouseUp, EventType::MouseUp) => true,
(LeftMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Left),
(RightMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Right),
(MiddleMouseUp, EventType::MouseUp) => check_mouse_button(&event.data, MouseButton::Middle),
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => true,
(TextInput, EventType::KeyPress) => true,
(TextInput, EventType::Change) => true,
(RightMouseDown, EventType::ContextMenu) => true,
(VirtualKeyDown, EventType::KeyDown) => true,
(VirtualKeyUp, EventType::KeyUp) => true,
(HoveredFile, EventType::FileHover) => true,
(DroppedFile, EventType::FileDrop) => true,
(HoveredFileCancelled, EventType::FileHoverCancel) => true,
(Resized, EventType::WindowResize) => true,
(FrameChanged, EventType::WindowFrameChanged) => true,
(Moved, EventType::WindowMove) => true,
(TouchStart, EventType::TouchStart) => true,
(TouchMove, EventType::TouchMove) => true,
(TouchEnd, EventType::TouchEnd) => true,
(TouchCancel, EventType::TouchCancel) => true,
(PenDown, EventType::PenDown) => true,
(PenMove, EventType::PenMove) => true,
(PenUp, EventType::PenUp) => true,
(PenEnter, EventType::PenEnter) => true,
(PenLeave, EventType::PenLeave) => true,
(FocusReceived, EventType::Focus) => true,
(FocusLost, EventType::Blur) => true,
(CloseRequested, EventType::WindowClose) => true,
(ThemeChanged, EventType::ThemeChange) => true,
(WindowFocusReceived, EventType::WindowFocusIn) => true,
(WindowFocusLost, EventType::WindowFocusOut) => true,
(PointerLockChange, EventType::PointerLockChange) => true,
(SensorChanged, EventType::SensorChanged) => true,
(GamepadInput, EventType::GamepadInput) => true,
(GeolocationFix, EventType::GeolocationFix) => true,
(GeolocationError, EventType::GeolocationError) => true,
(PermissionChanged, EventType::PermissionChanged) => true,
(BiometricResult, EventType::BiometricResult) => true,
(ScreenColorPicked, EventType::ScreenColorPicked) => true,
(KeyringResult, EventType::KeyringResult) => true,
(DragStart, EventType::DragStart) => true,
(Drag, EventType::Drag) => true,
(DragEnd, EventType::DragEnd) => true,
(DragEnter, EventType::DragEnter) => true,
(DragOver, EventType::DragOver) => true,
(DragLeave, EventType::DragLeave) => true,
(Drop, EventType::Drop) => true,
(WindowEventFilter::LongPress, EventType::LongPress) => true,
(WindowEventFilter::SwipeLeft, EventType::SwipeLeft) => true,
(WindowEventFilter::SwipeRight, EventType::SwipeRight) => true,
(WindowEventFilter::SwipeUp, EventType::SwipeUp) => true,
(WindowEventFilter::SwipeDown, EventType::SwipeDown) => true,
(WindowEventFilter::PinchIn, EventType::PinchIn) => true,
(WindowEventFilter::PinchOut, EventType::PinchOut) => true,
(WindowEventFilter::RotateClockwise, EventType::RotateClockwise) => true,
(WindowEventFilter::RotateCounterClockwise, EventType::RotateCounterClockwise) => true,
(PenSqueeze, EventType::PenSqueeze) => true,
(PenDoubleTap, EventType::PenDoubleTap) => true,
(PenHover, EventType::PenHover) => true,
(WindowEventFilter::BackMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(WindowEventFilter::BackMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_BACK))
}
(WindowEventFilter::ForwardMouseDown, EventType::MouseDown) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(WindowEventFilter::ForwardMouseUp, EventType::MouseUp) => {
check_mouse_button(&event.data, MouseButton::Other(MOUSE_BUTTON_FORWARD))
}
(RawMouseMotion, EventType::RawMouseMotion) => true,
(ModifiersChanged, EventType::ModifiersChanged) => true,
(WindowEventFilter::HidReport, EventType::HidReport) => true,
(WindowEventFilter::DialRotate, EventType::DialRotate) => true,
(WindowEventFilter::DialClick, EventType::DialClick) => true,
(WindowEventFilter::Play, EventType::Play) => true,
(WindowEventFilter::Pause, EventType::Pause) => true,
(WindowEventFilter::Ended, EventType::Ended) => true,
(WindowEventFilter::TimeUpdate, EventType::TimeUpdate) => true,
(WindowEventFilter::VolumeChange, EventType::VolumeChange) => true,
(WindowEventFilter::MediaError, EventType::MediaError) => true,
_ => false,
}
}
#[allow(clippy::needless_pass_by_value)] #[must_use]
pub fn detect_lifecycle_events(
old_dom_id: DomId,
new_dom_id: DomId,
old_hierarchy: Option<&crate::id::NodeHierarchy>,
new_hierarchy: Option<&crate::id::NodeHierarchy>,
old_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
new_layout: Option<&BTreeMap<NodeId, LogicalRect>>,
timestamp: Instant,
) -> Vec<SyntheticEvent> {
let old_nodes = collect_node_ids(old_hierarchy);
let new_nodes = collect_node_ids(new_hierarchy);
let mut events = Vec::new();
if let Some(layout) = new_layout {
for &node_id in new_nodes.difference(&old_nodes) {
events.push(create_mount_event(node_id, new_dom_id, layout, ×tamp));
}
}
if let Some(layout) = old_layout {
for &node_id in old_nodes.difference(&new_nodes) {
events.push(create_unmount_event(
node_id, old_dom_id, layout, ×tamp,
));
}
}
if let (Some(old_l), Some(new_l)) = (old_layout, new_layout) {
for &node_id in old_nodes.intersection(&new_nodes) {
if let Some(ev) = create_resize_event(node_id, new_dom_id, old_l, new_l, ×tamp) {
events.push(ev);
}
}
}
events
}
fn collect_node_ids(hierarchy: Option<&crate::id::NodeHierarchy>) -> BTreeSet<NodeId> {
hierarchy
.map(|h| h.as_ref().linear_iter().collect())
.unwrap_or_default()
}
fn create_lifecycle_event(
event_type: EventType,
node_id: NodeId,
dom_id: DomId,
timestamp: &Instant,
data: LifecycleEventData,
) -> SyntheticEvent {
let dom_node_id = DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
};
SyntheticEvent {
event_type,
source: EventSource::Lifecycle,
phase: EventPhase::Target,
target: dom_node_id,
current_target: dom_node_id,
timestamp: timestamp.clone(),
data: EventData::Lifecycle(data),
stopped: false,
stopped_immediate: false,
prevented_default: false,
at_target_only: false,
}
}
fn create_mount_event(
node_id: NodeId,
dom_id: DomId,
layout: &BTreeMap<NodeId, LogicalRect>,
timestamp: &Instant,
) -> SyntheticEvent {
let current_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
create_lifecycle_event(
EventType::Mount,
node_id,
dom_id,
timestamp,
LifecycleEventData {
reason: LifecycleReason::InitialMount,
previous_bounds: None,
current_bounds,
},
)
}
fn create_unmount_event(
node_id: NodeId,
dom_id: DomId,
layout: &BTreeMap<NodeId, LogicalRect>,
timestamp: &Instant,
) -> SyntheticEvent {
let previous_bounds = layout.get(&node_id).copied().unwrap_or(LogicalRect::zero());
create_lifecycle_event(
EventType::Unmount,
node_id,
dom_id,
timestamp,
LifecycleEventData {
reason: LifecycleReason::Unmount,
previous_bounds: Some(previous_bounds),
current_bounds: LogicalRect::zero(),
},
)
}
fn size_changed(old: crate::geom::LogicalSize, new: crate::geom::LogicalSize) -> bool {
fn dim_changed(a: f32, b: f32) -> bool {
if a.is_nan() && b.is_nan() {
return false;
}
#[allow(clippy::cast_possible_truncation)]
let q = |v: f32| -> i64 {
if v.is_nan() {
i64::MIN
} else {
(v * 1000.0) as i64
}
};
q(a) != q(b)
}
dim_changed(old.width, new.width) || dim_changed(old.height, new.height)
}
fn create_resize_event(
node_id: NodeId,
dom_id: DomId,
old_layout: &BTreeMap<NodeId, LogicalRect>,
new_layout: &BTreeMap<NodeId, LogicalRect>,
timestamp: &Instant,
) -> Option<SyntheticEvent> {
let old_bounds = *old_layout.get(&node_id)?;
let new_bounds = *new_layout.get(&node_id)?;
if !size_changed(old_bounds.size, new_bounds.size) {
return None;
}
Some(create_lifecycle_event(
EventType::Resize,
node_id,
dom_id,
timestamp,
LifecycleEventData {
reason: LifecycleReason::Resize,
previous_bounds: Some(old_bounds),
current_bounds: new_bounds,
},
))
}
#[must_use]
pub fn resize_event_for_bounds(
dom_id: DomId,
node_id: NodeId,
old: LogicalRect,
new: LogicalRect,
timestamp: &Instant,
) -> Option<SyntheticEvent> {
if !size_changed(old.size, new.size) {
return None;
}
Some(create_lifecycle_event(
EventType::Resize,
node_id,
dom_id,
timestamp,
LifecycleEventData {
reason: LifecycleReason::Resize,
previous_bounds: Some(old),
current_bounds: new,
},
))
}
#[derive(Debug, Clone, Default)]
pub struct LifecycleEventResult {
pub events: Vec<SyntheticEvent>,
pub node_id_mapping: OrderedMap<NodeId, NodeId>,
}
#[must_use]
pub fn detect_lifecycle_events_with_reconciliation(
dom_id: DomId,
old_node_data: &[crate::dom::NodeData],
new_node_data: &[crate::dom::NodeData],
old_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
new_hierarchy: &[crate::styled_dom::NodeHierarchyItem],
old_layout: &OrderedMap<NodeId, LogicalRect>,
new_layout: &OrderedMap<NodeId, LogicalRect>,
timestamp: Instant,
) -> LifecycleEventResult {
let diff_result = crate::diff::reconcile_dom(
old_node_data,
new_node_data,
old_hierarchy,
new_hierarchy,
old_layout,
new_layout,
dom_id,
timestamp,
);
LifecycleEventResult {
events: diff_result.events,
node_id_mapping: crate::diff::create_migration_map(&diff_result.node_moves),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum HoverEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
Click,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
TouchStart,
TouchMove,
TouchEnd,
TouchCancel,
PenDown,
PenMove,
PenUp,
PenEnter,
PenLeave,
PenSqueeze,
PenDoubleTap,
PenHover,
GeolocationFix,
GeolocationError,
SensorChanged,
GamepadInput,
DragStart,
Drag,
DragEnd,
DragEnter,
DragOver,
DragLeave,
Drop,
DoubleClick,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
MouseOut,
FocusIn,
FocusOut,
CompositionStart,
CompositionUpdate,
CompositionEnd,
#[doc(hidden)]
SystemTextSingleClick,
#[doc(hidden)]
SystemTextDoubleClick,
#[doc(hidden)]
SystemTextTripleClick,
PermissionChanged,
BiometricResult,
ScreenColorPicked,
KeyringResult,
BackMouseDown,
BackMouseUp,
ForwardMouseDown,
ForwardMouseUp,
Submit,
Change,
Reset,
Invalid,
DialRotate,
DialClick,
MouseMove,
}
impl HoverEventFilter {
#[must_use]
pub const fn is_system_internal(&self) -> bool {
matches!(
self,
Self::SystemTextSingleClick | Self::SystemTextDoubleClick | Self::SystemTextTripleClick
)
}
#[allow(clippy::match_same_arms)]
#[must_use]
pub const fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
match self {
Self::DialRotate | Self::DialClick => None,
Self::MouseOver => Some(FocusEventFilter::MouseOver),
Self::MouseMove => Some(FocusEventFilter::MouseMove),
Self::MouseDown => Some(FocusEventFilter::MouseDown),
Self::LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
Self::RightMouseDown => Some(FocusEventFilter::RightMouseDown),
Self::MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
Self::Click => None,
Self::MouseUp => Some(FocusEventFilter::MouseUp),
Self::LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
Self::RightMouseUp => Some(FocusEventFilter::RightMouseUp),
Self::MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
Self::MouseEnter => Some(FocusEventFilter::MouseEnter),
Self::MouseLeave => Some(FocusEventFilter::MouseLeave),
Self::Scroll => Some(FocusEventFilter::Scroll),
Self::ScrollStart => Some(FocusEventFilter::ScrollStart),
Self::ScrollEnd => Some(FocusEventFilter::ScrollEnd),
Self::TextInput => Some(FocusEventFilter::TextInput),
Self::VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
Self::VirtualKeyUp => Some(FocusEventFilter::VirtualKeyUp),
Self::HoveredFile => None,
Self::DroppedFile => None,
Self::HoveredFileCancelled => None,
Self::TouchStart => None,
Self::TouchMove => None,
Self::TouchEnd => None,
Self::TouchCancel => None,
Self::PenDown => Some(FocusEventFilter::PenDown),
Self::PenMove => Some(FocusEventFilter::PenMove),
Self::PenUp => Some(FocusEventFilter::PenUp),
Self::PenEnter => None,
Self::PenLeave => None,
Self::PenSqueeze => None,
Self::PenDoubleTap => None,
Self::PenHover => None,
Self::GeolocationFix => None,
Self::GeolocationError => None,
Self::SensorChanged => None,
Self::GamepadInput => None,
Self::DragStart => Some(FocusEventFilter::DragStart),
Self::Drag => Some(FocusEventFilter::Drag),
Self::DragEnd => Some(FocusEventFilter::DragEnd),
Self::DragEnter => Some(FocusEventFilter::DragEnter),
Self::DragOver => Some(FocusEventFilter::DragOver),
Self::DragLeave => Some(FocusEventFilter::DragLeave),
Self::Drop => Some(FocusEventFilter::Drop),
Self::DoubleClick => Some(FocusEventFilter::DoubleClick),
Self::LongPress => Some(FocusEventFilter::LongPress),
Self::SwipeLeft => Some(FocusEventFilter::SwipeLeft),
Self::SwipeRight => Some(FocusEventFilter::SwipeRight),
Self::SwipeUp => Some(FocusEventFilter::SwipeUp),
Self::SwipeDown => Some(FocusEventFilter::SwipeDown),
Self::PinchIn => Some(FocusEventFilter::PinchIn),
Self::PinchOut => Some(FocusEventFilter::PinchOut),
Self::RotateClockwise => Some(FocusEventFilter::RotateClockwise),
Self::RotateCounterClockwise => Some(FocusEventFilter::RotateCounterClockwise),
Self::MouseOut => Some(FocusEventFilter::MouseLeave), Self::FocusIn => Some(FocusEventFilter::FocusIn),
Self::FocusOut => Some(FocusEventFilter::FocusOut),
Self::CompositionStart => Some(FocusEventFilter::CompositionStart),
Self::CompositionUpdate => Some(FocusEventFilter::CompositionUpdate),
Self::CompositionEnd => Some(FocusEventFilter::CompositionEnd),
Self::SystemTextSingleClick => None,
Self::SystemTextDoubleClick => None,
Self::SystemTextTripleClick => None,
Self::PermissionChanged => None,
Self::BiometricResult => None,
Self::ScreenColorPicked => None,
Self::KeyringResult => None,
Self::BackMouseDown => Some(FocusEventFilter::BackMouseDown),
Self::BackMouseUp => Some(FocusEventFilter::BackMouseUp),
Self::ForwardMouseDown => Some(FocusEventFilter::ForwardMouseDown),
Self::ForwardMouseUp => Some(FocusEventFilter::ForwardMouseUp),
Self::Submit => Some(FocusEventFilter::Submit),
Self::Change => Some(FocusEventFilter::Change),
Self::Reset => Some(FocusEventFilter::Reset),
Self::Invalid => Some(FocusEventFilter::Invalid),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum FocusEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
FocusReceived,
FocusLost,
PenDown,
PenMove,
PenUp,
DragStart,
Drag,
DragEnd,
DragEnter,
DragOver,
DragLeave,
Drop,
DoubleClick,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
FocusIn,
FocusOut,
CompositionStart,
CompositionUpdate,
CompositionEnd,
Copy,
Cut,
Paste,
DocumentEdit,
TextChanged,
BackMouseDown,
BackMouseUp,
ForwardMouseDown,
ForwardMouseUp,
Submit,
Change,
Reset,
Invalid,
MouseMove,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub enum WindowEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
Resized,
Moved,
FrameChanged,
TouchStart,
TouchMove,
TouchEnd,
TouchCancel,
FocusReceived,
FocusLost,
CloseRequested,
ThemeChanged,
WindowFocusReceived,
WindowFocusLost,
PenDown,
PenMove,
PenUp,
PenEnter,
PenLeave,
PenSqueeze,
PenDoubleTap,
PenHover,
GeolocationFix,
GeolocationError,
SensorChanged,
GamepadInput,
DragStart,
Drag,
DragEnd,
DragEnter,
DragOver,
DragLeave,
Drop,
DoubleClick,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
DpiChanged,
MonitorChanged,
PermissionChanged,
BiometricResult,
ScreenColorPicked,
KeyringResult,
BackMouseDown,
BackMouseUp,
ForwardMouseDown,
ForwardMouseUp,
RawMouseMotion,
ModifiersChanged,
HidReport,
DialRotate,
DialClick,
MouseMove,
PointerLockChange,
Play,
Pause,
Ended,
TimeUpdate,
VolumeChange,
MediaError,
}
impl WindowEventFilter {
#[allow(clippy::match_same_arms)]
#[must_use]
pub const fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
match self {
Self::DialRotate => Some(HoverEventFilter::DialRotate),
Self::DialClick => Some(HoverEventFilter::DialClick),
Self::MouseOver => Some(HoverEventFilter::MouseOver),
Self::MouseMove => Some(HoverEventFilter::MouseMove),
Self::MouseDown => Some(HoverEventFilter::MouseDown),
Self::LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
Self::RightMouseDown => Some(HoverEventFilter::RightMouseDown),
Self::MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
Self::MouseUp => Some(HoverEventFilter::MouseUp),
Self::LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
Self::RightMouseUp => Some(HoverEventFilter::RightMouseUp),
Self::MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
Self::Scroll => Some(HoverEventFilter::Scroll),
Self::ScrollStart => Some(HoverEventFilter::ScrollStart),
Self::ScrollEnd => Some(HoverEventFilter::ScrollEnd),
Self::TextInput => Some(HoverEventFilter::TextInput),
Self::VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
Self::VirtualKeyUp => Some(HoverEventFilter::VirtualKeyUp),
Self::HoveredFile => Some(HoverEventFilter::HoveredFile),
Self::DroppedFile => Some(HoverEventFilter::DroppedFile),
Self::HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
Self::MouseEnter => None,
Self::MouseLeave => None,
Self::Resized => None,
Self::Moved => None,
Self::FrameChanged => None,
Self::TouchStart => Some(HoverEventFilter::TouchStart),
Self::TouchMove => Some(HoverEventFilter::TouchMove),
Self::TouchEnd => Some(HoverEventFilter::TouchEnd),
Self::TouchCancel => Some(HoverEventFilter::TouchCancel),
Self::FocusReceived => None,
Self::FocusLost => None,
Self::CloseRequested => None,
Self::ThemeChanged => None,
Self::WindowFocusReceived => None, Self::WindowFocusLost => None, Self::PointerLockChange => None, Self::PenDown => Some(HoverEventFilter::PenDown),
Self::PenMove => Some(HoverEventFilter::PenMove),
Self::PenUp => Some(HoverEventFilter::PenUp),
Self::PenEnter => Some(HoverEventFilter::PenEnter),
Self::PenLeave => Some(HoverEventFilter::PenLeave),
Self::PenSqueeze => Some(HoverEventFilter::PenSqueeze),
Self::PenDoubleTap => Some(HoverEventFilter::PenDoubleTap),
Self::PenHover => Some(HoverEventFilter::PenHover),
Self::GeolocationFix => Some(HoverEventFilter::GeolocationFix),
Self::GeolocationError => Some(HoverEventFilter::GeolocationError),
Self::SensorChanged => Some(HoverEventFilter::SensorChanged),
Self::GamepadInput => Some(HoverEventFilter::GamepadInput),
Self::DragStart => Some(HoverEventFilter::DragStart),
Self::Drag => Some(HoverEventFilter::Drag),
Self::DragEnd => Some(HoverEventFilter::DragEnd),
Self::DragEnter => Some(HoverEventFilter::DragEnter),
Self::DragOver => Some(HoverEventFilter::DragOver),
Self::DragLeave => Some(HoverEventFilter::DragLeave),
Self::Drop => Some(HoverEventFilter::Drop),
Self::DoubleClick => Some(HoverEventFilter::DoubleClick),
Self::LongPress => Some(HoverEventFilter::LongPress),
Self::SwipeLeft => Some(HoverEventFilter::SwipeLeft),
Self::SwipeRight => Some(HoverEventFilter::SwipeRight),
Self::SwipeUp => Some(HoverEventFilter::SwipeUp),
Self::SwipeDown => Some(HoverEventFilter::SwipeDown),
Self::PinchIn => Some(HoverEventFilter::PinchIn),
Self::PinchOut => Some(HoverEventFilter::PinchOut),
Self::RotateClockwise => Some(HoverEventFilter::RotateClockwise),
Self::RotateCounterClockwise => Some(HoverEventFilter::RotateCounterClockwise),
Self::DpiChanged => None,
Self::MonitorChanged => None,
Self::PermissionChanged => Some(HoverEventFilter::PermissionChanged),
Self::BiometricResult => Some(HoverEventFilter::BiometricResult),
Self::ScreenColorPicked => Some(HoverEventFilter::ScreenColorPicked),
Self::KeyringResult => Some(HoverEventFilter::KeyringResult),
Self::BackMouseDown => Some(HoverEventFilter::BackMouseDown),
Self::BackMouseUp => Some(HoverEventFilter::BackMouseUp),
Self::ForwardMouseDown => Some(HoverEventFilter::ForwardMouseDown),
Self::ForwardMouseUp => Some(HoverEventFilter::ForwardMouseUp),
Self::RawMouseMotion | Self::ModifiersChanged | Self::HidReport => None,
Self::Play
| Self::Pause
| Self::Ended
| Self::TimeUpdate
| Self::VolumeChange
| Self::MediaError => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ComponentEventFilter {
AfterMount,
BeforeUnmount,
NodeResized,
DefaultAction,
Selected,
Updated,
Dismissed,
TornOff,
Docked,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ApplicationEventFilter {
DeviceConnected,
DeviceDisconnected,
MonitorConnected,
MonitorDisconnected,
MediaControl,
SystemAudioChange,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ExternalEventFilter {
Play,
Pause,
Ended,
TimeUpdate,
VolumeChange,
MediaError,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum EventFilter {
Hover(HoverEventFilter),
Focus(FocusEventFilter),
Window(WindowEventFilter),
Component(ComponentEventFilter),
Application(ApplicationEventFilter),
External(ExternalEventFilter),
}
impl EventFilter {
#[must_use]
pub const fn is_focus_callback(&self) -> bool {
matches!(self, Self::Focus(_))
}
#[must_use]
pub const fn is_window_callback(&self) -> bool {
matches!(self, Self::Window(_))
}
}
macro_rules! get_single_enum_type {
($fn_name:ident, $enum_name:ident:: $variant:ident($return_type:ty)) => {
#[must_use]
pub const fn $fn_name(&self) -> Option<$return_type> {
use self::$enum_name::*;
match self {
$variant(e) => Some(*e),
_ => None,
}
}
};
}
impl EventFilter {
get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
get_single_enum_type!(
as_window_event_filter,
EventFilter::Window(WindowEventFilter)
);
}
impl From<On> for EventFilter {
#[allow(clippy::match_same_arms)]
fn from(input: On) -> Self {
use crate::dom::On::{
Collapse, Decrement, Default, DroppedFile, Expand, FocusLost, FocusReceived,
HoveredFile, HoveredFileCancelled, Increment, LeftMouseDown, LeftMouseUp,
MiddleMouseDown, MiddleMouseUp, MouseDown, MouseEnter, MouseLeave, MouseMove,
MouseOver, MouseUp, RightMouseDown, RightMouseUp, Scroll, TextInput, VirtualKeyDown,
VirtualKeyUp,
};
match input {
MouseOver => Self::Hover(HoverEventFilter::MouseOver),
MouseMove => Self::Hover(HoverEventFilter::MouseMove),
MouseDown => Self::Hover(HoverEventFilter::MouseDown),
LeftMouseDown => Self::Hover(HoverEventFilter::LeftMouseDown),
MiddleMouseDown => Self::Hover(HoverEventFilter::MiddleMouseDown),
RightMouseDown => Self::Hover(HoverEventFilter::RightMouseDown),
On::Click => Self::Hover(HoverEventFilter::Click),
MouseUp => Self::Hover(HoverEventFilter::MouseUp),
LeftMouseUp => Self::Hover(HoverEventFilter::LeftMouseUp),
MiddleMouseUp => Self::Hover(HoverEventFilter::MiddleMouseUp),
RightMouseUp => Self::Hover(HoverEventFilter::RightMouseUp),
MouseEnter => Self::Hover(HoverEventFilter::MouseEnter),
MouseLeave => Self::Hover(HoverEventFilter::MouseLeave),
Scroll => Self::Hover(HoverEventFilter::Scroll),
TextInput => Self::Focus(FocusEventFilter::TextInput), On::DocumentEdit => Self::Focus(FocusEventFilter::DocumentEdit), On::TextChanged => Self::Focus(FocusEventFilter::TextChanged), VirtualKeyDown => Self::Window(WindowEventFilter::VirtualKeyDown), VirtualKeyUp => Self::Window(WindowEventFilter::VirtualKeyUp), HoveredFile => Self::Hover(HoverEventFilter::HoveredFile),
DroppedFile => Self::Hover(HoverEventFilter::DroppedFile),
HoveredFileCancelled => Self::Hover(HoverEventFilter::HoveredFileCancelled),
FocusReceived => Self::Focus(FocusEventFilter::FocusReceived), FocusLost => Self::Focus(FocusEventFilter::FocusLost),
Default => Self::Hover(HoverEventFilter::Click),
Collapse => Self::Hover(HoverEventFilter::Click),
Expand => Self::Hover(HoverEventFilter::Click),
Increment => Self::Hover(HoverEventFilter::Click),
Decrement => Self::Hover(HoverEventFilter::Click),
}
}
}
pub trait EventProvider {
fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent>;
}
#[must_use]
pub fn deduplicate_synthetic_events(mut events: Vec<SyntheticEvent>) -> Vec<SyntheticEvent> {
if events.len() <= 1 {
return events;
}
let seat_of = |e: &SyntheticEvent| match &e.data {
EventData::Mouse(m) => m.seat_id,
EventData::Scroll(s) => s.seat_id,
EventData::Touch(t) => t.seat_id,
EventData::Composition(c) => c.seat_id,
_ => crate::window::PRIMARY_POINTER_SEAT,
};
events.sort_by_key(|e| (e.target.dom, e.target.node, e.event_type, seat_of(e)));
let mut result = Vec::with_capacity(events.len());
let mut iter = events.into_iter();
if let Some(mut prev) = iter.next() {
for curr in iter {
if prev.target == curr.target
&& prev.event_type == curr.event_type
&& seat_of(&prev) == seat_of(&curr)
{
prev = if curr.timestamp > prev.timestamp {
curr
} else {
prev
};
} else {
result.push(prev);
prev = curr;
}
}
result.push(prev);
}
result
}
static ALL_HOVER: &[HoverEventFilter] = &[
HoverEventFilter::MouseOver,
HoverEventFilter::MouseMove,
HoverEventFilter::MouseDown,
HoverEventFilter::LeftMouseDown,
HoverEventFilter::RightMouseDown,
HoverEventFilter::MiddleMouseDown,
HoverEventFilter::Click,
HoverEventFilter::MouseUp,
HoverEventFilter::LeftMouseUp,
HoverEventFilter::RightMouseUp,
HoverEventFilter::MiddleMouseUp,
HoverEventFilter::MouseEnter,
HoverEventFilter::MouseLeave,
HoverEventFilter::Scroll,
HoverEventFilter::ScrollStart,
HoverEventFilter::ScrollEnd,
HoverEventFilter::TextInput,
HoverEventFilter::VirtualKeyDown,
HoverEventFilter::VirtualKeyUp,
HoverEventFilter::HoveredFile,
HoverEventFilter::DroppedFile,
HoverEventFilter::HoveredFileCancelled,
HoverEventFilter::TouchStart,
HoverEventFilter::TouchMove,
HoverEventFilter::TouchEnd,
HoverEventFilter::TouchCancel,
HoverEventFilter::PenDown,
HoverEventFilter::PenMove,
HoverEventFilter::PenUp,
HoverEventFilter::PenEnter,
HoverEventFilter::PenLeave,
HoverEventFilter::PenSqueeze,
HoverEventFilter::PenDoubleTap,
HoverEventFilter::PenHover,
HoverEventFilter::GeolocationFix,
HoverEventFilter::GeolocationError,
HoverEventFilter::SensorChanged,
HoverEventFilter::GamepadInput,
HoverEventFilter::DragStart,
HoverEventFilter::Drag,
HoverEventFilter::DragEnd,
HoverEventFilter::DragEnter,
HoverEventFilter::DragOver,
HoverEventFilter::DragLeave,
HoverEventFilter::Drop,
HoverEventFilter::DoubleClick,
HoverEventFilter::LongPress,
HoverEventFilter::SwipeLeft,
HoverEventFilter::SwipeRight,
HoverEventFilter::SwipeUp,
HoverEventFilter::SwipeDown,
HoverEventFilter::PinchIn,
HoverEventFilter::PinchOut,
HoverEventFilter::RotateClockwise,
HoverEventFilter::RotateCounterClockwise,
HoverEventFilter::MouseOut,
HoverEventFilter::FocusIn,
HoverEventFilter::FocusOut,
HoverEventFilter::CompositionStart,
HoverEventFilter::CompositionUpdate,
HoverEventFilter::CompositionEnd,
HoverEventFilter::SystemTextSingleClick,
HoverEventFilter::SystemTextDoubleClick,
HoverEventFilter::SystemTextTripleClick,
HoverEventFilter::PermissionChanged,
HoverEventFilter::BiometricResult,
HoverEventFilter::ScreenColorPicked,
HoverEventFilter::KeyringResult,
HoverEventFilter::Submit,
HoverEventFilter::Change,
HoverEventFilter::Reset,
HoverEventFilter::Invalid,
HoverEventFilter::DialRotate,
HoverEventFilter::DialClick,
];
static ALL_FOCUS: &[FocusEventFilter] = &[
FocusEventFilter::MouseOver,
FocusEventFilter::MouseMove,
FocusEventFilter::MouseDown,
FocusEventFilter::LeftMouseDown,
FocusEventFilter::RightMouseDown,
FocusEventFilter::MiddleMouseDown,
FocusEventFilter::MouseUp,
FocusEventFilter::LeftMouseUp,
FocusEventFilter::RightMouseUp,
FocusEventFilter::MiddleMouseUp,
FocusEventFilter::MouseEnter,
FocusEventFilter::MouseLeave,
FocusEventFilter::Scroll,
FocusEventFilter::ScrollStart,
FocusEventFilter::ScrollEnd,
FocusEventFilter::TextInput,
FocusEventFilter::VirtualKeyDown,
FocusEventFilter::VirtualKeyUp,
FocusEventFilter::FocusReceived,
FocusEventFilter::FocusLost,
FocusEventFilter::PenDown,
FocusEventFilter::PenMove,
FocusEventFilter::PenUp,
FocusEventFilter::DragStart,
FocusEventFilter::Drag,
FocusEventFilter::DragEnd,
FocusEventFilter::DragEnter,
FocusEventFilter::DragOver,
FocusEventFilter::DragLeave,
FocusEventFilter::Drop,
FocusEventFilter::DoubleClick,
FocusEventFilter::LongPress,
FocusEventFilter::SwipeLeft,
FocusEventFilter::SwipeRight,
FocusEventFilter::SwipeUp,
FocusEventFilter::SwipeDown,
FocusEventFilter::PinchIn,
FocusEventFilter::PinchOut,
FocusEventFilter::RotateClockwise,
FocusEventFilter::RotateCounterClockwise,
FocusEventFilter::FocusIn,
FocusEventFilter::FocusOut,
FocusEventFilter::CompositionStart,
FocusEventFilter::CompositionUpdate,
FocusEventFilter::CompositionEnd,
FocusEventFilter::Copy,
FocusEventFilter::Cut,
FocusEventFilter::Paste,
FocusEventFilter::DocumentEdit,
FocusEventFilter::TextChanged,
FocusEventFilter::Submit,
FocusEventFilter::Change,
FocusEventFilter::Reset,
FocusEventFilter::Invalid,
];
static ALL_WINDOW: &[WindowEventFilter] = &[
WindowEventFilter::MouseOver,
WindowEventFilter::MouseMove,
WindowEventFilter::MouseDown,
WindowEventFilter::LeftMouseDown,
WindowEventFilter::RightMouseDown,
WindowEventFilter::MiddleMouseDown,
WindowEventFilter::MouseUp,
WindowEventFilter::LeftMouseUp,
WindowEventFilter::RightMouseUp,
WindowEventFilter::MiddleMouseUp,
WindowEventFilter::MouseEnter,
WindowEventFilter::MouseLeave,
WindowEventFilter::Scroll,
WindowEventFilter::ScrollStart,
WindowEventFilter::ScrollEnd,
WindowEventFilter::TextInput,
WindowEventFilter::VirtualKeyDown,
WindowEventFilter::VirtualKeyUp,
WindowEventFilter::HoveredFile,
WindowEventFilter::DroppedFile,
WindowEventFilter::HoveredFileCancelled,
WindowEventFilter::Resized,
WindowEventFilter::Moved,
WindowEventFilter::FrameChanged,
WindowEventFilter::TouchStart,
WindowEventFilter::TouchMove,
WindowEventFilter::TouchEnd,
WindowEventFilter::TouchCancel,
WindowEventFilter::FocusReceived,
WindowEventFilter::FocusLost,
WindowEventFilter::CloseRequested,
WindowEventFilter::ThemeChanged,
WindowEventFilter::WindowFocusReceived,
WindowEventFilter::WindowFocusLost,
WindowEventFilter::PointerLockChange,
WindowEventFilter::PenDown,
WindowEventFilter::PenMove,
WindowEventFilter::PenUp,
WindowEventFilter::PenEnter,
WindowEventFilter::PenLeave,
WindowEventFilter::PenSqueeze,
WindowEventFilter::PenDoubleTap,
WindowEventFilter::PenHover,
WindowEventFilter::GeolocationFix,
WindowEventFilter::GeolocationError,
WindowEventFilter::SensorChanged,
WindowEventFilter::GamepadInput,
WindowEventFilter::DragStart,
WindowEventFilter::Drag,
WindowEventFilter::DragEnd,
WindowEventFilter::DragEnter,
WindowEventFilter::DragOver,
WindowEventFilter::DragLeave,
WindowEventFilter::Drop,
WindowEventFilter::DoubleClick,
WindowEventFilter::LongPress,
WindowEventFilter::SwipeLeft,
WindowEventFilter::SwipeRight,
WindowEventFilter::SwipeUp,
WindowEventFilter::SwipeDown,
WindowEventFilter::PinchIn,
WindowEventFilter::PinchOut,
WindowEventFilter::RotateClockwise,
WindowEventFilter::RotateCounterClockwise,
WindowEventFilter::DpiChanged,
WindowEventFilter::MonitorChanged,
WindowEventFilter::PermissionChanged,
WindowEventFilter::BiometricResult,
WindowEventFilter::ScreenColorPicked,
WindowEventFilter::KeyringResult,
WindowEventFilter::DialRotate,
WindowEventFilter::DialClick,
WindowEventFilter::Play,
WindowEventFilter::Pause,
WindowEventFilter::Ended,
WindowEventFilter::TimeUpdate,
WindowEventFilter::VolumeChange,
WindowEventFilter::MediaError,
];
static ALL_COMPONENT: &[ComponentEventFilter] = &[
ComponentEventFilter::AfterMount,
ComponentEventFilter::BeforeUnmount,
ComponentEventFilter::NodeResized,
ComponentEventFilter::DefaultAction,
ComponentEventFilter::Selected,
ComponentEventFilter::Updated,
ComponentEventFilter::Dismissed,
ComponentEventFilter::TornOff,
ComponentEventFilter::Docked,
];
static ALL_EXTERNAL: &[ExternalEventFilter] = &[
ExternalEventFilter::Play,
ExternalEventFilter::Pause,
ExternalEventFilter::Ended,
ExternalEventFilter::TimeUpdate,
ExternalEventFilter::VolumeChange,
ExternalEventFilter::MediaError,
];
static ALL_APPLICATION: &[ApplicationEventFilter] = &[
ApplicationEventFilter::DeviceConnected,
ApplicationEventFilter::DeviceDisconnected,
ApplicationEventFilter::MonitorConnected,
ApplicationEventFilter::MonitorDisconnected,
ApplicationEventFilter::MediaControl,
ApplicationEventFilter::SystemAudioChange,
];
#[must_use]
pub fn event_type_to_filters(event_type: EventType, event_data: &EventData) -> Vec<EventFilter> {
let probe = SyntheticEvent::new(
event_type,
EventSource::User,
DomNodeId {
dom: DomId::ROOT_ID,
node: crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0))),
},
crate::task::Instant::Tick(crate::task::SystemTick::new(0)),
event_data.clone(),
);
let mut out = Vec::new();
for f in ALL_HOVER {
if matches_filter_phase(EventFilter::Hover(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::Hover(*f));
}
}
for f in ALL_FOCUS {
if matches_filter_phase(EventFilter::Focus(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::Focus(*f));
}
}
for f in ALL_WINDOW {
if matches_filter_phase(EventFilter::Window(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::Window(*f));
}
}
for f in ALL_COMPONENT {
if matches_filter_phase(EventFilter::Component(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::Component(*f));
}
}
for f in ALL_APPLICATION {
if matches_filter_phase(EventFilter::Application(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::Application(*f));
}
}
for f in ALL_EXTERNAL {
if matches_filter_phase(EventFilter::External(*f), &probe, EventPhase::Bubble) {
out.push(EventFilter::External(*f));
}
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use = "SystemChange must be processed through apply_system_change()"]
pub enum SystemChange {
TextSelectionClick {
position: LogicalPosition,
timestamp: Instant,
},
TextSelectionDrag {
start_position: LogicalPosition,
current_position: LogicalPosition,
},
ApplySelectionOp {
target: DomNodeId,
op: SelectionOp,
seat_id: u64,
},
CopyToClipboard,
CutToClipboard { target: DomNodeId },
PasteFromClipboard,
SelectAllText,
UndoTextEdit { target: DomNodeId },
RedoTextEdit { target: DomNodeId },
AddCursorAtClick { position: LogicalPosition },
SelectNextOccurrence { target: DomNodeId },
SeatShortcut {
seat_id: u64,
target: DomNodeId,
shortcut: KeyboardShortcut,
},
ApplyPendingTextInput,
ApplyTextChangeset,
ActivateNodeDrag { dom_id: DomId, node_id: NodeId },
ActivateWindowDrag,
InitDragVisualState,
SetDragOverState { target: DomNodeId, active: bool },
UpdateDropTarget { target: DomNodeId },
UpdateDragGpuTransform,
DeactivateDrag,
SetSeatFocus {
seat_id: u64,
new_focus: Option<DomNodeId>,
old_focus: Option<DomNodeId>,
},
SetFocus {
new_focus: Option<DomNodeId>,
old_focus: Option<DomNodeId>,
visible: bool,
},
ClearAllSelections,
FinalizePendingFocusChanges,
ScrollSelectionIntoView,
ScrollNodeIntoView { target: DomNodeId },
ScrollCursorIntoViewAfterTextInput,
StartAutoScrollTimer,
StopAutoScrollTimer,
}
impl_option!(
SystemChange,
OptionSystemChange,
copy = false,
clone = false,
[Debug, Clone, PartialEq, Eq]
);
impl_vec!(
SystemChange,
SystemChangeVec,
SystemChangeVecDestructor,
SystemChangeVecDestructorType,
SystemChangeVecSlice,
OptionSystemChange
);
impl_vec_debug!(SystemChange, SystemChangeVec);
impl_vec_clone!(SystemChange, SystemChangeVec, SystemChangeVecDestructor);
impl_vec_partialeq!(SystemChange, SystemChangeVec);
#[derive(Debug, Clone, PartialEq)]
pub struct PreCallbackFilterResult {
pub system_changes: Vec<SystemChange>,
pub user_events: Vec<SyntheticEvent>,
}
#[derive(Debug, Clone, Copy)]
pub struct InputInterpreterState {
pub focused_node: Option<DomNodeId>,
pub click_count: u8,
pub drag_start_position: Option<LogicalPosition>,
pub has_selection: bool,
pub focus_is_editable: bool,
}
#[derive(Debug)]
pub struct InputInterpreterInfo<'a> {
pub events: &'a [SyntheticEvent],
pub hit_test: Option<&'a FullHitTest>,
pub keyboard_state: &'a crate::window::KeyboardState,
pub mouse_state: &'a crate::window::MouseState,
pub state: InputInterpreterState,
pub seat_focus: &'a [(u64, Option<DomNodeId>)],
}
pub type InputInterpreterCallbackType = extern "C" fn(
crate::refany::RefAny,
*const InputInterpreterInfo<'static>, ) -> PreCallbackFilterResult;
#[repr(C)]
pub struct InputInterpreterCallback {
pub cb: InputInterpreterCallbackType,
pub ctx: crate::refany::OptionRefAny,
}
impl_callback!(InputInterpreterCallback, InputInterpreterCallbackType);
impl Default for InputInterpreterCallback {
fn default() -> Self {
Self {
cb: default_input_interpreter_extern,
ctx: crate::refany::OptionRefAny::None,
}
}
}
pub type PostFilterCallbackType = extern "C" fn(
crate::refany::RefAny,
bool, SystemChangeVecSlice, DomNodeId, DomNodeId, ) -> SystemChangeVec;
#[repr(C)]
pub struct PostFilterCallback {
pub cb: PostFilterCallbackType,
pub ctx: crate::refany::OptionRefAny,
}
impl_callback!(PostFilterCallback, PostFilterCallbackType);
impl Default for PostFilterCallback {
fn default() -> Self {
Self {
cb: default_post_filter_extern,
ctx: crate::refany::OptionRefAny::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde-json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde-json", serde(rename_all = "lowercase"))]
pub enum E2eOpArgType {
String,
Number,
Bool,
Object,
Array,
Any,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde-json", derive(serde::Serialize, serde::Deserialize))]
pub struct E2eOpArg {
pub name: String,
#[cfg_attr(feature = "serde-json", serde(rename = "type"))]
pub arg_type: E2eOpArgType,
pub required: bool,
pub description: String,
}
impl Default for E2eOpArgType {
fn default() -> Self {
Self::Any
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde-json", derive(serde::Serialize, serde::Deserialize))]
pub struct E2eOpExample {
pub description: String,
pub args: crate::json::Json,
pub returns: crate::json::Json,
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde-json", derive(serde::Serialize, serde::Deserialize))]
pub struct E2eOpDef {
pub name: String,
pub summary: String,
pub description: String,
pub args: Vec<E2eOpArg>,
pub examples: Vec<E2eOpExample>,
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde-json", derive(serde::Serialize, serde::Deserialize))]
pub struct E2eOpSchema {
pub ops: Vec<E2eOpDef>,
}
#[allow(clippy::missing_const_for_fn)]
fn json_has_success_bool(v: &crate::json::Json) -> bool {
#[cfg(feature = "serde-json")]
{
v.to_serde_value()
.get("success")
.is_some_and(serde_json::Value::is_boolean)
}
#[cfg(not(feature = "serde-json"))]
{
let _ = v;
true
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum E2eSchemaError {
UnnamedOp { index: usize },
DuplicateOpName { name: String },
UnnamedArg { op: String, index: usize },
ExampleMissingSuccess { op: String, index: usize },
}
impl core::fmt::Display for E2eSchemaError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnnamedOp { index } => write!(f, "op #{index} has an empty name"),
Self::DuplicateOpName { name } => {
write!(
f,
"two ops are both named '{name}'; dispatch would be ambiguous"
)
}
Self::UnnamedArg { op, index } => {
write!(f, "op '{op}' argument #{index} has an empty name")
}
Self::ExampleMissingSuccess { op, index } => write!(
f,
"op '{op}' example #{index}: `returns` has no `success` boolean. Every op \
result must say whether it worked, or a failure is indistinguishable from a \
success"
),
}
}
}
impl E2eOpSchema {
pub fn validate(&self) -> Result<(), E2eSchemaError> {
let mut seen: Vec<&str> = Vec::new();
for (i, op) in self.ops.iter().enumerate() {
if op.name.trim().is_empty() {
return Err(E2eSchemaError::UnnamedOp { index: i });
}
if seen.contains(&op.name.as_str()) {
return Err(E2eSchemaError::DuplicateOpName {
name: op.name.clone(),
});
}
seen.push(op.name.as_str());
for (a, arg) in op.args.iter().enumerate() {
if arg.name.trim().is_empty() {
return Err(E2eSchemaError::UnnamedArg {
op: op.name.clone(),
index: a,
});
}
}
for (e, ex) in op.examples.iter().enumerate() {
if !json_has_success_bool(&ex.returns) {
return Err(E2eSchemaError::ExampleMissingSuccess {
op: op.name.clone(),
index: e,
});
}
}
}
Ok(())
}
#[must_use]
pub fn to_json(&self) -> crate::json::Json {
#[cfg(feature = "serde-json")]
{
serde_json::to_string(self).ok().map_or_else(
|| crate::json::Json {
value_type: crate::json::JsonType::Object,
internal: crate::json::JsonInternal {
string_value: AzString::from_const_str(r#"{"ops":[]}"#),
..Default::default()
},
},
|text| crate::json::Json {
value_type: crate::json::JsonType::Object,
internal: crate::json::JsonInternal {
string_value: AzString::from(text),
..Default::default()
},
},
)
}
#[cfg(not(feature = "serde-json"))]
crate::json::Json {
value_type: crate::json::JsonType::Object,
internal: crate::json::JsonInternal {
string_value: AzString::from_const_str(r#"{"ops":[]}"#),
..Default::default()
},
}
}
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomE2eOpResult {
pub handled: bool,
pub json: AzString,
}
impl Default for CustomE2eOpResult {
fn default() -> Self {
Self {
handled: false,
json: AzString::from_const_str(""),
}
}
}
pub type CustomE2eOpCallbackType = extern "C" fn(
crate::refany::RefAny, AzString, AzString, ) -> CustomE2eOpResult;
#[repr(C)]
pub struct CustomE2eOpCallback {
pub cb: CustomE2eOpCallbackType,
pub ctx: crate::refany::OptionRefAny,
pub op_schema: crate::json::Json,
}
impl_callback_traits!(CustomE2eOpCallback);
impl Clone for CustomE2eOpCallback {
fn clone(&self) -> Self {
Self {
cb: self.cb,
ctx: self.ctx.clone(),
op_schema: self.op_schema.clone(),
}
}
}
impl From<CustomE2eOpCallbackType> for CustomE2eOpCallback {
fn from(cb: CustomE2eOpCallbackType) -> Self {
Self {
cb,
..Self::default()
}
}
}
impl Default for CustomE2eOpCallback {
fn default() -> Self {
Self {
cb: default_custom_e2e_op_extern,
ctx: crate::refany::OptionRefAny::None,
op_schema: E2eOpSchema::default().to_json(),
}
}
}
#[must_use]
pub extern "C" fn default_custom_e2e_op_extern(
_ctx: crate::refany::RefAny,
_op: AzString,
_args: AzString,
) -> CustomE2eOpResult {
CustomE2eOpResult {
handled: false,
json: AzString::from_const_str(""),
}
}
pub type InputInterpreterFn = fn(info: &InputInterpreterInfo<'_>) -> PreCallbackFilterResult;
pub type PostFilterFn = fn(
prevent_default: bool,
pre_changes: &[SystemChange],
old_focus: Option<DomNodeId>,
new_focus: Option<DomNodeId>,
) -> Vec<SystemChange>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseButtonState {
pub left_down: bool,
pub right_down: bool,
pub middle_down: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArrowDirection {
Left,
Right,
Up,
Down,
LineStart,
LineEnd,
DocumentStart,
DocumentEnd,
}
impl ArrowDirection {
#[must_use]
pub const fn from_key(vk: crate::window::VirtualKeyCode, ctrl: bool) -> Option<Self> {
use crate::window::VirtualKeyCode::{Down, End, Home, Left, Right, Up};
Some(match vk {
Left => Self::Left,
Right => Self::Right,
Up => Self::Up,
Down => Self::Down,
Home if ctrl => Self::DocumentStart,
Home => Self::LineStart,
End if ctrl => Self::DocumentEnd,
End => Self::LineEnd,
_ => return None,
})
}
#[must_use]
pub const fn to_selection(self, ctrl: bool) -> (SelectionDirection, SelectionStep) {
match self {
Self::Left if ctrl => (SelectionDirection::Backward, SelectionStep::Word),
Self::Right if ctrl => (SelectionDirection::Forward, SelectionStep::Word),
Self::Left => (SelectionDirection::Backward, SelectionStep::Character),
Self::Right => (SelectionDirection::Forward, SelectionStep::Character),
Self::Up => (SelectionDirection::Backward, SelectionStep::VisualLine),
Self::Down => (SelectionDirection::Forward, SelectionStep::VisualLine),
Self::LineStart => (SelectionDirection::Backward, SelectionStep::Line),
Self::LineEnd => (SelectionDirection::Forward, SelectionStep::Line),
Self::DocumentStart => (SelectionDirection::Backward, SelectionStep::Document),
Self::DocumentEnd => (SelectionDirection::Forward, SelectionStep::Document),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum SelectionDirection {
Forward,
Backward,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum SelectionStep {
Character,
Word,
Line,
VisualLine,
Document,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum SelectionMode {
Move,
Extend,
Delete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct SelectionOp {
pub direction: SelectionDirection,
pub step: SelectionStep,
pub mode: SelectionMode,
pub repeat: usize,
}
impl SelectionOp {
#[must_use]
pub const fn new(
direction: SelectionDirection,
step: SelectionStep,
mode: SelectionMode,
) -> Self {
Self {
direction,
step,
mode,
repeat: 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyboardShortcut {
Copy, Cut, Paste, SelectAll, Undo, Redo, }
impl KeyboardShortcut {
#[must_use]
pub const fn from_key(
vk: crate::window::VirtualKeyCode,
primary: bool,
shift: bool,
) -> Option<Self> {
use crate::window::VirtualKeyCode::{A, C, V, X, Y, Z};
if !primary {
return None;
}
Some(match vk {
C => Self::Copy,
X => Self::Cut,
V => Self::Paste,
A => Self::SelectAll,
Z if shift => Self::Redo,
Z => Self::Undo,
Y => Self::Redo,
_ => return None,
})
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[must_use]
pub extern "C" fn default_input_interpreter_extern(
_user_data: crate::refany::RefAny,
info_ptr: *const InputInterpreterInfo<'static>,
) -> PreCallbackFilterResult {
if info_ptr.is_null() {
return PreCallbackFilterResult {
system_changes: Vec::new(),
user_events: Vec::new(),
};
}
let info = unsafe { &*info_ptr };
default_input_interpreter(info)
}
#[must_use]
pub extern "C" fn default_post_filter_extern(
_user_data: crate::refany::RefAny,
prevent_default: bool,
pre_changes: SystemChangeVecSlice,
old_focus: DomNodeId,
new_focus: DomNodeId,
) -> SystemChangeVec {
let pre_changes_slice = pre_changes.as_slice();
let old = old_focus.node.into_crate_internal().map(|_| old_focus);
let new = new_focus.node.into_crate_internal().map(|_| new_focus);
default_post_filter(prevent_default, pre_changes_slice, old, new).into()
}
#[must_use]
pub fn default_input_interpreter(info: &InputInterpreterInfo<'_>) -> PreCallbackFilterResult {
let ctx = FilterContext {
hit_test: info.hit_test,
keyboard_state: info.keyboard_state,
mouse_state: info.mouse_state,
click_count: info.state.click_count,
focused_node: info.state.focused_node,
drag_start_position: info.state.drag_start_position,
focus_is_editable: info.state.focus_is_editable,
seat_focus: info.seat_focus,
};
let (system_changes, user_events) = info.events.iter().fold(
(Vec::new(), Vec::new()),
|(mut internal, mut user), event| {
match process_event_for_internal(&ctx, event) {
Some(InternalEventAction::AddAndSkip(evt)) => {
internal.push(evt);
}
Some(InternalEventAction::AddAndPass(evt)) => {
internal.push(evt);
user.push(event.clone());
}
None => {
user.push(event.clone());
}
}
(internal, user)
},
);
PreCallbackFilterResult {
system_changes,
user_events,
}
}
pub fn pre_callback_filter_internal_events<SM, FM>(
events: &[SyntheticEvent],
hit_test: Option<&FullHitTest>,
keyboard_state: &crate::window::KeyboardState,
mouse_state: &crate::window::MouseState,
selection_manager: &SM,
focus_manager: &FM,
focus_is_editable: bool,
) -> PreCallbackFilterResult
where
SM: SelectionManagerQuery,
FM: FocusManagerQuery,
{
let seat_focus = seat_focus_of_events(events, focus_manager);
let info = InputInterpreterInfo {
events,
hit_test,
keyboard_state,
mouse_state,
seat_focus: &seat_focus,
state: InputInterpreterState {
focused_node: focus_manager.get_focused_node_id(),
click_count: selection_manager.get_click_count(),
drag_start_position: selection_manager.get_drag_start_position(),
has_selection: selection_manager.has_selection(),
focus_is_editable,
},
};
default_input_interpreter(&info)
}
struct FilterContext<'a> {
hit_test: Option<&'a FullHitTest>,
keyboard_state: &'a crate::window::KeyboardState,
mouse_state: &'a crate::window::MouseState,
click_count: u8,
focused_node: Option<DomNodeId>,
drag_start_position: Option<LogicalPosition>,
focus_is_editable: bool,
seat_focus: &'a [(u64, Option<DomNodeId>)],
}
impl FilterContext<'_> {
fn focused_node_for(&self, seat_id: u64) -> Option<DomNodeId> {
if seat_id == crate::window::PRIMARY_POINTER_SEAT {
return self.focused_node;
}
self.seat_focus
.iter()
.find(|(seat, _)| *seat == seat_id)
.map_or(self.focused_node, |(_, focus)| *focus)
}
}
fn process_event_for_internal(
ctx: &FilterContext<'_>,
event: &SyntheticEvent,
) -> Option<InternalEventAction> {
match event.event_type {
EventType::MouseDown => handle_mouse_down(
event,
ctx.hit_test,
ctx.click_count,
ctx.mouse_state,
ctx.keyboard_state,
),
EventType::MouseMove => handle_mouse_move(
event,
ctx.hit_test,
ctx.mouse_state,
ctx.drag_start_position,
),
EventType::KeyDown => {
let seat_id = match &event.data {
EventData::Keyboard(k) => k.seat_id,
_ => crate::window::PRIMARY_POINTER_SEAT,
};
handle_key_down(
event,
ctx.keyboard_state,
ctx.focused_node_for(seat_id),
ctx.focus_is_editable,
)
}
EventType::MouseUp => Some(handle_mouse_up()),
_ => None,
}
}
const fn handle_mouse_up() -> InternalEventAction {
InternalEventAction::AddAndPass(SystemChange::StopAutoScrollTimer)
}
enum InternalEventAction {
AddAndSkip(SystemChange),
AddAndPass(SystemChange),
}
fn get_first_hovered_node(hit_test: Option<&FullHitTest>) -> Option<DomNodeId> {
let ht = hit_test?;
let mut best: Option<(DomId, NodeId, u32)> = None;
for (dom_id, hit_data) in &ht.hovered_nodes {
for (node_id, item) in &hit_data.regular_hit_test_nodes {
let is_better = match best {
None => true,
Some((_, _, best_depth)) => item.hit_depth < best_depth,
};
if is_better {
best = Some((*dom_id, *node_id, item.hit_depth));
}
}
}
let (dom_id, node_id, _) = best?;
Some(DomNodeId {
dom: dom_id,
node: NodeHierarchyItemId::from_crate_internal(Some(node_id)),
})
}
fn get_mouse_position_with_fallback(
event: &SyntheticEvent,
mouse_state: &crate::window::MouseState,
) -> LogicalPosition {
match &event.data {
EventData::Mouse(mouse_data) => mouse_data.position,
_ => {
mouse_state
.cursor_position
.get_position()
.unwrap_or(LogicalPosition::zero())
}
}
}
fn handle_mouse_down(
event: &SyntheticEvent,
hit_test: Option<&FullHitTest>,
click_count: u8,
mouse_state: &crate::window::MouseState,
keyboard_state: &crate::window::KeyboardState,
) -> Option<InternalEventAction> {
let effective_click_count = if click_count == 0 { 1 } else { click_count };
if effective_click_count > 3 {
return None;
}
let _target = get_first_hovered_node(hit_test)?;
let position = get_mouse_position_with_fallback(event, mouse_state);
if keyboard_state.primary_down() && effective_click_count == 1 {
return Some(InternalEventAction::AddAndPass(
SystemChange::AddCursorAtClick { position },
));
}
Some(InternalEventAction::AddAndPass(
SystemChange::TextSelectionClick {
position,
timestamp: event.timestamp.clone(),
},
))
}
fn handle_mouse_move(
event: &SyntheticEvent,
_hit_test: Option<&FullHitTest>,
mouse_state: &crate::window::MouseState,
drag_start_position: Option<LogicalPosition>,
) -> Option<InternalEventAction> {
if !mouse_state.left_down {
return None;
}
let start_position = drag_start_position?;
let current_position = get_mouse_position_with_fallback(event, mouse_state);
Some(InternalEventAction::AddAndPass(
SystemChange::TextSelectionDrag {
start_position,
current_position,
},
))
}
fn handle_key_down(
event: &SyntheticEvent,
keyboard_state: &crate::window::KeyboardState,
focused_node: Option<DomNodeId>,
focus_is_editable: bool,
) -> Option<InternalEventAction> {
use crate::window::VirtualKeyCode;
let target = focused_node?;
let EventData::Keyboard(kbd) = &event.data else {
return None;
};
let _ = keyboard_state;
let primary = if cfg!(target_os = "macos") {
kbd.modifiers.meta
} else {
kbd.modifiers.ctrl
};
let word_mod = if cfg!(target_os = "macos") {
kbd.modifiers.alt
} else {
kbd.modifiers.ctrl
};
let shift = kbd.modifiers.shift;
let vk_owned = VirtualKeyCode::from_u32(kbd.key_code)?;
let vk = &vk_owned;
if primary {
if let Some(shortcut) = KeyboardShortcut::from_key(*vk, primary, shift) {
if kbd.seat_id != crate::window::PRIMARY_POINTER_SEAT {
return Some(InternalEventAction::AddAndSkip(SystemChange::SeatShortcut {
seat_id: kbd.seat_id,
target,
shortcut,
}));
}
let change = match shortcut {
KeyboardShortcut::Copy => SystemChange::CopyToClipboard,
KeyboardShortcut::Cut => SystemChange::CutToClipboard { target },
KeyboardShortcut::Paste => SystemChange::PasteFromClipboard,
KeyboardShortcut::SelectAll => SystemChange::SelectAllText,
KeyboardShortcut::Undo => SystemChange::UndoTextEdit { target },
KeyboardShortcut::Redo => SystemChange::RedoTextEdit { target },
};
return Some(InternalEventAction::AddAndSkip(change));
}
if matches!(vk, VirtualKeyCode::D) {
if kbd.seat_id != crate::window::PRIMARY_POINTER_SEAT {
return None;
}
return Some(InternalEventAction::AddAndSkip(
SystemChange::SelectNextOccurrence { target },
));
}
}
let mode_for_shift = if shift {
SelectionMode::Extend
} else {
SelectionMode::Move
};
let selection_op = if let Some(arrow) = ArrowDirection::from_key(*vk, word_mod) {
if !focus_is_editable {
return None;
}
let (direction, step) = arrow.to_selection(word_mod);
SelectionOp::new(direction, step, mode_for_shift)
} else {
match vk {
VirtualKeyCode::Back => SelectionOp::new(
SelectionDirection::Backward,
if word_mod {
SelectionStep::Word
} else {
SelectionStep::Character
},
SelectionMode::Delete,
),
VirtualKeyCode::Delete => SelectionOp::new(
SelectionDirection::Forward,
if word_mod {
SelectionStep::Word
} else {
SelectionStep::Character
},
SelectionMode::Delete,
),
_ => return None,
}
};
Some(InternalEventAction::AddAndSkip(
SystemChange::ApplySelectionOp {
target,
op: selection_op,
seat_id: kbd.seat_id,
},
))
}
pub trait SelectionManagerQuery {
fn get_click_count(&self) -> u8;
fn get_drag_start_position(&self) -> Option<LogicalPosition>;
fn has_selection(&self) -> bool;
}
pub trait FocusManagerQuery {
fn get_focused_node_id(&self) -> Option<DomNodeId>;
fn get_focused_node_for_seat(&self, seat_id: u64) -> Option<DomNodeId> {
let _ = seat_id;
self.get_focused_node_id()
}
}
#[must_use]
pub fn seat_focus_of_events<FM: FocusManagerQuery + ?Sized>(
events: &[SyntheticEvent],
focus_manager: &FM,
) -> Vec<(u64, Option<DomNodeId>)> {
let mut out: Vec<(u64, Option<DomNodeId>)> = Vec::new();
for event in events {
let EventData::Keyboard(k) = &event.data else {
continue;
};
if k.seat_id == crate::window::PRIMARY_POINTER_SEAT
|| out.iter().any(|(seat, _)| *seat == k.seat_id)
{
continue;
}
out.push((k.seat_id, focus_manager.get_focused_node_for_seat(k.seat_id)));
}
out
}
#[must_use]
pub fn default_post_filter(
prevent_default: bool,
pre_changes: &[SystemChange],
old_focus: Option<DomNodeId>,
new_focus: Option<DomNodeId>,
) -> Vec<SystemChange> {
post_callback_filter_system_changes(prevent_default, pre_changes, old_focus, new_focus)
}
#[allow(clippy::match_same_arms)]
#[must_use]
pub fn post_callback_filter_system_changes(
prevent_default: bool,
pre_changes: &[SystemChange],
old_focus: Option<DomNodeId>,
new_focus: Option<DomNodeId>,
) -> Vec<SystemChange> {
let mut changes = Vec::new();
if prevent_default {
if old_focus != new_focus {
changes.push(SystemChange::SetFocus {
new_focus,
old_focus,
visible: false,
});
}
return changes;
}
changes.push(SystemChange::ApplyPendingTextInput);
for change in pre_changes {
match change {
SystemChange::TextSelectionClick { .. }
| SystemChange::ApplySelectionOp { .. }
| SystemChange::AddCursorAtClick { .. }
| SystemChange::SelectNextOccurrence { .. } => {
changes.push(SystemChange::ScrollSelectionIntoView);
}
SystemChange::TextSelectionDrag { .. } => {
changes.push(SystemChange::StartAutoScrollTimer);
}
SystemChange::CutToClipboard { .. }
| SystemChange::PasteFromClipboard
| SystemChange::UndoTextEdit { .. }
| SystemChange::RedoTextEdit { .. }
| SystemChange::SelectAllText => {
changes.push(SystemChange::ScrollSelectionIntoView);
}
_ => {}
}
}
if old_focus != new_focus {
changes.push(SystemChange::SetFocus {
new_focus,
old_focus,
visible: false,
});
}
changes
}
#[cfg(test)]
#[path = "events_test.rs"]
mod events_test;