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