denise-ui 0.0.1

Scene graph, widgets and compositor for Denise.
Documentation
//! What a widget is, and the two contexts it is handed.
//!
//! A widget owns its own state and knows how to draw itself inside a rectangle it
//! is given. It does not own its position, its children, its z-order or its
//! damage — the tree owns those, which is what keeps the invalidation rules in one
//! place instead of scattered across every widget.

use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::Any;

use denise::{InputEvent, Rect, Theme};
use denise_render::Canvas;
use denise_text::TextEngine;

/// Upcast to [`Any`], so an application can get its concrete widget type back out
/// of the tree. Blanket-implemented; never implement it by hand.
pub trait AsAny: 'static {
    /// Borrows as `dyn Any`.
    fn as_any(&self) -> &dyn Any;
    /// Mutably borrows as `dyn Any`.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl<T: Any> AsAny for T {
    #[inline]
    fn as_any(&self) -> &dyn Any {
        self
    }
    #[inline]
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

/// Visual state the tree tracks on the widget's behalf.
///
/// Widgets do not track hover or press themselves. The tree does, and it marks the
/// node dirty when any of these change — which is the whole reason a stale-pixel
/// bug cannot come from a widget forgetting to invalidate on hover.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct VisualState(u8);

impl VisualState {
    /// Nothing set.
    pub const NONE: Self = Self(0);
    /// The pointer is over this widget.
    pub const HOVERED: Self = Self(1 << 0);
    /// A pointer button went down on this widget and has not come up.
    pub const PRESSED: Self = Self(1 << 1);
    /// This widget has keyboard focus.
    pub const FOCUSED: Self = Self(1 << 2);
    /// This widget, or an ancestor, is disabled.
    pub const DISABLED: Self = Self(1 << 3);

    /// Returns `true` if every bit in `other` is set.
    #[inline]
    pub const fn contains(self, other: Self) -> bool {
        self.0 & other.0 == other.0
    }

    /// Sets or clears `other`.
    #[inline]
    pub const fn set(self, other: Self, on: bool) -> Self {
        Self(if on {
            self.0 | other.0
        } else {
            self.0 & !other.0
        })
    }

    /// Returns `true` if no bit is set.
    #[inline]
    pub const fn is_empty(self) -> bool {
        self.0 == 0
    }
}

impl core::ops::BitOr for VisualState {
    type Output = Self;
    #[inline]
    fn bitor(self, rhs: Self) -> Self {
        Self(self.0 | rhs.0)
    }
}

/// Whether an event was consumed.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Handled {
    /// The widget ignored the event.
    #[default]
    No,
    /// The widget acted on the event.
    ///
    /// **This also marks the node dirty.** A widget that consumes an event has
    /// almost always changed what it draws, and the cost of being wrong is
    /// repainting one widget-sized rectangle. Missing an invalidation costs a
    /// stale frame that only shows up on hardware, so the default errs the cheap
    /// way. Use [`EventCtx::invalidate`] for the rare change that consumes nothing.
    Yes,
}

impl Handled {
    /// Returns `true` for [`Handled::Yes`].
    #[inline]
    pub const fn is_handled(self) -> bool {
        matches!(self, Handled::Yes)
    }
}

/// Something a widget is asked to react to.
#[derive(Debug)]
#[non_exhaustive]
pub enum Event<'a> {
    /// Raw input, already routed to this widget by hit test or focus.
    Input(&'a InputEvent),
    /// This widget just took keyboard focus.
    FocusGained,
    /// This widget just lost keyboard focus.
    FocusLost,
}

/// What a widget needs in order to draw itself.
///
/// Mutable, because [`PaintCtx::text`] is: measuring a string is what fills the
/// glyph cache, so a widget that measures and then draws rasterises each glyph
/// once rather than twice. The widget itself is still `&self`.
#[derive(Debug)]
pub struct PaintCtx<'a> {
    /// The active theme. Widgets name roles, never colours.
    pub theme: &'a Theme,
    /// Fonts and the glyph cache.
    pub text: &'a mut TextEngine,
    /// Absolute bounds of this widget, in surface pixels.
    pub bounds: Rect,
    /// Hover, press, focus and enabled state, tracked by the tree.
    pub state: VisualState,
    /// Milliseconds from an arbitrary epoch, as last given to [`crate::Ui::tick`].
    pub now_ms: u64,
}

/// What a widget can do while handling an event.
#[derive(Debug)]
pub struct EventCtx<'a, M> {
    /// Absolute bounds of this widget, in surface pixels.
    pub bounds: Rect,
    /// The active theme.
    pub theme: &'a Theme,
    /// Fonts and the glyph cache.
    ///
    /// Needed while *handling* events, not only while painting: a text field with
    /// a proportional font cannot work out where its caret is without measuring
    /// the text in front of it.
    pub text: &'a mut TextEngine,
    /// Hover, press, focus and enabled state.
    pub state: VisualState,
    /// Milliseconds from an arbitrary epoch.
    pub now_ms: u64,
    messages: &'a mut Vec<M>,
    dirty: bool,
    wants_focus: bool,
}

