use std::collections::HashSet;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
#[cfg(target_arch = "wasm32")]
use web_time::Instant;
use winit::window::WindowId;
const PLATFORM_FOCUS_CONFIRMATION_TIMEOUT: Duration = Duration::from_millis(250);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct PendingPlatformFocus {
window_id: WindowId,
deadline: Instant,
retry_pending: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(super) struct PlatformFocusState {
pending: Option<PendingPlatformFocus>,
}
impl PlatformFocusState {
pub(super) fn request(&mut self, window_id: WindowId, now: Instant) {
self.pending = Some(PendingPlatformFocus {
window_id,
deadline: now + PLATFORM_FOCUS_CONFIRMATION_TIMEOUT,
retry_pending: true,
});
}
pub(super) fn cancel(&mut self, window_id: WindowId) {
if self
.pending
.is_some_and(|pending| pending.window_id == window_id)
{
self.pending = None;
}
}
pub(super) fn note_native_event(&mut self, focused: bool) {
if focused {
self.pending = None;
}
}
pub(super) fn advance(
&mut self,
now: Instant,
owned_windows: &HashSet<WindowId>,
) -> Option<WindowId> {
let mut pending = self.pending?;
if now >= pending.deadline || !owned_windows.contains(&pending.window_id) {
self.pending = None;
return None;
}
let retry = pending.retry_pending.then_some(pending.window_id);
pending.retry_pending = false;
self.pending = Some(pending);
retry
}
pub(super) fn has_pending_for_owned_window(
&self,
now: Instant,
owned_windows: &HashSet<WindowId>,
) -> bool {
self.pending.is_some_and(|pending| {
now < pending.deadline && owned_windows.contains(&pending.window_id)
})
}
pub(super) fn effective_focus(
self,
now: Instant,
window_id: WindowId,
native_focused: bool,
) -> bool {
self.pending
.filter(|pending| now < pending.deadline)
.map_or(native_focused, |pending| pending.window_id == window_id)
}
}
#[derive(Debug, Default, Eq, PartialEq)]
pub(super) struct ContextFocusState {
focused_windows: HashSet<WindowId>,
context_focused: bool,
focus_loss_pending: bool,
}
impl ContextFocusState {
pub(super) fn with_focused_window(window_id: Option<WindowId>) -> Self {
let mut state = Self {
context_focused: true,
..Self::default()
};
if let Some(window_id) = window_id {
state.focused_windows.insert(window_id);
}
state
}
pub(super) fn note_window_focus(&mut self, window_id: WindowId, focused: bool) -> bool {
if focused {
self.focused_windows.insert(window_id);
self.focus_loss_pending = false;
if !self.context_focused {
self.context_focused = true;
return true;
}
} else if self.focused_windows.remove(&window_id)
&& self.focused_windows.is_empty()
&& self.context_focused
{
self.focus_loss_pending = true;
}
false
}
pub(super) fn reconcile_owned_windows(
&mut self,
owned_windows: &HashSet<WindowId>,
platform_focus_pending: bool,
) -> bool {
self.focused_windows
.retain(|window_id| owned_windows.contains(window_id));
if self.context_focused && self.focused_windows.is_empty() && !platform_focus_pending {
self.focus_loss_pending = true;
}
if self.focus_loss_pending && self.focused_windows.is_empty() && !platform_focus_pending {
self.focus_loss_pending = false;
self.context_focused = false;
true
} else {
false
}
}
}