1use crate::application_types::{WindowError, WindowRestorationId};
2use crate::context::BuildContext;
3use crate::file_dialogs::FileDialogService;
4use crate::request_channel::{ChannelError, RequestChannel, RequestReceiver};
5use crate::tasks::RuntimeWake;
6use crate::transient_presentation::TransientPresentationResolution;
7use crate::window_state::WindowManager;
8use incular_platform::{
9 CapabilitySupport, CursorGrabMode, DisplayPlacementArea, DisplaySnapshot, Fullscreen,
10 LogicalSizeLimits, LogicalWindowPosition, NativeRequestId, PhysicalScreenPosition,
11 PlatformCapabilities, PlatformOperationError, PlatformOperationResult, UserAttentionType,
12 WindowCommand, WindowIcon, WindowId, WindowLevel, WindowObservedState, WindowOperation,
13 WindowOptions,
14};
15use incular_widgets::Widget;
16use std::{
17 collections::BTreeMap,
18 future::Future,
19 pin::Pin,
20 sync::{
21 Arc, RwLock,
22 atomic::{AtomicBool, Ordering},
23 mpsc,
24 },
25 task::{Context, Poll},
26};
27use tokio::sync::oneshot;
28
29#[derive(Clone, Debug, PartialEq)]
33pub enum NativeWindowCommand {
34 Create {
35 window_id: WindowId,
36 options: WindowOptions,
37 },
38 Operate(WindowCommand),
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum WindowCommandEnqueueError {
47 RuntimeStopped,
48 RequestIdExhausted,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub enum WindowPlacementError {
53 Unsupported,
54 OuterSizeUnavailable,
55 DisplayGeometryUnavailable(DisplayPlacementArea),
56 Enqueue(WindowCommandEnqueueError),
57}
58
59#[derive(Default)]
60pub(crate) struct DisplayCatalog {
61 displays: BTreeMap<incular_platform::DisplayId, DisplaySnapshot>,
62 primary: Option<incular_platform::DisplayId>,
63}
64
65impl DisplayCatalog {
66 pub(crate) fn replace(
67 &mut self,
68 displays: impl IntoIterator<Item = DisplaySnapshot>,
69 primary: Option<incular_platform::DisplayId>,
70 ) {
71 self.displays = displays
72 .into_iter()
73 .map(|display| (display.id, display))
74 .collect();
75 self.primary = primary.filter(|id| self.displays.contains_key(id));
76 }
77
78 pub(crate) fn all(&self) -> Vec<DisplaySnapshot> {
79 self.displays.values().cloned().collect()
80 }
81
82 pub(crate) fn get(&self, id: incular_platform::DisplayId) -> Option<DisplaySnapshot> {
83 self.displays.get(&id).cloned()
84 }
85
86 pub(crate) fn primary(&self) -> Option<DisplaySnapshot> {
87 self.primary.and_then(|id| self.get(id))
88 }
89}
90
91impl std::fmt::Display for WindowPlacementError {
92 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 Self::Unsupported => formatter.write_str("top-level window placement is unsupported"),
95 Self::OuterSizeUnavailable => {
96 formatter.write_str("native outer window size is not available yet")
97 }
98 Self::DisplayGeometryUnavailable(area) => {
99 write!(formatter, "display {area:?} geometry is unavailable")
100 }
101 Self::Enqueue(error) => error.fmt(formatter),
102 }
103 }
104}
105
106impl std::error::Error for WindowPlacementError {}
107
108impl From<WindowCommandEnqueueError> for WindowPlacementError {
109 fn from(value: WindowCommandEnqueueError) -> Self {
110 Self::Enqueue(value)
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub enum NativeOperationCompletionStatus {
117 Completed,
118 UnknownRequest,
119 TargetMismatch,
120 StaleTarget,
121}
122
123impl std::fmt::Display for WindowCommandEnqueueError {
124 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self {
126 Self::RuntimeStopped => {
127 formatter.write_str("the Incular runtime command bridge has stopped")
128 }
129 Self::RequestIdExhausted => {
130 formatter.write_str("the native operation request-id space is exhausted")
131 }
132 }
133 }
134}
135
136impl std::error::Error for WindowCommandEnqueueError {}
137
138#[must_use = "native operation requests must be awaited, inspected, or explicitly dropped"]
144pub struct NativeOperationRequest {
145 id: NativeRequestId,
146 receiver: RequestReceiver<PlatformOperationResult>,
147}
148
149impl NativeOperationRequest {
150 #[must_use]
151 pub const fn id(&self) -> NativeRequestId {
152 self.id
153 }
154
155 pub fn try_result(&mut self) -> Option<PlatformOperationResult> {
157 self.receiver
158 .try_result(|| Err(PlatformOperationError::unavailable()))
159 }
160}
161
162impl Future for NativeOperationRequest {
163 type Output = PlatformOperationResult;
164 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
165 self.receiver
166 .poll_result(context, || Err(PlatformOperationError::unavailable()))
167 }
168}
169
170#[derive(Clone)]
173pub struct WindowHandle {
174 pub(crate) id: WindowId,
175 pub(crate) bridge: Arc<WindowCommandBridge>,
176 pub(crate) file_dialogs: FileDialogService,
177 pub(crate) capabilities: Arc<RwLock<PlatformCapabilities>>,
178 pub(crate) observed_state: Arc<RwLock<WindowObservedState>>,
179 pub(crate) displays: Arc<RwLock<DisplayCatalog>>,
180 pub(crate) transient_presentations: Arc<RwLock<Vec<TransientPresentationResolution>>>,
181}
182
183impl std::fmt::Debug for WindowHandle {
184 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 formatter
186 .debug_tuple("WindowHandle")
187 .field(&self.id)
188 .finish()
189 }
190}
191
192impl WindowHandle {
193 #[must_use]
194 pub const fn id(&self) -> WindowId {
195 self.id
196 }
197
198 #[must_use]
199 pub fn capabilities(&self) -> PlatformCapabilities {
200 *self
201 .capabilities
202 .read()
203 .expect("window capability snapshot lock")
204 }
205
206 #[must_use]
210 pub fn file_dialogs(&self) -> FileDialogService {
211 self.file_dialogs.clone()
212 }
213
214 #[must_use]
215 pub fn observed_state(&self) -> WindowObservedState {
216 *self
217 .observed_state
218 .read()
219 .expect("window observed-state snapshot lock")
220 }
221
222 #[must_use]
226 pub fn transient_presentations(&self) -> Vec<TransientPresentationResolution> {
227 self.transient_presentations
228 .read()
229 .expect("transient presentation snapshot lock")
230 .clone()
231 }
232
233 #[must_use]
237 pub fn displays(&self) -> Vec<DisplaySnapshot> {
238 self.displays.read().expect("display catalog lock").all()
239 }
240
241 #[must_use]
242 pub fn display(&self, id: incular_platform::DisplayId) -> Option<DisplaySnapshot> {
243 self.displays.read().expect("display catalog lock").get(id)
244 }
245
246 #[must_use]
247 pub fn primary_display(&self) -> Option<DisplaySnapshot> {
248 self.displays
249 .read()
250 .expect("display catalog lock")
251 .primary()
252 }
253
254 #[must_use]
255 pub fn current_display(&self) -> Option<DisplaySnapshot> {
256 let id = self.observed_state().current_display?;
257 self.display(id)
258 }
259
260 pub fn set_title(&self, title: impl Into<String>) -> Result<(), WindowCommandEnqueueError> {
261 self.send(WindowOperation::SetTitle(title.into()))
262 }
263
264 pub fn set_visible(&self, visible: bool) -> Result<(), WindowCommandEnqueueError> {
265 self.send(WindowOperation::SetVisible(visible))
266 }
267
268 pub fn request_logical_size(
269 &self,
270 size: incular_core::Size,
271 ) -> Result<(), WindowCommandEnqueueError> {
272 self.send(WindowOperation::SetLogicalSize(size))
273 }
274
275 pub fn set_outer_position(
276 &self,
277 position: PhysicalScreenPosition,
278 ) -> Result<(), WindowCommandEnqueueError> {
279 self.send(WindowOperation::SetOuterPosition(position))
280 }
281
282 pub fn center_on_display(
286 &self,
287 display: &DisplaySnapshot,
288 area: DisplayPlacementArea,
289 ) -> Result<PhysicalScreenPosition, WindowPlacementError> {
290 if self.capabilities().display.set_window_position == CapabilitySupport::Unsupported {
291 return Err(WindowPlacementError::Unsupported);
292 }
293 let outer_size = self
294 .observed_state()
295 .outer_size
296 .ok_or(WindowPlacementError::OuterSizeUnavailable)?;
297 let bounds = display
298 .placement_rect(area)
299 .ok_or(WindowPlacementError::DisplayGeometryUnavailable(area))?;
300 let target = bounds.centered_position(outer_size);
301 self.set_outer_position(target)?;
302 Ok(target)
303 }
304
305 pub fn move_relative_to_display(
309 &self,
310 display: &DisplaySnapshot,
311 position: incular_platform::PhysicalDisplayPosition,
312 ) -> Result<PhysicalScreenPosition, WindowPlacementError> {
313 if self.capabilities().display.set_window_position == CapabilitySupport::Unsupported {
314 return Err(WindowPlacementError::Unsupported);
315 }
316 let target = display.screen_position(position).ok_or(
317 WindowPlacementError::DisplayGeometryUnavailable(DisplayPlacementArea::FullBounds),
318 )?;
319 self.set_outer_position(target)?;
320 Ok(target)
321 }
322
323 pub fn begin_move_drag(&self) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
326 self.bridge.request(self.id, WindowOperation::BeginMoveDrag)
327 }
328
329 pub fn begin_resize_drag(
332 &self,
333 direction: incular_core::WindowResizeDirection,
334 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
335 self.bridge
336 .request(self.id, WindowOperation::BeginResizeDrag(direction))
337 }
338
339 pub fn set_minimized(&self, minimized: bool) -> Result<(), WindowCommandEnqueueError> {
340 self.send(WindowOperation::SetMinimized(minimized))
341 }
342
343 pub fn set_maximized(&self, maximized: bool) -> Result<(), WindowCommandEnqueueError> {
344 self.send(WindowOperation::SetMaximized(maximized))
345 }
346
347 pub fn set_fullscreen(
348 &self,
349 fullscreen: Option<Fullscreen>,
350 ) -> Result<(), WindowCommandEnqueueError> {
351 self.send(WindowOperation::SetFullscreen(fullscreen))
352 }
353
354 pub fn set_resizable(&self, resizable: bool) -> Result<(), WindowCommandEnqueueError> {
355 self.send(WindowOperation::SetResizable(resizable))
356 }
357
358 pub fn set_decorations(&self, decorations: bool) -> Result<(), WindowCommandEnqueueError> {
359 self.send(WindowOperation::SetDecorations(decorations))
360 }
361
362 pub fn set_logical_size_limits(
363 &self,
364 limits: LogicalSizeLimits,
365 ) -> Result<(), WindowCommandEnqueueError> {
366 self.send(WindowOperation::SetLogicalSizeLimits(limits))
367 }
368
369 pub fn set_window_level(&self, level: WindowLevel) -> Result<(), WindowCommandEnqueueError> {
370 self.send(WindowOperation::SetWindowLevel(level))
371 }
372
373 pub fn set_window_icon(
374 &self,
375 icon: Option<WindowIcon>,
376 ) -> Result<(), WindowCommandEnqueueError> {
377 self.send(WindowOperation::SetWindowIcon(icon))
378 }
379
380 pub fn request_user_attention(
381 &self,
382 attention: UserAttentionType,
383 ) -> Result<(), WindowCommandEnqueueError> {
384 self.send(WindowOperation::RequestUserAttention(Some(attention)))
385 }
386
387 pub fn cancel_user_attention(&self) -> Result<(), WindowCommandEnqueueError> {
388 self.send(WindowOperation::RequestUserAttention(None))
389 }
390
391 pub fn set_cursor_grab(
395 &self,
396 mode: CursorGrabMode,
397 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
398 self.bridge
399 .request(self.id, WindowOperation::SetCursorGrab(mode))
400 }
401
402 pub fn set_cursor_visible(
404 &self,
405 visible: bool,
406 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
407 self.bridge
408 .request(self.id, WindowOperation::SetCursorVisible(visible))
409 }
410
411 pub fn set_cursor_position(
413 &self,
414 position: LogicalWindowPosition,
415 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
416 self.bridge
417 .request(self.id, WindowOperation::SetCursorPosition(position))
418 }
419
420 pub fn set_content_sensitivity(
424 &self,
425 sensitivity: incular_config::ContentSensitivity,
426 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
427 self.bridge
428 .request(self.id, WindowOperation::SetContentSensitivity(sensitivity))
429 }
430
431 pub fn request_focus(&self) -> Result<(), WindowCommandEnqueueError> {
432 self.send(WindowOperation::RequestFocus)
433 }
434
435 pub fn request_redraw(&self) -> Result<(), WindowCommandEnqueueError> {
436 self.send(WindowOperation::RequestRedraw)
437 }
438
439 pub fn close(&self) -> Result<(), WindowCommandEnqueueError> {
440 self.send(WindowOperation::Close)
441 }
442
443 fn send(&self, operation: WindowOperation) -> Result<(), WindowCommandEnqueueError> {
444 self.bridge.send(WindowCommand::new(self.id, operation))
445 }
446}
447
448#[derive(Clone)]
452pub struct WindowOpener {
453 pub(crate) manager: WindowManager,
454}
455
456impl WindowOpener {
457 pub fn open_window(
458 &self,
459 options: WindowOptions,
460 root: Widget,
461 ) -> Result<WindowHandle, WindowError> {
462 self.manager.open_window(options, root)
463 }
464
465 pub fn open_window_with(
466 &self,
467 options: WindowOptions,
468 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
469 ) -> Result<WindowHandle, WindowError> {
470 self.manager.open_window_with(options, build)
471 }
472
473 pub fn open_restorable_window_with(
477 &self,
478 restoration_id: WindowRestorationId,
479 kind: impl Into<String>,
480 options: WindowOptions,
481 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
482 ) -> Result<WindowHandle, WindowError> {
483 self.manager
484 .open_restorable_window_with(restoration_id, kind, options, build)
485 }
486}
487
488pub(crate) struct QueuedNativeRequest {
489 pub(crate) sender: oneshot::Sender<PlatformOperationResult>,
490 pub(crate) cancelled: Arc<AtomicBool>,
491}
492
493pub(crate) struct QueuedWindowCommand {
494 pub(crate) command: WindowCommand,
495 pub(crate) request: Option<QueuedNativeRequest>,
496}
497
498pub(crate) struct WindowCommandBridge {
499 channel: RequestChannel<QueuedWindowCommand>,
500 cancellation_sender: mpsc::Sender<NativeRequestId>,
501}
502
503impl WindowCommandBridge {
504 pub(crate) fn new(
505 sender: mpsc::Sender<QueuedWindowCommand>,
506 cancellation_sender: mpsc::Sender<NativeRequestId>,
507 ) -> Self {
508 Self {
509 channel: RequestChannel::new(sender),
510 cancellation_sender,
511 }
512 }
513 pub(crate) fn send(&self, command: WindowCommand) -> Result<(), WindowCommandEnqueueError> {
514 self.channel
515 .send(QueuedWindowCommand {
516 command,
517 request: None,
518 })
519 .map_err(channel_error)
520 }
521 pub(crate) fn request(
522 self: &Arc<Self>,
523 window_id: WindowId,
524 operation: WindowOperation,
525 ) -> Result<NativeOperationRequest, WindowCommandEnqueueError> {
526 self.channel
527 .request(|id| {
528 let request_id = NativeRequestId::new(id);
529 let (sender, receiver) = oneshot::channel();
530 let cancelled = Arc::new(AtomicBool::new(false));
531 let queued = QueuedWindowCommand {
532 command: WindowCommand::with_request(window_id, request_id, operation),
533 request: Some(QueuedNativeRequest {
534 sender,
535 cancelled: cancelled.clone(),
536 }),
537 };
538 let bridge = self.clone();
539 let receiver = RequestReceiver::new(receiver, move || {
540 cancelled.store(true, Ordering::Release);
541 bridge.cancel_request(request_id);
542 });
543 (
544 queued,
545 NativeOperationRequest {
546 id: request_id,
547 receiver,
548 },
549 )
550 })
551 .map_err(channel_error)
552 }
553 pub(crate) fn cancel_request(&self, request_id: NativeRequestId) {
554 self.channel.cancel(&self.cancellation_sender, request_id);
555 }
556 pub(crate) fn stop(&self) {
557 self.channel.stop();
558 }
559 pub(crate) fn set_wake(&self, wake: Arc<dyn RuntimeWake>) {
560 self.channel.set_wake(wake);
561 }
562}
563fn channel_error(error: ChannelError) -> WindowCommandEnqueueError {
564 match error {
565 ChannelError::Stopped => WindowCommandEnqueueError::RuntimeStopped,
566 ChannelError::IdExhausted => WindowCommandEnqueueError::RequestIdExhausted,
567 }
568}