use std::{any::Any, collections::HashMap};
use instant::Instant;
use ori_core::{
canvas::{Canvas, Color},
command::{CommandProxy, CommandReceiver},
context::{BaseCx, BuildCx, Contexts, DrawCx, EventCx, LayoutCx, RebuildCx},
event::{
Code, Event, FocusTarget, Ime, Key, KeyPressed, KeyReleased, Modifiers, PointerButton,
PointerId, PointerLeft, PointerMoved, PointerPressed, PointerReleased, PointerScrolled,
RequestFocus, RequestFocusNext, RequestFocusPrev, WindowCloseRequested, WindowMaximized,
WindowResized, WindowScaled,
},
layout::{Point, Size, Space, Vector},
log::trace,
style::{Styles, Theme},
view::{any, AnyState, BoxedView, View, ViewState},
views::opaque,
window::{Cursor, Window, WindowId, WindowSizing, WindowSnapshot, WindowUpdate},
};
use crate::{AppBuilder, AppCommand, AppDelegate, AppRequest, DelegateCx, UiBuilder};
pub struct WindowRenderState {
pub canvas: Canvas,
pub logical_size: Size,
pub clear_color: Color,
}
pub(crate) struct WindowState<T> {
ui: UiBuilder<T>,
view: BoxedView<T>,
cursor: Cursor,
ime: Option<Ime>,
state: AnyState,
canvas: Canvas,
view_state: ViewState,
window: Window,
snapshot: WindowSnapshot,
animate: Option<Instant>,
}
impl<T> WindowState<T> {
fn rebuild(&mut self, data: &mut T, base: &mut BaseCx) {
let t = Instant::now();
self.view_state.prepare();
base.insert_context(self.window.clone());
let mut cx = RebuildCx::new(base, &mut self.view_state);
let mut new_view = (self.ui)(data);
new_view.rebuild(&mut self.state, &mut cx, data, &self.view);
self.view = new_view;
self.window = base.remove_context().expect("Window context missing");
trace!(
window = ?self.window.id(),
elapsed = ?t.elapsed(),
"Window rebuilt"
);
}
#[must_use]
fn event(
&mut self,
data: &mut T,
base: &mut BaseCx,
rebuild: &mut bool,
event: &Event,
) -> bool {
let t = Instant::now();
let hovered = self.window.is_hovered(self.view_state.id());
self.view_state.set_hovered(hovered);
self.view_state.prepare();
base.insert_context(self.window.clone());
let mut cx = EventCx::new(base, &mut self.view_state, rebuild);
let handled = self.view.event(&mut self.state, &mut cx, data, event);
self.window = base.remove_context().expect("Window context missing");
trace!(
window = %self.window.id(),
elapsed = ?t.elapsed(),
"Window event"
);
handled
}
fn layout(&mut self, data: &mut T, base: &mut BaseCx) {
let t = Instant::now();
self.view_state.mark_layed_out();
base.insert_context(self.window.clone());
let max_size = match self.window.sizing {
WindowSizing::Fixed => self.window.size,
WindowSizing::Content => Size::INFINITY,
};
let space = Space::new(Size::ZERO, max_size);
let mut cx = LayoutCx::new(base, &mut self.view_state);
let size = self.view.layout(&mut self.state, &mut cx, data, space);
self.window = base.remove_context().expect("Window context missing");
self.view_state.set_size(size);
if let WindowSizing::Content = self.window.sizing {
if size.is_infinite() {
ori_core::log::warn!("Window content size is non-finite.");
}
self.window.size = size;
}
trace!(
window = %self.window.id(),
elapsed = ?t.elapsed(),
"Window layout"
);
}
fn draw(&mut self, data: &mut T, base: &mut BaseCx) {
let t = Instant::now();
self.view_state.mark_drawn();
self.canvas.clear();
base.insert_context(self.window.clone());
let mut cx = DrawCx::new(base, &mut self.view_state, &mut self.canvas);
self.view.draw(&mut self.state, &mut cx, data);
self.window = base.remove_context().expect("Window context missing");
trace!(
window = %self.window.id(),
elapsed = ?t.elapsed(),
"Window draw"
);
}
fn animate(&mut self, animate: Instant) -> Vec<AppRequest<T>> {
if self.view_state.needs_animate() && self.animate.is_none() {
self.animate = Some(animate);
return vec![AppRequest::RequestRedraw(self.window.id())];
}
Vec::new()
}
}
pub struct App<T> {
pub proxy: CommandProxy,
pub contexts: Contexts,
pub(crate) windows: HashMap<WindowId, WindowState<T>>,
pub(crate) modifiers: Modifiers,
pub(crate) delegates: Vec<Box<dyn AppDelegate<T>>>,
pub(crate) receiver: CommandReceiver,
pub(crate) requests: Vec<AppRequest<T>>,
}
impl<T> App<T> {
pub fn build() -> AppBuilder<T> {
AppBuilder::new()
}
pub fn close_requested(&mut self, data: &mut T, window_id: WindowId) -> bool {
let event = Event::WindowCloseRequested(WindowCloseRequested { window: window_id });
let handled = self.window_event(data, window_id, &event);
if !handled {
self.remove_window(window_id);
self.requests.push(AppRequest::CloseWindow(window_id));
if self.windows.is_empty() {
self.requests.push(AppRequest::Quit);
}
}
!handled
}
pub fn window_resized(
&mut self,
data: &mut T,
window_id: WindowId,
width: u32,
height: u32,
) -> bool {
if let Some(window_state) = self.windows.get_mut(&window_id) {
window_state.view_state.request_layout();
window_state.window.size = Size::new(width as f32, height as f32);
window_state.snapshot.size = Size::new(width as f32, height as f32);
}
let event = Event::WindowResized(WindowResized {
window: window_id,
width,
height,
});
self.window_event(data, window_id, &event)
}
pub fn window_scaled(&mut self, data: &mut T, window_id: WindowId, scale: f32) -> bool {
if let Some(window_state) = self.windows.get_mut(&window_id) {
window_state.view_state.request_layout();
window_state.window.scale = scale;
window_state.snapshot.scale = scale;
}
let event = Event::WindowScaled(WindowScaled {
window: window_id,
scale_factor: scale,
});
self.window_event(data, window_id, &event)
}
pub fn window_maximized(&mut self, data: &mut T, window_id: WindowId, maximized: bool) -> bool {
if let Some(window_state) = self.windows.get_mut(&window_id) {
window_state.view_state.request_layout();
window_state.window.maximized = maximized;
window_state.snapshot.maximized = maximized;
}
let event = Event::WindowMaximized(WindowMaximized {
window: window_id,
maximized,
});
self.window_event(data, window_id, &event)
}
pub fn window_decorated(&mut self, data: &mut T, window_id: WindowId, decorated: bool) -> bool {
if let Some(window_state) = self.windows.get_mut(&window_id) {
window_state.view_state.request_layout();
window_state.window.decorated = decorated;
window_state.snapshot.decorated = decorated;
}
let event = Event::Notify;
self.window_event(data, window_id, &event)
}
pub fn pointer_moved(
&mut self,
data: &mut T,
window_id: WindowId,
pointer_id: PointerId,
position: Point,
) -> bool {
let Some(window_state) = self.windows.get_mut(&window_id) else {
return false;
};
let delta = window_state.window.move_pointer(pointer_id, position);
self.update_hovered(window_id);
let event = Event::PointerMoved(PointerMoved {
id: pointer_id,
modifiers: self.modifiers,
position,
delta,
});
self.window_event(data, window_id, &event)
}
pub fn pointer_left(
&mut self,
data: &mut T,
window_id: WindowId,
pointer_id: PointerId,
) -> bool {
let Some(window_state) = self.windows.get_mut(&window_id) else {
return false;
};
window_state.window.remove_pointer(pointer_id);
let event = Event::PointerLeft(PointerLeft { id: pointer_id });
self.window_event(data, window_id, &event)
}
fn pointer_position(&self, window_id: WindowId, pointer_id: PointerId) -> Option<Point> {
let window = self.get_window(window_id)?;
let pointer = window.get_pointer(pointer_id)?;
Some(pointer.position)
}
pub fn pointer_scrolled(
&mut self,
data: &mut T,
window_id: WindowId,
pointer_id: PointerId,
delta: Vector,
) -> bool {
let position = self
.pointer_position(window_id, pointer_id)
.unwrap_or(Point::ZERO);
let event = Event::PointerScrolled(PointerScrolled {
id: pointer_id,
modifiers: self.modifiers,
position,
delta,
});
self.window_event(data, window_id, &event)
}
pub fn pointer_button(
&mut self,
data: &mut T,
window_id: WindowId,
pointer_id: PointerId,
button: PointerButton,
pressed: bool,
) -> bool {
let position = self
.pointer_position(window_id, pointer_id)
.unwrap_or(Point::ZERO);
if pressed {
if let Some(window_state) = self.windows.get_mut(&window_id) {
window_state.window.press_pointer(pointer_id, button);
}
let event = Event::PointerPressed(PointerPressed {
id: pointer_id,
modifiers: self.modifiers,
position,
button,
});
self.window_event(data, window_id, &event)
} else {
let clicked = (self.windows.get_mut(&window_id)).map_or(false, move |window_state| {
window_state.window.release_pointer(pointer_id, button)
});
let event = Event::PointerReleased(PointerReleased {
id: pointer_id,
modifiers: self.modifiers,
clicked,
position,
button,
});
self.window_event(data, window_id, &event)
}
}
pub fn keyboard_key(
&mut self,
data: &mut T,
window_id: WindowId,
key: Key,
code: Option<Code>,
text: Option<String>,
pressed: bool,
) -> bool {
if pressed {
let event = Event::KeyPressed(KeyPressed {
key,
code,
text,
modifiers: self.modifiers,
});
let mut handled = self.window_event(data, window_id, &event);
if let (Some(window), Key::Tab, false) = (self.windows.get(&window_id), key, handled) {
let event = match window.view_state.has_focused() {
true if self.modifiers.shift => Event::FocusPrev,
false if self.modifiers.shift => Event::FocusGiven(FocusTarget::Prev),
true => Event::FocusNext,
false => Event::FocusGiven(FocusTarget::Next),
};
handled |= self.window_event(data, window_id, &event);
}
handled
} else {
let event = Event::KeyReleased(KeyReleased {
key,
code,
modifiers: self.modifiers,
});
self.window_event(data, window_id, &event)
}
}
pub fn modifiers_changed(&mut self, modifiers: Modifiers) {
self.modifiers = modifiers;
}
}
impl<T> App<T> {
pub fn add_window(&mut self, data: &mut T, mut ui: UiBuilder<T>, mut window: Window) {
let mut view = ui(data);
let mut view_state = ViewState::default();
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
let snapshot = window.snapshot();
let mut cx = BuildCx::new(&mut base, &mut view_state);
cx.insert_context(window.clone());
let state = view.build(&mut cx, data);
window = cx.remove_context().expect("Window context missing");
let window_id = window.id();
let window_state = WindowState {
ui,
view,
cursor: Cursor::Default,
ime: None,
state,
canvas: Canvas::new(),
view_state,
window,
snapshot,
animate: None,
};
self.windows.insert(window_id, window_state);
}
pub fn remove_window(&mut self, window_id: WindowId) {
self.windows.remove(&window_id);
}
pub fn get_window(&self, window_id: WindowId) -> Option<&Window> {
self.windows.get(&window_id).map(|w| &w.window)
}
pub fn get_window_mut(&mut self, window_id: WindowId) -> Option<&mut Window> {
self.windows.get_mut(&window_id).map(|w| &mut w.window)
}
pub fn add_context(&mut self, context: impl Any) {
self.contexts.insert(context);
}
pub fn take_requests(&mut self) -> impl Iterator<Item = AppRequest<T>> {
std::mem::take(&mut self.requests).into_iter()
}
fn handle_app_command(&mut self, data: &mut T, command: AppCommand) {
match command {
AppCommand::OpenWindow(window, mut ui) => {
let builder: UiBuilder<T> = Box::new(move |_| any(opaque(ui())));
self.add_window(data, builder, window);
}
AppCommand::CloseWindow(window_id) => {
self.requests.push(AppRequest::CloseWindow(window_id));
}
AppCommand::DragWindow(window_id) => {
self.requests.push(AppRequest::DragWindow(window_id));
}
AppCommand::Quit => {
self.requests.push(AppRequest::Quit);
}
}
}
pub fn handle_commands(&mut self, data: &mut T) {
while let Some(command) = self.receiver.try_recv() {
if command.is::<AppCommand>() {
let app_command = command.to_any().downcast().unwrap();
self.handle_app_command(data, *app_command);
continue;
}
if let Some(&RequestFocus(window, view)) = command.get() {
self.window_event(data, window, &Event::FocusWanted);
self.window_event(data, window, &Event::FocusGiven(FocusTarget::View(view)));
continue;
}
if let Some(&RequestFocusNext(window)) = command.get() {
if let Some(window_state) = self.windows.get_mut(&window) {
match window_state.view_state.has_focused() {
true => {
self.window_event(data, window, &Event::FocusNext);
}
false => {
self.window_event(data, window, &Event::FocusGiven(FocusTarget::Next));
}
}
}
continue;
}
if let Some(&RequestFocusPrev(window)) = command.get() {
if let Some(window_state) = self.windows.get_mut(&window) {
match window_state.view_state.has_focused() {
true => {
self.window_event(data, window, &Event::FocusPrev);
}
false => {
self.window_event(data, window, &Event::FocusGiven(FocusTarget::Prev));
}
}
}
continue;
}
self.event(data, &Event::Command(command));
}
self.handle_window_requests();
}
pub fn update_hovered(&mut self, window_id: WindowId) -> bool {
let mut changed = false;
if let Some(window_state) = self.windows.get_mut(&window_id) {
for i in 0..window_state.window.pointers().len() {
let pointer = &window_state.window.pointers()[i];
let position = pointer.position;
let hovered = window_state.canvas.view_at(position);
let pointer = &mut window_state.window.pointers_mut()[i];
changed |= pointer.hovering != hovered;
pointer.hovering = hovered;
}
}
changed
}
pub fn init(&mut self, data: &mut T) {
let mut rebuild = false;
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
for delegate in &mut self.delegates {
let mut cx = DelegateCx::new(&mut base, &mut self.requests, &mut rebuild);
delegate.init(&mut cx, data);
}
if rebuild {
self.rebuild(data);
self.handle_window_requests();
}
}
pub fn idle(&mut self, data: &mut T) {
let mut rebuild = false;
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
for delegate in &mut self.delegates {
let mut cx = DelegateCx::new(&mut base, &mut self.requests, &mut rebuild);
delegate.idle(&mut cx, data);
}
if rebuild {
self.rebuild(data);
self.handle_window_requests();
}
}
fn delegate_event(&mut self, data: &mut T, event: &Event) -> bool {
let mut rebuild = false;
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
for delegate in &mut self.delegates {
let mut cx = DelegateCx::new(&mut base, &mut self.requests, &mut rebuild);
if delegate.event(&mut cx, data, event) {
rebuild = true;
break;
}
}
if rebuild {
self.rebuild(data);
}
false
}
fn handle_window_requests(&mut self) {
for window_state in self.windows.values_mut() {
let id = window_state.window.id();
let updates = window_state.snapshot.difference(&window_state.window);
window_state.snapshot = window_state.window.snapshot();
for update in updates {
self.requests.push(AppRequest::UpdateWindow(id, update));
}
if window_state.view_state.needs_draw()
|| window_state.view_state.needs_layout()
|| window_state.view_state.needs_animate()
{
self.requests.push(AppRequest::RequestRedraw(id));
}
let cursor = window_state.view_state.cursor().unwrap_or_default();
if window_state.cursor != cursor {
let update = WindowUpdate::Cursor(cursor);
self.requests.push(AppRequest::UpdateWindow(id, update));
window_state.cursor = cursor;
}
if window_state.ime.as_ref() != window_state.view_state.ime() {
let update = WindowUpdate::Ime(window_state.view_state.ime().cloned());
self.requests.push(AppRequest::UpdateWindow(id, update));
window_state.ime = window_state.view_state.ime().cloned();
}
}
}
pub fn rebuild(&mut self, data: &mut T) {
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
for window_state in self.windows.values_mut() {
window_state.rebuild(data, &mut base);
}
}
pub fn event(&mut self, data: &mut T, event: &Event) -> bool {
trace!(event = ?event, "Event");
let animate = Instant::now();
let mut handled = self.delegate_event(data, event);
let mut rebuild = false;
if !handled {
for window_state in self.windows.values_mut() {
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
handled |= window_state.event(data, &mut base, &mut rebuild, event);
}
}
if rebuild {
self.rebuild(data);
}
for window_state in self.windows.values_mut() {
let requests = window_state.animate(animate);
self.requests.extend(requests);
}
self.handle_commands(data);
handled
}
pub fn window_event(&mut self, data: &mut T, window_id: WindowId, event: &Event) -> bool {
trace!(event = ?event, window = %window_id, "Window event");
let animate = Instant::now();
let mut handled = self.delegate_event(data, event);
let mut rebuild = false;
if !handled {
if let Some(window_state) = self.windows.get_mut(&window_id) {
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
handled |= window_state.event(data, &mut base, &mut rebuild, event);
}
}
if rebuild {
self.rebuild(data);
}
if let Some(window_state) = self.windows.get_mut(&window_id) {
let requests = window_state.animate(animate);
self.requests.extend(requests);
}
self.handle_commands(data);
handled
}
fn animate_window(&mut self, data: &mut T, window_id: WindowId) {
if let Some(window_state) = self.windows.get_mut(&window_id) {
if window_state.view_state.needs_animate() {
window_state.view_state.mark_animated();
let delta_time = match window_state.animate.take() {
Some(t) => t.elapsed().as_secs_f32(),
None => 0.0,
};
let event = Event::Animate(delta_time);
self.window_event(data, window_id, &event);
}
}
}
pub fn draw_window(&mut self, data: &mut T, window_id: WindowId) -> Option<WindowRenderState> {
trace!(window = %window_id, "Draw window");
self.animate_window(data, window_id);
let animate = Instant::now();
let window_state = self.windows.get_mut(&window_id)?;
let mut base = BaseCx::new(&mut self.contexts, &mut self.proxy);
if window_state.view_state.needs_layout() {
window_state.layout(data, &mut base);
}
if window_state.view_state.needs_draw() {
window_state.draw(data, &mut base);
if self.update_hovered(window_id) {
self.window_event(data, window_id, &Event::Notify);
}
}
let window_state = self.windows.get_mut(&window_id)?;
let requests = window_state.animate(animate);
self.requests.extend(requests);
self.handle_commands(data);
let window_state = self.windows.get(&window_id)?;
let clear_color = match window_state.window.color {
Some(color) => color,
None => {
let styles = (self.contexts.get_mut::<Styles>()).expect("app has styles context");
styles.style::<Theme>().background
}
};
Some(WindowRenderState {
canvas: window_state.canvas.clone(),
logical_size: window_state.window.size,
clear_color,
})
}
}