use std::ops::{Add, Sub};
use druid_shell::{Clipboard, KeyEvent, TimerToken};
use crate::kurbo::{Rect, Size};
use crate::mouse::MouseEvent;
use crate::{Command, Notification, Point, Scale, WidgetId};
#[derive(Debug, Clone)]
pub enum Event {
WindowConnected,
WindowCloseRequested,
WindowDisconnected,
WindowScale(Scale),
WindowSize(Size),
MouseDown(MouseEvent),
MouseUp(MouseEvent),
MouseMove(MouseEvent),
Wheel(MouseEvent),
KeyDown(KeyEvent),
KeyUp(KeyEvent),
Paste(Clipboard),
Zoom(f64),
Timer(TimerToken),
AnimFrame(u64),
Command(Command),
Notification(Notification),
ImeStateChange,
Internal(InternalEvent),
}
#[derive(Debug, Clone)]
pub enum InternalEvent {
MouseLeave,
TargetedCommand(Command),
RouteTimer(TimerToken, WidgetId),
RouteImeStateChange(WidgetId),
}
#[derive(Debug, Clone)]
pub enum LifeCycle {
WidgetAdded,
Size(Size),
DisabledChanged(bool),
HotChanged(bool),
BuildFocusChain,
FocusChanged(bool),
ViewContextChanged(ViewContext),
Internal(InternalLifeCycle),
}
#[derive(Debug, Clone)]
pub enum InternalLifeCycle {
RouteWidgetAdded,
RouteFocusChanged {
old: Option<WidgetId>,
new: Option<WidgetId>,
},
RouteDisabledChanged,
RouteViewContextChanged(ViewContext),
DebugRequestState {
widget: WidgetId,
state_cell: StateCell,
},
DebugRequestDebugState {
widget: WidgetId,
state_cell: DebugStateCell,
},
DebugInspectState(StateCheckFn),
}
#[derive(Debug, Copy, Clone)]
pub struct ViewContext {
pub window_origin: Point,
pub last_mouse_position: Option<Point>,
pub clip: Rect,
}
impl Event {
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
Event::WindowConnected
| Event::WindowCloseRequested
| Event::WindowDisconnected
| Event::WindowScale(_)
| Event::WindowSize(_)
| Event::Timer(_)
| Event::AnimFrame(_)
| Event::Command(_)
| Event::Notification(_)
| Event::Internal(_) => true,
Event::MouseDown(_)
| Event::MouseUp(_)
| Event::MouseMove(_)
| Event::Wheel(_)
| Event::KeyDown(_)
| Event::KeyUp(_)
| Event::Paste(_)
| Event::ImeStateChange
| Event::Zoom(_) => false,
}
}
pub fn is_pointer_event(&self) -> bool {
matches!(
self,
Event::MouseDown(_) | Event::MouseUp(_) | Event::MouseMove(_) | Event::Wheel(_)
)
}
}
impl LifeCycle {
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
LifeCycle::Internal(internal) => internal.should_propagate_to_hidden(),
LifeCycle::WidgetAdded | LifeCycle::DisabledChanged(_) => true,
LifeCycle::Size(_)
| LifeCycle::HotChanged(_)
| LifeCycle::FocusChanged(_)
| LifeCycle::BuildFocusChain
| LifeCycle::ViewContextChanged { .. } => false,
}
}
pub fn ignore_hot(&self, ignore: bool) -> Self {
if ignore {
match self {
LifeCycle::ViewContextChanged(view_ctx) => {
let mut view_ctx = view_ctx.to_owned();
view_ctx.last_mouse_position = None;
LifeCycle::ViewContextChanged(view_ctx)
}
LifeCycle::Internal(InternalLifeCycle::RouteViewContextChanged(view_ctx)) => {
let mut view_ctx = view_ctx.to_owned();
view_ctx.last_mouse_position = None;
LifeCycle::Internal(InternalLifeCycle::RouteViewContextChanged(view_ctx))
}
_ => self.to_owned(),
}
} else {
self.to_owned()
}
}
}
impl InternalLifeCycle {
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
InternalLifeCycle::RouteWidgetAdded
| InternalLifeCycle::RouteFocusChanged { .. }
| InternalLifeCycle::RouteDisabledChanged => true,
InternalLifeCycle::RouteViewContextChanged { .. } => false,
InternalLifeCycle::DebugRequestState { .. }
| InternalLifeCycle::DebugRequestDebugState { .. }
| InternalLifeCycle::DebugInspectState(_) => true,
}
}
}
impl ViewContext {
pub(crate) fn for_child_widget(&self, child_origin: Point) -> Self {
let child_origin = child_origin.to_vec2();
ViewContext {
window_origin: self.window_origin.add(child_origin),
last_mouse_position: self.last_mouse_position.map(|pos| pos.sub(child_origin)),
clip: self.clip.sub(child_origin),
}
}
}
pub(crate) use state_cell::{DebugStateCell, StateCell, StateCheckFn};
mod state_cell {
use crate::core::WidgetState;
use crate::debug_state::DebugState;
use crate::WidgetId;
use std::{cell::RefCell, rc::Rc};
#[derive(Clone, Default)]
pub struct StateCell(Rc<RefCell<Option<WidgetState>>>);
#[derive(Clone, Default)]
pub struct DebugStateCell(Rc<RefCell<Option<DebugState>>>);
#[derive(Clone)]
pub struct StateCheckFn(Rc<dyn Fn(&WidgetState)>);
struct WidgetDrop(bool, WidgetId);
impl Drop for WidgetDrop {
fn drop(&mut self) {
if self.0 {
eprintln!("panic in {:?}", self.1);
}
}
}
impl StateCell {
pub(crate) fn set(&self, state: WidgetState) {
assert!(
self.0.borrow_mut().replace(state).is_none(),
"StateCell already set"
)
}
#[allow(dead_code)]
pub(crate) fn take(&self) -> Option<WidgetState> {
self.0.borrow_mut().take()
}
}
impl DebugStateCell {
pub(crate) fn set(&self, state: DebugState) {
assert!(
self.0.borrow_mut().replace(state).is_none(),
"DebugStateCell already set"
)
}
#[allow(dead_code)]
pub(crate) fn take(&self) -> Option<DebugState> {
self.0.borrow_mut().take()
}
}
impl StateCheckFn {
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn new(f: impl Fn(&WidgetState) + 'static) -> Self {
StateCheckFn(Rc::new(f))
}
pub(crate) fn call(&self, state: &WidgetState) {
let mut panic_reporter = WidgetDrop(true, state.id);
(self.0)(state);
panic_reporter.0 = false;
}
}
impl std::fmt::Debug for StateCell {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let inner = if self.0.borrow().is_some() {
"Some"
} else {
"None"
};
write!(f, "StateCell({inner})")
}
}
impl std::fmt::Debug for DebugStateCell {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let inner = if self.0.borrow().is_some() {
"Some"
} else {
"None"
};
write!(f, "DebugStateCell({inner})")
}
}
impl std::fmt::Debug for StateCheckFn {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "StateCheckFn")
}
}
}