#[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::{
props::{
basic::{LayoutPoint, LayoutRect, LayoutSize},
property::CssProperty,
},
AzString, LayoutDebugMessage,
};
use rust_fontconfig::FcFontCache;
use crate::{
callbacks::Update,
dom::{DomId, DomNodeId, On},
geom::{LogicalPosition, LogicalRect},
gl::OptionGlContextPtr,
gpu::GpuEventChanges,
hit_test::{FullHitTest, HitTestItem, ScrollPosition},
id::NodeId,
resources::{ImageCache, RendererResources},
styled_dom::{ChangedCssProperty, NodeHierarchyItemId},
task::Instant,
window::RawWindowHandle,
FastBTreeSet, FastHashMap,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EasingFunction {
Linear,
EaseInOut,
EaseOut,
}
pub type RestyleNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutNodes = BTreeMap<NodeId, Vec<ChangedCssProperty>>;
pub type RelayoutWords = BTreeMap<NodeId, AzString>;
#[derive(Debug, Clone, PartialEq)]
pub struct FocusChange {
pub old: Option<DomNodeId>,
pub new: Option<DomNodeId>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CallbackToCall {
pub node_id: NodeId,
pub hit_test_item: Option<HitTestItem>,
pub event_filter: EventFilter,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ProcessEventResult {
DoNothing = 0,
ShouldReRenderCurrentWindow = 1,
ShouldUpdateDisplayListCurrentWindow = 2,
UpdateHitTesterAndProcessAgain = 3,
ShouldRegenerateDomCurrentWindow = 4,
ShouldRegenerateDomAllWindows = 5,
}
impl ProcessEventResult {
pub fn order(&self) -> usize {
use self::ProcessEventResult::*;
match self {
DoNothing => 0,
ShouldReRenderCurrentWindow => 1,
ShouldUpdateDisplayListCurrentWindow => 2,
UpdateHitTesterAndProcessAgain => 3,
ShouldRegenerateDomCurrentWindow => 4,
ShouldRegenerateDomAllWindows => 5,
}
}
}
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)]
pub enum EventPhase {
Capture,
Target,
Bubble,
}
impl Default for EventPhase {
fn default() -> Self {
EventPhase::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 {
pub fn nearest() -> Self {
Self {
block: ScrollLogicalPosition::Nearest,
inline_axis: ScrollLogicalPosition::Nearest,
behavior: ScrollIntoViewBehavior::Auto,
}
}
pub fn center() -> Self {
Self {
block: ScrollLogicalPosition::Center,
inline_axis: ScrollLogicalPosition::Center,
behavior: ScrollIntoViewBehavior::Auto,
}
}
pub fn start() -> Self {
Self {
block: ScrollLogicalPosition::Start,
inline_axis: ScrollLogicalPosition::Start,
behavior: ScrollIntoViewBehavior::Auto,
}
}
pub fn end() -> Self {
Self {
block: ScrollLogicalPosition::End,
inline_axis: ScrollLogicalPosition::End,
behavior: ScrollIntoViewBehavior::Auto,
}
}
pub fn with_instant(mut self) -> Self {
self.behavior = ScrollIntoViewBehavior::Instant;
self
}
pub 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,
}
#[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 {
pub fn new() -> Self {
Self::default()
}
pub fn with_shift(mut self) -> Self {
self.shift = true;
self
}
pub fn with_ctrl(mut self) -> Self {
self.ctrl = true;
self
}
pub fn with_alt(mut self) -> Self {
self.alt = true;
self
}
pub fn with_meta(mut self) -> Self {
self.meta = true;
self
}
pub fn is_empty(&self) -> bool {
!self.shift && !self.ctrl && !self.alt && !self.meta
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MouseEventData {
pub position: LogicalPosition,
pub button: MouseButton,
pub buttons: u8,
pub modifiers: KeyModifiers,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeyboardEventData {
pub key_code: u32,
pub char_code: Option<char>,
pub modifiers: KeyModifiers,
pub repeat: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScrollEventData {
pub delta: LogicalPosition,
pub delta_mode: ScrollDeltaMode,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TouchEventData {
pub id: u64,
pub position: LogicalPosition,
pub force: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ClipboardEventData {
pub content: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LifecycleEventData {
pub reason: LifecycleReason,
pub previous_bounds: Option<LogicalRect>,
pub current_bounds: LogicalRect,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowEventData {
pub size: Option<LogicalRect>,
pub position: Option<LogicalPosition>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventData {
Mouse(MouseEventData),
Keyboard(KeyboardEventData),
Scroll(ScrollEventData),
Touch(TouchEventData),
Clipboard(ClipboardEventData),
Lifecycle(LifecycleEventData),
Window(WindowEventData),
None,
}
#[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,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
Copy,
Cut,
Paste,
Play,
Pause,
Ended,
TimeUpdate,
VolumeChange,
MediaError,
Mount,
Unmount,
Update,
Resize,
WindowResize,
WindowMove,
WindowClose,
WindowFocusIn,
WindowFocusOut,
ThemeChange,
WindowDpiChanged,
WindowMonitorChanged,
MonitorConnected,
MonitorDisconnected,
FileHover,
FileDrop,
FileHoverCancel,
}
#[derive(Debug, Clone, PartialEq)]
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,
}
impl SyntheticEvent {
pub 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,
}
}
pub fn stop_propagation(&mut self) {
self.stopped = true;
}
pub fn stop_immediate_propagation(&mut self) {
self.stopped_immediate = true;
self.stopped = true;
}
pub fn prevent_default(&mut self) {
self.prevented_default = true;
}
pub fn is_propagation_stopped(&self) -> bool {
self.stopped
}
pub fn is_immediate_propagation_stopped(&self) -> bool {
self.stopped_immediate
}
pub fn is_default_prevented(&self) -> bool {
self.prevented_default
}
}
#[derive(Debug, Clone)]
pub struct PropagationResult {
pub callbacks_to_invoke: Vec<(NodeId, EventFilter)>,
pub default_prevented: bool,
}
pub fn get_dom_path(
node_hierarchy: &crate::id::NodeHierarchy,
target_node: NodeHierarchyItemId,
) -> Vec<NodeId> {
let mut path = Vec::new();
let target_node_id = match target_node.into_crate_internal() {
Some(id) => id,
None => return path,
};
let hier_ref = node_hierarchy.as_ref();
let mut current = Some(target_node_id);
while let Some(node_id) = current {
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();
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 {
propagate_phase(
event,
ancestors.iter().rev().copied(),
EventPhase::Bubble,
callbacks,
&mut result,
);
}
result.default_prevented = event.prevented_default;
result
}
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);
}
impl Default for PropagationResult {
fn default() -> Self {
Self {
callbacks_to_invoke: Vec::new(),
default_prevented: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(C, u8)]
pub enum DefaultAction {
FocusNext,
FocusPrevious,
FocusFirst,
FocusLast,
ClearFocus,
ActivateFocusedElement {
target: DomNodeId,
},
SubmitForm {
form_node: DomNodeId,
},
CloseModal {
modal_node: DomNodeId,
},
ScrollFocusedContainer {
direction: ScrollDirection,
amount: ScrollAmount,
},
SelectAllText,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub enum ScrollAmount {
Line,
Page,
Document,
}
#[derive(Debug, Clone)]
#[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 {
pub fn new(action: DefaultAction) -> Self {
Self {
action,
prevented: false,
}
}
pub fn prevented() -> Self {
Self {
action: DefaultAction::None,
prevented: true,
}
}
pub 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 {
match self.get_tabindex() {
None => self.is_naturally_focusable(),
Some(i) => i >= 0,
}
}
fn is_naturally_focusable(&self) -> bool;
}
fn matches_filter_phase(
filter: &EventFilter,
event: &SyntheticEvent,
current_phase: EventPhase,
) -> bool {
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::Not(_) => {
false
}
EventFilter::Component(_) | EventFilter::Application(_) => {
false
}
}
}
fn matches_hover_filter(
filter: &HoverEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use HoverEventFilter::*;
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseUp, EventType::MouseUp) => true,
(LeftMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => 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,
(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,
_ => false,
}
}
fn matches_focus_filter(
filter: &FocusEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use FocusEventFilter::*;
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseUp, EventType::MouseUp) => true,
(LeftMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => 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,
_ => false,
}
}
fn matches_window_filter(
filter: &WindowEventFilter,
event: &SyntheticEvent,
_phase: EventPhase,
) -> bool {
use WindowEventFilter::*;
match (filter, &event.event_type) {
(MouseOver, EventType::MouseOver) => true,
(MouseDown, EventType::MouseDown) => true,
(LeftMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseDown, EventType::MouseDown) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseUp, EventType::MouseUp) => true,
(LeftMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Left
} else {
false
}
}
(RightMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Right
} else {
false
}
}
(MiddleMouseUp, EventType::MouseUp) => {
if let EventData::Mouse(mouse_data) = &event.data {
mouse_data.button == MouseButton::Middle
} else {
false
}
}
(MouseEnter, EventType::MouseEnter) => true,
(MouseLeave, EventType::MouseLeave) => true,
(Scroll, EventType::Scroll) => true,
(ScrollStart, EventType::ScrollStart) => true,
(ScrollEnd, EventType::ScrollEnd) => true,
(TextInput, EventType::Input) => 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,
(Moved, EventType::WindowMove) => true,
(TouchStart, EventType::TouchStart) => true,
(TouchMove, EventType::TouchMove) => true,
(TouchEnd, EventType::TouchEnd) => true,
(TouchCancel, EventType::TouchCancel) => 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,
(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,
_ => false,
}
}
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,
}
}
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::InitialMount,
previous_bounds: Some(previous_bounds),
current_bounds: LogicalRect::zero(),
},
)
}
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 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,
},
))
}
#[derive(Debug, Clone, Default)]
pub struct LifecycleEventResult {
pub events: Vec<SyntheticEvent>,
pub node_id_mapping: crate::FastHashMap<NodeId, NodeId>,
}
pub fn detect_lifecycle_events_with_reconciliation(
dom_id: DomId,
old_node_data: &[crate::dom::NodeData],
new_node_data: &[crate::dom::NodeData],
old_layout: &crate::FastHashMap<NodeId, LogicalRect>,
new_layout: &crate::FastHashMap<NodeId, LogicalRect>,
timestamp: Instant,
) -> LifecycleEventResult {
let diff_result = crate::diff::reconcile_dom(
old_node_data,
new_node_data,
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,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
TouchStart,
TouchMove,
TouchEnd,
TouchCancel,
PenDown,
PenMove,
PenUp,
PenEnter,
PenLeave,
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,
}
impl HoverEventFilter {
pub const fn is_system_internal(&self) -> bool {
matches!(
self,
HoverEventFilter::SystemTextSingleClick
| HoverEventFilter::SystemTextDoubleClick
| HoverEventFilter::SystemTextTripleClick
)
}
pub fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
match self {
HoverEventFilter::MouseOver => Some(FocusEventFilter::MouseOver),
HoverEventFilter::MouseDown => Some(FocusEventFilter::MouseDown),
HoverEventFilter::LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
HoverEventFilter::RightMouseDown => Some(FocusEventFilter::RightMouseDown),
HoverEventFilter::MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
HoverEventFilter::MouseUp => Some(FocusEventFilter::MouseUp),
HoverEventFilter::LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
HoverEventFilter::RightMouseUp => Some(FocusEventFilter::RightMouseUp),
HoverEventFilter::MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
HoverEventFilter::MouseEnter => Some(FocusEventFilter::MouseEnter),
HoverEventFilter::MouseLeave => Some(FocusEventFilter::MouseLeave),
HoverEventFilter::Scroll => Some(FocusEventFilter::Scroll),
HoverEventFilter::ScrollStart => Some(FocusEventFilter::ScrollStart),
HoverEventFilter::ScrollEnd => Some(FocusEventFilter::ScrollEnd),
HoverEventFilter::TextInput => Some(FocusEventFilter::TextInput),
HoverEventFilter::VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
HoverEventFilter::VirtualKeyUp => Some(FocusEventFilter::VirtualKeyDown),
HoverEventFilter::HoveredFile => None,
HoverEventFilter::DroppedFile => None,
HoverEventFilter::HoveredFileCancelled => None,
HoverEventFilter::TouchStart => None,
HoverEventFilter::TouchMove => None,
HoverEventFilter::TouchEnd => None,
HoverEventFilter::TouchCancel => None,
HoverEventFilter::PenDown => Some(FocusEventFilter::PenDown),
HoverEventFilter::PenMove => Some(FocusEventFilter::PenMove),
HoverEventFilter::PenUp => Some(FocusEventFilter::PenUp),
HoverEventFilter::PenEnter => None,
HoverEventFilter::PenLeave => None,
HoverEventFilter::DragStart => Some(FocusEventFilter::DragStart),
HoverEventFilter::Drag => Some(FocusEventFilter::Drag),
HoverEventFilter::DragEnd => Some(FocusEventFilter::DragEnd),
HoverEventFilter::DragEnter => Some(FocusEventFilter::DragEnter),
HoverEventFilter::DragOver => Some(FocusEventFilter::DragOver),
HoverEventFilter::DragLeave => Some(FocusEventFilter::DragLeave),
HoverEventFilter::Drop => Some(FocusEventFilter::Drop),
HoverEventFilter::DoubleClick => Some(FocusEventFilter::DoubleClick),
HoverEventFilter::LongPress => Some(FocusEventFilter::LongPress),
HoverEventFilter::SwipeLeft => Some(FocusEventFilter::SwipeLeft),
HoverEventFilter::SwipeRight => Some(FocusEventFilter::SwipeRight),
HoverEventFilter::SwipeUp => Some(FocusEventFilter::SwipeUp),
HoverEventFilter::SwipeDown => Some(FocusEventFilter::SwipeDown),
HoverEventFilter::PinchIn => Some(FocusEventFilter::PinchIn),
HoverEventFilter::PinchOut => Some(FocusEventFilter::PinchOut),
HoverEventFilter::RotateClockwise => Some(FocusEventFilter::RotateClockwise),
HoverEventFilter::RotateCounterClockwise => {
Some(FocusEventFilter::RotateCounterClockwise)
}
HoverEventFilter::MouseOut => Some(FocusEventFilter::MouseLeave), HoverEventFilter::FocusIn => Some(FocusEventFilter::FocusIn),
HoverEventFilter::FocusOut => Some(FocusEventFilter::FocusOut),
HoverEventFilter::CompositionStart => Some(FocusEventFilter::CompositionStart),
HoverEventFilter::CompositionUpdate => Some(FocusEventFilter::CompositionUpdate),
HoverEventFilter::CompositionEnd => Some(FocusEventFilter::CompositionEnd),
HoverEventFilter::SystemTextSingleClick => None,
HoverEventFilter::SystemTextDoubleClick => None,
HoverEventFilter::SystemTextTripleClick => None,
}
}
}
#[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,
}
#[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,
TouchStart,
TouchMove,
TouchEnd,
TouchCancel,
FocusReceived,
FocusLost,
CloseRequested,
ThemeChanged,
WindowFocusReceived,
WindowFocusLost,
PenDown,
PenMove,
PenUp,
PenEnter,
PenLeave,
DragStart,
Drag,
DragEnd,
DragEnter,
DragOver,
DragLeave,
Drop,
DoubleClick,
LongPress,
SwipeLeft,
SwipeRight,
SwipeUp,
SwipeDown,
PinchIn,
PinchOut,
RotateClockwise,
RotateCounterClockwise,
DpiChanged,
MonitorChanged,
}
impl WindowEventFilter {
pub fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
match self {
WindowEventFilter::MouseOver => Some(HoverEventFilter::MouseOver),
WindowEventFilter::MouseDown => Some(HoverEventFilter::MouseDown),
WindowEventFilter::LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
WindowEventFilter::RightMouseDown => Some(HoverEventFilter::RightMouseDown),
WindowEventFilter::MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
WindowEventFilter::MouseUp => Some(HoverEventFilter::MouseUp),
WindowEventFilter::LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
WindowEventFilter::RightMouseUp => Some(HoverEventFilter::RightMouseUp),
WindowEventFilter::MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
WindowEventFilter::Scroll => Some(HoverEventFilter::Scroll),
WindowEventFilter::ScrollStart => Some(HoverEventFilter::ScrollStart),
WindowEventFilter::ScrollEnd => Some(HoverEventFilter::ScrollEnd),
WindowEventFilter::TextInput => Some(HoverEventFilter::TextInput),
WindowEventFilter::VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
WindowEventFilter::VirtualKeyUp => Some(HoverEventFilter::VirtualKeyDown),
WindowEventFilter::HoveredFile => Some(HoverEventFilter::HoveredFile),
WindowEventFilter::DroppedFile => Some(HoverEventFilter::DroppedFile),
WindowEventFilter::HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
WindowEventFilter::MouseEnter => None,
WindowEventFilter::MouseLeave => None,
WindowEventFilter::Resized => None,
WindowEventFilter::Moved => None,
WindowEventFilter::TouchStart => Some(HoverEventFilter::TouchStart),
WindowEventFilter::TouchMove => Some(HoverEventFilter::TouchMove),
WindowEventFilter::TouchEnd => Some(HoverEventFilter::TouchEnd),
WindowEventFilter::TouchCancel => Some(HoverEventFilter::TouchCancel),
WindowEventFilter::FocusReceived => None,
WindowEventFilter::FocusLost => None,
WindowEventFilter::CloseRequested => None,
WindowEventFilter::ThemeChanged => None,
WindowEventFilter::WindowFocusReceived => None, WindowEventFilter::WindowFocusLost => None, WindowEventFilter::PenDown => Some(HoverEventFilter::PenDown),
WindowEventFilter::PenMove => Some(HoverEventFilter::PenMove),
WindowEventFilter::PenUp => Some(HoverEventFilter::PenUp),
WindowEventFilter::PenEnter => Some(HoverEventFilter::PenEnter),
WindowEventFilter::PenLeave => Some(HoverEventFilter::PenLeave),
WindowEventFilter::DragStart => Some(HoverEventFilter::DragStart),
WindowEventFilter::Drag => Some(HoverEventFilter::Drag),
WindowEventFilter::DragEnd => Some(HoverEventFilter::DragEnd),
WindowEventFilter::DragEnter => Some(HoverEventFilter::DragEnter),
WindowEventFilter::DragOver => Some(HoverEventFilter::DragOver),
WindowEventFilter::DragLeave => Some(HoverEventFilter::DragLeave),
WindowEventFilter::Drop => Some(HoverEventFilter::Drop),
WindowEventFilter::DoubleClick => Some(HoverEventFilter::DoubleClick),
WindowEventFilter::LongPress => Some(HoverEventFilter::LongPress),
WindowEventFilter::SwipeLeft => Some(HoverEventFilter::SwipeLeft),
WindowEventFilter::SwipeRight => Some(HoverEventFilter::SwipeRight),
WindowEventFilter::SwipeUp => Some(HoverEventFilter::SwipeUp),
WindowEventFilter::SwipeDown => Some(HoverEventFilter::SwipeDown),
WindowEventFilter::PinchIn => Some(HoverEventFilter::PinchIn),
WindowEventFilter::PinchOut => Some(HoverEventFilter::PinchOut),
WindowEventFilter::RotateClockwise => Some(HoverEventFilter::RotateClockwise),
WindowEventFilter::RotateCounterClockwise => {
Some(HoverEventFilter::RotateCounterClockwise)
}
WindowEventFilter::DpiChanged => None,
WindowEventFilter::MonitorChanged => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum NotEventFilter {
Hover(HoverEventFilter),
Focus(FocusEventFilter),
}
impl NotEventFilter {
pub fn as_event_filter(&self) -> EventFilter {
match self {
NotEventFilter::Hover(e) => EventFilter::Hover(*e),
NotEventFilter::Focus(e) => EventFilter::Focus(*e),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ComponentEventFilter {
AfterMount,
BeforeUnmount,
NodeResized,
DefaultAction,
Selected,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ApplicationEventFilter {
DeviceConnected,
DeviceDisconnected,
MonitorConnected,
MonitorDisconnected,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C, u8)]
pub enum EventFilter {
Hover(HoverEventFilter),
Not(NotEventFilter),
Focus(FocusEventFilter),
Window(WindowEventFilter),
Component(ComponentEventFilter),
Application(ApplicationEventFilter),
}
impl EventFilter {
pub const fn is_focus_callback(&self) -> bool {
match self {
EventFilter::Focus(_) => true,
_ => false,
}
}
pub const fn is_window_callback(&self) -> bool {
match self {
EventFilter::Window(_) => true,
_ => false,
}
}
}
macro_rules! get_single_enum_type {
($fn_name:ident, $enum_name:ident:: $variant:ident($return_type:ty)) => {
pub 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_not_event_filter, EventFilter::Not(NotEventFilter));
get_single_enum_type!(
as_window_event_filter,
EventFilter::Window(WindowEventFilter)
);
}
impl From<On> for EventFilter {
fn from(input: On) -> EventFilter {
use crate::dom::On::*;
match input {
MouseOver => EventFilter::Hover(HoverEventFilter::MouseOver),
MouseDown => EventFilter::Hover(HoverEventFilter::MouseDown),
LeftMouseDown => EventFilter::Hover(HoverEventFilter::LeftMouseDown),
MiddleMouseDown => EventFilter::Hover(HoverEventFilter::MiddleMouseDown),
RightMouseDown => EventFilter::Hover(HoverEventFilter::RightMouseDown),
MouseUp => EventFilter::Hover(HoverEventFilter::MouseUp),
LeftMouseUp => EventFilter::Hover(HoverEventFilter::LeftMouseUp),
MiddleMouseUp => EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
RightMouseUp => EventFilter::Hover(HoverEventFilter::RightMouseUp),
MouseEnter => EventFilter::Hover(HoverEventFilter::MouseEnter),
MouseLeave => EventFilter::Hover(HoverEventFilter::MouseLeave),
Scroll => EventFilter::Hover(HoverEventFilter::Scroll),
TextInput => EventFilter::Focus(FocusEventFilter::TextInput), VirtualKeyDown => EventFilter::Window(WindowEventFilter::VirtualKeyDown), VirtualKeyUp => EventFilter::Window(WindowEventFilter::VirtualKeyUp), HoveredFile => EventFilter::Hover(HoverEventFilter::HoveredFile),
DroppedFile => EventFilter::Hover(HoverEventFilter::DroppedFile),
HoveredFileCancelled => EventFilter::Hover(HoverEventFilter::HoveredFileCancelled),
FocusReceived => EventFilter::Focus(FocusEventFilter::FocusReceived), FocusLost => EventFilter::Focus(FocusEventFilter::FocusLost),
Default => EventFilter::Hover(HoverEventFilter::MouseUp), Collapse => EventFilter::Hover(HoverEventFilter::MouseUp), Expand => EventFilter::Hover(HoverEventFilter::MouseUp), Increment => EventFilter::Hover(HoverEventFilter::MouseUp), Decrement => EventFilter::Hover(HoverEventFilter::MouseUp), }
}
}
pub fn should_recurse_callbacks<T: CallbackResultRef>(
callback_results: &[T],
max_depth: usize,
current_depth: usize,
) -> bool {
if current_depth >= max_depth {
return false;
}
for result in callback_results {
if result.stop_propagation() {
return false;
}
if result.should_regenerate_dom() {
return current_depth + 1 < max_depth;
}
}
false
}
pub trait CallbackResultRef {
fn stop_propagation(&self) -> bool;
fn stop_immediate_propagation(&self) -> bool;
fn prevent_default(&self) -> bool;
fn should_regenerate_dom(&self) -> bool;
}
pub trait EventProvider {
fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent>;
}
pub fn deduplicate_synthetic_events(mut events: Vec<SyntheticEvent>) -> Vec<SyntheticEvent> {
if events.len() <= 1 {
return events;
}
events.sort_by_key(|e| (e.target.dom, e.target.node, e.event_type));
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 {
prev = if curr.timestamp > prev.timestamp {
curr
} else {
prev
};
} else {
result.push(prev);
prev = curr;
}
}
result.push(prev);
}
result
}
pub fn event_type_to_filters(event_type: EventType, event_data: &EventData) -> Vec<EventFilter> {
use EventFilter as EF;
use EventType as E;
use FocusEventFilter as F;
use HoverEventFilter as H;
use WindowEventFilter as W;
let button_specific_down = || -> Option<EventFilter> {
match event_data {
EventData::Mouse(m) => match m.button {
MouseButton::Left => Some(EF::Hover(H::LeftMouseDown)),
MouseButton::Right => Some(EF::Hover(H::RightMouseDown)),
MouseButton::Middle => Some(EF::Hover(H::MiddleMouseDown)),
MouseButton::Other(_) => None, },
_ => Some(EF::Hover(H::LeftMouseDown)), }
};
let button_specific_up = || -> Option<EventFilter> {
match event_data {
EventData::Mouse(m) => match m.button {
MouseButton::Left => Some(EF::Hover(H::LeftMouseUp)),
MouseButton::Right => Some(EF::Hover(H::RightMouseUp)),
MouseButton::Middle => Some(EF::Hover(H::MiddleMouseUp)),
MouseButton::Other(_) => None, },
_ => Some(EF::Hover(H::LeftMouseUp)), }
};
match event_type {
E::MouseDown => {
let mut v = vec![EF::Hover(H::MouseDown)];
if let Some(f) = button_specific_down() { v.push(f); }
v
}
E::MouseUp => {
let mut v = vec![EF::Hover(H::MouseUp)];
if let Some(f) = button_specific_up() { v.push(f); }
v
}
E::Click => vec![EF::Hover(H::LeftMouseDown)],
E::MouseOver => vec![EF::Hover(H::MouseOver)],
E::MouseEnter => vec![EF::Hover(H::MouseEnter)],
E::MouseLeave => vec![EF::Hover(H::MouseLeave)],
E::MouseOut => vec![EF::Hover(H::MouseOut)],
E::DoubleClick => vec![EF::Hover(H::DoubleClick), EF::Window(W::DoubleClick)],
E::ContextMenu => vec![EF::Hover(H::RightMouseDown)],
E::KeyDown => vec![EF::Focus(F::VirtualKeyDown)],
E::KeyUp => vec![EF::Focus(F::VirtualKeyUp)],
E::KeyPress => vec![EF::Focus(F::TextInput)],
E::CompositionStart => vec![EF::Hover(H::CompositionStart), EF::Focus(F::CompositionStart)],
E::CompositionUpdate => vec![EF::Hover(H::CompositionUpdate), EF::Focus(F::CompositionUpdate)],
E::CompositionEnd => vec![EF::Hover(H::CompositionEnd), EF::Focus(F::CompositionEnd)],
E::Focus => vec![EF::Focus(F::FocusReceived)],
E::Blur => vec![EF::Focus(F::FocusLost)],
E::FocusIn => vec![EF::Hover(H::FocusIn), EF::Focus(F::FocusIn)],
E::FocusOut => vec![EF::Hover(H::FocusOut), EF::Focus(F::FocusOut)],
E::Input | E::Change => vec![EF::Focus(F::TextInput)],
E::Scroll | E::ScrollStart | E::ScrollEnd => vec![EF::Hover(H::Scroll)],
E::DragStart => vec![EF::Hover(H::DragStart), EF::Window(W::DragStart)],
E::Drag => vec![EF::Hover(H::Drag), EF::Window(W::Drag)],
E::DragEnd => vec![EF::Hover(H::DragEnd), EF::Window(W::DragEnd)],
E::DragEnter => vec![EF::Hover(H::DragEnter), EF::Window(W::DragEnter)],
E::DragOver => vec![EF::Hover(H::DragOver), EF::Window(W::DragOver)],
E::DragLeave => vec![EF::Hover(H::DragLeave), EF::Window(W::DragLeave)],
E::Drop => vec![EF::Hover(H::Drop), EF::Window(W::Drop)],
E::TouchStart => vec![EF::Hover(H::TouchStart)],
E::TouchMove => vec![EF::Hover(H::TouchMove)],
E::TouchEnd => vec![EF::Hover(H::TouchEnd)],
E::TouchCancel => vec![EF::Hover(H::TouchCancel)],
E::WindowResize => vec![EF::Window(W::Resized)],
E::WindowMove => vec![EF::Window(W::Moved)],
E::WindowClose => vec![EF::Window(W::CloseRequested)],
E::WindowFocusIn => vec![EF::Window(W::WindowFocusReceived)],
E::WindowFocusOut => vec![EF::Window(W::WindowFocusLost)],
E::ThemeChange => vec![EF::Window(W::ThemeChanged)],
E::WindowDpiChanged => vec![EF::Window(W::DpiChanged)],
E::WindowMonitorChanged => vec![EF::Window(W::MonitorChanged)],
E::MonitorConnected => vec![EF::Application(ApplicationEventFilter::MonitorConnected)],
E::MonitorDisconnected => vec![EF::Application(ApplicationEventFilter::MonitorDisconnected)],
E::FileHover => vec![EF::Hover(H::HoveredFile)],
E::FileDrop => vec![EF::Hover(H::DroppedFile)],
E::FileHoverCancel => vec![EF::Hover(H::HoveredFileCancelled)],
_ => vec![],
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PreCallbackFilterResult {
pub internal_events: Vec<PreCallbackSystemEvent>,
pub user_events: Vec<SyntheticEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseButtonState {
pub left_down: bool,
pub right_down: bool,
pub middle_down: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PostCallbackSystemEvent {
ApplyTextInput,
FocusChanged,
ApplyTextChangeset,
ScrollIntoView,
StartAutoScrollTimer,
CancelAutoScrollTimer,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PreCallbackSystemEvent {
TextClick {
target: DomNodeId,
position: LogicalPosition,
click_count: u8,
timestamp: Instant,
},
TextDragSelection {
target: DomNodeId,
start_position: LogicalPosition,
current_position: LogicalPosition,
is_dragging: bool,
},
ArrowKeyNavigation {
target: DomNodeId,
direction: ArrowDirection,
extend_selection: bool, word_jump: bool, },
KeyboardShortcut {
target: DomNodeId,
shortcut: KeyboardShortcut,
},
DeleteSelection {
target: DomNodeId,
forward: bool, },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArrowDirection {
Left,
Right,
Up,
Down,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyboardShortcut {
Copy, Cut, Paste, SelectAll, Undo, Redo, }
#[derive(Debug, Clone, PartialEq)]
pub struct PostCallbackFilterResult {
pub system_events: Vec<PostCallbackSystemEvent>,
}
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,
) -> PreCallbackFilterResult
where
SM: SelectionManagerQuery,
FM: FocusManagerQuery,
{
let ctx = FilterContext {
hit_test,
keyboard_state,
mouse_state,
click_count: selection_manager.get_click_count(),
focused_node: focus_manager.get_focused_node_id(),
drag_start_position: selection_manager.get_drag_start_position(),
selection_manager,
};
let (internal_events, user_events) = 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 {
internal_events,
user_events,
}
}
struct FilterContext<'a, SM> {
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>,
selection_manager: &'a SM,
}
fn process_event_for_internal<SM: SelectionManagerQuery>(
ctx: &FilterContext<'_, SM>,
event: &SyntheticEvent,
) -> Option<InternalEventAction> {
match event.event_type {
EventType::MouseDown => handle_mouse_down(event, ctx.hit_test, ctx.click_count, ctx.mouse_state),
EventType::MouseOver => handle_mouse_over(
event,
ctx.hit_test,
ctx.mouse_state,
ctx.drag_start_position,
),
EventType::KeyDown => handle_key_down(
event,
ctx.keyboard_state,
ctx.selection_manager,
ctx.focused_node,
),
_ => None,
}
}
enum InternalEventAction {
AddAndSkip(PreCallbackSystemEvent),
AddAndPass(PreCallbackSystemEvent),
}
fn get_first_hovered_node(hit_test: Option<&FullHitTest>) -> Option<DomNodeId> {
let ht = hit_test?;
let (dom_id, hit_data) = ht.hovered_nodes.iter().next()?;
let node_id = hit_data.regular_hit_test_nodes.keys().next()?;
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,
) -> 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);
Some(InternalEventAction::AddAndPass(
PreCallbackSystemEvent::TextClick {
target,
position,
click_count: effective_click_count,
timestamp: event.timestamp.clone(),
},
))
}
fn handle_mouse_over(
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 target = get_first_hovered_node(hit_test)?;
let current_position = get_mouse_position_with_fallback(event, mouse_state);
Some(InternalEventAction::AddAndPass(
PreCallbackSystemEvent::TextDragSelection {
target,
start_position,
current_position,
is_dragging: true,
},
))
}
fn handle_key_down<SM: SelectionManagerQuery>(
event: &SyntheticEvent,
keyboard_state: &crate::window::KeyboardState,
selection_manager: &SM,
focused_node: Option<DomNodeId>,
) -> Option<InternalEventAction> {
use crate::window::VirtualKeyCode;
let target = focused_node?;
let EventData::Keyboard(_) = &event.data else {
return None;
};
let ctrl = keyboard_state.ctrl_down();
let shift = keyboard_state.shift_down();
let vk = keyboard_state.current_virtual_keycode.as_ref()?;
if ctrl {
let shortcut = match vk {
VirtualKeyCode::C => Some(KeyboardShortcut::Copy),
VirtualKeyCode::X => Some(KeyboardShortcut::Cut),
VirtualKeyCode::V => Some(KeyboardShortcut::Paste),
VirtualKeyCode::A => Some(KeyboardShortcut::SelectAll),
VirtualKeyCode::Z if !shift => Some(KeyboardShortcut::Undo),
VirtualKeyCode::Z if shift => Some(KeyboardShortcut::Redo),
VirtualKeyCode::Y => Some(KeyboardShortcut::Redo),
_ => None,
};
if let Some(shortcut) = shortcut {
return Some(InternalEventAction::AddAndSkip(
PreCallbackSystemEvent::KeyboardShortcut { target, shortcut },
));
}
}
let direction = match vk {
VirtualKeyCode::Left => Some(ArrowDirection::Left),
VirtualKeyCode::Up => Some(ArrowDirection::Up),
VirtualKeyCode::Right => Some(ArrowDirection::Right),
VirtualKeyCode::Down => Some(ArrowDirection::Down),
_ => None,
};
if let Some(direction) = direction {
return Some(InternalEventAction::AddAndSkip(
PreCallbackSystemEvent::ArrowKeyNavigation {
target,
direction,
extend_selection: shift,
word_jump: ctrl,
},
));
}
if !selection_manager.has_selection() {
return None;
}
let forward = match vk {
VirtualKeyCode::Back => Some(false),
VirtualKeyCode::Delete => Some(true),
_ => None,
}?;
Some(InternalEventAction::AddAndSkip(
PreCallbackSystemEvent::DeleteSelection { target, forward },
))
}
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>;
}
pub fn post_callback_filter_internal_events(
prevent_default: bool,
internal_events: &[PreCallbackSystemEvent],
old_focus: Option<DomNodeId>,
new_focus: Option<DomNodeId>,
) -> PostCallbackFilterResult {
if prevent_default {
let focus_event = (old_focus != new_focus).then_some(PostCallbackSystemEvent::FocusChanged);
return PostCallbackFilterResult {
system_events: focus_event.into_iter().collect(),
};
}
let event_actions = internal_events
.iter()
.filter_map(internal_event_to_system_event);
let focus_event = (old_focus != new_focus).then_some(PostCallbackSystemEvent::FocusChanged);
let system_events = core::iter::once(PostCallbackSystemEvent::ApplyTextInput)
.chain(event_actions)
.chain(focus_event)
.collect();
PostCallbackFilterResult { system_events }
}
fn internal_event_to_system_event(
event: &PreCallbackSystemEvent,
) -> Option<PostCallbackSystemEvent> {
use PostCallbackSystemEvent::*;
use PreCallbackSystemEvent::*;
match event {
TextClick { .. } | ArrowKeyNavigation { .. } | DeleteSelection { .. } => {
Some(ScrollIntoView)
}
TextDragSelection { is_dragging, .. } => Some(if *is_dragging {
StartAutoScrollTimer
} else {
CancelAutoScrollTimer
}),
KeyboardShortcut { shortcut, .. } => shortcut_to_system_event(*shortcut),
}
}
fn shortcut_to_system_event(shortcut: KeyboardShortcut) -> Option<PostCallbackSystemEvent> {
use KeyboardShortcut::*;
match shortcut {
Cut | Paste | Undo | Redo => Some(PostCallbackSystemEvent::ScrollIntoView),
Copy | SelectAll => None,
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use crate::{
dom::{DomId, DomNodeId},
events::*,
geom::{LogicalPosition, LogicalRect, LogicalSize},
id::{Node, NodeHierarchy, NodeId},
styled_dom::NodeHierarchyItemId,
task::{Instant, SystemTick},
};
fn test_instant() -> Instant {
Instant::Tick(SystemTick::new(0))
}
fn create_test_hierarchy() -> NodeHierarchy {
let nodes = vec![
Node {
parent: None,
previous_sibling: None,
next_sibling: None,
last_child: Some(NodeId::new(1)),
},
Node {
parent: Some(NodeId::new(0)),
previous_sibling: None,
next_sibling: None,
last_child: Some(NodeId::new(2)),
},
Node {
parent: Some(NodeId::new(1)),
previous_sibling: None,
next_sibling: None,
last_child: None,
},
];
NodeHierarchy::new(nodes)
}
#[test]
fn test_event_source_enum() {
let _user = EventSource::User;
let _programmatic = EventSource::Programmatic;
let _synthetic = EventSource::Synthetic;
let _lifecycle = EventSource::Lifecycle;
}
#[test]
fn test_event_phase_enum() {
let _capture = EventPhase::Capture;
let _target = EventPhase::Target;
let _bubble = EventPhase::Bubble;
assert_eq!(EventPhase::default(), EventPhase::Bubble);
}
#[test]
fn test_synthetic_event_creation() {
let dom_id = DomId { inner: 1 };
let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let target = DomNodeId {
dom: dom_id,
node: node_id,
};
let event = SyntheticEvent::new(
EventType::Click,
EventSource::User,
target,
test_instant(),
EventData::None,
);
assert_eq!(event.event_type, EventType::Click);
assert_eq!(event.source, EventSource::User);
assert_eq!(event.phase, EventPhase::Target);
assert_eq!(event.target, target);
assert_eq!(event.current_target, target);
assert!(!event.stopped);
assert!(!event.stopped_immediate);
assert!(!event.prevented_default);
}
#[test]
fn test_stop_propagation() {
let dom_id = DomId { inner: 1 };
let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let target = DomNodeId {
dom: dom_id,
node: node_id,
};
let mut event = SyntheticEvent::new(
EventType::Click,
EventSource::User,
target,
test_instant(),
EventData::None,
);
assert!(!event.is_propagation_stopped());
event.stop_propagation();
assert!(event.is_propagation_stopped());
assert!(!event.is_immediate_propagation_stopped());
}
#[test]
fn test_stop_immediate_propagation() {
let dom_id = DomId { inner: 1 };
let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let target = DomNodeId {
dom: dom_id,
node: node_id,
};
let mut event = SyntheticEvent::new(
EventType::Click,
EventSource::User,
target,
test_instant(),
EventData::None,
);
event.stop_immediate_propagation();
assert!(event.is_propagation_stopped());
assert!(event.is_immediate_propagation_stopped());
}
#[test]
fn test_prevent_default() {
let dom_id = DomId { inner: 1 };
let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let target = DomNodeId {
dom: dom_id,
node: node_id,
};
let mut event = SyntheticEvent::new(
EventType::Click,
EventSource::User,
target,
test_instant(),
EventData::None,
);
assert!(!event.is_default_prevented());
event.prevent_default();
assert!(event.is_default_prevented());
}
#[test]
fn test_get_dom_path_single_node() {
let hierarchy = NodeHierarchy::new(vec![Node {
parent: None,
previous_sibling: None,
next_sibling: None,
last_child: None,
}]);
let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let path = get_dom_path(&hierarchy, target);
assert_eq!(path.len(), 1);
assert_eq!(path[0], NodeId::new(0));
}
#[test]
fn test_get_dom_path_three_nodes() {
let hierarchy = create_test_hierarchy();
let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
let path = get_dom_path(&hierarchy, target);
assert_eq!(path.len(), 3);
assert_eq!(path[0], NodeId::new(0)); assert_eq!(path[1], NodeId::new(1)); assert_eq!(path[2], NodeId::new(2)); }
#[test]
fn test_get_dom_path_middle_node() {
let hierarchy = create_test_hierarchy();
let target = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(1)));
let path = get_dom_path(&hierarchy, target);
assert_eq!(path.len(), 2);
assert_eq!(path[0], NodeId::new(0)); assert_eq!(path[1], NodeId::new(1)); }
#[test]
fn test_propagate_event_empty_callbacks() {
let hierarchy = create_test_hierarchy();
let dom_id = DomId { inner: 1 };
let target_node = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(2)));
let target = DomNodeId {
dom: dom_id,
node: target_node,
};
let mut event = SyntheticEvent::new(
EventType::Click,
EventSource::User,
target,
test_instant(),
EventData::None,
);
let callbacks: BTreeMap<NodeId, Vec<EventFilter>> = BTreeMap::new();
let result = propagate_event(&mut event, &hierarchy, &callbacks);
assert_eq!(result.callbacks_to_invoke.len(), 0);
assert!(!result.default_prevented);
}
#[test]
fn test_mouse_event_data_creation() {
let mouse_data = MouseEventData {
position: LogicalPosition { x: 100.0, y: 200.0 },
button: MouseButton::Left,
buttons: 1,
modifiers: KeyModifiers::new(),
};
assert_eq!(mouse_data.position.x, 100.0);
assert_eq!(mouse_data.position.y, 200.0);
assert_eq!(mouse_data.button, MouseButton::Left);
}
#[test]
fn test_key_modifiers() {
let modifiers = KeyModifiers::new().with_shift().with_ctrl();
assert!(modifiers.shift);
assert!(modifiers.ctrl);
assert!(!modifiers.alt);
assert!(!modifiers.meta);
assert!(!modifiers.is_empty());
let empty = KeyModifiers::new();
assert!(empty.is_empty());
}
#[test]
fn test_lifecycle_event_mount() {
let dom_id = DomId { inner: 1 };
let old_hierarchy = None;
let new_hierarchy = create_test_hierarchy();
let old_layout = None;
let new_layout = {
let mut map = BTreeMap::new();
map.insert(
NodeId::new(0),
LogicalRect {
origin: LogicalPosition { x: 0.0, y: 0.0 },
size: LogicalSize {
width: 100.0,
height: 100.0,
},
},
);
map.insert(
NodeId::new(1),
LogicalRect {
origin: LogicalPosition { x: 10.0, y: 10.0 },
size: LogicalSize {
width: 80.0,
height: 80.0,
},
},
);
map.insert(
NodeId::new(2),
LogicalRect {
origin: LogicalPosition { x: 20.0, y: 20.0 },
size: LogicalSize {
width: 60.0,
height: 60.0,
},
},
);
Some(map)
};
let events = detect_lifecycle_events(
dom_id,
dom_id,
old_hierarchy,
Some(&new_hierarchy),
old_layout.as_ref(),
new_layout.as_ref(),
test_instant(),
);
assert_eq!(events.len(), 3);
for event in &events {
assert_eq!(event.event_type, EventType::Mount);
assert_eq!(event.source, EventSource::Lifecycle);
if let EventData::Lifecycle(data) = &event.data {
assert_eq!(data.reason, LifecycleReason::InitialMount);
assert!(data.previous_bounds.is_none());
} else {
panic!("Expected Lifecycle event data");
}
}
}
#[test]
fn test_lifecycle_event_unmount() {
let dom_id = DomId { inner: 1 };
let old_hierarchy = create_test_hierarchy();
let new_hierarchy = None;
let old_layout = {
let mut map = BTreeMap::new();
map.insert(
NodeId::new(0),
LogicalRect {
origin: LogicalPosition { x: 0.0, y: 0.0 },
size: LogicalSize {
width: 100.0,
height: 100.0,
},
},
);
Some(map)
};
let new_layout = None;
let events = detect_lifecycle_events(
dom_id,
dom_id,
Some(&old_hierarchy),
new_hierarchy,
old_layout.as_ref(),
new_layout,
test_instant(),
);
assert_eq!(events.len(), 3);
for event in &events {
assert_eq!(event.event_type, EventType::Unmount);
assert_eq!(event.source, EventSource::Lifecycle);
}
}
#[test]
fn test_lifecycle_event_resize() {
let dom_id = DomId { inner: 1 };
let hierarchy = create_test_hierarchy();
let old_layout = {
let mut map = BTreeMap::new();
map.insert(
NodeId::new(0),
LogicalRect {
origin: LogicalPosition { x: 0.0, y: 0.0 },
size: LogicalSize {
width: 100.0,
height: 100.0,
},
},
);
Some(map)
};
let new_layout = {
let mut map = BTreeMap::new();
map.insert(
NodeId::new(0),
LogicalRect {
origin: LogicalPosition { x: 0.0, y: 0.0 },
size: LogicalSize {
width: 200.0,
height: 100.0,
}, },
);
Some(map)
};
let events = detect_lifecycle_events(
dom_id,
dom_id,
Some(&hierarchy),
Some(&hierarchy),
old_layout.as_ref(),
new_layout.as_ref(),
test_instant(),
);
assert_eq!(events.len(), 1);
assert_eq!(events[0].event_type, EventType::Resize);
assert_eq!(events[0].source, EventSource::Lifecycle);
if let EventData::Lifecycle(data) = &events[0].data {
assert_eq!(data.reason, LifecycleReason::Resize);
assert!(data.previous_bounds.is_some());
assert_eq!(data.current_bounds.size.width, 200.0);
} else {
panic!("Expected Lifecycle event data");
}
}
#[test]
fn test_event_filter_hover_match() {
let dom_id = DomId { inner: 1 };
let node_id = NodeHierarchyItemId::from_crate_internal(Some(NodeId::new(0)));
let target = DomNodeId {
dom: dom_id,
node: node_id,
};
let _event = SyntheticEvent::new(
EventType::MouseDown,
EventSource::User,
target,
test_instant(),
EventData::Mouse(MouseEventData {
position: LogicalPosition { x: 0.0, y: 0.0 },
button: MouseButton::Left,
buttons: 1,
modifiers: KeyModifiers::new(),
}),
);
}
}