druid 0.8.2

Data-oriented Rust UI design toolkit.
Documentation
// Copyright 2019 The Druid Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Events.

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};

/// An event, propagated downwards during event flow.
///
/// With two exceptions ([`Event::Command`] and [`Event::Notification`], which
/// have special considerations outlined in their own docs) each event
/// corresponds to some user action or other message received from the platform.
///
/// Events are things that happen that can change the state of widgets.
/// An important category is events plumbed from the platform windowing
/// system, which includes mouse and keyboard events, but also (in the
/// future) status changes such as window focus changes.
///
/// Events can also be higher level concepts indicating state changes
/// within the widget hierarchy, for example when a widget gains or loses
/// focus or "hot" (also known as hover) status.
///
/// Events are a key part of what is called "event flow", which is
/// basically the propagation of an event through the widget hierarchy
/// through the [`event`] widget method. A container widget will
/// generally pass the event to its children, mediated through the
/// [`WidgetPod`] container, which is where most of the event flow logic
/// is applied (especially the decision whether or not to propagate).
///
/// This enum is expected to grow considerably, as there are many, many
/// different kinds of events that are relevant in a GUI.
///
/// [`event`]: crate::Widget::event
/// [`WidgetPod`]: crate::WidgetPod
#[derive(Debug, Clone)]
pub enum Event {
    /// Sent to all widgets in a given window when that window is first instantiated.
    ///
    /// This should always be the first `Event` received, although widgets will
    /// receive [`LifeCycle::WidgetAdded`] first.
    ///
    /// Widgets should handle this event if they need to do some addition setup
    /// when a window is first created.
    ///
    /// [`LifeCycle::WidgetAdded`]: enum.LifeCycle.html#variant.WidgetAdded
    WindowConnected,
    /// Sent to all widgets in a given window when the system requests to close the window.
    ///
    /// If the event is handled (with [`set_handled`]), the window will not be closed.
    /// All widgets are given an opportunity to handle this event; your widget should not assume
    /// that the window *will* close just because this event is received; for instance, you should
    /// avoid destructive side effects such as cleaning up resources.
    ///
    /// [`set_handled`]: crate::EventCtx::set_handled
    WindowCloseRequested,
    /// Sent to all widgets in a given window when the system is going to close that window.
    ///
    /// This event means the window *will* go away; it is safe to dispose of resources and
    /// do any other cleanup.
    WindowDisconnected,
    /// Called when the window's [`Scale`] changes.
    ///
    /// This information can be used to switch between different resolution image assets.
    ///
    /// [`Scale`]: crate::Scale
    WindowScale(Scale),
    /// Called on the root widget when the window size changes.
    ///
    /// Discussion: it's not obvious this should be propagated to user
    /// widgets. It *is* propagated through the RootWidget and handled
    /// in the WindowPod, but after that it might be considered better
    /// to just handle it in `layout`.
    WindowSize(Size),
    /// Called when a mouse button is pressed.
    MouseDown(MouseEvent),
    /// Called when a mouse button is released.
    MouseUp(MouseEvent),
    /// Called when the mouse is moved.
    ///
    /// The `MouseMove` event is propagated to the active widget, if
    /// there is one, otherwise to hot widgets (see [`HotChanged`]).
    /// If a widget loses its hot status due to `MouseMove` then that specific
    /// `MouseMove` event is also still sent to that widget. However a widget
    /// can lose its hot status even without a `MouseMove` event, so make
    /// sure to also handle [`HotChanged`] if you care about the hot status.
    ///
    /// The `MouseMove` event is also the primary mechanism for widgets
    /// to set a cursor, for example to an I-bar inside a text widget. A
    /// simple tactic is for the widget to unconditionally call
    /// [`set_cursor`] in the MouseMove handler, as `MouseMove` is only
    /// propagated to active or hot widgets.
    ///
    /// [`HotChanged`]: LifeCycle::HotChanged
    /// [`set_cursor`]: crate::EventCtx::set_cursor
    MouseMove(MouseEvent),
    /// Called when the mouse wheel or trackpad is scrolled.
    Wheel(MouseEvent),
    /// Called when a key is pressed.
    KeyDown(KeyEvent),
    /// Called when a key is released.
    ///
    /// Because of repeat, there may be a number `KeyDown` events before
    /// a corresponding `KeyUp` is sent.
    KeyUp(KeyEvent),
    /// Called when a paste command is received.
    Paste(Clipboard),
    /// Called when the trackpad is pinched.
    ///
    /// The value is a delta.
    Zoom(f64),
    /// Called on a timer event.
    ///
    /// Request a timer event through [`EventCtx::request_timer`]. That will
    /// cause a timer event later.
    ///
    /// Note that timer events from other widgets may be delivered as well. Use
    /// the token returned from the `request_timer` call to filter events more
    /// precisely.
    ///
    /// [`EventCtx::request_timer`]: crate::EventCtx::request_timer
    Timer(TimerToken),
    /// Called at the beginning of a new animation frame.
    ///
    /// On the first frame when transitioning from idle to animating, `interval`
    /// will be 0. (This logic is presently per-window but might change to
    /// per-widget to make it more consistent). Otherwise it is in nanoseconds.
    ///
    /// Receiving `AnimFrame` does not inherently mean a `paint` invocation will follow.
    /// If you want something actually painted you need to explicitly call [`request_paint`]
    /// or [`request_paint_rect`].
    ///
    /// If you do that, then the `paint` method will be called shortly after this event is finished.
    /// As a result, you should try to avoid doing anything computationally
    /// intensive in response to an `AnimFrame` event: it might make Druid miss
    /// the monitor's refresh, causing lag or jerky animation.
    ///
    /// You can request an `AnimFrame` via [`request_anim_frame`].
    ///
    /// [`request_paint`]: crate::EventCtx::request_paint
    /// [`request_paint_rect`]: crate::EventCtx::request_paint_rect
    /// [`request_anim_frame`]: crate::EventCtx::request_anim_frame
    AnimFrame(u64),
    /// An event containing a [`Command`] to be handled by the widget.
    ///
    /// [`Command`]s are messages, optionally with attached data, that can
    /// may be generated from a number of sources:
    ///
    /// - If your application uses  menus (either window or context menus)
    /// then the [`MenuItem`]s in the menu will each correspond to a `Command`.
    /// When the menu item is selected, that [`Command`] will be delivered to
    /// the root widget of the appropriate window.
    /// - If you are doing work in another thread (using an [`ExtEventSink`])
    /// then [`Command`]s are the mechanism by which you communicate back to
    /// the main thread.
    /// - Widgets and other Druid components can send custom [`Command`]s at
    /// runtime, via methods such as [`EventCtx::submit_command`].
    ///
    /// [`Widget`]: Widget
    /// [`EventCtx::submit_command`]: crate::EventCtx::submit_command
    /// [`ExtEventSink`]: crate::ExtEventSink
    /// [`MenuItem`]: crate::MenuItem
    Command(Command),
    /// A [`Notification`] from one of this widget's descendants.
    ///
    /// While handling events, widgets can submit notifications to be
    /// delivered to their ancestors immediately after they return.
    ///
    /// If you handle a [`Notification`], you should call [`EventCtx::set_handled`]
    /// to stop the notification from being delivered to further ancestors.
    ///
    /// ## Special considerations
    ///
    /// Notifications are slightly different from other events; they originate
    /// inside Druid, and they are delivered as part of the handling of another
    /// event. In this sense, they can sort of be thought of as an augmentation
    /// of an event; they are a way for multiple widgets to coordinate the
    /// handling of an event.
    ///
    /// [`EventCtx::set_handled`]: crate::EventCtx::set_handled
    Notification(Notification),
    /// Sent to a widget when the platform may have mutated shared IME state.
    ///
    /// This is sent to a widget that has an attached IME session anytime the
    /// platform has released a mutable lock on shared state.
    ///
    /// This does not *mean* that any state has changed, but the widget
    /// should check the shared state, perform invalidation, and update `Data`
    /// as necessary.
    ImeStateChange,
    /// Internal Druid event.
    ///
    /// This should always be passed down to descendant [`WidgetPod`]s.
    ///
    /// [`WidgetPod`]: crate::WidgetPod
    Internal(InternalEvent),
}

