use std::{
any::{Any, TypeId},
ops::{Deref, DerefMut},
time::Duration,
};
use crate::core::{CommandQueue, FocusChange, WidgetState};
use crate::piet::Piet;
use crate::piet::RenderContext;
use crate::{
commands, Affine, Command, ContextMenu, Cursor, Insets, MenuDesc, Point, Rect, SingleUse, Size,
Target, Text, TimerToken, Vec2, WidgetId, WindowDesc, WindowHandle, WindowId,
};
macro_rules! impl_context_method {
($ty:ty, { $($method:item)+ } ) => {
impl $ty { $($method)+ }
};
( $ty:ty, $($more:ty),+, { $($method:item)+ } ) => {
impl_context_method!($ty, { $($method)+ });
impl_context_method!($($more),+, { $($method)+ });
};
}
pub(crate) struct ContextState<'a> {
pub(crate) command_queue: &'a mut CommandQueue,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) focus_widget: Option<WidgetId>,
pub(crate) root_app_data_type: TypeId,
}
pub struct EventCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) cursor: &'a mut Option<Cursor>,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
}
pub struct LifeCycleCtx<'a, 'b> {
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) state: &'a mut ContextState<'b>,
}
pub struct UpdateCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
}
pub struct LayoutCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) mouse_pos: Option<Point>,
}
pub(crate) struct ZOrderPaintOp {
pub z_index: u32,
pub paint_func: Box<dyn FnOnce(&mut PaintCtx) + 'static>,
pub transform: Affine,
}
pub struct PaintCtx<'a, 'b, 'c> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a WidgetState,
pub render_ctx: &'a mut Piet<'c>,
pub(crate) z_ops: Vec<ZOrderPaintOp>,
pub(crate) region: Region,
pub(crate) depth: u32,
}
#[derive(Debug, Clone)]
pub struct Region(Rect);
impl_context_method!(
EventCtx<'_, '_>,
UpdateCtx<'_, '_>,
LifeCycleCtx<'_, '_>,
PaintCtx<'_, '_, '_>,
LayoutCtx<'_, '_>,
{
pub fn widget_id(&self) -> WidgetId {
self.widget_state.id
}
pub fn window(&self) -> &WindowHandle {
&self.state.window
}
pub fn window_id(&self) -> WindowId {
self.state.window_id
}
pub fn text(&self) -> Text {
self.state.window.text()
}
}
);
impl_context_method!(
EventCtx<'_, '_>,
UpdateCtx<'_, '_>,
LifeCycleCtx<'_, '_>,
PaintCtx<'_, '_, '_>,
{
pub fn size(&self) -> Size {
self.widget_state.size()
}
pub fn is_hot(&self) -> bool {
self.widget_state.is_hot
}
pub fn is_active(&self) -> bool {
self.widget_state.is_active
}
pub fn is_focused(&self) -> bool {
self.state.focus_widget == Some(self.widget_id())
}
pub fn has_focus(&self) -> bool {
self.widget_state.has_focus
}
}
);
impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>, {
#[deprecated(since = "0.5.0", note = "use request_paint instead")]
pub fn invalidate(&mut self) {
self.request_paint();
}
pub fn request_paint(&mut self) {
self.request_paint_rect(
self.widget_state.paint_rect() - self.widget_state.layout_rect().origin().to_vec2(),
);
}
pub fn request_paint_rect(&mut self, rect: Rect) {
self.widget_state.invalid.add_rect(rect);
}
pub fn request_layout(&mut self) {
self.widget_state.needs_layout = true;
}
pub fn request_anim_frame(&mut self) {
self.widget_state.request_anim = true;
self.request_paint();
}
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken {
self.state.request_timer(&mut self.widget_state, deadline)
}
pub fn children_changed(&mut self) {
self.widget_state.children_changed = true;
self.request_layout();
}
pub fn submit_command(&mut self, cmd: impl Into<Command>, target: impl Into<Option<Target>>) {
self.state.submit_command(cmd.into(), target.into())
}
pub fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
self.state.set_menu(menu);
}
});
impl EventCtx<'_, '_> {
pub fn set_cursor(&mut self, cursor: &Cursor) {
*self.cursor = Some(cursor.clone());
}
pub fn set_active(&mut self, active: bool) {
self.widget_state.is_active = active;
}
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
if self.state.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::NEW_WINDOW, SingleUse::new(Box::new(desc))),
Target::Global,
);
} else {
const MSG: &str = "WindowDesc<T> - T must match the application data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::new_window: {}", MSG)
}
}
}
pub fn show_context_menu<T: Any>(&mut self, menu: ContextMenu<T>) {
if self.state.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::SHOW_CONTEXT_MENU, Box::new(menu)),
Target::Window(self.state.window_id),
);
} else {
const MSG: &str = "ContextMenu<T> - T must match the application data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::show_context_menu: {}", MSG)
}
}
}
pub fn set_handled(&mut self) {
self.is_handled = true;
}
pub fn is_handled(&self) -> bool {
self.is_handled
}
pub fn request_focus(&mut self) {
let id = self.widget_id();
self.widget_state.request_focus = Some(FocusChange::Focus(id));
}
pub fn focus_next(&mut self) {
if self.is_focused() {
self.widget_state.request_focus = Some(FocusChange::Next);
} else {
log::warn!("focus_next can only be called by the currently focused widget");
}
}
pub fn focus_prev(&mut self) {
if self.is_focused() {
self.widget_state.request_focus = Some(FocusChange::Previous);
} else {
log::warn!("focus_prev can only be called by the currently focused widget");
}
}
pub fn resign_focus(&mut self) {
if self.is_focused() {
self.widget_state.request_focus = Some(FocusChange::Resign);
} else {
log::warn!(
"resign_focus can only be called by the currently focused widget ({:?})",
self.widget_id()
);
}
}
}
impl LifeCycleCtx<'_, '_> {
pub fn register_child(&mut self, child_id: WidgetId) {
self.widget_state.children.add(&child_id);
}
pub fn register_for_focus(&mut self) {
self.widget_state.focus_chain.push(self.widget_id());
}
}
impl LayoutCtx<'_, '_> {
pub fn set_paint_insets(&mut self, insets: impl Into<Insets>) {
self.widget_state.paint_insets = insets.into().nonnegative();
}
}
impl PaintCtx<'_, '_, '_> {
#[inline]
pub fn depth(&self) -> u32 {
self.depth
}
#[inline]
pub fn region(&self) -> &Region {
&self.region
}
pub fn with_child_ctx(&mut self, region: impl Into<Region>, f: impl FnOnce(&mut PaintCtx)) {
let mut child_ctx = PaintCtx {
render_ctx: self.render_ctx,
state: self.state,
widget_state: self.widget_state,
z_ops: Vec::new(),
region: region.into(),
depth: self.depth + 1,
};
f(&mut child_ctx);
self.z_ops.append(&mut child_ctx.z_ops);
}
pub fn with_save(&mut self, f: impl FnOnce(&mut PaintCtx)) {
if let Err(e) = self.render_ctx.save() {
log::error!("Failed to save RenderContext: '{}'", e);
return;
}
f(self);
if let Err(e) = self.render_ctx.restore() {
log::error!("Failed to restore RenderContext: '{}'", e);
}
}
pub fn paint_with_z_index(
&mut self,
z_index: u32,
paint_func: impl FnOnce(&mut PaintCtx) + 'static,
) {
let current_transform = self.render_ctx.current_transform();
self.z_ops.push(ZOrderPaintOp {
z_index,
paint_func: Box::new(paint_func),
transform: current_transform,
})
}
}
impl<'a> ContextState<'a> {
pub(crate) fn new<T: 'static>(
command_queue: &'a mut CommandQueue,
window: &'a WindowHandle,
window_id: WindowId,
focus_widget: Option<WidgetId>,
) -> Self {
ContextState {
command_queue,
window,
window_id,
focus_widget,
root_app_data_type: TypeId::of::<T>(),
}
}
fn submit_command(&mut self, command: Command, target: Option<Target>) {
let target = target.unwrap_or_else(|| self.window_id.into());
self.command_queue.push_back((target, command))
}
fn set_menu<T: Any>(&mut self, menu: MenuDesc<T>) {
if self.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
Command::new(commands::SET_MENU, Box::new(menu)),
Some(Target::Window(self.window_id)),
);
} else {
const MSG: &str = "MenuDesc<T> - T must match the application data type.";
if cfg!(debug_assertions) {
panic!(MSG);
} else {
log::error!("EventCtx::set_menu: {}", MSG)
}
}
}
fn request_timer(&self, widget_state: &mut WidgetState, deadline: Duration) -> TimerToken {
let timer_token = self.window.request_timer(deadline);
widget_state.add_timer(timer_token);
timer_token
}
}
impl Region {
pub const EMPTY: Region = Region(Rect::ZERO);
pub fn to_rect(&self) -> Rect {
self.0
}
#[inline]
pub fn intersects(&self, other: Rect) -> bool {
self.0.intersect(other).area() > 0.
}
pub fn is_empty(&self) -> bool {
self.0.width() <= 0.0 || self.0.height() <= 0.0
}
pub(crate) fn add_rect(&mut self, rect: Rect) {
if self.is_empty() {
self.0 = rect;
} else if rect.width() > 0.0 && rect.height() > 0.0 {
self.0 = self.0.union(rect);
}
}
pub(crate) fn merge_with(&mut self, other: Region) {
self.add_rect(other.0);
}
pub(crate) fn intersect_with(&mut self, rect: Rect) {
self.0 = self.0.intersect(rect);
}
}
impl std::ops::AddAssign<Vec2> for Region {
fn add_assign(&mut self, offset: Vec2) {
self.0 = self.0 + offset;
}
}
impl std::ops::SubAssign<Vec2> for Region {
fn sub_assign(&mut self, offset: Vec2) {
self.0 = self.0 - offset;
}
}
impl From<Rect> for Region {
fn from(src: Rect) -> Region {
Region(src.abs())
}
}
impl<'c> Deref for PaintCtx<'_, '_, 'c> {
type Target = Piet<'c>;
fn deref(&self) -> &Self::Target {
self.render_ctx
}
}
impl<'c> DerefMut for PaintCtx<'_, '_, 'c> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.render_ctx
}
}