Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/platform/mod.rs
1pub mod app;
2pub mod file_picker;
3pub mod keyboard;
4pub mod menu;
5 
6pub mod test;
7#[cfg(target_family = "wasm")]
8pub mod wasm;
9 
10pub use app::AppCallbacks;
11use derivative::Derivative;
12pub use file_picker::{
13 FilePickerCallback, FilePickerConfiguration, FileType, SaveFilePickerCallback,
14 SaveFilePickerConfiguration,
15};
16use serde::{Deserialize, Serialize};
17 
18use crate::fonts::SubpixelAlignment;
19use crate::keymap::Keystroke;
20use crate::modals::{AlertDialog, ModalId};
21use crate::notification::{NotificationSendError, RequestPermissionsOutcome};
22 
23use crate::rendering::{GPUPowerPreference, OnGPUDeviceSelected};
24use crate::text_layout::{ClipConfig, StyleAndFont, TextAlignment, TextFrame};
25use crate::{
26 accessibility::AccessibilityContent,
27 fonts::{
28 canvas::RasterFormat, FamilyId, FontId, GlyphId, Metrics, Properties, RasterizedGlyph,
29 },
30 notification::UserNotification,
31 text_layout::Line,
32 windowing::WindowCallbacks,
33 Scene, WindowId,
34};
35use crate::{
36 geometry, rendering, AppContext, ApplicationBundleInfo, Clipboard, DisplayId, DisplayIdx,
37 OptionalPlatformWindow,
38};
39use anyhow::Result;
40use async_task::Runnable;
41use lazy_static::lazy_static;
42use pathfinder_geometry::vector::Vector2I;
43use pathfinder_geometry::{
44 rect::{RectF, RectI},
45 vector::Vector2F,
46};
47use std::any::Any;
48use std::borrow::Cow;
49use std::collections::HashSet;
50use std::path::Path;
51use std::{ops::Range, rc::Rc, sync::Arc};
52 
53#[cfg(not(target_family = "wasm"))]
54lazy_static! {
55 pub static ref KEYS_TO_IGNORE: HashSet<Keystroke> = HashSet::new();
56}
57#[cfg(target_family = "wasm")]
58lazy_static! {
59 pub static ref KEYS_TO_IGNORE: HashSet<Keystroke> =
60 HashSet::from([Keystroke::parse("cmdorctrl-v").unwrap()]);
61}
62 
63/// Type of the callback function that provides the result of requesting
64/// desktop notification permissions.
65pub type RequestNotificationPermissionsCallback =
66 Box<dyn FnOnce(RequestPermissionsOutcome, &mut AppContext) + Send + Sync>;
67/// Type of the callback function invoked when an error occurs while sending
68/// a desktop notification.
69pub type SendNotificationErrorCallback =
70 Box<dyn FnOnce(NotificationSendError, &mut AppContext) + Send + Sync>;
71 
72/// The information needed to send a notification.
73#[derive(Derivative)]
74#[derivative(Debug)]
75pub struct NotificationInfo {
76 pub notification_content: UserNotification,
77 #[derivative(Debug = "ignore")]
78 pub on_error: SendNotificationErrorCallback,
79}
80 
81// TODO(advait): revisit this to check if there's a better approach.
82#[derive(Copy, Clone)]
83pub struct LineStyle {
84 pub font_size: f32,
85 pub line_height_ratio: f32,
86 pub baseline_ratio: f32,
87 /// Size of tab stops in spaces for fully fixed-width (monospace) text.
88 ///
89 /// `Some(n)` means `\t` advances to the next stop every `n` spaces. This is intended only for
90 /// paragraphs where all runs share the same fixed-width font metrics.
91 ///
92 /// `None` leaves tab stop behavior up to the backend defaults.
93 pub fixed_width_tab_size: Option<u8>,
94}
95 
96pub struct WindowOptions {
97 pub bounds: WindowBounds,
98 pub fullscreen_state: FullscreenState,
99 pub hide_title_bar: bool,
100 pub title: Option<String>,
101 pub style: WindowStyle,
102 pub background_blur_radius_pixels: Option<u8>,
103 pub background_blur_texture: bool,
104 pub gpu_power_preference: GPUPowerPreference,
105 pub backend_preference: Option<GraphicsBackend>,
106 pub on_gpu_device_info_reported: Box<OnGPUDeviceSelected>,
107 /// This is an identifier to distinguish different windows among one application. It is a no-op
108 /// on all platforms except X11 Linux.
109 /// See docs on the "WM_CLASS" property:
110 /// https://www.x.org/docs/ICCCM/icccm.pdf
111 pub window_instance: Option<String>,
112}
113 
114impl std::fmt::Debug for WindowOptions {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("WindowOptions")
117 .field("bounds", &self.bounds)
118 .field("hide_title_bar", &self.hide_title_bar)
119 .field("title", &self.title)
120 .field("style", &self.style)
121 .field(
122 "background_blur_radius_pixels",
123 &self.background_blur_radius_pixels,
124 )
125 .field("background_blur_texture", &self.background_blur_texture)
126 .field("gpu_power_preference", &self.gpu_power_preference)
127 .field("backend_preference", &self.backend_preference)
128 .field("window_instance", &self.window_instance)
129 .finish()
130 }
131}
132 
133#[derive(Clone, Copy, Debug, Default, PartialEq)]
134pub enum WindowStyle {
135 #[default]
136 Normal,
137 
138 /// If the window does not steal focus, the passed bounds won't be applied and the
139 /// the window will be set to default size.
140 NotStealFocus,
141 
142 /// If a window is pinned, it will be positioned above all other apps and steals focus
143 /// by default.
144 Pin,
145 
146 /// A window that needs to cascade in case of opening a new window with ExactPosition
147 Cascade,
148 
149 /// Position the window at exact bounds and show it, but don't make it key (no focus steal).
150 /// Used for drag preview windows that should appear but not interrupt the drag.
151 PositionedNoFocus,
152}
153 
154#[derive(Clone, Copy, PartialEq, Debug, Default)]
155pub enum WindowBounds {
156 /// The platform chooses the window size and origin.
157 #[default]
158 Default,
159 /// Use an exact size for the window, but let the platform choose its origin.
160 ExactSize(Vector2F),
161 /// Use an exact size and origin for the window.
162 ExactPosition(RectF),
163}
164 
165impl WindowBounds {
166 pub fn new(bounds: Option<RectF>) -> Self {
167 match bounds {
168 // Make sure the bounds are valid before passing down to platform call.
169 Some(bound) if bound.height() > 0. && bound.width() > 0. => {
170 WindowBounds::ExactPosition(bound)
171 }
172 _ => WindowBounds::Default,
173 }
174 }
175 
176 pub fn bounds(&self) -> Option<RectF> {
177 match &self {
178 Self::Default => None,
179 Self::ExactSize(_) => None,
180 Self::ExactPosition(bound) => Some(*bound),
181 }
182 }
183}
184 
185#[derive(Copy, Clone, Debug, PartialEq)]
186pub enum MicrophoneAccessState {
187 NotDetermined,
188 Denied,
189 Restricted,
190 Authorized,
191}
192 
193pub trait Delegate: 'static {
194 /// Returns a handle to the platform dispatch delegate.
195 fn dispatch_delegate(&self) -> Arc<dyn DispatchDelegate>;
196 
197 fn request_user_attention(&self, window_id: WindowId);
198 
199 fn clipboard(&mut self) -> &mut dyn Clipboard;
200 
201 fn system_theme(&self) -> SystemTheme;
202 
203 fn open_url(&self, url: &str);
204 
205 /// Opens an absolute file path with native system API.
206 fn open_file_path(&self, path: &Path);
207 
208 /// Opens an absolute file path in the file explorer with native system API.
209 fn open_file_path_in_explorer(&self, path: &Path);
210 
211 fn open_file_picker(
212 &self,
213 callback: FilePickerCallback,
214 file_picker_config: FilePickerConfiguration,
215 );
216 
217 fn open_save_file_picker(
218 &self,
219 callback: file_picker::SaveFilePickerCallback,
220 config: file_picker::SaveFilePickerConfiguration,
221 );
222 
223 /// Retrieve the absolute path of given application's bundle and its executable.
224 fn application_bundle_info(&self, bundle_identifier: &str)
225 -> Option<ApplicationBundleInfo<'_>>;
226 
227 /// Create a window showing a modal dialog native to the platform. The modal will synchronously
228 /// block all other interactions with the app until dismissed. The [`ModalId`] is a handle to
229 /// map the modal response to the right callback for the [`AppContext`].
230 fn show_native_platform_modal(&self, id: ModalId, modal: AlertDialog);
231 
232 /// Requests OS permissions for sending desktop notifications.
233 fn request_desktop_notification_permissions(
234 &self,
235 on_completion: RequestNotificationPermissionsCallback,
236 );
237 
238 /// Sends a desktop notification.
239 fn send_desktop_notification(
240 &self,
241 notification_content: UserNotification,
242 window_id: WindowId,
243 on_error: SendNotificationErrorCallback,
244 );
245 
246 /// Sets the cursor pointer
247 fn set_cursor_shape(&self, cursor: Cursor);
248 
249 /// Returns the current cursor pointer
250 #[cfg(feature = "test-util")]
251 fn get_cursor_shape(&self) -> Cursor;
252 fn close_ime_async(&self, window_id: WindowId);
253 fn is_ime_open(&self) -> bool;
254 
255 /// Requests that the system character palette (usually an emoji picker)
256 /// be shown.
257 fn open_character_palette(&self);
258 
259 /// Sets the passed string as the content available for a11y tools (such as screen readers).
260 fn set_accessibility_contents(&self, content: AccessibilityContent);
261 
262 fn register_global_shortcut(&self, shortcut: Keystroke);
263 fn unregister_global_shortcut(&self, shortcut: &Keystroke);
264 
265 fn terminate_app(&self, termination_mode: TerminationMode);
266 
267 /// Returns whether or not a screen reader is enabled, or None if we do not
268 /// know for sure.
269 fn is_screen_reader_enabled(&self) -> Option<bool>;
270 
271 /// Returns the current microphone access state.
272 fn microphone_access_state(&self) -> MicrophoneAccessState;
273 
274 /// Returns whether the app is running with a headless rendering backend
275 /// (no GUI or visible output).
276 fn is_headless(&self) -> bool {
277 false
278 }
279}
280 
281#[derive(Debug)]
282pub enum TerminationMode {
283 /// The termination can be interrupted. This is the default, and should be used most
284 /// of the time.
285 Cancellable,
286 /// The termination cannot be interrupted. This can be useful when we have received
287 /// confirmation from the user that it is ok to terminate, for example.
288 ForceTerminate,
289 /// The window's content (tab) has been transferred to another window, so the
290 /// now-empty source window should close without any confirmation dialogs.
291 ContentTransferred,
292}
293 
294/// A trait for interacting with the main thread.
295#[cfg(not(target_family = "wasm"))]
296pub trait DispatchDelegate: 'static + Send + Sync {
297 fn is_main_thread(&self) -> bool;
298 fn run_on_main_thread(&self, task: Runnable);
299}
300 
301#[cfg(target_family = "wasm")]
302pub trait DispatchDelegate: 'static {
303 fn is_main_thread(&self) -> bool;
304 fn run_on_main_thread(&self, task: Runnable);
305}
306 
307/// A marker trait for the types that [`FontDB`] implementations return from
308/// [`FontDB::load_all_system_fonts`].
309pub trait LoadedSystemFonts: 'static + Any + Send + Sync {
310 fn as_any(self: Box<Self>) -> Box<dyn Any>;
311}
312 
313/// Trait that implements text layout. Implementors must be [`Send`] and
314/// [`Sync`] so that text can be laid out in a background thread.
315pub trait TextLayoutSystem: 'static + Send + Sync {
316 /// Lays out a single line of text.
317 fn layout_line(
318 &self,
319 text: &str,
320 line_style: LineStyle,
321 style_runs: &[(Range<usize>, StyleAndFont)],
322 max_width: f32,
323 clip_config: ClipConfig,
324 ) -> Line;
325 
326 /// Lays out text into a series of lines that fit within the bounding box
327 /// defined by `max_width` and `max_height`.
328 #[allow(clippy::too_many_arguments)]
329 fn layout_text(
330 &self,
331 text: &str,
332 line_style: LineStyle,
333 style_runs: &[(Range<usize>, StyleAndFont)],
334 max_width: f32,
335 max_height: f32,
336 alignment: TextAlignment,
337 first_line_head_indent: Option<f32>,
338 ) -> TextFrame;
339}
340 
341/// A trait for working with fonts.
342///
343/// This interface provides a platform-agnostic API for loading fonts,
344/// retrieving font-related metrics, performing text shaping/layout, and
345/// rasterizing glyphs.
346///
347/// Implementations of this trait can rely on callers to cache returned values
348/// where appropriate.
349pub trait FontDB: 'static {
350 /// Loads a font family from the provided set of font data.
351 ///
352 /// Each bytestring should be decodable as a single font.
353 fn load_from_bytes(&mut self, name: &str, bytes: Vec<Vec<u8>>) -> Result<FamilyId>;
354 
355 /// Loads a font from the system by family name.
356 #[cfg(not(target_family = "wasm"))]
357 fn load_from_system(&mut self, font_family: &str) -> Result<FamilyId>;
358 
359 /// Returns a background task that produces the set of data the font DB
360 /// needs to make all system fonts available to the application.
361 #[cfg(not(target_family = "wasm"))]
362 fn load_all_system_fonts(
363 &self,
364 ) -> futures::future::BoxFuture<'static, Box<dyn LoadedSystemFonts>>;
365 
366 /// Processes the data produced by [`FontDB::load_all_system_fonts`],
367 /// returning the list of system fonts that can be used by the application.
368 #[cfg(not(target_family = "wasm"))]
369 fn process_loaded_system_fonts(
370 &mut self,
371 loaded_system_fonts: Box<dyn LoadedSystemFonts>,
372 ) -> Vec<(Option<FamilyId>, crate::fonts::FontInfo)>;
373 
374 /// Returns the [`FamilyId`] identified by `name`, or [`None`] if no font
375 /// with `name` has been inserted into the cache.
376 fn family_id_for_name(&self, name: &str) -> Option<FamilyId>;
377 
378 /// Gets the name of a font family by ID.
379 fn load_family_name_from_id(&self, id: FamilyId) -> Option<String>;
380 
381 /// Determines which font from a family should be used to display text with
382 /// the given properties.
383 fn select_font(&self, family_id: FamilyId, properties: Properties) -> FontId;
384 
385 /// Returns the ordered list of fonts which should be checked when the given
386 /// font is lacking a glyph for a character.
387 fn fallback_fonts(&self, character: char, font_id: FontId) -> Vec<FontId>;
388 
389 /// Returns a set of metrics about the font that aren't glyph-dependent.
390 fn font_metrics(&self, font_id: FontId) -> Metrics;
391 
392 /// Computes the position of a glyph that occurs after this one, relative to
393 /// this glyph.
394 ///
395 /// The `x` position within the resulting `Vector2F` is the horizontal distance to
396 /// increment (or decrement, for RTL text) the position after a glyph has been rendered. It is
397 /// always positive for horizontal layouts, and 0 for fonts that only support being
398 /// rendered vertically.
399 ///
400 /// The `y` position within the resulting `Vector2F` is the vertical distance to decrement (or
401 /// increment for bottom to top writing) the position after a glyph has been rendered. It is
402 /// always positive for vertical layouts, and 0 for fonts that only support being rendered
403 /// horizontally.
404 fn glyph_advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Vector2I>;
405 
406 /// Computes the size of the canvas needed to rasterize the glyph.
407 fn glyph_raster_bounds(
408 &self,
409 font_id: FontId,
410 size: f32,
411 glyph_id: GlyphId,
412 scale: Vector2F,
413 glyph_config: &rendering::GlyphConfig,
414 ) -> Result<RectI>;
415 
416 /// Computes the bounding box of a glyph with respect to surrounding glyphs.
417 fn glyph_typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<RectI>;
418 
419 /// Rasterizes a single glyph so it can be rendered to the screen.
420 #[allow(clippy::too_many_arguments)]
421 fn rasterize_glyph(
422 &self,
423 font_id: FontId,
424 size: f32,
425 glyph_id: GlyphId,
426 scale: Vector2F,
427 subpixel_alignment: SubpixelAlignment,
428 glyph_config: &rendering::GlyphConfig,
429 format: RasterFormat,
430 ) -> Result<RasterizedGlyph>;
431 
432 /// Returns the ID of the glyph which represents the given character in the
433 /// given font.
434 fn glyph_for_char(&self, font_id: FontId, char: char) -> Option<GlyphId>;
435 
436 fn text_layout_system(&self) -> &dyn TextLayoutSystem;
437}
438 
439#[derive(Clone, Copy, Debug, Default, num_derive::FromPrimitive, PartialEq, Eq)]
440pub enum FullscreenState {
441 #[default]
442 Normal = 0,
443 Fullscreen = 1,
444 Maximized = 2,
445}
446 
447pub trait Window: 'static + WindowContext + std::any::Any {
448 fn minimize(&self);
449 fn toggle_maximized(&self);
450 fn toggle_fullscreen(&self);
451 fn fullscreen_state(&self) -> FullscreenState;
452 /// Whether the window has the native OS window frame (title bar and buttons).
453 fn uses_native_window_decorations(&self) -> bool;
454 fn set_titlebar_height(&self, height: f64);
455 
456 /// Whether any hardware supports window transparency
457 fn supports_transparency(&self) -> bool;
458 fn graphics_backend(&self) -> GraphicsBackend;
459 fn supported_backends(&self) -> Vec<GraphicsBackend>;
460 
461 fn as_ctx(&self) -> &dyn WindowContext;
462 fn callbacks(&self) -> &WindowCallbacks;
463 
464 fn as_any(&self) -> &dyn std::any::Any;
465}
466 
467pub trait WindowContext {
468 /// Returns the current inner (content) size of the window, in logical
469 /// pixels.
470 fn size(&self) -> Vector2F;
471 
472 /// Returns the position of the window origin (top-left corner) within the
473 /// screen, in logical pixels.
474 fn origin(&self) -> Vector2F;
475 
476 /// Returns the scale factor for the window surface.
477 fn backing_scale_factor(&self) -> f32;
478 
479 /// The maximum dimension size in pixels, either width or height, for a 2D-texture. `None`
480 /// will be treated as unbounded.
481 fn max_texture_dimension_2d(&self) -> Option<u32>;
482 
483 /// Provides the window the next scene to render and asks it to schedule a
484 /// redraw.
485 fn render_scene(&self, scene: Rc<Scene>);
486 
487 /// Schedules a redraw of the window.
488 fn request_redraw(&self);
489 
490 /// Requests a frame capture on the next render.
491 ///
492 /// When the frame is captured, the provided callback will be invoked with the
493 /// captured frame data.
494 fn request_frame_capture(&self, callback: Box<dyn FnOnce(CapturedFrame) + Send + 'static>);
495}
496 
497/// Pixel format of the data in a `CapturedFrame`.
498#[derive(Clone, Copy, Debug, PartialEq, Eq)]
499pub enum CapturedFrameFormat {
500 Rgba,
501 Bgra,
502}
503 
504/// A captured frame containing pixel data in the format indicated by `format`.
505#[derive(Clone)]
506pub struct CapturedFrame {
507 pub width: u32,
508 pub height: u32,
509 pub data: Vec<u8>,
510 pub format: CapturedFrameFormat,
511}
512 
513impl CapturedFrame {
514 pub fn new(width: u32, height: u32, data: Vec<u8>) -> Self {
515 Self {
516 width,
517 height,
518 data,
519 format: CapturedFrameFormat::Rgba,
520 }
521 }
522 
523 pub fn new_bgra(width: u32, height: u32, data: Vec<u8>) -> Self {
524 Self {
525 width,
526 height,
527 data,
528 format: CapturedFrameFormat::Bgra,
529 }
530 }
531 
532 pub fn ensure_rgba(&mut self) {
533 if self.format == CapturedFrameFormat::Bgra {
534 for chunk in self.data.chunks_exact_mut(4) {
535 chunk.swap(0, 2);
536 }
537 self.format = CapturedFrameFormat::Rgba;
538 }
539 }
540}
541 
542#[derive(Copy, Clone, Default)]
543 
544pub enum WindowFocusBehavior {
545 /// Brings the window to the front when focusing the app.
546 #[default]
547 BringToFront,
548 /// Retain the window's current position in the z-index when
549 /// focusing the app. May not be supported on all platforms.
550 RetainZIndex,
551}
552 
553/// Common interface for abstracting platform-specific windowing logic.
554pub trait WindowManager {
555 fn open_window(
556 &mut self,
557 window_id: WindowId,
558 window_options: WindowOptions,
559 callbacks: WindowCallbacks,
560 ) -> Result<()>;
561 
562 /// Returns a platform-independent trait-object for the window with the given ID.
563 fn platform_window(&self, window_id: WindowId) -> OptionalPlatformWindow;
564 
565 /// Drop a window. Note that other pieces of state pointing to this window ID must also be
566 /// removed from [`AppContext`].
567 fn remove_window(&mut self, window_id: WindowId);
568 
569 /// \return the window ID of the window that is active (has typing focus), or None if none.
570 fn active_window_id(&self) -> Option<WindowId>;
571 
572 /// \return the active window is an alert modal
573 fn key_window_is_modal_panel(&self) -> bool;
574 
575 /// \return if the app is currently active.
576 fn app_is_active(&self) -> bool;
577 
578 /// Makes all the app's windows visible, and transfer focus to whichever window most recently
579 /// had focus.
580 /// \return the window ID of the window that will become active, which may be None if we are on
581 /// a platform that allows the app to run without any open windows.
582 fn activate_app(&self, last_active_window: Option<WindowId>) -> Option<WindowId>;
583 fn show_window_and_focus_app(&self, window_id: WindowId, behavior: WindowFocusBehavior);
584 fn hide_app(&self);
585 fn hide_window(&self, window_id: WindowId);
586 fn set_window_bounds(&self, window_id: WindowId, bound: RectF);
587 
588 /// Sets the background blur radius for all windows to the given `blur_radius_pixels` value.
589 fn set_all_windows_background_blur_radius(&self, blur_radius_pixels: u8);
590 
591 /// [Windows only] Sets the background blur texture (Acrylic) for all windows.
592 fn set_all_windows_background_blur_texture(&self, use_blur_texture: bool);
593 
594 fn set_window_title(&self, window_id: WindowId, title: &str);
595 
596 /// Closes a window asynchronously. This is done asynchronously solely because the UI framework
597 /// incorrectly assumes that a call to platform code cannot synchronously trigger a callback
598 /// back to the UI framework. For example, closing window will also synchronously trigger a
599 /// `window_will_close`, which will crash the app with a BorrowMut error. To avoid this error,
600 /// we do this asynchronously.
601 fn close_window_async(&self, window_id: WindowId, termination_mode: TerminationMode);
602 
603 /// Returns the display bound for the current active display.
604 fn active_display_bounds(&self) -> geometry::rect::RectF;
605 /// Returns the unique identifier for the current active display.
606 fn active_display_id(&self) -> DisplayId;
607 fn display_count(&self) -> usize;
608 fn bounds_for_display_idx(&self, idx: DisplayIdx) -> Option<RectF>;
609 
610 fn active_cursor_position_updated(&self);
611 
612 fn windowing_system(&self) -> Option<crate::windowing::System>;
613 
614 /// The name of the operating system's window server/manager/compositor.
615 fn os_window_manager_name(&self) -> Option<String>;
616 /// Whether or not this is a tiling window manager.
617 fn is_tiling_window_manager(&self) -> bool;
618 
619 /// Returns the IDs of all application windows in front-to-back z-order.
620 /// An empty vector indicates that z-ordering information is not available
621 /// on this platform.
622 fn ordered_window_ids(&self) -> Vec<WindowId> {
623 vec![]
624 }
625 
626 fn cancel_synthetic_drag(&self, _window_id: WindowId) {}
627}
628 
629#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
630pub enum SystemTheme {
631 #[default]
632 Light,
633 Dark,
634}
635 
636#[derive(Clone, Copy, Debug, PartialEq, Eq)]
637pub enum Cursor {
638 Arrow,
639 IBeam,
640 Crosshair,
641 OpenHand,
642 ClosedHand,
643 NotAllowed,
644 PointingHand,
645 ResizeLeftRight,
646 ResizeUpDown,
647 /// The drag copy cursor, indicating the currently will result in a copy action.
648 DragCopy,
649}
650 
651/// The current operating system in which this library is running. If on the web, this reads the
652/// user agent to determine the backing OS, otherwise this is determined at compile time based on
653/// the value of `target_arch` (<https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch>).
654#[derive(Copy, Clone, Debug, PartialEq)]
655pub enum OperatingSystem {
656 /// Any distribution of Linux.
657 Linux,
658 /// MacOS.
659 Mac,
660 /// Windows.
661 Windows,
662 /// The operating system is unknown or not one of the ones specified.
663 /// Contains the name of the operating system if it is known.
664 Other(Option<&'static str>),
665}
666 
667#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
668pub enum ShellFamily {
669 Posix,
670 PowerShell,
671}
672 
673impl ShellFamily {
674 pub fn escape<'a>(&self, value: &'a str) -> Cow<'a, str> {
675 match self {
676 ShellFamily::Posix => {
677 if value.is_empty() {
678 Cow::Borrowed("''")
679 } else if value.bytes().all(|byte| {
680 matches!(byte, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'/' | b'.' | b'_' | b'-')
681 }) {
682 Cow::Borrowed(value)
683 } else {
684 Cow::Owned(format!("'{}'", value.replace('\'', "'\"'\"'")))
685 }
686 }
687 ShellFamily::PowerShell => {
688 if value.is_empty() {
689 Cow::Borrowed("''")
690 } else {
691 Cow::Owned(format!("'{}'", value.replace('\'', "''")))
692 }
693 }
694 }
695 }
696}
697 
698impl OperatingSystem {
699 pub fn get() -> Self {
700 cfg_if::cfg_if! {
701 if #[cfg(target_family = "wasm")] {
702 wasm::current_platform()
703 } else if #[cfg(target_os = "linux")] {
704 OperatingSystem::Linux
705 } else if #[cfg(target_os = "macos")] {
706 OperatingSystem::Mac
707 } else if #[cfg(windows)] {
708 OperatingSystem::Windows
709 } else {
710 OperatingSystem::Other(None)
711 }
712 }
713 }
714 
715 /// Returns true if the current [`OperatingSystem`] is Mac.
716 pub fn is_mac(&self) -> bool {
717 *self == OperatingSystem::Mac
718 }
719 
720 /// Returns true if the current [`OperatingSystem`] is Linux.
721 pub fn is_linux(&self) -> bool {
722 *self == OperatingSystem::Linux
723 }
724 
725 /// Returns true if the current [`OperatingSystem`] is Windows.
726 pub fn is_windows(&self) -> bool {
727 *self == OperatingSystem::Windows
728 }
729 
730 pub fn default_shell_family(&self) -> ShellFamily {
731 match self {
732 OperatingSystem::Linux | OperatingSystem::Mac | OperatingSystem::Other(_) => {
733 ShellFamily::Posix
734 }
735 OperatingSystem::Windows => ShellFamily::PowerShell,
736 }
737 }
738}
739 
740#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deserialize, Serialize)]
741#[cfg_attr(feature = "schema_gen", derive(schemars::JsonSchema))]
742#[cfg_attr(
743 feature = "schema_gen",
744 schemars(
745 description = "Graphics rendering backend used for display output.",
746 rename_all = "snake_case"
747 )
748)]
749pub enum GraphicsBackend {
750 /// This maps to [`wgpu::Backend::Empty`].
751 #[cfg_attr(
752 feature = "schema_gen",
753 schemars(description = "No-op backend for testing.")
754 )]
755 Empty,
756 #[cfg_attr(feature = "schema_gen", schemars(description = "DirectX 12."))]
757 Dx12,
758 #[cfg_attr(feature = "schema_gen", schemars(description = "Vulkan."))]
759 Vulkan,
760 #[cfg_attr(feature = "schema_gen", schemars(description = "OpenGL."))]
761 Gl,
762 #[cfg_attr(feature = "schema_gen", schemars(description = "Metal."))]
763 Metal,
764 #[cfg_attr(feature = "schema_gen", schemars(description = "WebGPU (browser)."))]
765 BrowserWebGpu,
766}
767 
768impl GraphicsBackend {
769 pub fn to_label(&self) -> &'static str {
770 match self {
771 GraphicsBackend::Empty => "",
772 GraphicsBackend::Dx12 => "DirectX 12",
773 GraphicsBackend::Vulkan => "Vulkan",
774 GraphicsBackend::Gl => "OpenGL",
775 GraphicsBackend::Metal => "Metal",
776 GraphicsBackend::BrowserWebGpu => "WebGPU",
777 }
778 }
779}
780