/// Internal events used by Druid inside [`WidgetPod`].
///
/// These events are translated into regular [`Event`]s
/// and should not be used directly.
///
/// [`WidgetPod`]: crate::WidgetPod
#[derive(Debug, Clone)]
pub enum InternalEvent {
    /// Sent in some cases when the mouse has left the window.
    ///
    /// This is used in cases when the platform no longer sends mouse events,
    /// but we know that we've stopped receiving the mouse events.
    MouseLeave,
    /// A command still in the process of being dispatched.
    TargetedCommand(Command),
    /// Used for routing timer events.
    RouteTimer(TimerToken, WidgetId),
    /// Route an IME change event.
    RouteImeStateChange(WidgetId),
}

/// Application life cycle events.
///
/// Unlike [`Event`]s, [`LifeCycle`] events are generated by Druid, and
/// may occur at different times during a given pass of the event loop. The
/// [`LifeCycle::WidgetAdded`] event, for instance, may occur when the app
/// first launches (during the handling of [`Event::WindowConnected`]) or it
/// may occur during [`update`] cycle, if some widget has been added there.
///
/// Similarly the [`LifeCycle::Size`] method occurs during [`layout`], and
/// [`LifeCycle::HotChanged`] can occur both during [`event`] (if the mouse
/// moves over a widget) or in response to [`LifeCycle::ViewContextChanged`],
/// if a widget is moved away from under the mouse.
///
/// [`event`]: crate::Widget::event
/// [`update`]: crate::Widget::update
/// [`layout`]: crate::Widget::layout
#[derive(Debug, Clone)]
pub enum LifeCycle {
    /// Sent to a `Widget` when it is added to the widget tree. This should be
    /// the first message that each widget receives.
    ///
    /// Widgets should handle this event in order to do any initial setup.
    ///
    /// In addition to setup, this event is also used by the framework to
    /// track certain types of important widget state.
    ///
    /// ## Registering children
    ///
    /// Container widgets (widgets which use [`WidgetPod`] to manage children)
    /// must ensure that this event is forwarded to those children. The [`WidgetPod`]
    /// itself will handle registering those children with the system; this is
    /// required for things like correct routing of events.
    ///
    /// [`WidgetPod`]: crate::WidgetPod
    WidgetAdded,
    /// Called when the [`Size`] of the widget changes.
    ///
    /// This will be called after [`Widget::layout`], if the [`Size`] returned
    /// by the widget differs from its previous size.
    ///
    /// [`Size`]: crate::Size
    /// [`Widget::layout`]: crate::Widget::layout
    Size(Size),
    /// Called when the Disabled state of the widgets is changed.
    ///
    /// To check if a widget is disabled, see [`is_disabled`].
    ///
    /// To change a widget's disabled state, see [`set_disabled`].
    ///
    /// [`is_disabled`]: crate::EventCtx::is_disabled
    /// [`set_disabled`]: crate::EventCtx::set_disabled
    DisabledChanged(bool),
    /// Called when the "hot" status changes.
    ///
    /// This will always be called _before_ the event that triggered it; that is,
    /// when the mouse moves over a widget, that widget will receive
    /// [`LifeCycle::HotChanged`] before it receives [`Event::MouseMove`].
    ///
    /// See [`is_hot`](crate::EventCtx::is_hot) for
    /// discussion about the hot status.
    HotChanged(bool),
    /// This is called when the widget-tree changes and Druid wants to rebuild the
    /// Focus-chain.
    ///
    /// It is the only place from which [`register_for_focus`] should be called.
    /// By doing so the widget can get focused by other widgets using [`focus_next`] or [`focus_prev`].
    ///
    /// [`register_for_focus`]: crate::LifeCycleCtx::register_for_focus
    /// [`focus_next`]: crate::EventCtx::focus_next
    /// [`focus_prev`]: crate::EventCtx::focus_prev
    BuildFocusChain,
    /// Called when the focus status changes.
    ///
    /// This will always be called immediately after a new widget gains focus.
    /// The newly focused widget will receive this with `true` and the widget
    /// that lost focus will receive this with `false`.
    ///
    /// See [`EventCtx::is_focused`] for more information about focus.
    ///
    /// [`EventCtx::is_focused`]: crate::EventCtx::is_focused
    FocusChanged(bool),
    /// Called when the [`ViewContext`] of this widget changed.
    ///
    /// See [`view_context_changed`] on how and when to request this event.
    ///
    /// [`view_context_changed`]: crate::EventCtx::view_context_changed
    ViewContextChanged(ViewContext),
    /// Internal Druid lifecycle event.
    ///
    /// This should always be passed down to descendant [`WidgetPod`]s.
    ///
    /// [`WidgetPod`]: crate::WidgetPod
    Internal(InternalLifeCycle),
}

