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