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