platform.rs

   1// todo(windows): remove
   2#![cfg_attr(windows, allow(dead_code))]
   3
   4mod app_menu;
   5mod keystroke;
   6
   7#[cfg(target_os = "linux")]
   8mod linux;
   9
  10#[cfg(target_os = "macos")]
  11mod mac;
  12
  13#[cfg(any(
  14    all(target_os = "linux", any(feature = "x11", feature = "wayland")),
  15    target_os = "windows",
  16    feature = "macos-blade"
  17))]
  18mod blade;
  19
  20#[cfg(any(test, feature = "test-support"))]
  21mod test;
  22
  23#[cfg(target_os = "windows")]
  24mod windows;
  25
  26use crate::{
  27    point, Action, AnyWindowHandle, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds,
  28    DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor,
  29    GPUSpecs, GlyphId, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point,
  30    RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
  31    SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
  32};
  33use anyhow::{anyhow, Result};
  34use async_task::Runnable;
  35use futures::channel::oneshot;
  36use image::codecs::gif::GifDecoder;
  37use image::{AnimationDecoder as _, Frame};
  38use parking::Unparker;
  39use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
  40use seahash::SeaHasher;
  41use serde::{Deserialize, Serialize};
  42use smallvec::SmallVec;
  43use std::borrow::Cow;
  44use std::hash::{Hash, Hasher};
  45use std::io::Cursor;
  46use std::time::{Duration, Instant};
  47use std::{
  48    fmt::{self, Debug},
  49    ops::Range,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    sync::Arc,
  53};
  54use strum::EnumIter;
  55use uuid::Uuid;
  56
  57pub use app_menu::*;
  58pub use keystroke::*;
  59
  60#[cfg(target_os = "linux")]
  61pub(crate) use linux::*;
  62#[cfg(target_os = "macos")]
  63pub(crate) use mac::*;
  64pub use semantic_version::SemanticVersion;
  65#[cfg(any(test, feature = "test-support"))]
  66pub(crate) use test::*;
  67#[cfg(target_os = "windows")]
  68pub(crate) use windows::*;
  69
  70#[cfg(target_os = "macos")]
  71pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
  72    Rc::new(MacPlatform::new(headless))
  73}
  74
  75#[cfg(target_os = "linux")]
  76pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
  77    if headless {
  78        return Rc::new(HeadlessClient::new());
  79    }
  80
  81    match guess_compositor() {
  82        #[cfg(feature = "wayland")]
  83        "Wayland" => Rc::new(WaylandClient::new()),
  84
  85        #[cfg(feature = "x11")]
  86        "X11" => Rc::new(X11Client::new()),
  87
  88        "Headless" => Rc::new(HeadlessClient::new()),
  89        _ => unreachable!(),
  90    }
  91}
  92
  93/// Return which compositor we're guessing we'll use.
  94/// Does not attempt to connect to the given compositor
  95#[cfg(target_os = "linux")]
  96#[inline]
  97pub fn guess_compositor() -> &'static str {
  98    if std::env::var_os("ZED_HEADLESS").is_some() {
  99        return "Headless";
 100    }
 101
 102    #[cfg(feature = "wayland")]
 103    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
 104    #[cfg(not(feature = "wayland"))]
 105    let wayland_display: Option<std::ffi::OsString> = None;
 106
 107    #[cfg(feature = "x11")]
 108    let x11_display = std::env::var_os("DISPLAY");
 109    #[cfg(not(feature = "x11"))]
 110    let x11_display: Option<std::ffi::OsString> = None;
 111
 112    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
 113    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
 114
 115    if use_wayland {
 116        "Wayland"
 117    } else if use_x11 {
 118        "X11"
 119    } else {
 120        "Headless"
 121    }
 122}
 123
 124#[cfg(target_os = "windows")]
 125pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
 126    Rc::new(WindowsPlatform::new())
 127}
 128
 129pub(crate) trait Platform: 'static {
 130    fn background_executor(&self) -> BackgroundExecutor;
 131    fn foreground_executor(&self) -> ForegroundExecutor;
 132    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 133
 134    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
 135    fn quit(&self);
 136    fn restart(&self, binary_path: Option<PathBuf>);
 137    fn activate(&self, ignoring_other_apps: bool);
 138    fn hide(&self);
 139    fn hide_other_apps(&self);
 140    fn unhide_other_apps(&self);
 141
 142    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
 143    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 144    fn active_window(&self) -> Option<AnyWindowHandle>;
 145    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
 146        None
 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(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
 167    fn reveal_path(&self, path: &Path);
 168    fn open_with_system(&self, path: &Path);
 169
 170    fn on_quit(&self, callback: Box<dyn FnMut()>);
 171    fn on_reopen(&self, callback: Box<dyn FnMut()>);
 172
 173    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
 174    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
 175        None
 176    }
 177
 178    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
 179    fn add_recent_document(&self, _path: &Path) {}
 180    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
 181    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
 182    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 183
 184    fn compositor_name(&self) -> &'static str {
 185        ""
 186    }
 187    fn app_path(&self) -> Result<PathBuf>;
 188    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
 189
 190    fn set_cursor_style(&self, style: CursorStyle);
 191    fn should_auto_hide_scrollbars(&self) -> bool;
 192
 193    #[cfg(target_os = "linux")]
 194    fn write_to_primary(&self, item: ClipboardItem);
 195    fn write_to_clipboard(&self, item: ClipboardItem);
 196    #[cfg(target_os = "linux")]
 197    fn read_from_primary(&self) -> Option<ClipboardItem>;
 198    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 199
 200    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
 201    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
 202    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
 203}
 204
 205/// A handle to a platform's display, e.g. a monitor or laptop screen.
 206pub trait PlatformDisplay: Send + Sync + Debug {
 207    /// Get the ID for this display
 208    fn id(&self) -> DisplayId;
 209
 210    /// Returns a stable identifier for this display that can be persisted and used
 211    /// across system restarts.
 212    fn uuid(&self) -> Result<Uuid>;
 213
 214    /// Get the bounds for this display
 215    fn bounds(&self) -> Bounds<Pixels>;
 216
 217    /// Get the default bounds for this display to place a window
 218    fn default_bounds(&self) -> Bounds<Pixels> {
 219        let center = self.bounds().center();
 220        let offset = DEFAULT_WINDOW_SIZE / 2.0;
 221        let origin = point(center.x - offset.width, center.y - offset.height);
 222        Bounds::new(origin, DEFAULT_WINDOW_SIZE)
 223    }
 224}
 225
 226/// An opaque identifier for a hardware display
 227#[derive(PartialEq, Eq, Hash, Copy, Clone)]
 228pub struct DisplayId(pub(crate) u32);
 229
 230impl Debug for DisplayId {
 231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 232        write!(f, "DisplayId({})", self.0)
 233    }
 234}
 235
 236unsafe impl Send for DisplayId {}
 237
 238/// Which part of the window to resize
 239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 240pub enum ResizeEdge {
 241    /// The top edge
 242    Top,
 243    /// The top right corner
 244    TopRight,
 245    /// The right edge
 246    Right,
 247    /// The bottom right corner
 248    BottomRight,
 249    /// The bottom edge
 250    Bottom,
 251    /// The bottom left corner
 252    BottomLeft,
 253    /// The left edge
 254    Left,
 255    /// The top left corner
 256    TopLeft,
 257}
 258
 259/// A type to describe the appearance of a window
 260#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 261pub enum WindowDecorations {
 262    #[default]
 263    /// Server side decorations
 264    Server,
 265    /// Client side decorations
 266    Client,
 267}
 268
 269/// A type to describe how this window is currently configured
 270#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 271pub enum Decorations {
 272    /// The window is configured to use server side decorations
 273    #[default]
 274    Server,
 275    /// The window is configured to use client side decorations
 276    Client {
 277        /// The edge tiling state
 278        tiling: Tiling,
 279    },
 280}
 281
 282/// What window controls this platform supports
 283#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 284pub struct WindowControls {
 285    /// Whether this platform supports fullscreen
 286    pub fullscreen: bool,
 287    /// Whether this platform supports maximize
 288    pub maximize: bool,
 289    /// Whether this platform supports minimize
 290    pub minimize: bool,
 291    /// Whether this platform supports a window menu
 292    pub window_menu: bool,
 293}
 294
 295impl Default for WindowControls {
 296    fn default() -> Self {
 297        // Assume that we can do anything, unless told otherwise
 298        Self {
 299            fullscreen: true,
 300            maximize: true,
 301            minimize: true,
 302            window_menu: true,
 303        }
 304    }
 305}
 306
 307/// A type to describe which sides of the window are currently tiled in some way
 308#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 309pub struct Tiling {
 310    /// Whether the top edge is tiled
 311    pub top: bool,
 312    /// Whether the left edge is tiled
 313    pub left: bool,
 314    /// Whether the right edge is tiled
 315    pub right: bool,
 316    /// Whether the bottom edge is tiled
 317    pub bottom: bool,
 318}
 319
 320impl Tiling {
 321    /// Initializes a [`Tiling`] type with all sides tiled
 322    pub fn tiled() -> Self {
 323        Self {
 324            top: true,
 325            left: true,
 326            right: true,
 327            bottom: true,
 328        }
 329    }
 330
 331    /// Whether any edge is tiled
 332    pub fn is_tiled(&self) -> bool {
 333        self.top || self.left || self.right || self.bottom
 334    }
 335}
 336
 337pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
 338    fn bounds(&self) -> Bounds<Pixels>;
 339    fn is_maximized(&self) -> bool;
 340    fn window_bounds(&self) -> WindowBounds;
 341    fn content_size(&self) -> Size<Pixels>;
 342    fn scale_factor(&self) -> f32;
 343    fn appearance(&self) -> WindowAppearance;
 344    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 345    fn mouse_position(&self) -> Point<Pixels>;
 346    fn modifiers(&self) -> Modifiers;
 347    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
 348    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
 349    fn prompt(
 350        &self,
 351        level: PromptLevel,
 352        msg: &str,
 353        detail: Option<&str>,
 354        answers: &[&str],
 355    ) -> Option<oneshot::Receiver<usize>>;
 356    fn activate(&self);
 357    fn is_active(&self) -> bool;
 358    fn is_hovered(&self) -> bool;
 359    fn set_title(&mut self, title: &str);
 360    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
 361    fn minimize(&self);
 362    fn zoom(&self);
 363    fn toggle_fullscreen(&self);
 364    fn is_fullscreen(&self) -> bool;
 365    fn on_request_frame(&self, callback: Box<dyn FnMut()>);
 366    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
 367    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
 368    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
 369    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
 370    fn on_moved(&self, callback: Box<dyn FnMut()>);
 371    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
 372    fn on_close(&self, callback: Box<dyn FnOnce()>);
 373    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
 374    fn draw(&self, scene: &Scene);
 375    fn completed_frame(&self) {}
 376    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
 377
 378    // macOS specific methods
 379    fn set_edited(&mut self, _edited: bool) {}
 380    fn show_character_palette(&self) {}
 381
 382    #[cfg(target_os = "windows")]
 383    fn get_raw_handle(&self) -> windows::HWND;
 384
 385    // Linux specific methods
 386    fn request_decorations(&self, _decorations: WindowDecorations) {}
 387    fn show_window_menu(&self, _position: Point<Pixels>) {}
 388    fn start_window_move(&self) {}
 389    fn start_window_resize(&self, _edge: ResizeEdge) {}
 390    fn window_decorations(&self) -> Decorations {
 391        Decorations::Server
 392    }
 393    fn set_app_id(&mut self, _app_id: &str) {}
 394    fn window_controls(&self) -> WindowControls {
 395        WindowControls::default()
 396    }
 397    fn set_client_inset(&self, _inset: Pixels) {}
 398    fn gpu_specs(&self) -> Option<GPUSpecs>;
 399
 400    fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);
 401
 402    #[cfg(any(test, feature = "test-support"))]
 403    fn as_test(&mut self) -> Option<&mut TestWindow> {
 404        None
 405    }
 406}
 407
 408/// This type is public so that our test macro can generate and use it, but it should not
 409/// be considered part of our public API.
 410#[doc(hidden)]
 411pub trait PlatformDispatcher: Send + Sync {
 412    fn is_main_thread(&self) -> bool;
 413    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
 414    fn dispatch_on_main_thread(&self, runnable: Runnable);
 415    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
 416    fn park(&self, timeout: Option<Duration>) -> bool;
 417    fn unparker(&self) -> Unparker;
 418    fn now(&self) -> Instant {
 419        Instant::now()
 420    }
 421
 422    #[cfg(any(test, feature = "test-support"))]
 423    fn as_test(&self) -> Option<&TestDispatcher> {
 424        None
 425    }
 426}
 427
 428pub(crate) trait PlatformTextSystem: Send + Sync {
 429    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
 430    fn all_font_names(&self) -> Vec<String>;
 431    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
 432    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
 433    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
 434    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
 435    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
 436    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
 437    fn rasterize_glyph(
 438        &self,
 439        params: &RenderGlyphParams,
 440        raster_bounds: Bounds<DevicePixels>,
 441    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
 442    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
 443}
 444
 445pub(crate) struct NoopTextSystem;
 446
 447impl NoopTextSystem {
 448    #[allow(dead_code)]
 449    pub fn new() -> Self {
 450        Self
 451    }
 452}
 453
 454impl PlatformTextSystem for NoopTextSystem {
 455    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
 456        Ok(())
 457    }
 458
 459    fn all_font_names(&self) -> Vec<String> {
 460        Vec::new()
 461    }
 462
 463    fn font_id(&self, descriptor: &Font) -> Result<FontId> {
 464        Err(anyhow!("No font found for {:?}", descriptor))
 465    }
 466
 467    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
 468        unimplemented!()
 469    }
 470
 471    fn typographic_bounds(&self, font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
 472        Err(anyhow!("No font found for {:?}", font_id))
 473    }
 474
 475    fn advance(&self, font_id: FontId, _glyph_id: GlyphId) -> Result<Size<f32>> {
 476        Err(anyhow!("No font found for {:?}", font_id))
 477    }
 478
 479    fn glyph_for_char(&self, _font_id: FontId, _ch: char) -> Option<GlyphId> {
 480        None
 481    }
 482
 483    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
 484        Err(anyhow!("No font found for {:?}", params))
 485    }
 486
 487    fn rasterize_glyph(
 488        &self,
 489        params: &RenderGlyphParams,
 490        _raster_bounds: Bounds<DevicePixels>,
 491    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
 492        Err(anyhow!("No font found for {:?}", params))
 493    }
 494
 495    fn layout_line(&self, _text: &str, _font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
 496        unimplemented!()
 497    }
 498}
 499
 500#[derive(PartialEq, Eq, Hash, Clone)]
 501pub(crate) enum AtlasKey {
 502    Glyph(RenderGlyphParams),
 503    Svg(RenderSvgParams),
 504    Image(RenderImageParams),
 505}
 506
 507impl AtlasKey {
 508    #[cfg_attr(
 509        all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 510        allow(dead_code)
 511    )]
 512    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
 513        match self {
 514            AtlasKey::Glyph(params) => {
 515                if params.is_emoji {
 516                    AtlasTextureKind::Polychrome
 517                } else {
 518                    AtlasTextureKind::Monochrome
 519                }
 520            }
 521            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
 522            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
 523        }
 524    }
 525}
 526
 527impl From<RenderGlyphParams> for AtlasKey {
 528    fn from(params: RenderGlyphParams) -> Self {
 529        Self::Glyph(params)
 530    }
 531}
 532
 533impl From<RenderSvgParams> for AtlasKey {
 534    fn from(params: RenderSvgParams) -> Self {
 535        Self::Svg(params)
 536    }
 537}
 538
 539impl From<RenderImageParams> for AtlasKey {
 540    fn from(params: RenderImageParams) -> Self {
 541        Self::Image(params)
 542    }
 543}
 544
 545pub(crate) trait PlatformAtlas: Send + Sync {
 546    fn get_or_insert_with<'a>(
 547        &self,
 548        key: &AtlasKey,
 549        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
 550    ) -> Result<Option<AtlasTile>>;
 551}
 552
 553#[derive(Clone, Debug, PartialEq, Eq)]
 554#[repr(C)]
 555pub(crate) struct AtlasTile {
 556    pub(crate) texture_id: AtlasTextureId,
 557    pub(crate) tile_id: TileId,
 558    pub(crate) padding: u32,
 559    pub(crate) bounds: Bounds<DevicePixels>,
 560}
 561
 562#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 563#[repr(C)]
 564pub(crate) struct AtlasTextureId {
 565    // We use u32 instead of usize for Metal Shader Language compatibility
 566    pub(crate) index: u32,
 567    pub(crate) kind: AtlasTextureKind,
 568}
 569
 570#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 571#[repr(C)]
 572#[cfg_attr(
 573    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 574    allow(dead_code)
 575)]
 576pub(crate) enum AtlasTextureKind {
 577    Monochrome = 0,
 578    Polychrome = 1,
 579    Path = 2,
 580}
 581
 582#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 583#[repr(C)]
 584pub(crate) struct TileId(pub(crate) u32);
 585
 586impl From<etagere::AllocId> for TileId {
 587    fn from(id: etagere::AllocId) -> Self {
 588        Self(id.serialize())
 589    }
 590}
 591
 592impl From<TileId> for etagere::AllocId {
 593    fn from(id: TileId) -> Self {
 594        Self::deserialize(id.0)
 595    }
 596}
 597
 598pub(crate) struct PlatformInputHandler {
 599    cx: AsyncWindowContext,
 600    handler: Box<dyn InputHandler>,
 601}
 602
 603#[cfg_attr(
 604    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 605    allow(dead_code)
 606)]
 607impl PlatformInputHandler {
 608    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
 609        Self { cx, handler }
 610    }
 611
 612    fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
 613        self.cx
 614            .update(|cx| self.handler.selected_text_range(ignore_disabled_input, cx))
 615            .ok()
 616            .flatten()
 617    }
 618
 619    fn marked_text_range(&mut self) -> Option<Range<usize>> {
 620        self.cx
 621            .update(|cx| self.handler.marked_text_range(cx))
 622            .ok()
 623            .flatten()
 624    }
 625
 626    #[cfg_attr(target_os = "linux", allow(dead_code))]
 627    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
 628        self.cx
 629            .update(|cx| self.handler.text_for_range(range_utf16, cx))
 630            .ok()
 631            .flatten()
 632    }
 633
 634    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
 635        self.cx
 636            .update(|cx| {
 637                self.handler
 638                    .replace_text_in_range(replacement_range, text, cx);
 639            })
 640            .ok();
 641    }
 642
 643    fn replace_and_mark_text_in_range(
 644        &mut self,
 645        range_utf16: Option<Range<usize>>,
 646        new_text: &str,
 647        new_selected_range: Option<Range<usize>>,
 648    ) {
 649        self.cx
 650            .update(|cx| {
 651                self.handler.replace_and_mark_text_in_range(
 652                    range_utf16,
 653                    new_text,
 654                    new_selected_range,
 655                    cx,
 656                )
 657            })
 658            .ok();
 659    }
 660
 661    fn unmark_text(&mut self) {
 662        self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
 663    }
 664
 665    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
 666        self.cx
 667            .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
 668            .ok()
 669            .flatten()
 670    }
 671
 672    pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
 673        self.handler.replace_text_in_range(None, input, cx);
 674    }
 675
 676    pub fn selected_bounds(&mut self, cx: &mut WindowContext) -> Option<Bounds<Pixels>> {
 677        let selection = self.handler.selected_text_range(true, cx)?;
 678        self.handler.bounds_for_range(
 679            if selection.reversed {
 680                selection.range.start..selection.range.start
 681            } else {
 682                selection.range.end..selection.range.end
 683            },
 684            cx,
 685        )
 686    }
 687}
 688
 689/// A struct representing a selection in a text buffer, in UTF16 characters.
 690/// This is different from a range because the head may be before the tail.
 691pub struct UTF16Selection {
 692    /// The range of text in the document this selection corresponds to
 693    /// in UTF16 characters.
 694    pub range: Range<usize>,
 695    /// Whether the head of this selection is at the start (true), or end (false)
 696    /// of the range
 697    pub reversed: bool,
 698}
 699
 700/// Zed's interface for handling text input from the platform's IME system
 701/// This is currently a 1:1 exposure of the NSTextInputClient API:
 702///
 703/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
 704pub trait InputHandler: 'static {
 705    /// Get the range of the user's currently selected text, if any
 706    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
 707    ///
 708    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
 709    fn selected_text_range(
 710        &mut self,
 711        ignore_disabled_input: bool,
 712        cx: &mut WindowContext,
 713    ) -> Option<UTF16Selection>;
 714
 715    /// Get the range of the currently marked text, if any
 716    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
 717    ///
 718    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
 719    fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
 720
 721    /// Get the text for the given document range in UTF-16 characters
 722    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
 723    ///
 724    /// range_utf16 is in terms of UTF-16 characters
 725    fn text_for_range(
 726        &mut self,
 727        range_utf16: Range<usize>,
 728        cx: &mut WindowContext,
 729    ) -> Option<String>;
 730
 731    /// Replace the text in the given document range with the given text
 732    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
 733    ///
 734    /// replacement_range is in terms of UTF-16 characters
 735    fn replace_text_in_range(
 736        &mut self,
 737        replacement_range: Option<Range<usize>>,
 738        text: &str,
 739        cx: &mut WindowContext,
 740    );
 741
 742    /// Replace the text in the given document range with the given text,
 743    /// and mark the given text as part of an IME 'composing' state
 744    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
 745    ///
 746    /// range_utf16 is in terms of UTF-16 characters
 747    /// new_selected_range is in terms of UTF-16 characters
 748    fn replace_and_mark_text_in_range(
 749        &mut self,
 750        range_utf16: Option<Range<usize>>,
 751        new_text: &str,
 752        new_selected_range: Option<Range<usize>>,
 753        cx: &mut WindowContext,
 754    );
 755
 756    /// Remove the IME 'composing' state from the document
 757    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
 758    fn unmark_text(&mut self, cx: &mut WindowContext);
 759
 760    /// Get the bounds of the given document range in screen coordinates
 761    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
 762    ///
 763    /// This is used for positioning the IME candidate window
 764    fn bounds_for_range(
 765        &mut self,
 766        range_utf16: Range<usize>,
 767        cx: &mut WindowContext,
 768    ) -> Option<Bounds<Pixels>>;
 769}
 770
 771/// The variables that can be configured when creating a new window
 772#[derive(Debug)]
 773pub struct WindowOptions {
 774    /// Specifies the state and bounds of the window in screen coordinates.
 775    /// - `None`: Inherit the bounds.
 776    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
 777    pub window_bounds: Option<WindowBounds>,
 778
 779    /// The titlebar configuration of the window
 780    pub titlebar: Option<TitlebarOptions>,
 781
 782    /// Whether the window should be focused when created
 783    pub focus: bool,
 784
 785    /// Whether the window should be shown when created
 786    pub show: bool,
 787
 788    /// The kind of window to create
 789    pub kind: WindowKind,
 790
 791    /// Whether the window should be movable by the user
 792    pub is_movable: bool,
 793
 794    /// The display to create the window on, if this is None,
 795    /// the window will be created on the main display
 796    pub display_id: Option<DisplayId>,
 797
 798    /// The appearance of the window background.
 799    pub window_background: WindowBackgroundAppearance,
 800
 801    /// Application identifier of the window. Can by used by desktop environments to group applications together.
 802    pub app_id: Option<String>,
 803
 804    /// Window minimum size
 805    pub window_min_size: Option<Size<Pixels>>,
 806
 807    /// Whether to use client or server side decorations. Wayland only
 808    /// Note that this may be ignored.
 809    pub window_decorations: Option<WindowDecorations>,
 810}
 811
 812/// The variables that can be configured when creating a new window
 813#[derive(Debug)]
 814#[cfg_attr(
 815    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 816    allow(dead_code)
 817)]
 818pub(crate) struct WindowParams {
 819    pub bounds: Bounds<Pixels>,
 820
 821    /// The titlebar configuration of the window
 822    #[cfg_attr(feature = "wayland", allow(dead_code))]
 823    pub titlebar: Option<TitlebarOptions>,
 824
 825    /// The kind of window to create
 826    #[cfg_attr(target_os = "linux", allow(dead_code))]
 827    pub kind: WindowKind,
 828
 829    /// Whether the window should be movable by the user
 830    #[cfg_attr(target_os = "linux", allow(dead_code))]
 831    pub is_movable: bool,
 832
 833    #[cfg_attr(target_os = "linux", allow(dead_code))]
 834    pub focus: bool,
 835
 836    #[cfg_attr(target_os = "linux", allow(dead_code))]
 837    pub show: bool,
 838
 839    #[cfg_attr(feature = "wayland", allow(dead_code))]
 840    pub display_id: Option<DisplayId>,
 841
 842    pub window_min_size: Option<Size<Pixels>>,
 843}
 844
 845/// Represents the status of how a window should be opened.
 846#[derive(Debug, Copy, Clone, PartialEq)]
 847pub enum WindowBounds {
 848    /// Indicates that the window should open in a windowed state with the given bounds.
 849    Windowed(Bounds<Pixels>),
 850    /// Indicates that the window should open in a maximized state.
 851    /// The bounds provided here represent the restore size of the window.
 852    Maximized(Bounds<Pixels>),
 853    /// Indicates that the window should open in fullscreen mode.
 854    /// The bounds provided here represent the restore size of the window.
 855    Fullscreen(Bounds<Pixels>),
 856}
 857
 858impl Default for WindowBounds {
 859    fn default() -> Self {
 860        WindowBounds::Windowed(Bounds::default())
 861    }
 862}
 863
 864impl WindowBounds {
 865    /// Retrieve the inner bounds
 866    pub fn get_bounds(&self) -> Bounds<Pixels> {
 867        match self {
 868            WindowBounds::Windowed(bounds) => *bounds,
 869            WindowBounds::Maximized(bounds) => *bounds,
 870            WindowBounds::Fullscreen(bounds) => *bounds,
 871        }
 872    }
 873}
 874
 875impl Default for WindowOptions {
 876    fn default() -> Self {
 877        Self {
 878            window_bounds: None,
 879            titlebar: Some(TitlebarOptions {
 880                title: Default::default(),
 881                appears_transparent: Default::default(),
 882                traffic_light_position: Default::default(),
 883            }),
 884            focus: true,
 885            show: true,
 886            kind: WindowKind::Normal,
 887            is_movable: true,
 888            display_id: None,
 889            window_background: WindowBackgroundAppearance::default(),
 890            app_id: None,
 891            window_min_size: None,
 892            window_decorations: None,
 893        }
 894    }
 895}
 896
 897/// The options that can be configured for a window's titlebar
 898#[derive(Debug, Default)]
 899pub struct TitlebarOptions {
 900    /// The initial title of the window
 901    pub title: Option<SharedString>,
 902
 903    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
 904    /// Refer to [`WindowOptions::window_decorations`] on Linux
 905    pub appears_transparent: bool,
 906
 907    /// The position of the macOS traffic light buttons
 908    pub traffic_light_position: Option<Point<Pixels>>,
 909}
 910
 911/// The kind of window to create
 912#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 913pub enum WindowKind {
 914    /// A normal application window
 915    Normal,
 916
 917    /// A window that appears above all other windows, usually used for alerts or popups
 918    /// use sparingly!
 919    PopUp,
 920}
 921
 922/// The appearance of the window, as defined by the operating system.
 923///
 924/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
 925/// values.
 926#[derive(Copy, Clone, Debug)]
 927pub enum WindowAppearance {
 928    /// A light appearance.
 929    ///
 930    /// On macOS, this corresponds to the `aqua` appearance.
 931    Light,
 932
 933    /// A light appearance with vibrant colors.
 934    ///
 935    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
 936    VibrantLight,
 937
 938    /// A dark appearance.
 939    ///
 940    /// On macOS, this corresponds to the `darkAqua` appearance.
 941    Dark,
 942
 943    /// A dark appearance with vibrant colors.
 944    ///
 945    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
 946    VibrantDark,
 947}
 948
 949impl Default for WindowAppearance {
 950    fn default() -> Self {
 951        Self::Light
 952    }
 953}
 954
 955/// The appearance of the background of the window itself, when there is
 956/// no content or the content is transparent.
 957#[derive(Copy, Clone, Debug, Default, PartialEq)]
 958pub enum WindowBackgroundAppearance {
 959    /// Opaque.
 960    ///
 961    /// This lets the window manager know that content behind this
 962    /// window does not need to be drawn.
 963    ///
 964    /// Actual color depends on the system and themes should define a fully
 965    /// opaque background color instead.
 966    #[default]
 967    Opaque,
 968    /// Plain alpha transparency.
 969    Transparent,
 970    /// Transparency, but the contents behind the window are blurred.
 971    ///
 972    /// Not always supported.
 973    Blurred,
 974}
 975
 976/// The options that can be configured for a file dialog prompt
 977#[derive(Copy, Clone, Debug)]
 978pub struct PathPromptOptions {
 979    /// Should the prompt allow files to be selected?
 980    pub files: bool,
 981    /// Should the prompt allow directories to be selected?
 982    pub directories: bool,
 983    /// Should the prompt allow multiple files to be selected?
 984    pub multiple: bool,
 985}
 986
 987/// What kind of prompt styling to show
 988#[derive(Copy, Clone, Debug, PartialEq)]
 989pub enum PromptLevel {
 990    /// A prompt that is shown when the user should be notified of something
 991    Info,
 992
 993    /// A prompt that is shown when the user needs to be warned of a potential problem
 994    Warning,
 995
 996    /// A prompt that is shown when a critical problem has occurred
 997    Critical,
 998}
 999