impl<'a, M> EventCtx<'a, M> {
    pub(crate) fn new(
        bounds: Rect,
        theme: &'a Theme,
        text: &'a mut TextEngine,
        state: VisualState,
        now_ms: u64,
        messages: &'a mut Vec<M>,
    ) -> Self {
        Self {
            bounds,
            theme,
            text,
            state,
            now_ms,
            messages,
            dirty: false,
            wants_focus: false,
        }
    }

    /// Queues a message for the application to pick up with
    /// [`Ui::drain_messages`](crate::Ui::drain_messages).
    ///
    /// This is the whole event-handling story: no callbacks, no `Rc<RefCell<_>>`,
    /// no widget holding a reference to another widget. The application dispatches
    /// centrally, where it can see all of its own state.
    #[inline]
    pub fn emit(&mut self, message: M) {
        self.messages.push(message);
    }

    /// Marks this widget's rectangle for repaint.
    ///
    /// Rarely needed: returning [`Handled::Yes`] already does it.
    #[inline]
    pub fn invalidate(&mut self) {
        self.dirty = true;
    }

    /// Asks the tree to move keyboard focus here.
    #[inline]
    pub fn request_focus(&mut self) {
        self.wants_focus = true;
    }

    pub(crate) fn finish(self) -> (bool, bool) {
        (self.dirty, self.wants_focus)
    }
}

/// What a widget reports back after [`Widget::animate`].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Animation {
    /// `true` if the widget's appearance changed and it needs repainting.
    pub repaint: bool,
    /// When the widget wants to be asked again, in the same clock as `now_ms`.
    pub next_ms: Option<u64>,
}

impl Animation {
    /// Nothing is animating.
    pub const NONE: Self = Self {
        repaint: false,
        next_ms: None,
    };
}

/// A thing that draws itself in a rectangle and reacts to input.
///
/// `M` is the application's message type. A widget never calls back into the
/// application; it emits an `M` and the application decides what that means.
pub trait Widget<M>: AsAny {
    /// Draws into `canvas`, which is already clipped to this widget's bounds
    /// intersected with the damage region being repainted.
    ///
    /// Paint as though the whole widget were visible. The clip turns that into an
    /// incremental repaint, so there is never a second draw path to keep in step
    /// with the first.
    fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut Canvas<'_>);

    /// Reacts to an event routed to this widget.
    fn on_event(&mut self, event: &Event<'_>, ctx: &mut EventCtx<'_, M>) -> Handled {
        let _ = (event, ctx);
        Handled::No
    }

    /// Returns `true` if the pointer can hit this widget.
    ///
    /// Non-interactive widgets are invisible to hit testing, so a [`Label`] inside
    /// a [`Button`] does not swallow the click — the button is still the topmost
    /// hittable node under the pointer.
    ///
    /// [`Label`]: crate::widgets::Label
    /// [`Button`]: crate::widgets::Button
    fn accepts_pointer(&self) -> bool {
        false
    }

    /// Returns `true` if this widget can take keyboard focus.
    fn focusable(&self) -> bool {
        false
    }

    /// Advances time-based state. Only ever called on the focused widget.
    ///
    /// That restriction is deliberate: on a panel that spends its day idle, the one
    /// thing with a legitimate reason to redraw on a timer is the caret in the field
    /// the user is typing into. Anything that animates unconditionally would keep
    /// the CPU awake for the life of the device.
    fn animate(&mut self, now_ms: u64) -> Animation {
        let _ = now_ms;
        Animation::NONE
    }
}

/// A widget that draws nothing and hits nothing.
///
/// Used for scene roots and for grouping: a node exists to position and clip its
/// children, and does not have to paint to do that.
#[derive(Clone, Copy, Debug, Default)]
pub struct Void;

impl<M: 'static> Widget<M> for Void {
    fn paint(&self, _ctx: &mut PaintCtx<'_>, _canvas: &mut Canvas<'_>) {}
}

pub(crate) type BoxedWidget<M> = Box<dyn Widget<M>>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn visual_state_bits() {
        let s = VisualState::HOVERED | VisualState::FOCUSED;
        assert!(s.contains(VisualState::HOVERED));
        assert!(!s.contains(VisualState::PRESSED));
        assert!(
            s.set(VisualState::HOVERED, false)
                .contains(VisualState::FOCUSED)
        );
        assert!(VisualState::NONE.is_empty());
    }
}