use std::mem;
use std::time::Instant;
use crate::kurbo::{Insets, Point, Rect, Size};
use crate::piet::{Piet, RenderContext};
use crate::shell::{Counter, Cursor, WindowHandle};
use crate::core::{BaseState, CommandQueue, FocusChange};
use crate::win_handler::RUN_COMMANDS_TOKEN;
use crate::{
BoxConstraints, Command, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
LocalizedString, MenuDesc, PaintCtx, UpdateCtx, Widget, WidgetId, WidgetPod, WindowDesc,
};
#[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: LocalizedString<T>,
size: Size,
pub(crate) menu: Option<MenuDesc<T>>,
pub(crate) context_menu: Option<MenuDesc<T>>,
pub(crate) last_anim: Option<Instant>,
pub(crate) focus: Option<WidgetId>,
pub(crate) handle: WindowHandle,
}
impl<T> Window<T> {
pub(crate) fn new(id: WindowId, handle: WindowHandle, desc: WindowDesc<T>) -> Window<T> {
Window {
id,
root: WidgetPod::new(desc.root),
size: Size::ZERO,
title: desc.title,
menu: desc.menu,
context_menu: None,
last_anim: None,
focus: None,
handle,
}
}
}
impl<T: Data> Window<T> {
pub(crate) fn wants_animation_frame(&self) -> bool {
self.last_anim.is_some()
}
pub(crate) fn focus_chain(&self) -> &[WidgetId] {
&self.root.state().focus_chain
}
pub(crate) fn may_contain_widget(&self, widget_id: WidgetId) -> bool {
self.root.state().children.contains(&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);
}
}
pub(crate) fn event(
&mut self,
queue: &mut CommandQueue,
event: Event,
data: &mut T,
env: &Env,
) -> bool {
let mut cursor = match event {
Event::MouseMoved(..) => Some(Cursor::Arrow),
_ => None,
};
let event = match event {
Event::Size(size) => {
let dpi = f64::from(self.handle.get_dpi());
let scale = 96.0 / dpi;
self.size = Size::new(size.width * scale, size.height * scale);
Event::Size(self.size)
}
other => other,
};
if let Event::WindowConnected = event {
self.lifecycle(queue, &LifeCycle::RouteWidgetAdded, data, env);
}
let mut base_state = BaseState::new(self.root.id());
let is_handled = {
let mut ctx = EventCtx {
cursor: &mut cursor,
command_queue: queue,
base_state: &mut base_state,
is_handled: false,
is_root: true,
had_active: self.root.has_active(),
window: &self.handle,
window_id: self.id,
focus_widget: self.focus,
};
self.root.event(&mut ctx, &event, data, env);
ctx.is_handled
};
if let Some(focus_req) = base_state.request_focus.take() {
let old = self.focus;
let new = self.widget_for_focus_request(focus_req);
let event = LifeCycle::RouteFocusChanged { old, new };
self.lifecycle(queue, &event, data, env);
self.focus = new;
}
if let Some(cursor) = cursor {
self.handle.set_cursor(&cursor);
}
if base_state.children_changed {
self.lifecycle(queue, &LifeCycle::RouteWidgetAdded, data, env);
}
is_handled
}
pub(crate) fn lifecycle(
&mut self,
queue: &mut CommandQueue,
event: &LifeCycle,
data: &T,
env: &Env,
) {
let mut base_state = BaseState::new(self.root.id());
let mut ctx = LifeCycleCtx {
command_queue: queue,
window_id: self.id,
base_state: &mut base_state,
};
if let LifeCycle::AnimFrame(_) = event {
return self.do_anim_frame(&mut ctx, data, env);
}
self.root.lifecycle(&mut ctx, event, data, env);
}
fn do_anim_frame(&mut self, ctx: &mut LifeCycleCtx, data: &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;
let event = LifeCycle::AnimFrame(elapsed_ns);
self.root.lifecycle(ctx, &event, data, env);
if ctx.base_state.request_anim {
self.last_anim = Some(now);
}
}
pub(crate) fn update(&mut self, data: &T, env: &Env) {
self.update_title(data, env);
let mut base_state = BaseState::new(self.root.id());
let mut update_ctx = UpdateCtx {
base_state: &mut base_state,
window: &self.handle,
window_id: self.id,
};
self.root.update(&mut update_ctx, data, env);
}
pub(crate) fn invalidate_and_finalize(
&mut self,
queue: &mut CommandQueue,
data: &T,
env: &Env,
) {
if self.root.state().children_changed {
self.lifecycle(queue, &LifeCycle::RouteWidgetAdded, data, env);
}
if self.root.state().needs_inval {
self.handle.invalidate();
}
}
pub(crate) fn do_paint(
&mut self,
piet: &mut Piet,
queue: &mut CommandQueue,
data: &T,
env: &Env,
) {
self.lifecycle(queue, &LifeCycle::AnimFrame(0), data, env);
if self.root.state().needs_layout {
self.layout(piet, data, env);
}
piet.clear(env.get(crate::theme::WINDOW_BACKGROUND_COLOR));
self.paint(piet, data, env);
if !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");
}
}
}
fn layout(&mut self, piet: &mut Piet, data: &T, env: &Env) {
let mut layout_ctx = LayoutCtx {
text_factory: piet.text(),
window_id: self.id,
paint_insets: Insets::ZERO,
};
let bc = BoxConstraints::tight(self.size);
let size = self.root.layout(&mut layout_ctx, &bc, data, env);
self.root
.set_layout_rect(Rect::from_origin_size(Point::ORIGIN, size));
}
#[cfg(test)]
pub(crate) fn just_layout(&mut self, piet: &mut Piet, data: &T, env: &Env) {
self.layout(piet, data, env)
}
fn paint(&mut self, piet: &mut Piet, data: &T, env: &Env) {
let base_state = BaseState::new(self.root.id());
let mut ctx = PaintCtx {
render_ctx: piet,
base_state: &base_state,
window_id: self.id,
z_ops: Vec::new(),
focus_widget: self.focus,
region: Rect::ZERO.into(),
};
let visible = Rect::from_origin_size(Point::ZERO, self.size);
ctx.with_child_ctx(visible, |ctx| self.root.paint(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(visible, |ctx| {
ctx.with_save(|ctx| {
ctx.render_ctx.transform(z_op.transform);
(z_op.paint_func)(ctx);
});
});
}
}
pub(crate) fn update_title(&mut self, data: &T, env: &Env) {
if self.title.resolve(data, env) {
self.handle.set_title(self.title.localized_str());
}
}
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
.focus
.and_then(|id| self.focus_chain().iter().position(|i| i == &id))
.map(|idx| {
let next_idx = (idx + 1) % self.focus_chain().len();
self.focus_chain()[next_idx]
}),
FocusChange::Previous => self
.focus
.and_then(|id| self.focus_chain().iter().position(|i| i == &id))
.map(|idx| {
let len = self.focus_chain().len();
let prev_idx = (idx + len - 1) % len;
self.focus_chain()[prev_idx]
}),
}
}
}
impl WindowId {
pub fn next() -> WindowId {
static WINDOW_COUNTER: Counter = Counter::new();
WindowId(WINDOW_COUNTER.next())
}
}