1use directories::ProjectDirs;
7use incular_config::ApplicationDefaults;
8use incular_core::{Color, InputEvent, Offset, Rect, Size};
9use std::{fmt, path::PathBuf};
10
11mod application_activation;
12mod application_shell;
13mod capabilities;
14mod content_sensitivity;
15mod data_transfer;
16mod display;
17mod file_dialog;
18mod operation;
19mod pointer;
20mod system_environment;
21mod window_control;
22
23pub use application_activation::{
24 ApplicationActivation, GlobalShortcutChord, GlobalShortcutChordError, GlobalShortcutError,
25 GlobalShortcutId, LaunchActivation, SingleInstancePolicy, SingleInstancePolicyError,
26 UrlActivation,
27};
28pub use application_shell::{
29 ApplicationBadge, ApplicationShellError, ApplicationShellFeature, NotificationAction,
30 NotificationActionId, NotificationId, NotificationPresentation, TaskbarDockState,
31 TaskbarProgress, TaskbarProgressError, TrayItemId, TrayItemPresentation,
32};
33pub use capabilities::{
34 AdvancedInputCapabilities, ApplicationServiceCapabilities, CapabilitySupport,
35 DataTransferCapabilities, DisplayPlacementCapabilities, NativeMenuCapabilities,
36 PlatformCapabilities, TransientSurfaceCapabilities, WindowControlCapabilities,
37};
38pub use content_sensitivity::{
39 ContentSensitivityBackend, ContentSensitivityCapability, ContentSensitivityNoOpReason,
40 ContentSensitivityOutcome, MemoryContentSensitivityBackend, NoopContentSensitivityBackend,
41};
42pub use data_transfer::{
43 Clipboard, ClipboardCapabilities, ClipboardError, ClipboardWriteReport, DataTransfer,
44 ExternalDragEvent, ExternalDragPhase, ExternalDragResponse, MediaType, MemoryClipboard,
45 TransferData, TransferDataError, TransferFormat, TransferFormatCapabilities, TransferImage,
46 TransferItem, TransferOperation, TransferOperations, TransferReadRequest,
47 TransferRepresentation,
48};
49pub use display::{
50 DisplayId, DisplayPlacementArea, DisplaySnapshot, LogicalDisplayPosition,
51 LogicalScreenPosition, LogicalScreenRect, PhysicalDisplayPosition, PhysicalScreenPosition,
52 PhysicalScreenRect,
53};
54pub use file_dialog::{
55 DocumentActivation, DocumentDescriptor, FileContentType, FileContentTypeKind,
56 FileDialogCapabilities, FileDialogError, FileDialogFilter, FileDialogFilterId, FileDialogKind,
57 FileDialogOption, FileDialogOptions, FileDialogOutcome, FileDialogRequest, FileDialogRequestId,
58 FileDialogSelection, FileDialogValidationError, FileExtension,
59};
60pub use incular_config::{TransparencyMode, WindowSizePolicy};
61pub use operation::{
62 NativeOperationCompletion, NativeRequestId, PlatformOperationError, PlatformOperationErrorKind,
63 PlatformOperationResult,
64};
65pub use pointer::{
66 CursorGrabMode, LogicalWindowPosition, LogicalWindowPositionError, NativePointerSample,
67 PointerMetadata,
68};
69pub use system_environment::{
70 MemorySystemEnvironmentProvider, SystemEnvironmentPreferences, SystemEnvironmentProvider,
71 canonicalize_system_locales,
72};
73pub use window_control::{
74 LogicalSizeLimits, LogicalSizeLimitsError, UserAttentionType, WindowIcon, WindowIconError,
75 WindowLevel, WindowObservedState, WindowRequestedState,
76};
77
78#[must_use]
86pub fn application_data_local_directory(application_id: &str) -> Option<PathBuf> {
87 (!application_id.trim().is_empty())
88 .then(|| ProjectDirs::from("org", "Incular", application_id))
89 .flatten()
90 .map(|directories| directories.data_local_dir().to_path_buf())
91}
92
93#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
100pub struct WindowId {
101 index: u32,
102 generation: u32,
103}
104
105impl WindowId {
106 #[must_use]
111 pub const fn from_parts(index: u32, generation: u32) -> Self {
112 Self { index, generation }
113 }
114
115 #[must_use]
117 pub const fn index(self) -> u32 {
118 self.index
119 }
120
121 #[must_use]
123 pub const fn generation(self) -> u32 {
124 self.generation
125 }
126}
127
128impl fmt::Debug for WindowId {
129 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(formatter, "WindowId({}, {})", self.index, self.generation)
131 }
132}
133
134impl fmt::Display for WindowId {
135 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(formatter, "Window#{}@{}", self.index + 1, self.generation)
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum Fullscreen {
146 Borderless,
147}
148
149#[derive(Clone, Debug, PartialEq)]
156pub struct WindowOptions {
157 pub title: String,
158 pub initial_logical_size: Size,
159 pub size_policy: WindowSizePolicy,
168 pub minimum_logical_size: Option<Size>,
169 pub maximum_logical_size: Option<Size>,
170 pub resizable: bool,
171 pub visible: bool,
172 pub decorations: bool,
173 pub transparency_mode: TransparencyMode,
174 pub background_color: Color,
176 pub maximized: bool,
177 pub fullscreen: Option<Fullscreen>,
178 pub window_level: WindowLevel,
179 pub window_icon: Option<WindowIcon>,
180}
181
182impl Default for WindowOptions {
183 fn default() -> Self {
184 let defaults = ApplicationDefaults::DEFAULT;
185 Self {
186 title: defaults.window_title.to_owned(),
187 initial_logical_size: defaults.initial_window_size,
188 size_policy: defaults.window_size_policy,
189 minimum_logical_size: None,
190 maximum_logical_size: None,
191 resizable: defaults.resizable,
192 visible: defaults.visible,
193 decorations: defaults.decorations,
194 transparency_mode: defaults.transparency_mode,
195 background_color: defaults.background_color,
196 maximized: defaults.maximized,
197 fullscreen: None,
198 window_level: WindowLevel::Normal,
199 window_icon: None,
200 }
201 }
202}
203
204impl WindowOptions {
205 #[must_use]
207 pub fn new(title: impl Into<String>) -> Self {
208 Self {
209 title: title.into(),
210 ..Self::default()
211 }
212 }
213
214 pub fn validate(&self) -> Result<(), WindowOptionsError> {
216 if !is_positive_size(self.initial_logical_size) {
217 return Err(WindowOptionsError::InvalidInitialSize);
218 }
219 if self
220 .minimum_logical_size
221 .is_some_and(|size| !is_positive_size(size))
222 {
223 return Err(WindowOptionsError::InvalidMinimumSize);
224 }
225 if self
226 .maximum_logical_size
227 .is_some_and(|size| !is_positive_size(size))
228 {
229 return Err(WindowOptionsError::InvalidMaximumSize);
230 }
231 if let (Some(minimum), Some(maximum)) =
232 (self.minimum_logical_size, self.maximum_logical_size)
233 && (minimum.width > maximum.width || minimum.height > maximum.height)
234 {
235 return Err(WindowOptionsError::MinimumExceedsMaximum);
236 }
237 if self
238 .minimum_logical_size
239 .is_some_and(|minimum| !size_is_at_least(self.initial_logical_size, minimum))
240 {
241 return Err(WindowOptionsError::InitialSizeBelowMinimum);
242 }
243 if self
244 .maximum_logical_size
245 .is_some_and(|maximum| !size_is_at_least(maximum, self.initial_logical_size))
246 {
247 return Err(WindowOptionsError::InitialSizeAboveMaximum);
248 }
249 Ok(())
250 }
251
252 pub fn requested_state(&self) -> Result<WindowRequestedState, LogicalSizeLimitsError> {
253 let size_limits =
254 LogicalSizeLimits::new(self.minimum_logical_size, self.maximum_logical_size)?;
255 Ok(WindowRequestedState {
256 visible: self.visible,
257 minimized: false,
258 maximized: self.maximized,
259 fullscreen: self.fullscreen,
260 resizable: self.resizable,
261 decorations: self.decorations,
262 size_limits,
263 level: self.window_level,
264 })
265 }
266}
267
268fn is_positive_size(size: Size) -> bool {
269 size.width.is_finite() && size.height.is_finite() && size.width > 0.0 && size.height > 0.0
270}
271
272fn size_is_at_least(left: Size, right: Size) -> bool {
273 left.width >= right.width && left.height >= right.height
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
278pub enum WindowOptionsError {
279 #[error("initial logical size must be finite and positive")]
280 InvalidInitialSize,
281 #[error("minimum logical size must be finite and positive")]
282 InvalidMinimumSize,
283 #[error("maximum logical size must be finite and positive")]
284 InvalidMaximumSize,
285 #[error("minimum logical size cannot exceed maximum logical size")]
286 MinimumExceedsMaximum,
287 #[error("initial logical size cannot be smaller than the minimum logical size")]
288 InitialSizeBelowMinimum,
289 #[error("initial logical size cannot be larger than the maximum logical size")]
290 InitialSizeAboveMaximum,
291}
292
293#[derive(Clone, Debug, PartialEq)]
298pub struct WindowCommand {
299 pub window_id: WindowId,
300 pub operation: WindowOperation,
301 pub request_id: Option<NativeRequestId>,
302}
303
304impl WindowCommand {
305 #[must_use]
306 pub const fn new(window_id: WindowId, operation: WindowOperation) -> Self {
307 Self {
308 window_id,
309 operation,
310 request_id: None,
311 }
312 }
313
314 #[must_use]
316 pub const fn with_request(
317 window_id: WindowId,
318 request_id: NativeRequestId,
319 operation: WindowOperation,
320 ) -> Self {
321 Self {
322 window_id,
323 operation,
324 request_id: Some(request_id),
325 }
326 }
327}
328
329#[derive(Clone, Debug, PartialEq)]
335pub enum WindowOperation {
336 SetTitle(String),
337 SetVisible(bool),
338 SetLogicalSize(Size),
339 SetOuterPosition(PhysicalScreenPosition),
340 BeginMoveDrag,
341 BeginResizeDrag(incular_core::WindowResizeDirection),
342 SetMinimized(bool),
343 SetMaximized(bool),
344 SetFullscreen(Option<Fullscreen>),
345 SetResizable(bool),
346 SetDecorations(bool),
347 SetLogicalSizeLimits(LogicalSizeLimits),
348 SetWindowLevel(WindowLevel),
349 SetWindowIcon(Option<WindowIcon>),
350 RequestUserAttention(Option<UserAttentionType>),
351 SetCursorGrab(CursorGrabMode),
352 SetCursorVisible(bool),
353 SetCursorPosition(LogicalWindowPosition),
354 SetContentSensitivity(incular_config::ContentSensitivity),
356 RequestFocus,
357 RequestRedraw,
358 Close,
359}
360
361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
363pub enum WindowLifecycle {
364 Creating,
365 Visible,
366 Hidden,
367 Focused,
368 Unfocused,
369 Closing,
370 Closed,
371}
372
373#[derive(Clone, Debug, PartialEq)]
380pub struct WindowEvent {
381 pub window_id: WindowId,
382 pub kind: WindowEventKind,
383}
384
385impl WindowEvent {
386 #[must_use]
387 pub const fn new(window_id: WindowId, kind: WindowEventKind) -> Self {
388 Self { window_id, kind }
389 }
390
391 #[must_use]
392 pub fn platform(window_id: WindowId, event: PlatformEvent) -> Self {
393 Self::new(window_id, WindowEventKind::Platform(event))
394 }
395
396 #[must_use]
397 pub const fn lifecycle(window_id: WindowId, lifecycle: WindowLifecycle) -> Self {
398 Self::new(window_id, WindowEventKind::Lifecycle(lifecycle))
399 }
400
401 #[must_use]
402 pub const fn redraw_requested(window_id: WindowId) -> Self {
403 Self::new(window_id, WindowEventKind::RedrawRequested)
404 }
405
406 #[must_use]
407 pub const fn state_changed(window_id: WindowId, state: WindowObservedState) -> Self {
408 Self::new(window_id, WindowEventKind::StateChanged(state))
409 }
410
411 #[must_use]
413 pub fn platform_event(&self) -> Option<&PlatformEvent> {
414 match &self.kind {
415 WindowEventKind::Platform(event) => Some(event),
416 WindowEventKind::Lifecycle(_)
417 | WindowEventKind::RedrawRequested
418 | WindowEventKind::StateChanged(_) => None,
419 }
420 }
421}
422
423#[derive(Clone, Debug, PartialEq)]
425pub enum WindowEventKind {
426 Platform(PlatformEvent),
428 Lifecycle(WindowLifecycle),
430 RedrawRequested,
432 StateChanged(WindowObservedState),
434}
435
436#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
437pub struct PhysicalSize {
438 pub width: u32,
439 pub height: u32,
440}
441impl PhysicalSize {
442 #[must_use]
443 pub const fn new(width: u32, height: u32) -> Self {
444 Self { width, height }
445 }
446 #[must_use]
447 pub const fn is_zero(self) -> bool {
448 self.width == 0 || self.height == 0
449 }
450}
451#[derive(Clone, Copy, Debug, PartialEq)]
452pub struct WindowMetrics {
453 pub physical_size: PhysicalSize,
454 pub scale_factor: f64,
455}
456impl WindowMetrics {
457 #[must_use]
458 pub fn new(physical_size: PhysicalSize, scale_factor: f64) -> Self {
459 assert!(
460 scale_factor.is_finite() && scale_factor > 0.0,
461 "scale factor must be positive"
462 );
463 Self {
464 physical_size,
465 scale_factor,
466 }
467 }
468 #[must_use]
469 pub fn logical_size(self) -> Size {
470 Size::new(
471 self.physical_size.width as f32 / self.scale_factor as f32,
472 self.physical_size.height as f32 / self.scale_factor as f32,
473 )
474 }
475 #[must_use]
476 pub fn logical_to_physical(self, offset: Offset) -> Offset {
477 Offset::new(
478 offset.x * self.scale_factor as f32,
479 offset.y * self.scale_factor as f32,
480 )
481 }
482 #[must_use]
483 pub fn physical_to_logical(self, offset: Offset) -> Offset {
484 Offset::new(
485 offset.x / self.scale_factor as f32,
486 offset.y / self.scale_factor as f32,
487 )
488 }
489}
490
491#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
494pub struct TextInputClientId(pub u64);
495
496impl TextInputClientId {
497 #[must_use]
498 pub const fn new(value: u64) -> Self {
499 Self(value)
500 }
501
502 #[must_use]
503 pub const fn get(self) -> u64 {
504 self.0
505 }
506}
507
508#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
510pub enum TextInputType {
511 #[default]
512 Text,
513 Multiline,
514 Number,
515 Phone,
516 Email,
517 Url,
518 Password,
519}
520
521#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
522pub enum TextInputAction {
523 #[default]
524 Unspecified,
525 None,
526 Done,
527 Go,
528 Search,
529 Send,
530 Next,
531 Previous,
532 Newline,
533}
534
535#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
538pub enum TextCapitalization {
539 Words,
541 Sentences,
543 Characters,
545 #[default]
547 None,
548}
549
550#[derive(Clone, Debug, PartialEq, Eq)]
553pub struct TextInputTraits {
554 pub autocorrect: bool,
556 pub enable_suggestions: bool,
558 pub enable_interactive_selection: bool,
560 pub text_capitalization: TextCapitalization,
561 pub keyboard_appearance: incular_config::Brightness,
563 pub autofill_hints: Option<Vec<String>>,
567 pub enable_ime_personalized_learning: bool,
570}
571
572impl Default for TextInputTraits {
573 fn default() -> Self {
574 Self {
575 autocorrect: true,
576 enable_suggestions: true,
577 enable_interactive_selection: true,
578 text_capitalization: TextCapitalization::None,
579 keyboard_appearance: incular_config::Brightness::Light,
580 autofill_hints: None,
581 enable_ime_personalized_learning: true,
582 }
583 }
584}
585
586#[derive(Clone, Debug, PartialEq, Eq)]
587pub struct TextInputConfiguration {
588 pub client: TextInputClientId,
589 pub input_type: TextInputType,
590 pub action: TextInputAction,
591 pub multiline: bool,
592 pub enabled: bool,
593 pub read_only: bool,
594 pub obscure_text: bool,
595 pub traits: TextInputTraits,
596}
597
598#[derive(Clone, Debug, Default, PartialEq, Eq)]
599pub struct TextInputState {
600 pub text: String,
601 pub selection_start: usize,
602 pub selection_end: usize,
603 pub composing: Option<(usize, usize)>,
604}
605
606#[derive(Clone, Debug, PartialEq)]
610pub enum TextInputCommand {
611 SetClient {
612 configuration: TextInputConfiguration,
613 state: TextInputState,
614 caret_rect: Rect,
615 },
616 Update {
617 client: TextInputClientId,
618 state: TextInputState,
619 },
620 SetCaretRect {
621 client: TextInputClientId,
622 rect: Rect,
623 },
624 Hide {
625 client: TextInputClientId,
626 },
627 Clear {
628 client: TextInputClientId,
629 },
630}
631
632pub trait TextInputAdapter {
634 fn apply(&mut self, command: &TextInputCommand);
635}
636
637#[derive(Clone, Debug, Default, PartialEq)]
639pub struct MemoryTextInputAdapter {
640 commands: Vec<TextInputCommand>,
641}
642
643impl MemoryTextInputAdapter {
644 #[must_use]
645 pub fn commands(&self) -> &[TextInputCommand] {
646 &self.commands
647 }
648
649 pub fn clear(&mut self) {
650 self.commands.clear();
651 }
652}
653
654impl TextInputAdapter for MemoryTextInputAdapter {
655 fn apply(&mut self, command: &TextInputCommand) {
656 self.commands.push(command.clone());
657 }
658}
659
660#[derive(Clone, Debug, PartialEq)]
661pub enum PlatformEvent {
662 Input(InputEvent),
663 TextInputAction(TextInputAction),
668 Metrics(WindowMetrics),
669 Environment(incular_config::RuntimeEnvironment),
672 Lifecycle(PlatformLifecycle),
673 ExternalDrag(ExternalDragEvent),
674 CloseRequested,
675}
676
677#[derive(Clone, Copy, Debug, PartialEq, Eq)]
680pub enum PlatformLifecycle {
681 Active,
682 Inactive,
683 Suspended,
684 Resumed,
688 Stopping,
689}
690#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
694pub enum NativeWindowSystem {
695 Win32,
696 AppKit,
697 X11,
698 Wayland,
699 Web,
701 Other,
702}