1000/// The style of the cursor (pointer)
1001#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1002pub enum CursorStyle {
1003    /// The default cursor
1004    Arrow,
1005
1006    /// A text input cursor
1007    /// corresponds to the CSS cursor value `text`
1008    IBeam,
1009
1010    /// A crosshair cursor
1011    /// corresponds to the CSS cursor value `crosshair`
1012    Crosshair,
1013
1014    /// A closed hand cursor
1015    /// corresponds to the CSS cursor value `grabbing`
1016    ClosedHand,
1017
1018    /// An open hand cursor
1019    /// corresponds to the CSS cursor value `grab`
1020    OpenHand,
1021
1022    /// A pointing hand cursor
1023    /// corresponds to the CSS cursor value `pointer`
1024    PointingHand,
1025
1026    /// A resize left cursor
1027    /// corresponds to the CSS cursor value `w-resize`
1028    ResizeLeft,
1029
1030    /// A resize right cursor
1031    /// corresponds to the CSS cursor value `e-resize`
1032    ResizeRight,
1033
1034    /// A resize cursor to the left and right
1035    /// corresponds to the CSS cursor value `ew-resize`
1036    ResizeLeftRight,
1037
1038    /// A resize up cursor
1039    /// corresponds to the CSS cursor value `n-resize`
1040    ResizeUp,
1041
1042    /// A resize down cursor
1043    /// corresponds to the CSS cursor value `s-resize`
1044    ResizeDown,
1045
1046    /// A resize cursor directing up and down
1047    /// corresponds to the CSS cursor value `ns-resize`
1048    ResizeUpDown,
1049
1050    /// A resize cursor directing up-left and down-right
1051    /// corresponds to the CSS cursor value `nesw-resize`
1052    ResizeUpLeftDownRight,
1053
1054    /// A resize cursor directing up-right and down-left
1055    /// corresponds to the CSS cursor value `nwse-resize`
1056    ResizeUpRightDownLeft,
1057
1058    /// A cursor indicating that the item/column can be resized horizontally.
1059    /// corresponds to the CSS cursor value `col-resize`
1060    ResizeColumn,
1061
1062    /// A cursor indicating that the item/row can be resized vertically.
1063    /// corresponds to the CSS cursor value `row-resize`
1064    ResizeRow,
1065
1066    /// A text input cursor for vertical layout
1067    /// corresponds to the CSS cursor value `vertical-text`
1068    IBeamCursorForVerticalLayout,
1069
1070    /// A cursor indicating that the operation is not allowed
1071    /// corresponds to the CSS cursor value `not-allowed`
1072    OperationNotAllowed,
1073
1074    /// A cursor indicating that the operation will result in a link
1075    /// corresponds to the CSS cursor value `alias`
1076    DragLink,
1077
1078    /// A cursor indicating that the operation will result in a copy
1079    /// corresponds to the CSS cursor value `copy`
1080    DragCopy,
1081
1082    /// A cursor indicating that the operation will result in a context menu
1083    /// corresponds to the CSS cursor value `context-menu`
1084    ContextualMenu,
1085}
1086
1087impl Default for CursorStyle {
1088    fn default() -> Self {
1089        Self::Arrow
1090    }
1091}
1092
1093/// A clipboard item that should be copied to the clipboard
1094#[derive(Clone, Debug, Eq, PartialEq)]
1095pub struct ClipboardItem {
1096    entries: Vec<ClipboardEntry>,
1097}
1098
1099/// Either a ClipboardString or a ClipboardImage
1100#[derive(Clone, Debug, Eq, PartialEq)]
1101pub enum ClipboardEntry {
1102    /// A string entry
1103    String(ClipboardString),
1104    /// An image entry
1105    Image(Image),
1106}
1107
1108impl ClipboardItem {
1109    /// Create a new ClipboardItem::String with no associated metadata
1110    pub fn new_string(text: String) -> Self {
1111        Self {
1112            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1113        }
1114    }
1115
1116    /// Create a new ClipboardItem::String with the given text and associated metadata
1117    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1118        Self {
1119            entries: vec![ClipboardEntry::String(ClipboardString {
1120                text,
1121                metadata: Some(metadata),
1122            })],
1123        }
1124    }
1125
1126    /// Create a new ClipboardItem::String with the given text and associated metadata
1127    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1128        Self {
1129            entries: vec![ClipboardEntry::String(
1130                ClipboardString::new(text).with_json_metadata(metadata),
1131            )],
1132        }
1133    }
1134
1135    /// Create a new ClipboardItem::Image with the given image with no associated metadata
1136    pub fn new_image(image: &Image) -> Self {
1137        Self {
1138            entries: vec![ClipboardEntry::Image(image.clone())],
1139        }
1140    }
1141
1142    /// Concatenates together all the ClipboardString entries in the item.
1143    /// Returns None if there were no ClipboardString entries.
1144    pub fn text(&self) -> Option<String> {
1145        let mut answer = String::new();
1146        let mut any_entries = false;
1147
1148        for entry in self.entries.iter() {
1149            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1150                answer.push_str(text);
1151                any_entries = true;
1152            }
1153        }
1154
1155        if any_entries {
1156            Some(answer)
1157        } else {
1158            None
1159        }
1160    }
1161
1162    /// If this item is one ClipboardEntry::String, returns its metadata.
1163    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1164    pub fn metadata(&self) -> Option<&String> {
1165        match self.entries().first() {
1166            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1167                clipboard_string.metadata.as_ref()
1168            }
1169            _ => None,
1170        }
1171    }
1172
1173    /// Get the item's entries
1174    pub fn entries(&self) -> &[ClipboardEntry] {
1175        &self.entries
1176    }
1177
1178    /// Get owned versions of the item's entries
1179    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1180        self.entries.into_iter()
1181    }
1182}
1183
1184/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
1185#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1186pub enum ImageFormat {
1187    // Sorted from most to least likely to be pasted into an editor,
1188    // which matters when we iterate through them trying to see if
1189    // clipboard content matches them.
1190    /// .png
1191    Png,
1192    /// .jpeg or .jpg
1193    Jpeg,
1194    /// .webp
1195    Webp,
1196    /// .gif
1197    Gif,
1198    /// .svg
1199    Svg,
1200    /// .bmp
1201    Bmp,
1202    /// .tif or .tiff
1203    Tiff,
1204}
1205
1206/// An image, with a format and certain bytes
1207#[derive(Clone, Debug, PartialEq, Eq)]
1208pub struct Image {
1209    /// The image format the bytes represent (e.g. PNG)
1210    pub format: ImageFormat,
1211    /// The raw image bytes
1212    pub bytes: Vec<u8>,
1213    /// The unique ID for the image
1214    pub id: u64,
1215}
1216
1217impl Hash for Image {
1218    fn hash<H: Hasher>(&self, state: &mut H) {
1219        state.write_u64(self.id);
1220    }
1221}
1222
1223impl Image {
1224    /// Get this image's ID
1225    pub fn id(&self) -> u64 {
1226        self.id
1227    }
1228
1229    /// Use the GPUI `use_asset` API to make this image renderable
1230    pub fn use_render_image(self: Arc<Self>, cx: &mut WindowContext) -> Option<Arc<RenderImage>> {
1231        ImageSource::Image(self).use_data(cx)
1232    }
1233
1234    /// Convert the clipboard image to an `ImageData` object.
1235    pub fn to_image_data(&self, cx: &AppContext) -> Result<Arc<RenderImage>> {
1236        fn frames_for_image(
1237            bytes: &[u8],
1238            format: image::ImageFormat,
1239        ) -> Result<SmallVec<[Frame; 1]>> {
1240            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1241
1242            // Convert from RGBA to BGRA.
1243            for pixel in data.chunks_exact_mut(4) {
1244                pixel.swap(0, 2);
1245            }
1246
1247            Ok(SmallVec::from_elem(Frame::new(data), 1))
1248        }
1249
1250        let frames = match self.format {
1251            ImageFormat::Gif => {
1252                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1253                let mut frames = SmallVec::new();
1254
1255                for frame in decoder.into_frames() {
1256                    let mut frame = frame?;
1257                    // Convert from RGBA to BGRA.
1258                    for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1259                        pixel.swap(0, 2);
1260                    }
1261                    frames.push(frame);
1262                }
1263
1264                frames
1265            }
1266            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1267            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1268            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1269            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1270            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1271            ImageFormat::Svg => {
1272                // TODO: Fix this
1273                let pixmap = cx
1274                    .svg_renderer()
1275                    .render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
1276
1277                let buffer =
1278                    image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
1279                        .unwrap();
1280
1281                SmallVec::from_elem(Frame::new(buffer), 1)
1282            }
1283        };
1284
1285        Ok(Arc::new(RenderImage::new(frames)))
1286    }
1287
1288    /// Get the format of the clipboard image
1289    pub fn format(&self) -> ImageFormat {
1290        self.format
1291    }
1292
1293    /// Get the raw bytes of the clipboard image
1294    pub fn bytes(&self) -> &[u8] {
1295        self.bytes.as_slice()
1296    }
1297}
1298
1299/// A clipboard item that should be copied to the clipboard
1300#[derive(Clone, Debug, Eq, PartialEq)]
1301pub struct ClipboardString {
1302    pub(crate) text: String,
1303    pub(crate) metadata: Option<String>,
1304}
1305
1306impl ClipboardString {
1307    /// Create a new clipboard string with the given text
1308    pub fn new(text: String) -> Self {
1309        Self {
1310            text,
1311            metadata: None,
1312        }
1313    }
1314
1315    /// Return a new clipboard item with the metadata replaced by the given metadata,
1316    /// after serializing it as JSON.
1317    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1318        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1319        self
1320    }
1321
1322    /// Get the text of the clipboard string
1323    pub fn text(&self) -> &String {
1324        &self.text
1325    }
1326
1327    /// Get the owned text of the clipboard string
1328    pub fn into_text(self) -> String {
1329        self.text
1330    }
1331
1332    /// Get the metadata of the clipboard string, formatted as JSON
1333    pub fn metadata_json<T>(&self) -> Option<T>
1334    where
1335        T: for<'a> Deserialize<'a>,
1336    {
1337        self.metadata
1338            .as_ref()
1339            .and_then(|m| serde_json::from_str(m).ok())
1340    }
1341
1342    #[cfg_attr(target_os = "linux", allow(dead_code))]
1343    pub(crate) fn text_hash(text: &str) -> u64 {
1344        let mut hasher = SeaHasher::new();
1345        text.hash(&mut hasher);
1346        hasher.finish()
1347    }
1348}