use directories::ProjectDirs;
use incular_config::ApplicationDefaults;
use incular_core::{Color, InputEvent, Offset, Rect, Size};
use std::{fmt, path::PathBuf};
mod application_activation;
mod application_shell;
mod capabilities;
mod content_sensitivity;
mod data_transfer;
mod display;
mod file_dialog;
mod operation;
mod pointer;
mod system_environment;
mod window_control;
pub use application_activation::{
ApplicationActivation, GlobalShortcutChord, GlobalShortcutChordError, GlobalShortcutError,
GlobalShortcutId, LaunchActivation, SingleInstancePolicy, SingleInstancePolicyError,
UrlActivation,
};
pub use application_shell::{
ApplicationBadge, ApplicationShellError, ApplicationShellFeature, NotificationAction,
NotificationActionId, NotificationId, NotificationPresentation, TaskbarDockState,
TaskbarProgress, TaskbarProgressError, TrayItemId, TrayItemPresentation,
};
pub use capabilities::{
AdvancedInputCapabilities, ApplicationServiceCapabilities, CapabilitySupport,
DataTransferCapabilities, DisplayPlacementCapabilities, NativeMenuCapabilities,
PlatformCapabilities, TransientSurfaceCapabilities, WindowControlCapabilities,
};
pub use content_sensitivity::{
ContentSensitivityBackend, ContentSensitivityCapability, ContentSensitivityNoOpReason,
ContentSensitivityOutcome, MemoryContentSensitivityBackend, NoopContentSensitivityBackend,
};
pub use data_transfer::{
Clipboard, ClipboardCapabilities, ClipboardError, ClipboardWriteReport, DataTransfer,
ExternalDragEvent, ExternalDragPhase, ExternalDragResponse, MediaType, MemoryClipboard,
TransferData, TransferDataError, TransferFormat, TransferFormatCapabilities, TransferImage,
TransferItem, TransferOperation, TransferOperations, TransferReadRequest,
TransferRepresentation,
};
pub use display::{
DisplayId, DisplayPlacementArea, DisplaySnapshot, LogicalDisplayPosition,
LogicalScreenPosition, LogicalScreenRect, PhysicalDisplayPosition, PhysicalScreenPosition,
PhysicalScreenRect,
};
pub use file_dialog::{
DocumentActivation, DocumentDescriptor, FileContentType, FileContentTypeKind,
FileDialogCapabilities, FileDialogError, FileDialogFilter, FileDialogFilterId, FileDialogKind,
FileDialogOption, FileDialogOptions, FileDialogOutcome, FileDialogRequest, FileDialogRequestId,
FileDialogSelection, FileDialogValidationError, FileExtension,
};
pub use incular_config::{TransparencyMode, WindowSizePolicy};
pub use operation::{
NativeOperationCompletion, NativeRequestId, PlatformOperationError, PlatformOperationErrorKind,
PlatformOperationResult,
};
pub use pointer::{
CursorGrabMode, LogicalWindowPosition, LogicalWindowPositionError, NativePointerSample,
PointerMetadata,
};
pub use system_environment::{
MemorySystemEnvironmentProvider, SystemEnvironmentPreferences, SystemEnvironmentProvider,
canonicalize_system_locales,
};
pub use window_control::{
LogicalSizeLimits, LogicalSizeLimitsError, UserAttentionType, WindowIcon, WindowIconError,
WindowLevel, WindowObservedState, WindowRequestedState,
};
#[must_use]
pub fn application_data_local_directory(application_id: &str) -> Option<PathBuf> {
(!application_id.trim().is_empty())
.then(|| ProjectDirs::from("org", "Incular", application_id))
.flatten()
.map(|directories| directories.data_local_dir().to_path_buf())
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WindowId {
index: u32,
generation: u32,
}
impl WindowId {
#[must_use]
pub const fn from_parts(index: u32, generation: u32) -> Self {
Self { index, generation }
}
#[must_use]
pub const fn index(self) -> u32 {
self.index
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
}
impl fmt::Debug for WindowId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "WindowId({}, {})", self.index, self.generation)
}
}
impl fmt::Display for WindowId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Window#{}@{}", self.index + 1, self.generation)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fullscreen {
Borderless,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WindowOptions {
pub title: String,
pub initial_logical_size: Size,
pub size_policy: WindowSizePolicy,
pub minimum_logical_size: Option<Size>,
pub maximum_logical_size: Option<Size>,
pub resizable: bool,
pub visible: bool,
pub decorations: bool,
pub transparency_mode: TransparencyMode,
pub background_color: Color,
pub maximized: bool,
pub fullscreen: Option<Fullscreen>,
pub window_level: WindowLevel,
pub window_icon: Option<WindowIcon>,
}
impl Default for WindowOptions {
fn default() -> Self {
let defaults = ApplicationDefaults::DEFAULT;
Self {
title: defaults.window_title.to_owned(),
initial_logical_size: defaults.initial_window_size,
size_policy: defaults.window_size_policy,
minimum_logical_size: None,
maximum_logical_size: None,
resizable: defaults.resizable,
visible: defaults.visible,
decorations: defaults.decorations,
transparency_mode: defaults.transparency_mode,
background_color: defaults.background_color,
maximized: defaults.maximized,
fullscreen: None,
window_level: WindowLevel::Normal,
window_icon: None,
}
}
}
impl WindowOptions {
#[must_use]
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
..Self::default()
}
}
pub fn validate(&self) -> Result<(), WindowOptionsError> {
if !is_positive_size(self.initial_logical_size) {
return Err(WindowOptionsError::InvalidInitialSize);
}
if self
.minimum_logical_size
.is_some_and(|size| !is_positive_size(size))
{
return Err(WindowOptionsError::InvalidMinimumSize);
}
if self
.maximum_logical_size
.is_some_and(|size| !is_positive_size(size))
{
return Err(WindowOptionsError::InvalidMaximumSize);
}
if let (Some(minimum), Some(maximum)) =
(self.minimum_logical_size, self.maximum_logical_size)
&& (minimum.width > maximum.width || minimum.height > maximum.height)
{
return Err(WindowOptionsError::MinimumExceedsMaximum);
}
if self
.minimum_logical_size
.is_some_and(|minimum| !size_is_at_least(self.initial_logical_size, minimum))
{
return Err(WindowOptionsError::InitialSizeBelowMinimum);
}
if self
.maximum_logical_size
.is_some_and(|maximum| !size_is_at_least(maximum, self.initial_logical_size))
{
return Err(WindowOptionsError::InitialSizeAboveMaximum);
}
Ok(())
}
pub fn requested_state(&self) -> Result<WindowRequestedState, LogicalSizeLimitsError> {
let size_limits =
LogicalSizeLimits::new(self.minimum_logical_size, self.maximum_logical_size)?;
Ok(WindowRequestedState {
visible: self.visible,
minimized: false,
maximized: self.maximized,
fullscreen: self.fullscreen,
resizable: self.resizable,
decorations: self.decorations,
size_limits,
level: self.window_level,
})
}
}
fn is_positive_size(size: Size) -> bool {
size.width.is_finite() && size.height.is_finite() && size.width > 0.0 && size.height > 0.0
}
fn size_is_at_least(left: Size, right: Size) -> bool {
left.width >= right.width && left.height >= right.height
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum WindowOptionsError {
#[error("initial logical size must be finite and positive")]
InvalidInitialSize,
#[error("minimum logical size must be finite and positive")]
InvalidMinimumSize,
#[error("maximum logical size must be finite and positive")]
InvalidMaximumSize,
#[error("minimum logical size cannot exceed maximum logical size")]
MinimumExceedsMaximum,
#[error("initial logical size cannot be smaller than the minimum logical size")]
InitialSizeBelowMinimum,
#[error("initial logical size cannot be larger than the maximum logical size")]
InitialSizeAboveMaximum,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WindowCommand {
pub window_id: WindowId,
pub operation: WindowOperation,
pub request_id: Option<NativeRequestId>,
}
impl WindowCommand {
#[must_use]
pub const fn new(window_id: WindowId, operation: WindowOperation) -> Self {
Self {
window_id,
operation,
request_id: None,
}
}
#[must_use]
pub const fn with_request(
window_id: WindowId,
request_id: NativeRequestId,
operation: WindowOperation,
) -> Self {
Self {
window_id,
operation,
request_id: Some(request_id),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum WindowOperation {
SetTitle(String),
SetVisible(bool),
SetLogicalSize(Size),
SetOuterPosition(PhysicalScreenPosition),
BeginMoveDrag,
BeginResizeDrag(incular_core::WindowResizeDirection),
SetMinimized(bool),
SetMaximized(bool),
SetFullscreen(Option<Fullscreen>),
SetResizable(bool),
SetDecorations(bool),
SetLogicalSizeLimits(LogicalSizeLimits),
SetWindowLevel(WindowLevel),
SetWindowIcon(Option<WindowIcon>),
RequestUserAttention(Option<UserAttentionType>),
SetCursorGrab(CursorGrabMode),
SetCursorVisible(bool),
SetCursorPosition(LogicalWindowPosition),
SetContentSensitivity(incular_config::ContentSensitivity),
RequestFocus,
RequestRedraw,
Close,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindowLifecycle {
Creating,
Visible,
Hidden,
Focused,
Unfocused,
Closing,
Closed,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WindowEvent {
pub window_id: WindowId,
pub kind: WindowEventKind,
}
impl WindowEvent {
#[must_use]
pub const fn new(window_id: WindowId, kind: WindowEventKind) -> Self {
Self { window_id, kind }
}
#[must_use]
pub fn platform(window_id: WindowId, event: PlatformEvent) -> Self {
Self::new(window_id, WindowEventKind::Platform(event))
}
#[must_use]
pub const fn lifecycle(window_id: WindowId, lifecycle: WindowLifecycle) -> Self {
Self::new(window_id, WindowEventKind::Lifecycle(lifecycle))
}
#[must_use]
pub const fn redraw_requested(window_id: WindowId) -> Self {
Self::new(window_id, WindowEventKind::RedrawRequested)
}
#[must_use]
pub const fn state_changed(window_id: WindowId, state: WindowObservedState) -> Self {
Self::new(window_id, WindowEventKind::StateChanged(state))
}
#[must_use]
pub fn platform_event(&self) -> Option<&PlatformEvent> {
match &self.kind {
WindowEventKind::Platform(event) => Some(event),
WindowEventKind::Lifecycle(_)
| WindowEventKind::RedrawRequested
| WindowEventKind::StateChanged(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum WindowEventKind {
Platform(PlatformEvent),
Lifecycle(WindowLifecycle),
RedrawRequested,
StateChanged(WindowObservedState),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PhysicalSize {
pub width: u32,
pub height: u32,
}
impl PhysicalSize {
#[must_use]
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
#[must_use]
pub const fn is_zero(self) -> bool {
self.width == 0 || self.height == 0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WindowMetrics {
pub physical_size: PhysicalSize,
pub scale_factor: f64,
}
impl WindowMetrics {
#[must_use]
pub fn new(physical_size: PhysicalSize, scale_factor: f64) -> Self {
assert!(
scale_factor.is_finite() && scale_factor > 0.0,
"scale factor must be positive"
);
Self {
physical_size,
scale_factor,
}
}
#[must_use]
pub fn logical_size(self) -> Size {
Size::new(
self.physical_size.width as f32 / self.scale_factor as f32,
self.physical_size.height as f32 / self.scale_factor as f32,
)
}
#[must_use]
pub fn logical_to_physical(self, offset: Offset) -> Offset {
Offset::new(
offset.x * self.scale_factor as f32,
offset.y * self.scale_factor as f32,
)
}
#[must_use]
pub fn physical_to_logical(self, offset: Offset) -> Offset {
Offset::new(
offset.x / self.scale_factor as f32,
offset.y / self.scale_factor as f32,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TextInputClientId(pub u64);
impl TextInputClientId {
#[must_use]
pub const fn new(value: u64) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TextInputType {
#[default]
Text,
Multiline,
Number,
Phone,
Email,
Url,
Password,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TextInputAction {
#[default]
Unspecified,
None,
Done,
Go,
Search,
Send,
Next,
Previous,
Newline,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TextCapitalization {
Words,
Sentences,
Characters,
#[default]
None,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextInputTraits {
pub autocorrect: bool,
pub enable_suggestions: bool,
pub enable_interactive_selection: bool,
pub text_capitalization: TextCapitalization,
pub keyboard_appearance: incular_config::Brightness,
pub autofill_hints: Option<Vec<String>>,
pub enable_ime_personalized_learning: bool,
}
impl Default for TextInputTraits {
fn default() -> Self {
Self {
autocorrect: true,
enable_suggestions: true,
enable_interactive_selection: true,
text_capitalization: TextCapitalization::None,
keyboard_appearance: incular_config::Brightness::Light,
autofill_hints: None,
enable_ime_personalized_learning: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextInputConfiguration {
pub client: TextInputClientId,
pub input_type: TextInputType,
pub action: TextInputAction,
pub multiline: bool,
pub enabled: bool,
pub read_only: bool,
pub obscure_text: bool,
pub traits: TextInputTraits,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextInputState {
pub text: String,
pub selection_start: usize,
pub selection_end: usize,
pub composing: Option<(usize, usize)>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum TextInputCommand {
SetClient {
configuration: TextInputConfiguration,
state: TextInputState,
caret_rect: Rect,
},
Update {
client: TextInputClientId,
state: TextInputState,
},
SetCaretRect {
client: TextInputClientId,
rect: Rect,
},
Hide {
client: TextInputClientId,
},
Clear {
client: TextInputClientId,
},
}
pub trait TextInputAdapter {
fn apply(&mut self, command: &TextInputCommand);
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MemoryTextInputAdapter {
commands: Vec<TextInputCommand>,
}
impl MemoryTextInputAdapter {
#[must_use]
pub fn commands(&self) -> &[TextInputCommand] {
&self.commands
}
pub fn clear(&mut self) {
self.commands.clear();
}
}
impl TextInputAdapter for MemoryTextInputAdapter {
fn apply(&mut self, command: &TextInputCommand) {
self.commands.push(command.clone());
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PlatformEvent {
Input(InputEvent),
TextInputAction(TextInputAction),
Metrics(WindowMetrics),
Environment(incular_config::RuntimeEnvironment),
Lifecycle(PlatformLifecycle),
ExternalDrag(ExternalDragEvent),
CloseRequested,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlatformLifecycle {
Active,
Inactive,
Suspended,
Resumed,
Stopping,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NativeWindowSystem {
Win32,
AppKit,
X11,
Wayland,
Web,
Other,
}