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;
use crate::motion::Wake;
pub trait AsAny: 'static {
fn as_any(&self) -> &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
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct VisualState(u8);
impl VisualState {
pub const NONE: Self = Self(0);
pub const HOVERED: Self = Self(1 << 0);
pub const PRESSED: Self = Self(1 << 1);
pub const FOCUSED: Self = Self(1 << 2);
pub const DISABLED: Self = Self(1 << 3);
#[inline]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[inline]
pub const fn set(self, other: Self, on: bool) -> Self {
Self(if on {
self.0 | other.0
} else {
self.0 & !other.0
})
}
#[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)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Handled {
#[default]
No,
Yes,
}
impl Handled {
#[inline]
pub const fn is_handled(self) -> bool {
matches!(self, Handled::Yes)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Event<'a> {
Input(&'a InputEvent),
FocusGained,
FocusLost,
}
#[derive(Debug)]
pub struct PaintCtx<'a> {
pub theme: &'a Theme,
pub text: &'a mut TextEngine,
pub bounds: Rect,
pub state: VisualState,
pub now_ms: u64,
}
#[derive(Debug)]
pub struct EventCtx<'a, M> {
pub bounds: Rect,
pub theme: &'a Theme,
pub text: &'a mut TextEngine,
pub state: VisualState,
pub now_ms: u64,
messages: &'a mut Vec<M>,
dirty: bool,
wants_focus: bool,
wants_animation: bool,
reveal: Option<Rect>,
}
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,
wants_animation: false,
reveal: None,
}
}
#[inline]
pub fn emit(&mut self, message: M) {
self.messages.push(message);
}
#[inline]
pub fn invalidate(&mut self) {
self.dirty = true;
}
#[inline]
pub fn request_focus(&mut self) {
self.wants_focus = true;
}
#[inline]
pub fn request_animation(&mut self) {
self.wants_animation = true;
}
#[inline]
pub fn reveal(&mut self, rect: Rect) {
self.reveal = Some(rect);
}
pub(crate) fn finish(self) -> (bool, bool, bool, Option<Rect>) {
(
self.dirty,
self.wants_focus,
self.wants_animation,
self.reveal,
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Animation {
pub repaint: bool,
pub next: Wake,
}
impl Animation {
pub const NONE: Self = Self {
repaint: false,
next: Wake::Never,
};
pub const MOVING: Self = Self {
repaint: true,
next: Wake::Animating,
};
#[inline]
pub const fn due_at(due_ms: u64) -> Self {
Self {
repaint: false,
next: Wake::At(due_ms),
}
}
}
pub trait Widget<M>: AsAny {
fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut Canvas<'_>);
fn on_event(&mut self, event: &Event<'_>, ctx: &mut EventCtx<'_, M>) -> Handled {
let _ = (event, ctx);
Handled::No
}
fn accepts_pointer(&self) -> bool {
false
}
fn focusable(&self) -> bool {
false
}
fn animate(&mut self, now_ms: u64) -> Animation {
let _ = now_ms;
Animation::NONE
}
fn snap(&mut self, now_ms: u64) -> Animation {
let _ = now_ms;
Animation::NONE
}
}
#[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());
}
}