use std::collections::{HashMap, VecDeque};
use std::mem;
use instant::Instant;
use crate::piet::{Piet, RenderContext};
use crate::shell::{Counter, Cursor, Region, WindowHandle};
use crate::app::PendingWindow;
use crate::contexts::ContextState;
use crate::core::{CommandQueue, FocusChange, WidgetState};
use crate::util::ExtendDrain;
use crate::widget::LabelText;
use crate::win_handler::RUN_COMMANDS_TOKEN;
use crate::{
BoxConstraints, Command, Data, Env, Event, EventCtx, ExtEventSink, Handled, InternalEvent,
InternalLifeCycle, LayoutCtx, LifeCycle, LifeCycleCtx, MenuDesc, PaintCtx, Point, Size,
TimerToken, UpdateCtx, Widget, WidgetId, WidgetPod,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WindowId(u64);
pub struct Window<T> {
pub(crate) id: WindowId,
pub(crate) root: WidgetPod<T, Box<dyn Widget<T>>>,
pub(crate) title: LabelText<T>,
size: Size,
invalid: Region,
pub(crate) menu: Option<MenuDesc<T>>,
pub(crate) context_menu: Option<MenuDesc<T>>,
pub(crate) last_anim: Option<Instant>,
pub(crate) last_mouse_pos: Option<Point>,
pub(crate) focus: Option<WidgetId>,
pub(crate) handle: WindowHandle,
pub(crate) timers: HashMap<TimerToken, WidgetId>,
ext_handle: ExtEventSink,
}
impl<T> Window<T> {
pub(crate) fn new(
id: WindowId,
handle: WindowHandle,
pending: PendingWindow<T>,
ext_handle: ExtEventSink,
) -> Window<T> {
Window {
id,
root: WidgetPod::new(pending.root),
size: Size::ZERO,
invalid: Region::EMPTY,
title: pending.title,
menu: pending.menu,
context_menu: None,
last_anim: None,
last_mouse_pos: None,
focus: None,
handle,
timers: HashMap::new(),
ext_handle,
}
}
}
impl<T: Data> Window<T> {
pub(crate) fn wants_animation_frame(&self) -> bool {
self.root.state().request_anim
}
pub(crate) fn focus_chain(&self) -> &[WidgetId] {
&self.root.state().focus_chain
}
pub(crate) fn may_contain_widget(&self, widget_id: WidgetId) -> bool {
widget_id == self.root.id() || self.root.state().children.may_contain(&widget_id)
}
pub(crate) fn set_menu(&mut self, mut menu: MenuDesc<T>, data: &T, env: &Env) {
let platform_menu = menu.build_window_menu(data, env);
self.handle.set_menu(platform_menu);
self.menu = Some(menu);
}
pub(crate) fn show_context_menu(
&mut self,
mut menu: MenuDesc<T>,
point: Point,
data: &T,
env: &Env,
) {
let platform_menu = menu.build_popup_menu(data, env);
self.handle.show_context_menu(platform_menu, point);
self.context_menu = Some(menu);
}
#[cfg(target_os = "macos")]
pub(crate) fn macos_update_app_menu(&mut self, data: &T, env: &Env) {
if let Some(menu) = self.menu.as_mut().map(|m| m.build_window_menu(data, env)) {
self.handle.set_menu(menu);
}
}
fn post_event_processing(
&mut self,
widget_state: &mut WidgetState,
queue: &mut CommandQueue,
data: &T,
env: &Env,
process_commands: bool,
) {
if widget_state.children_changed {
self.lifecycle(
queue,
&LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded),
data,
env,
false,
);
}
self.timers.extend_drain(&mut widget_state.timers);
if self.wants_animation_frame() {
self.handle.request_anim_frame();
}
self.invalid.union_with(&widget_state.invalid);
if process_commands && !queue.is_empty() {
if let Some(mut handle) = self.handle.get_idle_handle() {
handle.schedule_idle(RUN_COMMANDS_TOKEN);
} else {
log::error!("failed to get idle handle");
}
}
}
pub(crate) fn event(
&mut self,
queue: &mut CommandQueue,
event: Event,
data: &mut T,
env: &Env,
) -> Handled {
match &event {
Event::WindowSize(size) => self.size = *size,
Event::MouseDown(e) | Event::MouseUp(e) | Event::MouseMove(e) | Event::Wheel(e) => {
self.last_mouse_pos = Some(e.pos)
}
Event::Internal(InternalEvent::MouseLeave) => self.last_mouse_pos = None,
_ => (),
}
let event = match event {
Event::Timer(token) => {
if let Some(widget_id) = self.timers.get(&token) {
Event::Internal(InternalEvent::RouteTimer(token, *widget_id))
} else {
log::error!("No widget found for timer {:?}", token);
return Handled::No;
}
}
other => other,
};
if let Event::WindowConnected = event {
self.lifecycle(
queue,
&LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded),
data,
env,
false,
);
}
let mut widget_state = WidgetState::new(self.root.id(), Some(self.size));
let is_handled = {
let mut state =
ContextState::new::<T>(queue, &self.ext_handle, &self.handle, self.id, self.focus);
let mut notifications = VecDeque::new();
let mut ctx = EventCtx {
state: &mut state,
notifications: &mut notifications,
widget_state: &mut widget_state,
is_handled: false,
is_root: true,
};
self.root.event(&mut ctx, &event, data, env);
if !ctx.notifications.is_empty() {
log::info!("{} unhandled notifications:", ctx.notifications.len());
for (i, n) in ctx.notifications.iter().enumerate() {
log::info!("{}: {:?}", i, n);
}
}
Handled::from(ctx.is_handled)
};
if let Event::Internal(InternalEvent::RouteTimer(token, _)) = event {
self.timers.remove(&token);
}
if let Some(focus_req) = widget_state.request_focus.take() {
let old = self.focus;
let new = self.widget_for_focus_request(focus_req);
if old != new {
let event = LifeCycle::Internal(InternalLifeCycle::RouteFocusChanged { old, new });
self.lifecycle(queue, &event, data, env, false);
self.focus = new;
}
}
if let Some(cursor) = &widget_state.cursor {
self.handle.set_cursor(&cursor);
} else if matches!(
event,
Event::MouseMove(..) | Event::Internal(InternalEvent::MouseLeave)
) {
self.handle.set_cursor(&Cursor::Arrow);
}
self.post_event_processing(&mut widget_state, queue, data, env, false);
is_handled
}
pub(crate) fn lifecycle(
&mut self,
queue: &mut CommandQueue,
event: &LifeCycle,
data: &T,
env: &Env,
process_commands: bool,
) {
let mut widget_state = WidgetState::new(self.root.id(), Some(self.size));
let mut state =
ContextState::new::<T>(queue, &self.ext_handle, &self.handle, self.id, self.focus);
let mut ctx = LifeCycleCtx {
state: &mut state,
widget_state: &mut widget_state,
};
self.root.lifecycle(&mut ctx, event, data, env);
self.post_event_processing(&mut widget_state, queue, data, env, process_commands);
}
pub(crate) fn update(&mut self, queue: &mut CommandQueue, data: &T, env: &Env) {
self.update_title(data, env);
let mut widget_state = WidgetState::new(self.root.id(), Some(self.size));
let mut state =
ContextState::new::<T>(queue, &self.ext_handle, &self.handle, self.id, self.focus);
let mut update_ctx = UpdateCtx {
widget_state: &mut widget_state,
state: &mut state,
prev_env: None,
env,
};
self.root.update(&mut update_ctx, data, env);
if let Some(cursor) = &widget_state.cursor {
self.handle.set_cursor(cursor);
}
self.post_event_processing(&mut widget_state, queue, data, env, false);
}
pub(crate) fn invalidate_and_finalize(&mut self) {
if self.root.state().needs_layout {
self.handle.invalidate();
} else {
for rect in self.invalid.rects() {
self.handle.invalidate_rect(*rect);
}
self.invalid.clear();
}
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn invalid(&self) -> &Region {
&self.invalid
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn invalid_mut(&mut self) -> &mut Region {
&mut self.invalid
}
pub(crate) fn prepare_paint(&mut self, queue: &mut CommandQueue, data: &mut T, env: &Env) {
let now = Instant::now();
let last = self.last_anim.take();
let elapsed_ns = last.map(|t| now.duration_since(t).as_nanos()).unwrap_or(0) as u64;
if self.wants_animation_frame() {
self.event(queue, Event::AnimFrame(elapsed_ns), data, env);
self.last_anim = Some(now);
}
}
pub(crate) fn do_paint(
&mut self,
piet: &mut Piet,
invalid: &Region,
queue: &mut CommandQueue,
data: &T,
env: &Env,
) {
if self.root.state().needs_layout {
self.layout(queue, data, env);
}
piet.fill(
invalid.bounding_box(),
&env.get(crate::theme::WINDOW_BACKGROUND_COLOR),
);
self.paint(piet, invalid, queue, data, env);
}
fn layout(&mut self, queue: &mut CommandQueue, data: &T, env: &Env) {
let mut widget_state = WidgetState::new(self.root.id(), Some(self.size));
let mut state =
ContextState::new::<T>(queue, &self.ext_handle, &self.handle, self.id, self.focus);
let mut layout_ctx = LayoutCtx {
state: &mut state,
widget_state: &mut widget_state,
mouse_pos: self.last_mouse_pos,
};
let bc = BoxConstraints::tight(self.size);
self.root.layout(&mut layout_ctx, &bc, data, env);
self.root
.set_origin(&mut layout_ctx, data, env, Point::ORIGIN);
self.post_event_processing(&mut widget_state, queue, data, env, true);
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
pub(crate) fn just_layout(&mut self, queue: &mut CommandQueue, data: &T, env: &Env) {
self.layout(queue, data, env)
}
fn paint(
&mut self,
piet: &mut Piet,
invalid: &Region,
queue: &mut CommandQueue,
data: &T,
env: &Env,
) {
let widget_state = WidgetState::new(self.root.id(), Some(self.size));
let mut state =
ContextState::new::<T>(queue, &self.ext_handle, &self.handle, self.id, self.focus);
let mut ctx = PaintCtx {
render_ctx: piet,
state: &mut state,
widget_state: &widget_state,
z_ops: Vec::new(),
region: invalid.clone(),
depth: 0,
};
let root = &mut self.root;
ctx.with_child_ctx(invalid.clone(), |ctx| root.paint_raw(ctx, data, env));
let mut z_ops = mem::take(&mut ctx.z_ops);
z_ops.sort_by_key(|k| k.z_index);
for z_op in z_ops.into_iter() {
ctx.with_child_ctx(invalid.clone(), |ctx| {
ctx.with_save(|ctx| {
ctx.render_ctx.transform(z_op.transform);
(z_op.paint_func)(ctx);
});
});
}
if self.wants_animation_frame() {
self.handle.request_anim_frame();
}
}
pub(crate) fn update_title(&mut self, data: &T, env: &Env) {
if self.title.resolve(data, env) {
self.handle.set_title(&self.title.display_text());
}
}
pub(crate) fn get_menu_cmd(&self, cmd_id: u32) -> Option<Command> {
self.context_menu
.as_ref()
.and_then(|m| m.command_for_id(cmd_id))
.or_else(|| self.menu.as_ref().and_then(|m| m.command_for_id(cmd_id)))
}
fn widget_for_focus_request(&self, focus: FocusChange) -> Option<WidgetId> {
match focus {
FocusChange::Resign => None,
FocusChange::Focus(id) => Some(id),
FocusChange::Next => self.widget_from_focus_chain(true),
FocusChange::Previous => self.widget_from_focus_chain(false),
}
}
fn widget_from_focus_chain(&self, forward: bool) -> Option<WidgetId> {
self.focus.and_then(|focus| {
self.focus_chain()
.iter()
.position(|id| id == &focus)
.map(|idx| {
let len = self.focus_chain().len();
let new_idx = if forward {
(idx + 1) % len
} else {
(idx + len - 1) % len
};
self.focus_chain()[new_idx]
})
.or_else(|| {
if forward {
self.focus_chain().first().copied()
} else {
self.focus_chain().last().copied()
}
})
})
}
}
impl WindowId {
pub fn next() -> WindowId {
static WINDOW_COUNTER: Counter = Counter::new();
WindowId(WINDOW_COUNTER.next())
}
}