/// Internal lifecycle events used by Druid inside [`WidgetPod`].
///
/// These events are translated into regular [`LifeCycle`] events
/// and should not be used directly.
///
/// [`WidgetPod`]: crate::WidgetPod
#[derive(Debug, Clone)]
pub enum InternalLifeCycle {
    /// Used to route the `WidgetAdded` event to the required widgets.
    RouteWidgetAdded,
    /// Used to route the `FocusChanged` event.
    RouteFocusChanged {
        /// the widget that is losing focus, if any
        old: Option<WidgetId>,
        /// the widget that is gaining focus, if any
        new: Option<WidgetId>,
    },
    /// Used to route the `DisabledChanged` event to the required widgets.
    RouteDisabledChanged,

    /// Used to route the `ViewContextChanged` event to the required widgets.
    RouteViewContextChanged(ViewContext),
    /// For testing: request the `WidgetState` of a specific widget.
    ///
    /// During testing, you may wish to verify that the state of a widget
    /// somewhere in the tree is as expected. In that case you can dispatch
    /// this event, specifying the widget in question, and that widget will
    /// set its state in the provided `Cell`, if it exists.
    DebugRequestState {
        /// the widget whose state is requested
        widget: WidgetId,
        /// a cell used to store the a widget's state
        state_cell: StateCell,
    },
    /// For testing: request the `DebugState` of a specific widget.
    ///
    /// This is useful if you need to get a best-effort description of the
    /// state of this widget and its children. You can dispatch this event,
    /// specifying the widget in question, and that widget will
    /// set its state in the provided `Cell`, if it exists.
    DebugRequestDebugState {
        /// the widget whose state is requested
        widget: WidgetId,
        /// a cell used to store the a widget's state
        state_cell: DebugStateCell,
    },
    /// For testing: apply the given function on every widget.
    DebugInspectState(StateCheckFn),
}

