use std::{
any::{Any, TypeId},
collections::{HashMap, VecDeque},
ops::{Deref, DerefMut},
rc::Rc,
time::Duration,
};
use tracing::{error, trace, warn};
use crate::commands::SCROLL_TO_VIEW;
use crate::core::{CommandQueue, CursorChange, FocusChange, WidgetState};
use crate::env::KeyLike;
use crate::menu::ContextMenu;
use crate::piet::{Piet, PietText, RenderContext};
use crate::shell::text::Event as ImeInvalidation;
use crate::shell::Region;
use crate::text::{ImeHandlerRef, TextFieldRegistration};
use crate::{
commands, sub_window::SubWindowDesc, widget::Widget, Affine, Command, Cursor, Data, Env,
ExtEventSink, Insets, Menu, Notification, Point, Rect, Scale, SingleUse, Size, Target,
TimerToken, Vec2, WidgetId, WindowConfig, 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)+ });
};
}
macro_rules! impl_context_trait {
($tr:ty => $ty:ty, { $($method:item)+ } ) => {
impl $tr for $ty { $($method)+ }
};
($tr:ty => $ty:ty, $($more:ty),+, { $($method:item)+ } ) => {
impl_context_trait!($tr => $ty, { $($method)+ });
impl_context_trait!($tr => $($more),+, { $($method)+ });
};
}
pub(crate) struct ContextState<'a> {
pub(crate) command_queue: &'a mut CommandQueue,
pub(crate) ext_handle: &'a ExtEventSink,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) text: PietText,
pub(crate) focus_widget: Option<WidgetId>,
pub(crate) root_app_data_type: TypeId,
pub(crate) timers: &'a mut HashMap<TimerToken, WidgetId>,
pub(crate) text_registrations: &'a mut Vec<TextFieldRegistration>,
}
pub struct EventCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) notifications: &'a mut VecDeque<Notification>,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
}
pub struct LifeCycleCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
}
pub struct UpdateCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
pub(crate) prev_env: Option<&'a Env>,
pub(crate) env: &'a Env,
}
pub struct LayoutCtx<'a, 'b> {
pub(crate) state: &'a mut ContextState<'b>,
pub(crate) widget_state: &'a mut WidgetState,
}
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,
}
pub struct State<'a> {
pub(crate) widget_state: &'a mut WidgetState,
}
pub trait ChangeCtx {
fn submit_command(&mut self, cmd: impl Into<Command>);
fn get_external_handle(&self) -> ExtEventSink;
fn request_timer(&mut self, deadline: Duration) -> TimerToken;
fn state(&mut self) -> State;
}
pub trait RequestCtx: ChangeCtx {
fn request_paint(&mut self);
fn request_paint_rect(&mut self, rect: Rect);
fn request_layout(&mut self);
fn request_anim_frame(&mut self);
fn children_changed(&mut self);
fn new_sub_window<W: Widget<U> + 'static, U: Data>(
&mut self,
window_config: WindowConfig,
widget: W,
data: U,
env: Env,
) -> WindowId;
fn set_disabled(&mut self, disabled: bool);
fn invalidate_text_input(&mut self, event: ImeInvalidation);
fn scroll_to_view(&mut self);
fn scroll_area_to_view(&mut self, area: Rect);
}
impl_context_trait!(
ChangeCtx => EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>, LayoutCtx<'_, '_>,
{
fn submit_command(&mut self, cmd: impl Into<Command>) {
Self::submit_command(self, cmd)
}
fn get_external_handle(&self) -> ExtEventSink {
Self::get_external_handle(self)
}
fn request_timer(&mut self, deadline: Duration) -> TimerToken {
Self::request_timer(self, deadline)
}
fn state(&mut self) -> State {
State {
widget_state: &mut *self.widget_state,
}
}
}
);
impl_context_trait!(
RequestCtx => EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>,
{
fn request_paint(&mut self) {
Self::request_paint(self)
}
fn request_paint_rect(&mut self, rect: Rect) {
Self::request_paint_rect(self, rect)
}
fn request_layout(&mut self) {
Self::request_layout(self)
}
fn request_anim_frame(&mut self) {
Self::request_anim_frame(self)
}
fn children_changed(&mut self) {
Self::children_changed(self)
}
fn new_sub_window<W: Widget<U> + 'static, U: Data>(
&mut self,
window_config: WindowConfig,
widget: W,
data: U,
env: Env,
) -> WindowId {
Self::new_sub_window(self, window_config, widget, data, env)
}
fn set_disabled(&mut self, disabled: bool) {
Self::set_disabled(self, disabled)
}
fn invalidate_text_input(&mut self, event: ImeInvalidation) {
Self::invalidate_text_input(self, event)
}
fn scroll_to_view(&mut self) {
Self::scroll_to_view(self)
}
fn scroll_area_to_view(&mut self, area: Rect) {
Self::scroll_area_to_view(self, area)
}
}
);
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(&mut self) -> &mut PietText {
&mut self.state.text
}
pub fn scale(&self) -> Scale {
self.state.window.get_scale().unwrap_or_default()
}
}
);
impl_context_method!(
EventCtx<'_, '_>,
UpdateCtx<'_, '_>,
LifeCycleCtx<'_, '_>,
PaintCtx<'_, '_, '_>,
{
pub fn size(&self) -> Size {
self.widget_state.size()
}
pub fn window_origin(&self) -> Point {
self.widget_state.window_origin()
}
pub fn to_window(&self, widget_point: Point) -> Point {
self.window_origin() + widget_point.to_vec2()
}
pub fn to_screen(&self, widget_point: Point) -> Point {
let insets = self.window().content_insets();
let content_origin = self.window().get_position() + Vec2::new(insets.x0, insets.y0);
content_origin + self.to_window(widget_point).to_vec2()
}
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
}
pub fn is_disabled(&self) -> bool {
self.widget_state.is_disabled()
}
}
);
impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, {
pub fn set_cursor(&mut self, cursor: &Cursor) {
trace!("set_cursor {:?}", cursor);
self.widget_state.cursor_change = CursorChange::Set(cursor.clone());
}
pub fn override_cursor(&mut self, cursor: &Cursor) {
trace!("override_cursor {:?}", cursor);
self.widget_state.cursor_change = CursorChange::Override(cursor.clone());
}
pub fn clear_cursor(&mut self) {
trace!("clear_cursor");
self.widget_state.cursor_change = CursorChange::Default;
}
});
impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, LayoutCtx<'_, '_>, {
pub fn view_context_changed(&mut self) {
self.widget_state.view_context_changed = true;
}
});
impl_context_method!(EventCtx<'_, '_>, UpdateCtx<'_, '_>, LifeCycleCtx<'_, '_>, {
pub fn request_paint(&mut self) {
trace!("request_paint");
self.widget_state.invalid.set_rect(
self.widget_state.paint_rect() - self.widget_state.layout_rect().origin().to_vec2(),
);
}
pub fn request_paint_rect(&mut self, rect: Rect) {
trace!("request_paint_rect {}", rect);
self.widget_state.invalid.add_rect(rect);
}
pub fn request_layout(&mut self) {
trace!("request_layout");
self.widget_state.needs_layout = true;
}
pub fn request_anim_frame(&mut self) {
trace!("request_anim_frame");
self.widget_state.request_anim = true;
}
pub fn children_changed(&mut self) {
trace!("children_changed");
self.widget_state.children_changed = true;
self.widget_state.update_focus_chain = true;
self.request_layout();
}
pub fn set_disabled(&mut self, disabled: bool) {
self.widget_state.is_explicitly_disabled_new = disabled;
}
pub fn invalidate_text_input(&mut self, event: ImeInvalidation) {
let payload = commands::ImeInvalidation {
widget: self.widget_id(),
event,
};
let cmd = commands::INVALIDATE_IME
.with(payload)
.to(Target::Window(self.window_id()));
self.submit_command(cmd);
}
pub fn new_sub_window<W: Widget<U> + 'static, U: Data>(
&mut self,
window_config: WindowConfig,
widget: W,
data: U,
env: Env,
) -> WindowId {
trace!("new_sub_window");
let req = SubWindowDesc::new(self.widget_id(), window_config, widget, data, env);
let window_id = req.window_id;
self.widget_state
.add_sub_window_host(window_id, req.host_id);
self.submit_command(commands::NEW_SUB_WINDOW.with(SingleUse::new(req)));
window_id
}
pub fn scroll_to_view(&mut self) {
self.scroll_area_to_view(self.size().to_rect())
}
});
impl_context_method!(
EventCtx<'_, '_>,
UpdateCtx<'_, '_>,
LifeCycleCtx<'_, '_>,
LayoutCtx<'_, '_>,
{
pub fn submit_command(&mut self, cmd: impl Into<Command>) {
trace!("submit_command");
self.state.submit_command(cmd.into())
}
pub fn get_external_handle(&self) -> ExtEventSink {
trace!("get_external_handle");
self.state.ext_handle.clone()
}
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken {
trace!("request_timer deadline={:?}", deadline);
self.state.request_timer(self.widget_state.id, deadline)
}
}
);
impl EventCtx<'_, '_> {
pub fn submit_notification(&mut self, note: impl Into<Command>) {
trace!("submit_notification");
let note = note.into().into_notification(self.widget_state.id);
self.notifications.push_back(note);
}
pub fn submit_notification_without_warning(&mut self, note: impl Into<Command>) {
trace!("submit_notification");
let note = note
.into()
.into_notification(self.widget_state.id)
.warn_if_unused(false);
self.notifications.push_back(note);
}
pub fn set_active(&mut self, active: bool) {
trace!("set_active({})", active);
self.widget_state.is_active = active;
}
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>) {
trace!("new_window");
if self.state.root_app_data_type == TypeId::of::<T>() {
self.submit_command(
commands::NEW_WINDOW
.with(SingleUse::new(Box::new(desc)))
.to(Target::Global),
);
} else {
debug_panic!("EventCtx::new_window<T> - T must match the application data type.");
}
}
pub fn show_context_menu<T: Any>(&mut self, menu: Menu<T>, location: Point) {
trace!("show_context_menu");
if self.state.root_app_data_type == TypeId::of::<T>() {
let menu = ContextMenu { menu, location };
self.submit_command(
commands::SHOW_CONTEXT_MENU
.with(SingleUse::new(Box::new(menu)))
.to(Target::Window(self.state.window_id)),
);
} else {
debug_panic!(
"EventCtx::show_context_menu<T> - T must match the application data type."
);
}
}
pub fn set_handled(&mut self) {
trace!("set_handled");
self.is_handled = true;
}
pub fn is_handled(&self) -> bool {
self.is_handled
}
pub fn request_focus(&mut self) {
trace!("request_focus");
let id = self.widget_id();
self.widget_state.request_focus = Some(FocusChange::Focus(id));
}
pub fn set_focus(&mut self, target: WidgetId) {
trace!("set_focus target={:?}", target);
self.widget_state.request_focus = Some(FocusChange::Focus(target));
}
pub fn focus_next(&mut self) {
trace!("focus_next");
if self.has_focus() {
self.widget_state.request_focus = Some(FocusChange::Next);
} else {
warn!(
"focus_next can only be called by the currently \
focused widget or one of its ancestors."
);
}
}
pub fn focus_prev(&mut self) {
trace!("focus_prev");
if self.has_focus() {
self.widget_state.request_focus = Some(FocusChange::Previous);
} else {
warn!(
"focus_prev can only be called by the currently \
focused widget or one of its ancestors."
);
}
}
pub fn resign_focus(&mut self) {
trace!("resign_focus");
if self.has_focus() {
self.widget_state.request_focus = Some(FocusChange::Resign);
} else {
warn!(
"resign_focus can only be called by the currently focused widget \
or one of its ancestors. ({:?})",
self.widget_id()
);
}
}
pub fn request_update(&mut self) {
trace!("request_update");
self.widget_state.request_update = true;
}
pub fn scroll_area_to_view(&mut self, area: Rect) {
self.submit_notification_without_warning(
SCROLL_TO_VIEW.with(area + self.window_origin().to_vec2()),
);
}
}
impl UpdateCtx<'_, '_> {
pub fn has_requested_update(&mut self) -> bool {
self.widget_state.request_update
}
pub fn env_changed(&self) -> bool {
self.prev_env.is_some()
}
pub fn env_key_changed<T>(&self, key: &impl KeyLike<T>) -> bool {
match self.prev_env.as_ref() {
Some(prev) => key.changed(prev, self.env),
None => false,
}
}
pub fn scroll_area_to_view(&mut self, area: Rect) {
self.submit_command(Command::new(
SCROLL_TO_VIEW,
area + self.window_origin().to_vec2(),
self.widget_id(),
));
}
}
impl LifeCycleCtx<'_, '_> {
pub fn register_child(&mut self, child_id: WidgetId) {
trace!("register_child id={:?}", child_id);
self.widget_state.children.add(&child_id);
}
pub fn register_for_focus(&mut self) {
trace!("register_for_focus");
self.widget_state.focus_chain.push(self.widget_id());
}
pub fn register_text_input(&mut self, document: impl ImeHandlerRef + 'static) {
let registration = TextFieldRegistration {
document: Rc::new(document),
widget_id: self.widget_id(),
};
self.state.text_registrations.push(registration);
}
pub fn scroll_area_to_view(&mut self, area: Rect) {
self.submit_command(
SCROLL_TO_VIEW
.with(area + self.window_origin().to_vec2())
.to(self.widget_id()),
);
}
}
impl<'a, 'b> LayoutCtx<'a, 'b> {
pub fn set_paint_insets(&mut self, insets: impl Into<Insets>) {
let insets = insets.into();
trace!("set_paint_insets {:?}", insets);
self.widget_state.paint_insets = insets.nonnegative();
}
pub fn set_baseline_offset(&mut self, baseline: f64) {
trace!("set_baseline_offset {}", baseline);
self.widget_state.baseline_offset = baseline
}
}
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() {
error!("Failed to save RenderContext: '{}'", e);
return;
}
f(self);
if let Err(e) = self.render_ctx.restore() {
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,
ext_handle: &'a ExtEventSink,
window: &'a WindowHandle,
window_id: WindowId,
focus_widget: Option<WidgetId>,
timers: &'a mut HashMap<TimerToken, WidgetId>,
text_registrations: &'a mut Vec<TextFieldRegistration>,
) -> Self {
ContextState {
command_queue,
ext_handle,
window,
window_id,
focus_widget,
timers,
text_registrations,
text: window.text(),
root_app_data_type: TypeId::of::<T>(),
}
}
fn submit_command(&mut self, command: Command) {
trace!("submit_command");
self.command_queue
.push_back(command.default_to(self.window_id.into()));
}
fn request_timer(&mut self, widget_id: WidgetId, deadline: Duration) -> TimerToken {
trace!("request_timer deadline={:?}", deadline);
let timer_token = self.window.request_timer(deadline);
self.timers.insert(timer_token, widget_id);
timer_token
}
}
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
}
}