platform.rs

   1mod app_menu;
   2mod keyboard;
   3mod keystroke;
   4
   5#[cfg(all(target_os = "linux", feature = "wayland"))]
   6#[expect(missing_docs)]
   7pub mod layer_shell;
   8
   9#[cfg(any(test, feature = "test-support"))]
  10mod test;
  11
  12#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
  13mod visual_test;
  14
  15#[cfg(all(
  16    feature = "screen-capture",
  17    any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
  18))]
  19pub mod scap_screen_capture;
  20
  21#[cfg(all(
  22    any(target_os = "windows", target_os = "linux"),
  23    feature = "screen-capture"
  24))]
  25pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
  26#[cfg(not(feature = "screen-capture"))]
  27pub(crate) type PlatformScreenCaptureFrame = ();
  28#[cfg(all(target_os = "macos", feature = "screen-capture"))]
  29pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
  30
  31use crate::{
  32    Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
  33    DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
  34    ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
  35    Point, Priority, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene,
  36    ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SystemWindowTab, Task,
  37    ThreadTaskTimings, Window, WindowControlArea, hash, point, px, size,
  38};
  39use anyhow::Result;
  40use async_task::Runnable;
  41use futures::channel::oneshot;
  42#[cfg(any(test, feature = "test-support"))]
  43use image::RgbaImage;
  44use image::codecs::gif::GifDecoder;
  45use image::{AnimationDecoder as _, Frame};
  46use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
  47use scheduler::Instant;
  48pub use scheduler::RunnableMeta;
  49use schemars::JsonSchema;
  50use seahash::SeaHasher;
  51use serde::{Deserialize, Serialize};
  52use smallvec::SmallVec;
  53use std::borrow::Cow;
  54use std::hash::{Hash, Hasher};
  55use std::io::Cursor;
  56use std::ops;
  57use std::time::Duration;
  58use std::{
  59    fmt::{self, Debug},
  60    ops::Range,
  61    path::{Path, PathBuf},
  62    rc::Rc,
  63    sync::Arc,
  64};
  65use strum::EnumIter;
  66use uuid::Uuid;
  67
  68pub use app_menu::*;
  69pub use keyboard::*;
  70pub use keystroke::*;
  71
  72#[cfg(any(test, feature = "test-support"))]
  73pub(crate) use test::*;
  74
  75#[cfg(any(test, feature = "test-support"))]
  76pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
  77
  78#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
  79pub use visual_test::VisualTestPlatform;
  80
  81/// Return which compositor we're guessing we'll use.
  82/// Does not attempt to connect to the given compositor.
  83#[cfg(any(target_os = "linux", target_os = "freebsd"))]
  84#[inline]
  85pub fn guess_compositor() -> &'static str {
  86    if std::env::var_os("ZED_HEADLESS").is_some() {
  87        return "Headless";
  88    }
  89
  90    #[cfg(feature = "wayland")]
  91    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
  92    #[cfg(not(feature = "wayland"))]
  93    let wayland_display: Option<std::ffi::OsString> = None;
  94
  95    #[cfg(feature = "x11")]
  96    let x11_display = std::env::var_os("DISPLAY");
  97    #[cfg(not(feature = "x11"))]
  98    let x11_display: Option<std::ffi::OsString> = None;
  99
 100    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
 101    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
 102
 103    if use_wayland {
 104        "Wayland"
 105    } else if use_x11 {
 106        "X11"
 107    } else {
 108        "Headless"
 109    }
 110}
 111
 112#[expect(missing_docs)]
 113pub trait Platform: 'static {
 114    fn background_executor(&self) -> BackgroundExecutor;
 115    fn foreground_executor(&self) -> ForegroundExecutor;
 116    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 117
 118    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
 119    fn quit(&self);
 120    fn restart(&self, binary_path: Option<PathBuf>);
 121    fn activate(&self, ignoring_other_apps: bool);
 122    fn hide(&self);
 123    fn hide_other_apps(&self);
 124    fn unhide_other_apps(&self);
 125
 126    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
 127    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 128    fn active_window(&self) -> Option<AnyWindowHandle>;
 129    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
 130        None
 131    }
 132
 133    fn is_screen_capture_supported(&self) -> bool {
 134        false
 135    }
 136
 137    fn screen_capture_sources(
 138        &self,
 139    ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
 140        let (sources_tx, sources_rx) = oneshot::channel();
 141        sources_tx
 142            .send(Err(anyhow::anyhow!(
 143                "gpui was compiled without the screen-capture feature"
 144            )))
 145            .ok();
 146        sources_rx
 147    }
 148
 149    fn open_window(
 150        &self,
 151        handle: AnyWindowHandle,
 152        options: WindowParams,
 153    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
 154
 155    /// Returns the appearance of the application's windows.
 156    fn window_appearance(&self) -> WindowAppearance;
 157
 158    fn open_url(&self, url: &str);
 159    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
 160    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
 161
 162    fn prompt_for_paths(
 163        &self,
 164        options: PathPromptOptions,
 165    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
 166    fn prompt_for_new_path(
 167        &self,
 168        directory: &Path,
 169        suggested_name: Option<&str>,
 170    ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
 171    fn can_select_mixed_files_and_dirs(&self) -> bool;
 172    fn reveal_path(&self, path: &Path);
 173    fn open_with_system(&self, path: &Path);
 174
 175    fn on_quit(&self, callback: Box<dyn FnMut()>);
 176    fn on_reopen(&self, callback: Box<dyn FnMut()>);
 177
 178    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
 179    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
 180        None
 181    }
 182
 183    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
 184    fn perform_dock_menu_action(&self, _action: usize) {}
 185    fn add_recent_document(&self, _path: &Path) {}
 186    fn update_jump_list(
 187        &self,
 188        _menus: Vec<MenuItem>,
 189        _entries: Vec<SmallVec<[PathBuf; 2]>>,
 190    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
 191        Task::ready(Vec::new())
 192    }
 193    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
 194    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
 195    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 196
 197    fn thermal_state(&self) -> ThermalState;
 198    fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
 199
 200    fn compositor_name(&self) -> &'static str {
 201        ""
 202    }
 203    fn app_path(&self) -> Result<PathBuf>;
 204    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
 205
 206    fn set_cursor_style(&self, style: CursorStyle);
 207    fn should_auto_hide_scrollbars(&self) -> bool;
 208
 209    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 210    fn write_to_clipboard(&self, item: ClipboardItem);
 211
 212    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 213    fn read_from_primary(&self) -> Option<ClipboardItem>;
 214    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 215    fn write_to_primary(&self, item: ClipboardItem);
 216
 217    #[cfg(target_os = "macos")]
 218    fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
 219    #[cfg(target_os = "macos")]
 220    fn write_to_find_pasteboard(&self, item: ClipboardItem);
 221
 222    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
 223    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
 224    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
 225
 226    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
 227    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
 228    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
 229}
 230
 231/// A handle to a platform's display, e.g. a monitor or laptop screen.
 232pub trait PlatformDisplay: Debug {
 233    /// Get the ID for this display
 234    fn id(&self) -> DisplayId;
 235
 236    /// Returns a stable identifier for this display that can be persisted and used
 237    /// across system restarts.
 238    fn uuid(&self) -> Result<Uuid>;
 239
 240    /// Get the bounds for this display
 241    fn bounds(&self) -> Bounds<Pixels>;
 242
 243    /// Get the visible bounds for this display, excluding taskbar/dock areas.
 244    /// This is the usable area where windows can be placed without being obscured.
 245    /// Defaults to the full display bounds if not overridden.
 246    fn visible_bounds(&self) -> Bounds<Pixels> {
 247        self.bounds()
 248    }
 249
 250    /// Get the default bounds for this display to place a window
 251    fn default_bounds(&self) -> Bounds<Pixels> {
 252        let bounds = self.bounds();
 253        let center = bounds.center();
 254        let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
 255
 256        let offset = clipped_window_size / 2.0;
 257        let origin = point(center.x - offset.width, center.y - offset.height);
 258        Bounds::new(origin, clipped_window_size)
 259    }
 260}
 261
 262/// Thermal state of the system
 263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 264pub enum ThermalState {
 265    /// System has no thermal constraints
 266    Nominal,
 267    /// System is slightly constrained, reduce discretionary work
 268    Fair,
 269    /// System is moderately constrained, reduce CPU/GPU intensive work
 270    Serious,
 271    /// System is critically constrained, minimize all resource usage
 272    Critical,
 273}
 274
 275/// Metadata for a given [ScreenCaptureSource]
 276#[derive(Clone)]
 277pub struct SourceMetadata {
 278    /// Opaque identifier of this screen.
 279    pub id: u64,
 280    /// Human-readable label for this source.
 281    pub label: Option<SharedString>,
 282    /// Whether this source is the main display.
 283    pub is_main: Option<bool>,
 284    /// Video resolution of this source.
 285    pub resolution: Size<DevicePixels>,
 286}
 287
 288/// A source of on-screen video content that can be captured.
 289pub trait ScreenCaptureSource {
 290    /// Returns metadata for this source.
 291    fn metadata(&self) -> Result<SourceMetadata>;
 292
 293    /// Start capture video from this source, invoking the given callback
 294    /// with each frame.
 295    fn stream(
 296        &self,
 297        foreground_executor: &ForegroundExecutor,
 298        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
 299    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
 300}
 301
 302/// A video stream captured from a screen.
 303pub trait ScreenCaptureStream {
 304    /// Returns metadata for this source.
 305    fn metadata(&self) -> Result<SourceMetadata>;
 306}
 307
 308/// A frame of video captured from a screen.
 309pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
 310
 311/// An opaque identifier for a hardware display
 312#[derive(PartialEq, Eq, Hash, Copy, Clone)]
 313pub struct DisplayId(pub(crate) u32);
 314
 315impl DisplayId {
 316    /// Create a new `DisplayId` from a raw platform display identifier.
 317    pub fn new(id: u32) -> Self {
 318        Self(id)
 319    }
 320}
 321
 322impl From<u32> for DisplayId {
 323    fn from(id: u32) -> Self {
 324        Self(id)
 325    }
 326}
 327
 328impl From<DisplayId> for u32 {
 329    fn from(id: DisplayId) -> Self {
 330        id.0
 331    }
 332}
 333
 334impl Debug for DisplayId {
 335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 336        write!(f, "DisplayId({})", self.0)
 337    }
 338}
 339
 340/// Which part of the window to resize
 341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 342pub enum ResizeEdge {
 343    /// The top edge
 344    Top,
 345    /// The top right corner
 346    TopRight,
 347    /// The right edge
 348    Right,
 349    /// The bottom right corner
 350    BottomRight,
 351    /// The bottom edge
 352    Bottom,
 353    /// The bottom left corner
 354    BottomLeft,
 355    /// The left edge
 356    Left,
 357    /// The top left corner
 358    TopLeft,
 359}
 360
 361/// A type to describe the appearance of a window
 362#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 363pub enum WindowDecorations {
 364    #[default]
 365    /// Server side decorations
 366    Server,
 367    /// Client side decorations
 368    Client,
 369}
 370
 371/// A type to describe how this window is currently configured
 372#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 373pub enum Decorations {
 374    /// The window is configured to use server side decorations
 375    #[default]
 376    Server,
 377    /// The window is configured to use client side decorations
 378    Client {
 379        /// The edge tiling state
 380        tiling: Tiling,
 381    },
 382}
 383
 384/// What window controls this platform supports
 385#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 386pub struct WindowControls {
 387    /// Whether this platform supports fullscreen
 388    pub fullscreen: bool,
 389    /// Whether this platform supports maximize
 390    pub maximize: bool,
 391    /// Whether this platform supports minimize
 392    pub minimize: bool,
 393    /// Whether this platform supports a window menu
 394    pub window_menu: bool,
 395}
 396
 397impl Default for WindowControls {
 398    fn default() -> Self {
 399        // Assume that we can do anything, unless told otherwise
 400        Self {
 401            fullscreen: true,
 402            maximize: true,
 403            minimize: true,
 404            window_menu: true,
 405        }
 406    }
 407}
 408
 409/// A type to describe which sides of the window are currently tiled in some way
 410#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 411pub struct Tiling {
 412    /// Whether the top edge is tiled
 413    pub top: bool,
 414    /// Whether the left edge is tiled
 415    pub left: bool,
 416    /// Whether the right edge is tiled
 417    pub right: bool,
 418    /// Whether the bottom edge is tiled
 419    pub bottom: bool,
 420}
 421
 422impl Tiling {
 423    /// Initializes a [`Tiling`] type with all sides tiled
 424    pub fn tiled() -> Self {
 425        Self {
 426            top: true,
 427            left: true,
 428            right: true,
 429            bottom: true,
 430        }
 431    }
 432
 433    /// Whether any edge is tiled
 434    pub fn is_tiled(&self) -> bool {
 435        self.top || self.left || self.right || self.bottom
 436    }
 437}
 438
 439#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
 440#[expect(missing_docs)]
 441pub struct RequestFrameOptions {
 442    /// Whether a presentation is required.
 443    pub require_presentation: bool,
 444    /// Force refresh of all rendering states when true.
 445    pub force_render: bool,
 446}
 447
 448#[expect(missing_docs)]
 449pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
 450    fn bounds(&self) -> Bounds<Pixels>;
 451    fn is_maximized(&self) -> bool;
 452    fn window_bounds(&self) -> WindowBounds;
 453    fn content_size(&self) -> Size<Pixels>;
 454    fn resize(&mut self, size: Size<Pixels>);
 455    fn scale_factor(&self) -> f32;
 456    fn appearance(&self) -> WindowAppearance;
 457    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 458    fn mouse_position(&self) -> Point<Pixels>;
 459    fn modifiers(&self) -> Modifiers;
 460    fn capslock(&self) -> Capslock;
 461    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
 462    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
 463    fn prompt(
 464        &self,
 465        level: PromptLevel,
 466        msg: &str,
 467        detail: Option<&str>,
 468        answers: &[PromptButton],
 469    ) -> Option<oneshot::Receiver<usize>>;
 470    fn activate(&self);
 471    fn is_active(&self) -> bool;
 472    fn is_hovered(&self) -> bool;
 473    fn background_appearance(&self) -> WindowBackgroundAppearance;
 474    fn set_title(&mut self, title: &str);
 475    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
 476    fn minimize(&self);
 477    fn zoom(&self);
 478    fn toggle_fullscreen(&self);
 479    fn is_fullscreen(&self) -> bool;
 480    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
 481    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
 482    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
 483    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
 484    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
 485    fn on_moved(&self, callback: Box<dyn FnMut()>);
 486    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
 487    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
 488    fn on_close(&self, callback: Box<dyn FnOnce()>);
 489    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
 490    fn draw(&self, scene: &Scene);
 491    fn completed_frame(&self) {}
 492    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
 493    fn is_subpixel_rendering_supported(&self) -> bool;
 494
 495    // macOS specific methods
 496    fn get_title(&self) -> String {
 497        String::new()
 498    }
 499    fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
 500        None
 501    }
 502    fn tab_bar_visible(&self) -> bool {
 503        false
 504    }
 505    fn set_edited(&mut self, _edited: bool) {}
 506    fn show_character_palette(&self) {}
 507    fn titlebar_double_click(&self) {}
 508    fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
 509    fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
 510    fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
 511    fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
 512    fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
 513    fn merge_all_windows(&self) {}
 514    fn move_tab_to_new_window(&self) {}
 515    fn toggle_window_tab_overview(&self) {}
 516    fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
 517
 518    #[cfg(target_os = "windows")]
 519    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
 520
 521    // Linux specific methods
 522    fn inner_window_bounds(&self) -> WindowBounds {
 523        self.window_bounds()
 524    }
 525    fn request_decorations(&self, _decorations: WindowDecorations) {}
 526    fn show_window_menu(&self, _position: Point<Pixels>) {}
 527    fn start_window_move(&self) {}
 528    fn start_window_resize(&self, _edge: ResizeEdge) {}
 529    fn window_decorations(&self) -> Decorations {
 530        Decorations::Server
 531    }
 532    fn set_app_id(&mut self, _app_id: &str) {}
 533    fn map_window(&mut self) -> anyhow::Result<()> {
 534        Ok(())
 535    }
 536    fn window_controls(&self) -> WindowControls {
 537        WindowControls::default()
 538    }
 539    fn set_client_inset(&self, _inset: Pixels) {}
 540    fn gpu_specs(&self) -> Option<GpuSpecs>;
 541
 542    fn update_ime_position(&self, _bounds: Bounds<Pixels>);
 543
 544    #[cfg(any(test, feature = "test-support"))]
 545    fn as_test(&mut self) -> Option<&mut TestWindow> {
 546        None
 547    }
 548
 549    /// Renders the given scene to a texture and returns the pixel data as an RGBA image.
 550    /// This does not present the frame to screen - useful for visual testing where we want
 551    /// to capture what would be rendered without displaying it or requiring the window to be visible.
 552    #[cfg(any(test, feature = "test-support"))]
 553    fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
 554        anyhow::bail!("render_to_image not implemented for this platform")
 555    }
 556}
 557
 558/// Type alias for runnables with metadata.
 559/// Previously an enum with a single variant, now simplified to a direct type alias.
 560#[doc(hidden)]
 561pub type RunnableVariant = Runnable<RunnableMeta>;
 562
 563#[doc(hidden)]
 564pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
 565
 566/// This type is public so that our test macro can generate and use it, but it should not
 567/// be considered part of our public API.
 568#[doc(hidden)]
 569pub trait PlatformDispatcher: Send + Sync {
 570    fn get_all_timings(&self) -> Vec<ThreadTaskTimings>;
 571    fn get_current_thread_timings(&self) -> ThreadTaskTimings;
 572    fn is_main_thread(&self) -> bool;
 573    fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
 574    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
 575    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
 576    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
 577
 578    fn now(&self) -> Instant {
 579        Instant::now()
 580    }
 581
 582    fn increase_timer_resolution(&self) -> TimerResolutionGuard {
 583        gpui_util::defer(Box::new(|| {}))
 584    }
 585
 586    #[cfg(any(test, feature = "test-support"))]
 587    fn as_test(&self) -> Option<&TestDispatcher> {
 588        None
 589    }
 590}
 591
 592#[expect(missing_docs)]
 593pub trait PlatformTextSystem: Send + Sync {
 594    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
 595    fn all_font_names(&self) -> Vec<String>;
 596    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
 597    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
 598    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
 599    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
 600    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
 601    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
 602    fn rasterize_glyph(
 603        &self,
 604        params: &RenderGlyphParams,
 605        raster_bounds: Bounds<DevicePixels>,
 606    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
 607    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
 608    fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
 609    -> TextRenderingMode;
 610}
 611
 612#[expect(missing_docs)]
 613pub struct NoopTextSystem;
 614
 615#[expect(missing_docs)]
 616impl NoopTextSystem {
 617    #[allow(dead_code)]
 618    pub fn new() -> Self {
 619        Self
 620    }
 621}
 622
 623impl PlatformTextSystem for NoopTextSystem {
 624    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
 625        Ok(())
 626    }
 627
 628    fn all_font_names(&self) -> Vec<String> {
 629        Vec::new()
 630    }
 631
 632    fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
 633        Ok(FontId(1))
 634    }
 635
 636    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
 637        FontMetrics {
 638            units_per_em: 1000,
 639            ascent: 1025.0,
 640            descent: -275.0,
 641            line_gap: 0.0,
 642            underline_position: -95.0,
 643            underline_thickness: 60.0,
 644            cap_height: 698.0,
 645            x_height: 516.0,
 646            bounding_box: Bounds {
 647                origin: Point {
 648                    x: -260.0,
 649                    y: -245.0,
 650                },
 651                size: Size {
 652                    width: 1501.0,
 653                    height: 1364.0,
 654                },
 655            },
 656        }
 657    }
 658
 659    fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
 660        Ok(Bounds {
 661            origin: Point { x: 54.0, y: 0.0 },
 662            size: size(392.0, 528.0),
 663        })
 664    }
 665
 666    fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
 667        Ok(size(600.0 * glyph_id.0 as f32, 0.0))
 668    }
 669
 670    fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
 671        Some(GlyphId(ch.len_utf16() as u32))
 672    }
 673
 674    fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
 675        Ok(Default::default())
 676    }
 677
 678    fn rasterize_glyph(
 679        &self,
 680        _params: &RenderGlyphParams,
 681        raster_bounds: Bounds<DevicePixels>,
 682    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
 683        Ok((raster_bounds.size, Vec::new()))
 684    }
 685
 686    fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
 687        let mut position = px(0.);
 688        let metrics = self.font_metrics(FontId(0));
 689        let em_width = font_size
 690            * self
 691                .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
 692                .unwrap()
 693                .width
 694            / metrics.units_per_em as f32;
 695        let mut glyphs = Vec::new();
 696        for (ix, c) in text.char_indices() {
 697            if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
 698                glyphs.push(ShapedGlyph {
 699                    id: glyph,
 700                    position: point(position, px(0.)),
 701                    index: ix,
 702                    is_emoji: glyph.0 == 2,
 703                });
 704                if glyph.0 == 2 {
 705                    position += em_width * 2.0;
 706                } else {
 707                    position += em_width;
 708                }
 709            } else {
 710                position += em_width
 711            }
 712        }
 713        let mut runs = Vec::default();
 714        if !glyphs.is_empty() {
 715            runs.push(ShapedRun {
 716                font_id: FontId(0),
 717                glyphs,
 718            });
 719        } else {
 720            position = px(0.);
 721        }
 722
 723        LineLayout {
 724            font_size,
 725            width: position,
 726            ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
 727            descent: font_size * (metrics.descent / metrics.units_per_em as f32),
 728            runs,
 729            len: text.len(),
 730        }
 731    }
 732
 733    fn recommended_rendering_mode(
 734        &self,
 735        _font_id: FontId,
 736        _font_size: Pixels,
 737    ) -> TextRenderingMode {
 738        TextRenderingMode::Grayscale
 739    }
 740}
 741
 742// Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
 743// Copyright (c) Microsoft Corporation.
 744// Licensed under the MIT license.
 745/// Compute gamma correction ratios for subpixel text rendering.
 746#[allow(dead_code)]
 747pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
 748    const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
 749        [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
 750        [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
 751        [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
 752        [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
 753        [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
 754        [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
 755        [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
 756        [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
 757        [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
 758        [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
 759        [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
 760        [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
 761        [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
 762    ];
 763
 764    const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
 765    const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
 766
 767    let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
 768    let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
 769
 770    [
 771        ratios[0] * NORM13,
 772        ratios[1] * NORM24,
 773        ratios[2] * NORM13,
 774        ratios[3] * NORM24,
 775    ]
 776}
 777
 778#[derive(PartialEq, Eq, Hash, Clone)]
 779#[expect(missing_docs)]
 780pub enum AtlasKey {
 781    Glyph(RenderGlyphParams),
 782    Svg(RenderSvgParams),
 783    Image(RenderImageParams),
 784}
 785
 786impl AtlasKey {
 787    #[cfg_attr(
 788        all(
 789            any(target_os = "linux", target_os = "freebsd"),
 790            not(any(feature = "x11", feature = "wayland"))
 791        ),
 792        allow(dead_code)
 793    )]
 794    /// Returns the texture kind for this atlas key.
 795    pub fn texture_kind(&self) -> AtlasTextureKind {
 796        match self {
 797            AtlasKey::Glyph(params) => {
 798                if params.is_emoji {
 799                    AtlasTextureKind::Polychrome
 800                } else if params.subpixel_rendering {
 801                    AtlasTextureKind::Subpixel
 802                } else {
 803                    AtlasTextureKind::Monochrome
 804                }
 805            }
 806            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
 807            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
 808        }
 809    }
 810}
 811
 812impl From<RenderGlyphParams> for AtlasKey {
 813    fn from(params: RenderGlyphParams) -> Self {
 814        Self::Glyph(params)
 815    }
 816}
 817
 818impl From<RenderSvgParams> for AtlasKey {
 819    fn from(params: RenderSvgParams) -> Self {
 820        Self::Svg(params)
 821    }
 822}
 823
 824impl From<RenderImageParams> for AtlasKey {
 825    fn from(params: RenderImageParams) -> Self {
 826        Self::Image(params)
 827    }
 828}
 829
 830#[expect(missing_docs)]
 831pub trait PlatformAtlas {
 832    fn get_or_insert_with<'a>(
 833        &self,
 834        key: &AtlasKey,
 835        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
 836    ) -> Result<Option<AtlasTile>>;
 837    fn remove(&self, key: &AtlasKey);
 838}
 839
 840#[doc(hidden)]
 841pub struct AtlasTextureList<T> {
 842    pub textures: Vec<Option<T>>,
 843    pub free_list: Vec<usize>,
 844}
 845
 846impl<T> Default for AtlasTextureList<T> {
 847    fn default() -> Self {
 848        Self {
 849            textures: Vec::default(),
 850            free_list: Vec::default(),
 851        }
 852    }
 853}
 854
 855impl<T> ops::Index<usize> for AtlasTextureList<T> {
 856    type Output = Option<T>;
 857
 858    fn index(&self, index: usize) -> &Self::Output {
 859        &self.textures[index]
 860    }
 861}
 862
 863impl<T> AtlasTextureList<T> {
 864    #[allow(unused)]
 865    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
 866        self.free_list.clear();
 867        self.textures.drain(..)
 868    }
 869
 870    #[allow(dead_code)]
 871    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
 872        self.textures.iter_mut().flatten()
 873    }
 874}
 875
 876#[derive(Clone, Debug, PartialEq, Eq)]
 877#[repr(C)]
 878#[expect(missing_docs)]
 879pub struct AtlasTile {
 880    /// The texture this tile belongs to.
 881    pub texture_id: AtlasTextureId,
 882    /// The unique ID of this tile within its texture.
 883    pub tile_id: TileId,
 884    /// Padding around the tile content in pixels.
 885    pub padding: u32,
 886    /// The bounds of this tile within the texture.
 887    pub bounds: Bounds<DevicePixels>,
 888}
 889
 890#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 891#[repr(C)]
 892#[expect(missing_docs)]
 893pub struct AtlasTextureId {
 894    // We use u32 instead of usize for Metal Shader Language compatibility
 895    /// The index of this texture in the atlas.
 896    pub index: u32,
 897    /// The kind of content stored in this texture.
 898    pub kind: AtlasTextureKind,
 899}
 900
 901#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 902#[repr(C)]
 903#[cfg_attr(
 904    all(
 905        any(target_os = "linux", target_os = "freebsd"),
 906        not(any(feature = "x11", feature = "wayland"))
 907    ),
 908    allow(dead_code)
 909)]
 910#[expect(missing_docs)]
 911pub enum AtlasTextureKind {
 912    Monochrome = 0,
 913    Polychrome = 1,
 914    Subpixel = 2,
 915}
 916
 917#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 918#[repr(C)]
 919#[expect(missing_docs)]
 920pub struct TileId(pub u32);
 921
 922impl From<etagere::AllocId> for TileId {
 923    fn from(id: etagere::AllocId) -> Self {
 924        Self(id.serialize())
 925    }
 926}
 927
 928impl From<TileId> for etagere::AllocId {
 929    fn from(id: TileId) -> Self {
 930        Self::deserialize(id.0)
 931    }
 932}
 933
 934#[expect(missing_docs)]
 935pub struct PlatformInputHandler {
 936    cx: AsyncWindowContext,
 937    handler: Box<dyn InputHandler>,
 938}
 939
 940#[expect(missing_docs)]
 941#[cfg_attr(
 942    all(
 943        any(target_os = "linux", target_os = "freebsd"),
 944        not(any(feature = "x11", feature = "wayland"))
 945    ),
 946    allow(dead_code)
 947)]
 948impl PlatformInputHandler {
 949    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
 950        Self { cx, handler }
 951    }
 952
 953    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
 954        self.cx
 955            .update(|window, cx| {
 956                self.handler
 957                    .selected_text_range(ignore_disabled_input, window, cx)
 958            })
 959            .ok()
 960            .flatten()
 961    }
 962
 963    #[cfg_attr(target_os = "windows", allow(dead_code))]
 964    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
 965        self.cx
 966            .update(|window, cx| self.handler.marked_text_range(window, cx))
 967            .ok()
 968            .flatten()
 969    }
 970
 971    #[cfg_attr(
 972        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
 973        allow(dead_code)
 974    )]
 975    pub fn text_for_range(
 976        &mut self,
 977        range_utf16: Range<usize>,
 978        adjusted: &mut Option<Range<usize>>,
 979    ) -> Option<String> {
 980        self.cx
 981            .update(|window, cx| {
 982                self.handler
 983                    .text_for_range(range_utf16, adjusted, window, cx)
 984            })
 985            .ok()
 986            .flatten()
 987    }
 988
 989    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
 990        self.cx
 991            .update(|window, cx| {
 992                self.handler
 993                    .replace_text_in_range(replacement_range, text, window, cx);
 994            })
 995            .ok();
 996    }
 997
 998    pub fn replace_and_mark_text_in_range(
 999        &mut self,
1000        range_utf16: Option<Range<usize>>,
1001        new_text: &str,
1002        new_selected_range: Option<Range<usize>>,
1003    ) {
1004        self.cx
1005            .update(|window, cx| {
1006                self.handler.replace_and_mark_text_in_range(
1007                    range_utf16,
1008                    new_text,
1009                    new_selected_range,
1010                    window,
1011                    cx,
1012                )
1013            })
1014            .ok();
1015    }
1016
1017    #[cfg_attr(target_os = "windows", allow(dead_code))]
1018    pub fn unmark_text(&mut self) {
1019        self.cx
1020            .update(|window, cx| self.handler.unmark_text(window, cx))
1021            .ok();
1022    }
1023
1024    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1025        self.cx
1026            .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1027            .ok()
1028            .flatten()
1029    }
1030
1031    #[allow(dead_code)]
1032    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1033        self.handler.apple_press_and_hold_enabled()
1034    }
1035
1036    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1037        self.handler.replace_text_in_range(None, input, window, cx);
1038    }
1039
1040    pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1041        let selection = self.handler.selected_text_range(true, window, cx)?;
1042        self.handler.bounds_for_range(
1043            if selection.reversed {
1044                selection.range.start..selection.range.start
1045            } else {
1046                selection.range.end..selection.range.end
1047            },
1048            window,
1049            cx,
1050        )
1051    }
1052
1053    #[allow(unused)]
1054    pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1055        self.cx
1056            .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1057            .ok()
1058            .flatten()
1059    }
1060
1061    #[allow(dead_code)]
1062    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1063        self.handler.accepts_text_input(window, cx)
1064    }
1065}
1066
1067/// A struct representing a selection in a text buffer, in UTF16 characters.
1068/// This is different from a range because the head may be before the tail.
1069#[derive(Debug)]
1070pub struct UTF16Selection {
1071    /// The range of text in the document this selection corresponds to
1072    /// in UTF16 characters.
1073    pub range: Range<usize>,
1074    /// Whether the head of this selection is at the start (true), or end (false)
1075    /// of the range
1076    pub reversed: bool,
1077}
1078
1079/// Zed's interface for handling text input from the platform's IME system
1080/// This is currently a 1:1 exposure of the NSTextInputClient API:
1081///
1082/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
1083pub trait InputHandler: 'static {
1084    /// Get the range of the user's currently selected text, if any
1085    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
1086    ///
1087    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1088    fn selected_text_range(
1089        &mut self,
1090        ignore_disabled_input: bool,
1091        window: &mut Window,
1092        cx: &mut App,
1093    ) -> Option<UTF16Selection>;
1094
1095    /// Get the range of the currently marked text, if any
1096    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
1097    ///
1098    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
1099    fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1100
1101    /// Get the text for the given document range in UTF-16 characters
1102    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
1103    ///
1104    /// range_utf16 is in terms of UTF-16 characters
1105    fn text_for_range(
1106        &mut self,
1107        range_utf16: Range<usize>,
1108        adjusted_range: &mut Option<Range<usize>>,
1109        window: &mut Window,
1110        cx: &mut App,
1111    ) -> Option<String>;
1112
1113    /// Replace the text in the given document range with the given text
1114    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
1115    ///
1116    /// replacement_range is in terms of UTF-16 characters
1117    fn replace_text_in_range(
1118        &mut self,
1119        replacement_range: Option<Range<usize>>,
1120        text: &str,
1121        window: &mut Window,
1122        cx: &mut App,
1123    );
1124
1125    /// Replace the text in the given document range with the given text,
1126    /// and mark the given text as part of an IME 'composing' state
1127    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
1128    ///
1129    /// range_utf16 is in terms of UTF-16 characters
1130    /// new_selected_range is in terms of UTF-16 characters
1131    fn replace_and_mark_text_in_range(
1132        &mut self,
1133        range_utf16: Option<Range<usize>>,
1134        new_text: &str,
1135        new_selected_range: Option<Range<usize>>,
1136        window: &mut Window,
1137        cx: &mut App,
1138    );
1139
1140    /// Remove the IME 'composing' state from the document
1141    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
1142    fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1143
1144    /// Get the bounds of the given document range in screen coordinates
1145    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
1146    ///
1147    /// This is used for positioning the IME candidate window
1148    fn bounds_for_range(
1149        &mut self,
1150        range_utf16: Range<usize>,
1151        window: &mut Window,
1152        cx: &mut App,
1153    ) -> Option<Bounds<Pixels>>;
1154
1155    /// Get the character offset for the given point in terms of UTF16 characters
1156    ///
1157    /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:))
1158    fn character_index_for_point(
1159        &mut self,
1160        point: Point<Pixels>,
1161        window: &mut Window,
1162        cx: &mut App,
1163    ) -> Option<usize>;
1164
1165    /// Allows a given input context to opt into getting raw key repeats instead of
1166    /// sending these to the platform.
1167    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
1168    /// (which is how iTerm does it) but it doesn't seem to work for me.
1169    #[allow(dead_code)]
1170    fn apple_press_and_hold_enabled(&mut self) -> bool {
1171        true
1172    }
1173
1174    /// Returns whether this handler is accepting text input to be inserted.
1175    fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1176        true
1177    }
1178}
1179
1180/// The variables that can be configured when creating a new window
1181#[derive(Debug)]
1182pub struct WindowOptions {
1183    /// Specifies the state and bounds of the window in screen coordinates.
1184    /// - `None`: Inherit the bounds.
1185    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
1186    pub window_bounds: Option<WindowBounds>,
1187
1188    /// The titlebar configuration of the window
1189    pub titlebar: Option<TitlebarOptions>,
1190
1191    /// Whether the window should be focused when created
1192    pub focus: bool,
1193
1194    /// Whether the window should be shown when created
1195    pub show: bool,
1196
1197    /// The kind of window to create
1198    pub kind: WindowKind,
1199
1200    /// Whether the window should be movable by the user
1201    pub is_movable: bool,
1202
1203    /// Whether the window should be resizable by the user
1204    pub is_resizable: bool,
1205
1206    /// Whether the window should be minimized by the user
1207    pub is_minimizable: bool,
1208
1209    /// The display to create the window on, if this is None,
1210    /// the window will be created on the main display
1211    pub display_id: Option<DisplayId>,
1212
1213    /// The appearance of the window background.
1214    pub window_background: WindowBackgroundAppearance,
1215
1216    /// Application identifier of the window. Can by used by desktop environments to group applications together.
1217    pub app_id: Option<String>,
1218
1219    /// Window minimum size
1220    pub window_min_size: Option<Size<Pixels>>,
1221
1222    /// Whether to use client or server side decorations. Wayland only
1223    /// Note that this may be ignored.
1224    pub window_decorations: Option<WindowDecorations>,
1225
1226    /// Tab group name, allows opening the window as a native tab on macOS 10.12+. Windows with the same tabbing identifier will be grouped together.
1227    pub tabbing_identifier: Option<String>,
1228}
1229
1230/// The variables that can be configured when creating a new window
1231#[derive(Debug)]
1232#[cfg_attr(
1233    all(
1234        any(target_os = "linux", target_os = "freebsd"),
1235        not(any(feature = "x11", feature = "wayland"))
1236    ),
1237    allow(dead_code)
1238)]
1239#[allow(missing_docs)]
1240pub struct WindowParams {
1241    pub bounds: Bounds<Pixels>,
1242
1243    /// The titlebar configuration of the window
1244    #[cfg_attr(feature = "wayland", allow(dead_code))]
1245    pub titlebar: Option<TitlebarOptions>,
1246
1247    /// The kind of window to create
1248    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1249    pub kind: WindowKind,
1250
1251    /// Whether the window should be movable by the user
1252    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1253    pub is_movable: bool,
1254
1255    /// Whether the window should be resizable by the user
1256    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1257    pub is_resizable: bool,
1258
1259    /// Whether the window should be minimized by the user
1260    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1261    pub is_minimizable: bool,
1262
1263    #[cfg_attr(
1264        any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1265        allow(dead_code)
1266    )]
1267    pub focus: bool,
1268
1269    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1270    pub show: bool,
1271
1272    #[cfg_attr(feature = "wayland", allow(dead_code))]
1273    pub display_id: Option<DisplayId>,
1274
1275    pub window_min_size: Option<Size<Pixels>>,
1276    #[cfg(target_os = "macos")]
1277    pub tabbing_identifier: Option<String>,
1278}
1279
1280/// Represents the status of how a window should be opened.
1281#[derive(Debug, Copy, Clone, PartialEq)]
1282pub enum WindowBounds {
1283    /// Indicates that the window should open in a windowed state with the given bounds.
1284    Windowed(Bounds<Pixels>),
1285    /// Indicates that the window should open in a maximized state.
1286    /// The bounds provided here represent the restore size of the window.
1287    Maximized(Bounds<Pixels>),
1288    /// Indicates that the window should open in fullscreen mode.
1289    /// The bounds provided here represent the restore size of the window.
1290    Fullscreen(Bounds<Pixels>),
1291}
1292
1293impl Default for WindowBounds {
1294    fn default() -> Self {
1295        WindowBounds::Windowed(Bounds::default())
1296    }
1297}
1298
1299impl WindowBounds {
1300    /// Retrieve the inner bounds
1301    pub fn get_bounds(&self) -> Bounds<Pixels> {
1302        match self {
1303            WindowBounds::Windowed(bounds) => *bounds,
1304            WindowBounds::Maximized(bounds) => *bounds,
1305            WindowBounds::Fullscreen(bounds) => *bounds,
1306        }
1307    }
1308
1309    /// Creates a new window bounds that centers the window on the screen.
1310    pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1311        WindowBounds::Windowed(Bounds::centered(None, size, cx))
1312    }
1313}
1314
1315impl Default for WindowOptions {
1316    fn default() -> Self {
1317        Self {
1318            window_bounds: None,
1319            titlebar: Some(TitlebarOptions {
1320                title: Default::default(),
1321                appears_transparent: Default::default(),
1322                traffic_light_position: Default::default(),
1323            }),
1324            focus: true,
1325            show: true,
1326            kind: WindowKind::Normal,
1327            is_movable: true,
1328            is_resizable: true,
1329            is_minimizable: true,
1330            display_id: None,
1331            window_background: WindowBackgroundAppearance::default(),
1332            app_id: None,
1333            window_min_size: None,
1334            window_decorations: None,
1335            tabbing_identifier: None,
1336        }
1337    }
1338}
1339
1340/// The options that can be configured for a window's titlebar
1341#[derive(Debug, Default)]
1342pub struct TitlebarOptions {
1343    /// The initial title of the window
1344    pub title: Option<SharedString>,
1345
1346    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
1347    /// Refer to [`WindowOptions::window_decorations`] on Linux
1348    pub appears_transparent: bool,
1349
1350    /// The position of the macOS traffic light buttons
1351    pub traffic_light_position: Option<Point<Pixels>>,
1352}
1353
1354/// The kind of window to create
1355#[derive(Clone, Debug, PartialEq, Eq)]
1356pub enum WindowKind {
1357    /// A normal application window
1358    Normal,
1359
1360    /// A window that appears above all other windows, usually used for alerts or popups
1361    /// use sparingly!
1362    PopUp,
1363
1364    /// A floating window that appears on top of its parent window
1365    Floating,
1366
1367    /// A Wayland LayerShell window, used to draw overlays or backgrounds for applications such as
1368    /// docks, notifications or wallpapers.
1369    #[cfg(all(target_os = "linux", feature = "wayland"))]
1370    LayerShell(layer_shell::LayerShellOptions),
1371
1372    /// A window that appears on top of its parent window and blocks interaction with it
1373    /// until the modal window is closed
1374    Dialog,
1375}
1376
1377/// The appearance of the window, as defined by the operating system.
1378///
1379/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
1380/// values.
1381#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1382pub enum WindowAppearance {
1383    /// A light appearance.
1384    ///
1385    /// On macOS, this corresponds to the `aqua` appearance.
1386    #[default]
1387    Light,
1388
1389    /// A light appearance with vibrant colors.
1390    ///
1391    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
1392    VibrantLight,
1393
1394    /// A dark appearance.
1395    ///
1396    /// On macOS, this corresponds to the `darkAqua` appearance.
1397    Dark,
1398
1399    /// A dark appearance with vibrant colors.
1400    ///
1401    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
1402    VibrantDark,
1403}
1404
1405/// The appearance of the background of the window itself, when there is
1406/// no content or the content is transparent.
1407#[derive(Copy, Clone, Debug, Default, PartialEq)]
1408pub enum WindowBackgroundAppearance {
1409    /// Opaque.
1410    ///
1411    /// This lets the window manager know that content behind this
1412    /// window does not need to be drawn.
1413    ///
1414    /// Actual color depends on the system and themes should define a fully
1415    /// opaque background color instead.
1416    #[default]
1417    Opaque,
1418    /// Plain alpha transparency.
1419    Transparent,
1420    /// Transparency, but the contents behind the window are blurred.
1421    ///
1422    /// Not always supported.
1423    Blurred,
1424    /// The Mica backdrop material, supported on Windows 11.
1425    MicaBackdrop,
1426    /// The Mica Alt backdrop material, supported on Windows 11.
1427    MicaAltBackdrop,
1428}
1429
1430/// The text rendering mode to use for drawing glyphs.
1431#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1432pub enum TextRenderingMode {
1433    /// Use the platform's default text rendering mode.
1434    #[default]
1435    PlatformDefault,
1436    /// Use subpixel (ClearType-style) text rendering.
1437    Subpixel,
1438    /// Use grayscale text rendering.
1439    Grayscale,
1440}
1441
1442/// The options that can be configured for a file dialog prompt
1443#[derive(Clone, Debug)]
1444pub struct PathPromptOptions {
1445    /// Should the prompt allow files to be selected?
1446    pub files: bool,
1447    /// Should the prompt allow directories to be selected?
1448    pub directories: bool,
1449    /// Should the prompt allow multiple files to be selected?
1450    pub multiple: bool,
1451    /// The prompt to show to a user when selecting a path
1452    pub prompt: Option<SharedString>,
1453}
1454
1455/// What kind of prompt styling to show
1456#[derive(Copy, Clone, Debug, PartialEq)]
1457pub enum PromptLevel {
1458    /// A prompt that is shown when the user should be notified of something
1459    Info,
1460
1461    /// A prompt that is shown when the user needs to be warned of a potential problem
1462    Warning,
1463
1464    /// A prompt that is shown when a critical problem has occurred
1465    Critical,
1466}
1467
1468/// Prompt Button
1469#[derive(Clone, Debug, PartialEq)]
1470pub enum PromptButton {
1471    /// Ok button
1472    Ok(SharedString),
1473    /// Cancel button
1474    Cancel(SharedString),
1475    /// Other button
1476    Other(SharedString),
1477}
1478
1479impl PromptButton {
1480    /// Create a button with label
1481    pub fn new(label: impl Into<SharedString>) -> Self {
1482        PromptButton::Other(label.into())
1483    }
1484
1485    /// Create an Ok button
1486    pub fn ok(label: impl Into<SharedString>) -> Self {
1487        PromptButton::Ok(label.into())
1488    }
1489
1490    /// Create a Cancel button
1491    pub fn cancel(label: impl Into<SharedString>) -> Self {
1492        PromptButton::Cancel(label.into())
1493    }
1494
1495    /// Returns true if this button is a cancel button.
1496    #[allow(dead_code)]
1497    pub fn is_cancel(&self) -> bool {
1498        matches!(self, PromptButton::Cancel(_))
1499    }
1500
1501    /// Returns the label of the button
1502    pub fn label(&self) -> &SharedString {
1503        match self {
1504            PromptButton::Ok(label) => label,
1505            PromptButton::Cancel(label) => label,
1506            PromptButton::Other(label) => label,
1507        }
1508    }
1509}
1510
1511impl From<&str> for PromptButton {
1512    fn from(value: &str) -> Self {
1513        match value.to_lowercase().as_str() {
1514            "ok" => PromptButton::Ok("Ok".into()),
1515            "cancel" => PromptButton::Cancel("Cancel".into()),
1516            _ => PromptButton::Other(SharedString::from(value.to_owned())),
1517        }
1518    }
1519}
1520
1521/// The style of the cursor (pointer)
1522#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1523pub enum CursorStyle {
1524    /// The default cursor
1525    #[default]
1526    Arrow,
1527
1528    /// A text input cursor
1529    /// corresponds to the CSS cursor value `text`
1530    IBeam,
1531
1532    /// A crosshair cursor
1533    /// corresponds to the CSS cursor value `crosshair`
1534    Crosshair,
1535
1536    /// A closed hand cursor
1537    /// corresponds to the CSS cursor value `grabbing`
1538    ClosedHand,
1539
1540    /// An open hand cursor
1541    /// corresponds to the CSS cursor value `grab`
1542    OpenHand,
1543
1544    /// A pointing hand cursor
1545    /// corresponds to the CSS cursor value `pointer`
1546    PointingHand,
1547
1548    /// A resize left cursor
1549    /// corresponds to the CSS cursor value `w-resize`
1550    ResizeLeft,
1551
1552    /// A resize right cursor
1553    /// corresponds to the CSS cursor value `e-resize`
1554    ResizeRight,
1555
1556    /// A resize cursor to the left and right
1557    /// corresponds to the CSS cursor value `ew-resize`
1558    ResizeLeftRight,
1559
1560    /// A resize up cursor
1561    /// corresponds to the CSS cursor value `n-resize`
1562    ResizeUp,
1563
1564    /// A resize down cursor
1565    /// corresponds to the CSS cursor value `s-resize`
1566    ResizeDown,
1567
1568    /// A resize cursor directing up and down
1569    /// corresponds to the CSS cursor value `ns-resize`
1570    ResizeUpDown,
1571
1572    /// A resize cursor directing up-left and down-right
1573    /// corresponds to the CSS cursor value `nesw-resize`
1574    ResizeUpLeftDownRight,
1575
1576    /// A resize cursor directing up-right and down-left
1577    /// corresponds to the CSS cursor value `nwse-resize`
1578    ResizeUpRightDownLeft,
1579
1580    /// A cursor indicating that the item/column can be resized horizontally.
1581    /// corresponds to the CSS cursor value `col-resize`
1582    ResizeColumn,
1583
1584    /// A cursor indicating that the item/row can be resized vertically.
1585    /// corresponds to the CSS cursor value `row-resize`
1586    ResizeRow,
1587
1588    /// A text input cursor for vertical layout
1589    /// corresponds to the CSS cursor value `vertical-text`
1590    IBeamCursorForVerticalLayout,
1591
1592    /// A cursor indicating that the operation is not allowed
1593    /// corresponds to the CSS cursor value `not-allowed`
1594    OperationNotAllowed,
1595
1596    /// A cursor indicating that the operation will result in a link
1597    /// corresponds to the CSS cursor value `alias`
1598    DragLink,
1599
1600    /// A cursor indicating that the operation will result in a copy
1601    /// corresponds to the CSS cursor value `copy`
1602    DragCopy,
1603
1604    /// A cursor indicating that the operation will result in a context menu
1605    /// corresponds to the CSS cursor value `context-menu`
1606    ContextualMenu,
1607
1608    /// Hide the cursor
1609    None,
1610}
1611
1612/// A clipboard item that should be copied to the clipboard
1613#[derive(Clone, Debug, Eq, PartialEq)]
1614pub struct ClipboardItem {
1615    /// The entries in this clipboard item.
1616    pub entries: Vec<ClipboardEntry>,
1617}
1618
1619/// Either a ClipboardString or a ClipboardImage
1620#[derive(Clone, Debug, Eq, PartialEq)]
1621pub enum ClipboardEntry {
1622    /// A string entry
1623    String(ClipboardString),
1624    /// An image entry
1625    Image(Image),
1626    /// A file entry
1627    ExternalPaths(crate::ExternalPaths),
1628}
1629
1630impl ClipboardItem {
1631    /// Create a new ClipboardItem::String with no associated metadata
1632    pub fn new_string(text: String) -> Self {
1633        Self {
1634            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1635        }
1636    }
1637
1638    /// Create a new ClipboardItem::String with the given text and associated metadata
1639    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1640        Self {
1641            entries: vec![ClipboardEntry::String(ClipboardString {
1642                text,
1643                metadata: Some(metadata),
1644            })],
1645        }
1646    }
1647
1648    /// Create a new ClipboardItem::String with the given text and associated metadata
1649    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1650        Self {
1651            entries: vec![ClipboardEntry::String(
1652                ClipboardString::new(text).with_json_metadata(metadata),
1653            )],
1654        }
1655    }
1656
1657    /// Create a new ClipboardItem::Image with the given image with no associated metadata
1658    pub fn new_image(image: &Image) -> Self {
1659        Self {
1660            entries: vec![ClipboardEntry::Image(image.clone())],
1661        }
1662    }
1663
1664    /// Concatenates together all the ClipboardString entries in the item.
1665    /// Returns None if there were no ClipboardString entries.
1666    pub fn text(&self) -> Option<String> {
1667        let mut answer = String::new();
1668
1669        for entry in self.entries.iter() {
1670            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1671                answer.push_str(text);
1672            }
1673        }
1674
1675        if answer.is_empty() {
1676            for entry in self.entries.iter() {
1677                if let ClipboardEntry::ExternalPaths(paths) = entry {
1678                    for path in &paths.0 {
1679                        use std::fmt::Write as _;
1680                        _ = write!(answer, "{}", path.display());
1681                    }
1682                }
1683            }
1684        }
1685
1686        if !answer.is_empty() {
1687            Some(answer)
1688        } else {
1689            None
1690        }
1691    }
1692
1693    /// If this item is one ClipboardEntry::String, returns its metadata.
1694    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1695    pub fn metadata(&self) -> Option<&String> {
1696        match self.entries().first() {
1697            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1698                clipboard_string.metadata.as_ref()
1699            }
1700            _ => None,
1701        }
1702    }
1703
1704    /// Get the item's entries
1705    pub fn entries(&self) -> &[ClipboardEntry] {
1706        &self.entries
1707    }
1708
1709    /// Get owned versions of the item's entries
1710    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1711        self.entries.into_iter()
1712    }
1713}
1714
1715impl From<ClipboardString> for ClipboardEntry {
1716    fn from(value: ClipboardString) -> Self {
1717        Self::String(value)
1718    }
1719}
1720
1721impl From<String> for ClipboardEntry {
1722    fn from(value: String) -> Self {
1723        Self::from(ClipboardString::from(value))
1724    }
1725}
1726
1727impl From<Image> for ClipboardEntry {
1728    fn from(value: Image) -> Self {
1729        Self::Image(value)
1730    }
1731}
1732
1733impl From<ClipboardEntry> for ClipboardItem {
1734    fn from(value: ClipboardEntry) -> Self {
1735        Self {
1736            entries: vec![value],
1737        }
1738    }
1739}
1740
1741impl From<String> for ClipboardItem {
1742    fn from(value: String) -> Self {
1743        Self::from(ClipboardEntry::from(value))
1744    }
1745}
1746
1747impl From<Image> for ClipboardItem {
1748    fn from(value: Image) -> Self {
1749        Self::from(ClipboardEntry::from(value))
1750    }
1751}
1752
1753/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
1754#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1755pub enum ImageFormat {
1756    // Sorted from most to least likely to be pasted into an editor,
1757    // which matters when we iterate through them trying to see if
1758    // clipboard content matches them.
1759    /// .png
1760    Png,
1761    /// .jpeg or .jpg
1762    Jpeg,
1763    /// .webp
1764    Webp,
1765    /// .gif
1766    Gif,
1767    /// .svg
1768    Svg,
1769    /// .bmp
1770    Bmp,
1771    /// .tif or .tiff
1772    Tiff,
1773    /// .ico
1774    Ico,
1775}
1776
1777impl ImageFormat {
1778    /// Returns the mime type for the ImageFormat
1779    pub const fn mime_type(self) -> &'static str {
1780        match self {
1781            ImageFormat::Png => "image/png",
1782            ImageFormat::Jpeg => "image/jpeg",
1783            ImageFormat::Webp => "image/webp",
1784            ImageFormat::Gif => "image/gif",
1785            ImageFormat::Svg => "image/svg+xml",
1786            ImageFormat::Bmp => "image/bmp",
1787            ImageFormat::Tiff => "image/tiff",
1788            ImageFormat::Ico => "image/ico",
1789        }
1790    }
1791
1792    /// Returns the ImageFormat for the given mime type
1793    pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1794        match mime_type {
1795            "image/png" => Some(Self::Png),
1796            "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1797            "image/webp" => Some(Self::Webp),
1798            "image/gif" => Some(Self::Gif),
1799            "image/svg+xml" => Some(Self::Svg),
1800            "image/bmp" => Some(Self::Bmp),
1801            "image/tiff" | "image/tif" => Some(Self::Tiff),
1802            "image/ico" => Some(Self::Ico),
1803            _ => None,
1804        }
1805    }
1806}
1807
1808/// An image, with a format and certain bytes
1809#[derive(Clone, Debug, PartialEq, Eq)]
1810pub struct Image {
1811    /// The image format the bytes represent (e.g. PNG)
1812    pub format: ImageFormat,
1813    /// The raw image bytes
1814    pub bytes: Vec<u8>,
1815    /// The unique ID for the image
1816    pub id: u64,
1817}
1818
1819impl Hash for Image {
1820    fn hash<H: Hasher>(&self, state: &mut H) {
1821        state.write_u64(self.id);
1822    }
1823}
1824
1825impl Image {
1826    /// An empty image containing no data
1827    pub fn empty() -> Self {
1828        Self::from_bytes(ImageFormat::Png, Vec::new())
1829    }
1830
1831    /// Create an image from a format and bytes
1832    pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1833        Self {
1834            id: hash(&bytes),
1835            format,
1836            bytes,
1837        }
1838    }
1839
1840    /// Get this image's ID
1841    pub fn id(&self) -> u64 {
1842        self.id
1843    }
1844
1845    /// Use the GPUI `use_asset` API to make this image renderable
1846    pub fn use_render_image(
1847        self: Arc<Self>,
1848        window: &mut Window,
1849        cx: &mut App,
1850    ) -> Option<Arc<RenderImage>> {
1851        ImageSource::Image(self)
1852            .use_data(None, window, cx)
1853            .and_then(|result| result.ok())
1854    }
1855
1856    /// Use the GPUI `get_asset` API to make this image renderable
1857    pub fn get_render_image(
1858        self: Arc<Self>,
1859        window: &mut Window,
1860        cx: &mut App,
1861    ) -> Option<Arc<RenderImage>> {
1862        ImageSource::Image(self)
1863            .get_data(None, window, cx)
1864            .and_then(|result| result.ok())
1865    }
1866
1867    /// Use the GPUI `remove_asset` API to drop this image, if possible.
1868    pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1869        ImageSource::Image(self).remove_asset(cx);
1870    }
1871
1872    /// Convert the clipboard image to an `ImageData` object.
1873    pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1874        fn frames_for_image(
1875            bytes: &[u8],
1876            format: image::ImageFormat,
1877        ) -> Result<SmallVec<[Frame; 1]>> {
1878            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1879
1880            // Convert from RGBA to BGRA.
1881            for pixel in data.chunks_exact_mut(4) {
1882                pixel.swap(0, 2);
1883            }
1884
1885            Ok(SmallVec::from_elem(Frame::new(data), 1))
1886        }
1887
1888        let frames = match self.format {
1889            ImageFormat::Gif => {
1890                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1891                let mut frames = SmallVec::new();
1892
1893                for frame in decoder.into_frames() {
1894                    let mut frame = frame?;
1895                    // Convert from RGBA to BGRA.
1896                    for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1897                        pixel.swap(0, 2);
1898                    }
1899                    frames.push(frame);
1900                }
1901
1902                frames
1903            }
1904            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1905            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1906            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1907            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1908            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1909            ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
1910            ImageFormat::Svg => {
1911                return svg_renderer
1912                    .render_single_frame(&self.bytes, 1.0, false)
1913                    .map_err(Into::into);
1914            }
1915        };
1916
1917        Ok(Arc::new(RenderImage::new(frames)))
1918    }
1919
1920    /// Get the format of the clipboard image
1921    pub fn format(&self) -> ImageFormat {
1922        self.format
1923    }
1924
1925    /// Get the raw bytes of the clipboard image
1926    pub fn bytes(&self) -> &[u8] {
1927        self.bytes.as_slice()
1928    }
1929}
1930
1931/// A clipboard item that should be copied to the clipboard
1932#[derive(Clone, Debug, Eq, PartialEq)]
1933pub struct ClipboardString {
1934    /// The text content.
1935    pub text: String,
1936    /// Optional metadata associated with this clipboard string.
1937    pub metadata: Option<String>,
1938}
1939
1940impl ClipboardString {
1941    /// Create a new clipboard string with the given text
1942    pub fn new(text: String) -> Self {
1943        Self {
1944            text,
1945            metadata: None,
1946        }
1947    }
1948
1949    /// Return a new clipboard item with the metadata replaced by the given metadata,
1950    /// after serializing it as JSON.
1951    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1952        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1953        self
1954    }
1955
1956    /// Get the text of the clipboard string
1957    pub fn text(&self) -> &String {
1958        &self.text
1959    }
1960
1961    /// Get the owned text of the clipboard string
1962    pub fn into_text(self) -> String {
1963        self.text
1964    }
1965
1966    /// Get the metadata of the clipboard string, formatted as JSON
1967    pub fn metadata_json<T>(&self) -> Option<T>
1968    where
1969        T: for<'a> Deserialize<'a>,
1970    {
1971        self.metadata
1972            .as_ref()
1973            .and_then(|m| serde_json::from_str(m).ok())
1974    }
1975
1976    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1977    /// Compute a hash of the given text for clipboard change detection.
1978    pub fn text_hash(text: &str) -> u64 {
1979        let mut hasher = SeaHasher::new();
1980        text.hash(&mut hasher);
1981        hasher.finish()
1982    }
1983}
1984
1985impl From<String> for ClipboardString {
1986    fn from(value: String) -> Self {
1987        Self {
1988            text: value,
1989            metadata: None,
1990        }
1991    }
1992}