/// Information about the widget's surroundings.
///
/// The global origin is also saved in the widget state.
///
/// When the `ViewContext` of a widget changes it receives a `ViewContextChanged` event.
#[derive(Debug, Copy, Clone)]
pub struct ViewContext {
    /// The origin of this widget relative to the window.
    ///
    /// This is written from the perspective of the Widget and not the Pod.
    /// For the Pod this is its parent's window origin.
    pub window_origin: Point,

    /// The last position the cursor was at, relative to the widget.
    pub last_mouse_position: Option<Point>,

    /// The visible area, this widget is contained in, relative to the widget.
    ///
    /// The area may be larger than the widget's `paint_rect`.
    pub clip: Rect,
}

impl Event {
    /// Whether this event should be sent to widgets which are currently not visible and not
    /// accessible.
    ///
    /// For example: the hidden tabs in a tabs widget are `hidden` whereas the non-visible
    /// widgets in a scroll are not, since you can bring them into view by scrolling.
    ///
    /// This distinction between scroll and tabs is due to one of the main purposes of
    /// this method: determining which widgets are allowed to receive focus. As a rule
    /// of thumb a widget counts as `hidden` if it makes no sense for it to receive focus
    /// when the user presses the 'tab' key.
    ///
    /// If a widget changes which children are hidden it must call [`children_changed`].
    ///
    /// See also [`LifeCycle::should_propagate_to_hidden`].
    ///
    /// [`children_changed`]: crate::EventCtx::children_changed
    /// [`LifeCycle::should_propagate_to_hidden`]: LifeCycle::should_propagate_to_hidden
    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,
        }
    }

    /// Returns true if the event involves a cursor.
    ///
    /// These events interact with the hot state and
    pub fn is_pointer_event(&self) -> bool {
        matches!(
            self,
            Event::MouseDown(_) | Event::MouseUp(_) | Event::MouseMove(_) | Event::Wheel(_)
        )
    }
}

impl LifeCycle {
    /// Whether this event should be sent to widgets which are currently not visible and not
    /// accessible.
    ///
    /// If a widget changes which children are `hidden` it must call [`children_changed`].
    /// For a more detailed explanation of the `hidden` state, see [`Event::should_propagate_to_hidden`].
    ///
    /// [`children_changed`]: crate::EventCtx::children_changed
    /// [`Event::should_propagate_to_hidden`]: Event::should_propagate_to_hidden
    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,
        }
    }

    /// Returns an event for a widget which maybe is overlapped by another widget.
    ///
    /// When `ignore` is set to `true` the widget will set its hot state to `false` even if the cursor
    /// is inside its bounds.
    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 {
    /// Whether this event should be sent to widgets which are currently not visible and not
    /// accessible.
    ///
    /// If a widget changes which children are `hidden` it must call [`children_changed`].
    /// For a more detailed explanation of the `hidden` state, see [`Event::should_propagate_to_hidden`].
    ///
    /// [`children_changed`]: crate::EventCtx::children_changed
    /// [`Event::should_propagate_to_hidden`]: Event::should_propagate_to_hidden
    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 {
    /// Transforms the `ViewContext` into the coordinate space of its child.
    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};

    /// An interior-mutable struct for fetching WidgetState.
    #[derive(Clone, Default)]
    pub struct StateCell(Rc<RefCell<Option<WidgetState>>>);

    /// An interior-mutable struct for fetching DebugState.
    #[derive(Clone, Default)]
    pub struct DebugStateCell(Rc<RefCell<Option<DebugState>>>);

    #[derive(Clone)]
    pub struct StateCheckFn(Rc<dyn Fn(&WidgetState)>);

    /// a hacky way of printing the widget id if we panic
    struct WidgetDrop(bool, WidgetId);

    impl Drop for WidgetDrop {
        fn drop(&mut self) {
            if self.0 {
                eprintln!("panic in {:?}", self.1);
            }
        }
    }

    impl StateCell {
        /// Set the state. This will panic if it is called twice.
        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 {
        /// Set the state. This will panic if it is called twice.
        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;
        }
    }

    // TODO - Use fmt.debug_tuple?

    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")
        }
    }
}