use super::{AppData, GraphicsInstance, Pending, Shared};
use super::{ProxyAction, Window};
use crate::theme::Theme;
use crate::window::WindowId;
use std::collections::HashMap;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::event::StartCause;
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window as ww;
pub(super) struct Loop<A: AppData, G: GraphicsInstance, T: Theme<G::Shared>>
where
T::Window: kas::theme::Window,
{
suspended: bool,
windows: HashMap<WindowId, Box<Window<A, G, T>>>,
popups: HashMap<WindowId, WindowId>,
id_map: HashMap<ww::WindowId, WindowId>,
shared: Shared<A, G, T>,
data: A,
resumes: Vec<(Instant, WindowId)>,
}
impl<A: AppData, G, T> ApplicationHandler for Loop<A, G, T>
where
G: GraphicsInstance,
T: Theme<G::Shared>,
T::Window: kas::theme::Window,
{
fn new_events(&mut self, el: &dyn ActiveEventLoop, cause: StartCause) {
el.set_control_flow(ControlFlow::Wait);
match cause {
StartCause::ResumeTimeReached { .. } | StartCause::WaitCancelled { .. } => {
let mut first_future = 0;
for (i, resume) in self.resumes.iter().enumerate() {
if resume.0 > Instant::now() {
break;
}
first_future = i;
if let Some(w) = self.windows.get_mut(&resume.1) {
w.update_timer(&mut self.shared, &self.data, resume.0)
}
}
self.resumes.drain(..first_future);
}
StartCause::Poll => (),
StartCause::Init => (),
}
}
fn proxy_wake_up(&mut self, _: &dyn ActiveEventLoop) {
while let Ok(event) = self.shared.proxy_rx.try_recv() {
match event {
ProxyAction::Close(id) => {
if let Some(window) = self.windows.get_mut(&id) {
window.send_close(id);
}
}
ProxyAction::CloseAll => {
for (id, window) in self.windows.iter_mut() {
window.send_close(*id);
}
}
ProxyAction::Message(msg) => {
self.shared.messages.push_erased(msg.into_erased());
}
#[cfg(feature = "accesskit")]
ProxyAction::AccessKit(window_id, event) => {
if let Some(id) = self.id_map.get(&window_id)
&& let Some(window) = self.windows.get_mut(id)
{
window.accesskit_event(&mut self.shared, &self.data, event);
}
}
}
}
}
fn resumed(&mut self, _: &dyn ActiveEventLoop) {
if self.suspended {
self.data.resumed();
self.suspended = false;
}
}
fn can_create_surfaces(&mut self, el: &dyn ActiveEventLoop) {
if self.suspended {
self.resumed(el);
}
for window in self.windows.values_mut() {
match window.create_surfaces(&mut self.shared, &self.data, el, None) {
Ok(winit_id) => {
self.id_map.insert(winit_id, window.window_id());
}
Err(e) => {
log::error!("Unable to create window: {e}");
}
}
}
}
fn window_event(
&mut self,
el: &dyn ActiveEventLoop,
window_id: ww::WindowId,
event: winit::event::WindowEvent,
) {
if let Some(id) = self.id_map.get(&window_id)
&& let Some(window) = self.windows.get_mut(id)
&& window.handle_event(&mut self.shared, &self.data, event)
{
el.set_control_flow(ControlFlow::Poll);
}
}
fn about_to_wait(&mut self, el: &dyn ActiveEventLoop) {
self.flush_pending(el);
self.shared.handle_messages(&mut self.data);
while let Some((id, msg)) = self.shared.send_queue.pop_front() {
if let Some(mut window_id) = id.window_id() {
if let Some(win_id) = self.popups.get(&window_id) {
window_id = *win_id;
}
if let Some(window) = self.windows.get_mut(&window_id) {
window.send_erased(id, msg);
}
} else {
log::warn!("unable to send message (no target): {msg:?}");
}
}
self.resumes.sort_by_key(|item| item.0);
if self.windows.is_empty() {
if !self.suspended {
self.destroy_surfaces(el);
}
el.exit();
} else if matches!(el.control_flow(), ControlFlow::Poll) {
} else if let Some((instant, _)) = self.resumes.first() {
el.set_control_flow(ControlFlow::WaitUntil(*instant));
} else {
el.set_control_flow(ControlFlow::Wait);
};
}
fn suspended(&mut self, _: &dyn ActiveEventLoop) {
if !self.suspended {
self.windows
.retain(|_, window| window.suspend(&mut self.shared, &self.data).is_none());
self.data.suspended();
self.shared.suspended();
self.suspended = true;
}
}
fn destroy_surfaces(&mut self, el: &dyn ActiveEventLoop) {
if !self.suspended {
self.suspended(el);
}
for window in self.windows.values_mut() {
window.destroy_surfaces();
}
}
fn memory_warning(&mut self, _: &dyn ActiveEventLoop) {
self.data.memory_warning();
}
}
impl<A: AppData, G: GraphicsInstance, T: Theme<G::Shared>> Loop<A, G, T>
where
T::Window: kas::theme::Window,
{
pub(super) fn new(
mut windows: Vec<Box<Window<A, G, T>>>,
shared: Shared<A, G, T>,
data: A,
) -> Self {
Loop {
suspended: true,
windows: windows.drain(..).map(|w| (w.window_id(), w)).collect(),
popups: Default::default(),
id_map: Default::default(),
shared,
data,
resumes: vec![],
}
}
fn flush_pending(&mut self, el: &dyn ActiveEventLoop) {
let mut close_all = false;
while let Some(pending) = self.shared.pending.pop_front() {
match pending {
Pending::Update => {
for (_, window) in self.windows.iter_mut() {
window.update(&self.data);
}
}
Pending::ConfigUpdate(action) => {
for (_, window) in self.windows.iter_mut() {
window.config_update(&mut self.shared, &self.data, action);
}
}
Pending::AddPopup(parent_id, id, popup) => {
log::debug!("Pending: adding overlay");
self.windows
.get_mut(&parent_id)
.unwrap()
.add_popup(&self.data, id, popup);
self.popups.insert(id, parent_id);
}
Pending::RepositionPopup(id, popup) => {
if let Some(parent_id) = self.popups.get(&id) {
self.windows
.get_mut(parent_id)
.unwrap()
.add_popup(&self.data, id, popup);
}
}
Pending::AddWindow(id, window) => {
let mut window = Box::new(Window::new(
self.shared.config.clone(),
self.shared.platform,
id,
window,
));
log::debug!("Pending: adding window {}", window.widget.title());
if !self.suspended {
let mut modal_parent = None;
if let Some(id) = window.widget.properties().modal_parent
&& let Some(window) = self.windows.get(&id)
{
modal_parent = window.winit_window();
}
match window.create_surfaces(&mut self.shared, &self.data, el, modal_parent)
{
Ok(winit_id) => {
self.id_map.insert(winit_id, id);
}
Err(e) => {
log::error!("Unable to create window: {e}");
}
}
}
self.windows.insert(id, window);
}
Pending::CloseWindow(target) => {
let mut win_id = target;
if let Some(id) = self.popups.remove(&target) {
win_id = id;
}
if let Some(window) = self.windows.get_mut(&win_id) {
window.send_close(target);
}
}
Pending::Exit => close_all = true,
}
}
self.resumes.clear();
self.windows.retain(|window_id, window| {
let (actions, resume) = window.flush_pending(&mut self.shared, &self.data);
if let Some(instant) = resume {
self.resumes.push((instant, *window_id));
}
if close_all || actions.close.is_some() {
window.suspend(&mut self.shared, &self.data);
let _ = window.flush_pending(&mut self.shared, &self.data);
self.id_map.retain(|_, v| v != window_id);
false
} else {
true
}
});
}
}