1mod app_menu;
2mod keyboard;
3mod keystroke;
4#[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
6pub mod layer_shell;
7mod platform_info;
8pub mod single_instance;
10pub mod window_positioner;
12
13#[cfg(all(
14 feature = "screen-capture",
15 any(target_os = "linux", target_os = "freebsd")
16))]
17type PlatformScreenCaptureFrame = scap::frame::Frame;
18#[cfg(all(
19 any(target_os = "linux", target_os = "freebsd"),
20 not(feature = "screen-capture")
21))]
22type PlatformScreenCaptureFrame = ();
23
24#[cfg(target_os = "macos")]
25type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
26
27#[cfg(all(target_os = "windows", feature = "screen-capture"))]
28type PlatformScreenCaptureFrame = scap::frame::Frame;
29#[cfg(all(target_os = "windows", not(feature = "screen-capture")))]
30type PlatformScreenCaptureFrame = ();
31
32#[cfg(any(test, feature = "test-support"))]
33#[doc(hidden)]
34#[allow(missing_docs)]
35pub mod render_image;
36
37#[cfg(any(test, feature = "test-support"))]
38mod test;
39#[cfg(any(test, feature = "test-support"))]
40mod threaded_dispatcher;
41
42#[cfg(all(
43 feature = "screen-capture",
44 any(
45 target_os = "windows",
46 all(
47 any(target_os = "linux", target_os = "freebsd"),
48 any(feature = "wayland", feature = "x11"),
49 )
50 )
51))]
52#[doc(hidden)]
54pub mod scap_screen_capture;
55
56use crate::{
57 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
58 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
59 ForegroundExecutor, GlyphId, GpuResourceBudget, GpuSpecs, Hsla, ImageSource, Keymap,
60 LineLayout, MissingGlyphSink, Pixels, PlatformInput, Point, QuitMode, RenderGlyphParams,
61 RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString,
62 Size, SvgRenderer, SystemWindowTab, Task, TaskLabel, Window, WindowControlArea, hash, point,
63 px, size,
64};
65
66use anyhow::{Context as _, Result};
67use async_task::Runnable;
68use futures::channel::oneshot;
69#[cfg(feature = "image-format-gif")]
70use image::AnimationDecoder as _;
71#[cfg(feature = "image-format-gif")]
72use image::codecs::gif::GifDecoder;
73use image::{DynamicImage, Frame};
74use parking::Unparker;
75pub use platform_info::{OperatingSystem, PlatformInfo, PlatformTarget};
76use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
77use schemars::JsonSchema;
78use seahash::SeaHasher;
79use serde::{Deserialize, Serialize};
80use smallvec::SmallVec;
81use std::borrow::Cow;
82use std::hash::{Hash, Hasher};
83use std::io::Cursor;
84use std::ops;
85use std::time::{Duration, Instant};
86use std::{
87 ffi::OsString,
88 fmt::{self, Debug},
89 ops::Range,
90 path::{Path, PathBuf},
91 rc::Rc,
92 sync::Arc,
93};
94use strum::EnumIter;
95use uuid::Uuid;
96
97pub use app_menu::*;
98pub use keyboard::*;
99pub use keystroke::*;
100
101pub use semantic_version::SemanticVersion;
102#[cfg(any(test, feature = "test-support"))]
103pub(crate) use test::*;
104
105pub struct ActivityGuard {
107 _release: util::Deferred<Box<dyn FnOnce() + Send>>,
108}
109
110impl std::fmt::Debug for ActivityGuard {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("ActivityGuard").finish_non_exhaustive()
113 }
114}
115
116impl ActivityGuard {
117 pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
119 Self {
120 _release: util::defer(Box::new(release)),
121 }
122 }
123
124 pub fn noop() -> Self {
126 Self::new(|| {})
127 }
128}
129
130#[cfg(any(test, feature = "test-support"))]
131pub use test::{
132 TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream, VisualRenderArtifact,
133};
134#[cfg(any(test, feature = "test-support"))]
135pub use threaded_dispatcher::ThreadedDispatcher;
136
137#[cfg(all(
138 any(test, feature = "test-support"),
139 any(
140 target_os = "macos",
141 target_os = "linux",
142 target_os = "freebsd",
143 target_os = "windows"
144 )
145))]
146#[doc(hidden)]
147pub use test::VisualTestPlatform;
148
149#[cfg(any(target_os = "linux", target_os = "freebsd"))]
151#[inline]
152pub fn guess_compositor() -> &'static str {
153 if std::env::var_os("ZED_HEADLESS").is_some() {
154 return "Headless";
155 }
156
157 #[cfg(feature = "wayland")]
158 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
159 #[cfg(not(feature = "wayland"))]
160 let wayland_display: Option<std::ffi::OsString> = None;
161
162 #[cfg(feature = "x11")]
163 let x11_display = std::env::var_os("DISPLAY");
164 #[cfg(not(feature = "x11"))]
165 let x11_display: Option<std::ffi::OsString> = None;
166
167 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
168 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
169
170 if use_wayland {
171 "Wayland"
172 } else if use_x11 {
173 "X11"
174 } else {
175 "Headless"
176 }
177}
178
179#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186#[non_exhaustive]
187pub struct RendererCacheStats {
188 pub idle_gpu_buffers: usize,
190 pub gpu_buffer_size_bytes: usize,
192}
193
194impl RendererCacheStats {
195 #[doc(hidden)]
197 pub fn new(idle_gpu_buffers: usize, gpu_buffer_size_bytes: usize) -> Self {
198 Self {
199 idle_gpu_buffers,
200 gpu_buffer_size_bytes,
201 }
202 }
203}
204
205#[allow(missing_docs)]
207pub trait Platform: 'static {
208 fn platform_info(&self) -> PlatformInfo;
210
211 fn background_executor(&self) -> BackgroundExecutor;
212 fn foreground_executor(&self) -> ForegroundExecutor;
213 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
214
215 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
216 fn quit(&self);
217 fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
218 fn activate(&self, ignoring_other_apps: bool);
219 fn hide(&self);
220 fn hide_other_apps(&self);
221 fn unhide_other_apps(&self);
222
223 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
224 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
225 fn active_window(&self) -> Option<AnyWindowHandle>;
226 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
227 None
228 }
229
230 #[cfg(feature = "screen-capture")]
231 fn is_screen_capture_supported(&self) -> bool;
232 #[cfg(not(feature = "screen-capture"))]
233 fn is_screen_capture_supported(&self) -> bool {
234 false
235 }
236 #[cfg(feature = "screen-capture")]
237 fn screen_capture_sources(&self)
238 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
239 #[cfg(not(feature = "screen-capture"))]
240 fn screen_capture_sources(
241 &self,
242 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
243 let (sources_tx, sources_rx) = oneshot::channel();
244 sources_tx
245 .send(Err(anyhow::anyhow!(
246 "gpui was compiled without the screen-capture feature"
247 )))
248 .ok();
249 sources_rx
250 }
251
252 fn open_window(
253 &self,
254 handle: AnyWindowHandle,
255 options: WindowParams,
256 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
257
258 fn trim_renderer_caches(&self) {}
262
263 fn configure_gpu_resources(&self, _gpu: &GpuResourceBudget) {}
266
267 fn renderer_cache_stats(&self) -> RendererCacheStats {
271 RendererCacheStats::default()
272 }
273
274 fn window_appearance(&self) -> WindowAppearance;
276
277 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
286
287 fn open_url(&self, url: &str);
288 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
289 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
290
291 fn prompt_for_paths(
292 &self,
293 options: PathPromptOptions,
294 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
295 fn prompt_for_new_path(
296 &self,
297 directory: &Path,
298 suggested_name: Option<&str>,
299 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
300 fn can_select_mixed_files_and_dirs(&self) -> bool;
301 fn reveal_path(&self, path: &Path);
302 fn open_with_system(&self, path: &Path);
303
304 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
305 fn on_reopen(&self, callback: Box<dyn FnMut()>);
306
307 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
308 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
309 None
310 }
311
312 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
313 fn perform_dock_menu_action(&self, _action: usize) {}
314 fn add_recent_document(&self, _path: &Path) {}
315 fn update_jump_list(
316 &self,
317 _menus: Vec<MenuItem>,
318 _entries: Vec<SmallVec<[PathBuf; 2]>>,
319 ) -> Vec<SmallVec<[PathBuf; 2]>> {
320 Vec::new()
321 }
322 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
323 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
324 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
325
326 fn thermal_state(&self) -> ThermalState {
327 ThermalState::Nominal
328 }
329 fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
330
331 fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
335
336 fn compositor_name(&self) -> &'static str {
337 ""
338 }
339 fn app_path(&self) -> Result<PathBuf>;
340 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
341
342 fn set_cursor_style(&self, style: CursorStyle);
343 fn should_auto_hide_scrollbars(&self) -> bool;
344
345 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
346 fn write_to_primary(&self, item: ClipboardItem);
347 fn write_to_clipboard(&self, item: ClipboardItem);
348 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
349 fn read_from_primary(&self) -> Option<ClipboardItem>;
350 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
351
352 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
353
354 #[cfg(target_os = "macos")]
358 fn write_credentials_if_not_exists(
359 &self,
360 _url: &str,
361 _username: &str,
362 _password: &[u8],
363 ) -> Task<Result<bool>> {
364 Task::ready(Err(anyhow::anyhow!(
365 "this platform does not support atomic credential writes"
366 )))
367 }
368
369 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
370 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
371
372 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
373 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
374 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
375
376 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
377 fn set_tray_icon_rendering_mode(&self, _rendering_mode: TrayIconRenderingMode) {}
378 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
379 fn set_tray_tooltip(&self, _tooltip: &str) {}
380 fn set_tray_panel_mode(&self, _enabled: bool) {}
381 fn get_tray_icon_anchor(&self) -> Option<TrayAnchor> {
382 None
383 }
384 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
385 None
386 }
387 fn on_tray_icon_event(&self, mut callback: Box<dyn FnMut(TrayIconEvent)>) {
388 self.on_tray_icon_click_event(Box::new(move |event| callback(event.kind)));
389 }
390 fn on_tray_icon_click_event(&self, _callback: Box<dyn FnMut(TrayIconClickEvent)>) {}
391 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
392
393 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
394 Err(anyhow::anyhow!(
395 "Global hotkeys not supported on this platform"
396 ))
397 }
398 fn unregister_global_hotkey(&self, _id: u32) {}
399 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
400
401 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
402 None
403 }
404
405 fn accessibility_status(&self) -> PermissionStatus {
406 PermissionStatus::Unavailable
407 }
408 fn request_accessibility_permission(&self) -> PermissionRequestStatus {
409 PermissionRequestStatus::Unavailable
410 }
411
412 fn microphone_status(&self) -> PermissionStatus {
413 PermissionStatus::Unavailable
414 }
415 fn request_microphone_permission(
416 &self,
417 callback: Box<dyn FnOnce(bool)>,
418 ) -> PermissionRequestStatus {
419 callback(false);
420 PermissionRequestStatus::Unavailable
421 }
422
423 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
424 Err(anyhow::anyhow!(
425 "Auto-launch not supported on this platform"
426 ))
427 }
428 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
429 false
430 }
431
432 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
433 Err(anyhow::anyhow!(
434 "Notifications not supported on this platform"
435 ))
436 }
437
438 fn set_quit_mode(&self, _mode: QuitMode) {}
445
446 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
447 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
448 None
449 }
450 fn stop_power_save_blocker(&self, _id: u32) {}
451 fn system_idle_time(&self) -> Option<Duration> {
452 None
453 }
454 fn network_status(&self) -> NetworkStatus {
455 NetworkStatus::Online
456 }
457 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
458 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
459 fn request_user_attention(&self, _attention_type: AttentionType) {}
460 fn cancel_user_attention(&self) {}
461 fn set_dock_badge(&self, _label: Option<&str>) {}
462 fn show_context_menu(
463 &self,
464 _position: Point<Pixels>,
465 _items: Vec<TrayMenuItem>,
466 _callback: Box<dyn FnMut(SharedString)>,
467 ) {
468 }
469 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
470 let (tx, rx) = oneshot::channel();
471 tx.send(0).ok();
472 rx
473 }
474 fn os_info(&self) -> OsInfo {
475 OsInfo {
476 name: std::env::consts::OS.into(),
477 arch: std::env::consts::ARCH.into(),
478 version: String::new().into(),
479 locale: String::new().into(),
480 hostname: String::new().into(),
481 }
482 }
483 fn biometric_status(&self) -> BiometricStatus {
484 BiometricStatus::Unavailable
485 }
486 fn authenticate_biometric(&self, _reason: &str, callback: Box<dyn FnOnce(bool) + Send>) {
487 callback(false);
488 }
489}
490
491pub trait PlatformDisplay: Send + Sync + Debug {
493 fn id(&self) -> DisplayId;
495
496 fn uuid(&self) -> Result<Uuid>;
499
500 fn bounds(&self) -> Bounds<Pixels>;
502
503 fn visible_bounds(&self) -> Bounds<Pixels> {
507 self.bounds()
508 }
509
510 fn default_bounds(&self) -> Bounds<Pixels> {
512 let bounds = self.visible_bounds();
513 let center = bounds.center();
514 let window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
515 let offset = window_size / 2.0;
516 let origin = point(center.x - offset.width, center.y - offset.height);
517 Bounds::new(origin, window_size)
518 }
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523pub enum ThermalState {
524 Nominal,
526 Fair,
528 Serious,
530 Critical,
532}
533
534#[derive(Clone)]
536pub struct SourceMetadata {
537 pub id: u64,
539 pub label: Option<SharedString>,
541 pub is_main: Option<bool>,
543 pub resolution: Size<DevicePixels>,
545}
546
547pub trait ScreenCaptureSource {
549 fn metadata(&self) -> Result<SourceMetadata>;
551
552 fn stream(
555 &self,
556 foreground_executor: &ForegroundExecutor,
557 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
558 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
559
560 fn stream_with_termination(
566 &self,
567 _foreground_executor: &ForegroundExecutor,
568 _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
569 _termination_callback: ScreenCaptureTerminationCallback,
570 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
571 let (mut sender, receiver) = oneshot::channel();
572 sender
573 .send(Err(anyhow::anyhow!(
574 "runtime termination notification is not supported by this screen capture source"
575 )))
576 .ok();
577 receiver
578 }
579}
580
581pub type ScreenCaptureTerminationCallback = Box<dyn FnOnce(ScreenCaptureStreamTermination) + Send>;
583
584#[derive(Debug, Clone, PartialEq, Eq)]
586pub enum ScreenCaptureStreamTermination {
587 Ended,
589 Cancelled,
591 Failed(SharedString),
593}
594
595pub trait ScreenCaptureStream {
597 fn metadata(&self) -> Result<SourceMetadata>;
599}
600
601pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
603
604#[derive(PartialEq, Eq, Hash, Copy, Clone)]
606pub struct DisplayId(pub(crate) u64);
607
608impl DisplayId {
609 #[doc(hidden)]
611 pub fn new(id: u64) -> Self {
612 Self(id)
613 }
614
615 #[doc(hidden)]
617 pub fn index(self) -> usize {
618 self.0 as usize
619 }
620}
621
622impl From<u32> for DisplayId {
623 fn from(id: u32) -> Self {
624 Self(u64::from(id))
625 }
626}
627
628impl From<DisplayId> for u32 {
629 fn from(id: DisplayId) -> Self {
630 id.0 as u32
631 }
632}
633
634impl From<DisplayId> for u64 {
635 fn from(id: DisplayId) -> Self {
636 id.0
637 }
638}
639
640impl Debug for DisplayId {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 write!(f, "DisplayId({})", self.0)
643 }
644}
645
646unsafe impl Send for DisplayId {}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650pub enum ResizeEdge {
651 Top,
653 TopRight,
655 Right,
657 BottomRight,
659 Bottom,
661 BottomLeft,
663 Left,
665 TopLeft,
667}
668
669#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
671pub enum WindowDecorations {
672 #[default]
673 Server,
675 Client,
677}
678
679#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
681pub enum Decorations {
682 #[default]
684 Server,
685 Client {
687 tiling: Tiling,
689 },
690}
691
692#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
694pub struct WindowControls {
695 pub fullscreen: bool,
697 pub maximize: bool,
699 pub minimize: bool,
701 pub window_menu: bool,
703}
704
705impl Default for WindowControls {
706 fn default() -> Self {
707 Self {
709 fullscreen: true,
710 maximize: true,
711 minimize: true,
712 window_menu: true,
713 }
714 }
715}
716
717#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
719pub struct Tiling {
720 pub top: bool,
722 pub left: bool,
724 pub right: bool,
726 pub bottom: bool,
728}
729
730impl Tiling {
731 pub fn tiled() -> Self {
733 Self {
734 top: true,
735 left: true,
736 right: true,
737 bottom: true,
738 }
739 }
740
741 pub fn is_tiled(&self) -> bool {
743 self.top || self.left || self.right || self.bottom
744 }
745}
746
747#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
748#[allow(missing_docs)]
749pub struct RequestFrameOptions {
750 pub require_presentation: bool,
751 pub force_render: bool,
753}
754
755pub struct A11yCallbacks {
757 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
759 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
761 pub deactivation: Box<dyn Fn() + Send + 'static>,
763}
764
765#[derive(Debug, Clone, Copy, PartialEq, Eq)]
786pub enum WindowVisibility {
787 Visible,
790 Hidden,
793}
794
795impl WindowVisibility {
796 pub fn is_visible(self) -> bool {
798 self == Self::Visible
799 }
800}
801
802#[allow(missing_docs)]
803pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
804 fn bounds(&self) -> Bounds<Pixels>;
805 fn is_maximized(&self) -> bool;
806 fn window_bounds(&self) -> WindowBounds;
807 fn content_size(&self) -> Size<Pixels>;
808 fn resize(&mut self, size: Size<Pixels>);
809 fn resize_anchored(&mut self, size: Size<Pixels>, _anchor: ResizeAnchor) {
820 self.resize(size);
821 }
822 fn scale_factor(&self) -> f32;
823 fn appearance(&self) -> WindowAppearance;
824 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
825 fn mouse_position(&self) -> Point<Pixels>;
826 fn modifiers(&self) -> Modifiers;
827 fn capslock(&self) -> Capslock;
828 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
829 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
830 fn prompt(
831 &self,
832 level: PromptLevel,
833 msg: &str,
834 detail: Option<&str>,
835 answers: &[PromptButton],
836 ) -> Option<oneshot::Receiver<usize>>;
837 fn activate(&self);
838 fn request_attention(&self) {}
840 fn is_active(&self) -> bool;
841 fn visibility(&self) -> WindowVisibility;
844 fn is_hovered(&self) -> bool;
845 fn set_title(&mut self, title: &str);
846 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
847 fn minimize(&self);
848 fn zoom(&self);
849 fn toggle_fullscreen(&self);
850 fn is_fullscreen(&self) -> bool;
851 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
852 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
853 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
854 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
858 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
859 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
860 fn on_moved(&self, callback: Box<dyn FnMut()>);
861 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
862 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
863 fn on_close(&self, callback: Box<dyn FnOnce()>);
864 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
865 fn draw(&self, scene: &Scene);
866 #[cfg(any(test, feature = "test-support"))]
867 fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
868 anyhow::bail!("render_to_image not implemented for this platform")
869 }
870 fn schedule_frame(&self) {}
871 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
872
873 fn get_title(&self) -> String {
875 String::new()
876 }
877 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
878 None
879 }
880 fn tab_bar_visible(&self) -> bool {
881 false
882 }
883 fn set_edited(&mut self, _edited: bool) {}
884 fn toggle_simple_fullscreen(&self) {}
885 fn is_simple_fullscreen(&self) -> bool {
886 false
887 }
888 fn show_character_palette(&self) {}
889 fn titlebar_double_click(&self) {}
890 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
891 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
892 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
893 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
894 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
895 fn merge_all_windows(&self) {}
896 fn move_tab_to_new_window(&self) {}
897 fn toggle_window_tab_overview(&self) {}
898 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
899
900 fn native_window_state(&self) -> Option<Vec<u8>> {
901 None
902 }
903 fn restore_native_window_state(&self, _state: &[u8]) {}
904
905 #[cfg(target_os = "windows")]
906 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
907
908 fn inner_window_bounds(&self) -> WindowBounds {
910 self.window_bounds()
911 }
912 fn request_decorations(&self, _decorations: WindowDecorations) {}
913 fn show_window_menu(&self, _position: Point<Pixels>) {}
914 fn start_window_move(&self) {}
915 fn start_window_resize(&self, _edge: ResizeEdge) {}
916 fn set_exclusive_zone(&self, _zone: Pixels) {}
917 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
918 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
919 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
920 fn window_decorations(&self) -> Decorations {
921 Decorations::Server
922 }
923 fn set_app_id(&mut self, _app_id: &str) {}
924 fn map_window(&mut self) -> anyhow::Result<()> {
925 Ok(())
926 }
927 fn window_controls(&self) -> WindowControls {
928 WindowControls::default()
929 }
930 fn set_client_inset(&self, _inset: Pixels) {}
931 fn gpu_specs(&self) -> Option<GpuSpecs>;
932
933 fn trim_renderer_caches(&self) {}
938
939 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
940
941 fn play_system_bell(&self) {}
942
943 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
945
946 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
948
949 fn a11y_update_window_bounds(&self) {}
951
952 fn show(&self) {}
953 fn hide(&self) {}
954 fn is_visible(&self) -> bool {
955 true
956 }
957 fn set_mouse_passthrough(&self, _passthrough: bool) {}
958 fn set_progress_bar(&self, _state: ProgressBarState) {}
959 #[allow(dead_code)]
960 fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
961
962 fn set_cursor_style(&self, _style: CursorStyle) {}
963
964 #[cfg(any(test, feature = "test-support"))]
965 fn as_test(&mut self) -> Option<&mut TestWindow> {
966 None
967 }
968}
969
970#[doc(hidden)]
973pub trait PlatformDispatcher: Send + Sync {
974 fn is_main_thread(&self) -> bool;
975 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
976 fn dispatch_on_main_thread(&self, runnable: Runnable);
977 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
978 fn park(&self, timeout: Option<Duration>) -> bool;
979 fn unparker(&self) -> Unparker;
980 fn now(&self) -> Instant {
981 Instant::now()
982 }
983
984 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
988 ActivityGuard::noop()
989 }
990
991 #[cfg(any(test, feature = "test-support"))]
992 fn as_test(&self) -> Option<&TestDispatcher> {
993 None
994 }
995
996 #[cfg(any(test, feature = "test-support"))]
997 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
998 None
999 }
1000}
1001
1002#[allow(missing_docs)]
1003pub trait PlatformTextSystem: Send + Sync {
1004 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1005 fn all_font_names(&self) -> Vec<String>;
1006 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1007 fn clear_glyph_raster_cache(&self) {}
1009 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1011 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1012 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1013 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1014 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1015 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1016 fn rasterize_glyph(
1017 &self,
1018 params: &RenderGlyphParams,
1019 raster_bounds: Bounds<DevicePixels>,
1020 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1021 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1022
1023 fn set_missing_glyph_sink(&self, _sink: Arc<dyn MissingGlyphSink>) {}
1027
1028 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1029 0
1030 }
1031}
1032
1033#[allow(missing_docs)]
1034pub struct NoopTextSystem;
1035
1036#[allow(missing_docs)]
1037impl NoopTextSystem {
1038 #[allow(dead_code)]
1039 pub fn new() -> Self {
1040 Self
1041 }
1042}
1043
1044impl PlatformTextSystem for NoopTextSystem {
1045 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1046 Ok(())
1047 }
1048
1049 fn all_font_names(&self) -> Vec<String> {
1050 Vec::new()
1051 }
1052
1053 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1054 Ok(FontId(1))
1055 }
1056
1057 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1058 FontMetrics {
1059 units_per_em: 1000,
1060 ascent: 1025.0,
1061 descent: -275.0,
1062 line_gap: 0.0,
1063 underline_position: -95.0,
1064 underline_thickness: 60.0,
1065 cap_height: 698.0,
1066 x_height: 516.0,
1067 bounding_box: Bounds {
1068 origin: Point {
1069 x: -260.0,
1070 y: -245.0,
1071 },
1072 size: Size {
1073 width: 1501.0,
1074 height: 1364.0,
1075 },
1076 },
1077 }
1078 }
1079
1080 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1081 Ok(Bounds {
1082 origin: Point { x: 54.0, y: 0.0 },
1083 size: size(392.0, 528.0),
1084 })
1085 }
1086
1087 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1088 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1089 }
1090
1091 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1092 Some(GlyphId(ch.len_utf16() as u32))
1093 }
1094
1095 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1096 Ok(Default::default())
1097 }
1098
1099 fn rasterize_glyph(
1100 &self,
1101 _params: &RenderGlyphParams,
1102 raster_bounds: Bounds<DevicePixels>,
1103 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1104 Ok((raster_bounds.size, Vec::new()))
1105 }
1106
1107 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1108 let mut position = px(0.);
1109 let metrics = self.font_metrics(FontId(0));
1110 let em_width = font_size
1111 * self
1112 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1113 .unwrap()
1114 .width
1115 / metrics.units_per_em as f32;
1116 let mut glyphs = Vec::new();
1117 for (ix, c) in text.char_indices() {
1118 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1119 glyphs.push(ShapedGlyph {
1120 id: glyph,
1121 position: point(position, px(0.)),
1122 index: ix,
1123 is_emoji: glyph.0 == 2,
1124 });
1125 if glyph.0 == 2 {
1126 position += em_width * 2.0;
1127 } else {
1128 position += em_width;
1129 }
1130 } else {
1131 position += em_width
1132 }
1133 }
1134 let mut runs = Vec::default();
1135 if !glyphs.is_empty() {
1136 runs.push(ShapedRun {
1137 font_id: FontId(0),
1138 glyphs,
1139 });
1140 } else {
1141 position = px(0.);
1142 }
1143
1144 LineLayout {
1145 font_size,
1146 width: position,
1147 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1148 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1149 runs,
1150 len: text.len(),
1151 }
1152 }
1153}
1154
1155#[derive(PartialEq, Eq, Hash, Clone)]
1156#[allow(missing_docs)]
1157pub enum AtlasKey {
1158 Glyph(RenderGlyphParams),
1159 Svg(RenderSvgParams),
1160 Image(RenderImageParams),
1161}
1162
1163#[allow(missing_docs)]
1164impl AtlasKey {
1165 #[cfg_attr(
1166 all(
1167 any(target_os = "linux", target_os = "freebsd"),
1168 not(any(feature = "x11", feature = "wayland"))
1169 ),
1170 allow(dead_code)
1171 )]
1172 pub fn texture_kind(&self) -> AtlasTextureKind {
1173 match self {
1174 AtlasKey::Glyph(params) => {
1175 if params.is_emoji {
1176 AtlasTextureKind::Polychrome
1177 } else {
1178 AtlasTextureKind::Monochrome
1179 }
1180 }
1181 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1182 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1183 }
1184 }
1185}
1186
1187impl From<RenderGlyphParams> for AtlasKey {
1188 fn from(params: RenderGlyphParams) -> Self {
1189 Self::Glyph(params)
1190 }
1191}
1192
1193impl From<RenderSvgParams> for AtlasKey {
1194 fn from(params: RenderSvgParams) -> Self {
1195 Self::Svg(params)
1196 }
1197}
1198
1199impl From<RenderImageParams> for AtlasKey {
1200 fn from(params: RenderImageParams) -> Self {
1201 Self::Image(params)
1202 }
1203}
1204
1205#[allow(missing_docs)]
1206pub trait PlatformAtlas: Send + Sync {
1207 fn get_or_insert_with<'a>(
1208 &self,
1209 key: &AtlasKey,
1210 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1211 ) -> Result<Option<AtlasTile>>;
1212 fn remove(&self, key: &AtlasKey);
1213}
1214
1215#[doc(hidden)]
1216pub struct AtlasTextureList<T> {
1217 pub textures: Vec<Option<T>>,
1218 pub free_list: Vec<usize>,
1219}
1220
1221impl<T> Default for AtlasTextureList<T> {
1222 fn default() -> Self {
1223 Self {
1224 textures: Vec::default(),
1225 free_list: Vec::default(),
1226 }
1227 }
1228}
1229
1230impl<T> ops::Index<usize> for AtlasTextureList<T> {
1231 type Output = Option<T>;
1232
1233 fn index(&self, index: usize) -> &Self::Output {
1234 &self.textures[index]
1235 }
1236}
1237
1238#[allow(missing_docs)]
1239impl<T> AtlasTextureList<T> {
1240 #[allow(unused)]
1241 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1242 self.free_list.clear();
1243 self.textures.drain(..)
1244 }
1245
1246 #[allow(dead_code)]
1247 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1248 self.textures.iter_mut().flatten()
1249 }
1250}
1251
1252#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1253#[repr(C)]
1254#[allow(missing_docs)]
1255pub struct AtlasTile {
1256 pub texture_id: AtlasTextureId,
1257 pub tile_id: TileId,
1258 pub padding: u32,
1259 pub bounds: Bounds<DevicePixels>,
1260}
1261
1262#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1263#[repr(C)]
1264#[allow(missing_docs)]
1265pub struct AtlasTextureId {
1266 pub index: u32,
1268 pub kind: AtlasTextureKind,
1269}
1270
1271#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1272#[repr(C)]
1273#[cfg_attr(
1274 all(
1275 any(target_os = "linux", target_os = "freebsd"),
1276 not(any(feature = "x11", feature = "wayland"))
1277 ),
1278 allow(dead_code)
1279)]
1280#[allow(missing_docs)]
1281pub enum AtlasTextureKind {
1282 Monochrome = 0,
1283 Polychrome = 1,
1284}
1285
1286#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1287#[repr(C)]
1288#[allow(missing_docs)]
1289pub struct TileId(pub u32);
1290
1291impl From<etagere::AllocId> for TileId {
1292 fn from(id: etagere::AllocId) -> Self {
1293 Self(id.serialize())
1294 }
1295}
1296
1297impl From<TileId> for etagere::AllocId {
1298 fn from(id: TileId) -> Self {
1299 Self::deserialize(id.0)
1300 }
1301}
1302
1303#[allow(missing_docs)]
1304pub struct PlatformInputHandler {
1305 cx: AsyncWindowContext,
1306 handler: Box<dyn InputHandler>,
1307}
1308
1309#[cfg_attr(
1310 all(
1311 any(target_os = "linux", target_os = "freebsd"),
1312 not(any(feature = "x11", feature = "wayland"))
1313 ),
1314 allow(dead_code)
1315)]
1316#[allow(missing_docs)]
1317impl PlatformInputHandler {
1318 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1319 Self { cx, handler }
1320 }
1321
1322 fn mark_keyboard_input(window: &Window) {
1323 window.input_rate_tracker.borrow_mut().record_input();
1324 window.last_input_was_keyboard.set(true);
1325 }
1326
1327 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1328 self.cx
1329 .update(|window, cx| {
1330 self.handler
1331 .selected_text_range(ignore_disabled_input, window, cx)
1332 })
1333 .ok()
1334 .flatten()
1335 }
1336
1337 #[cfg_attr(target_os = "windows", allow(dead_code))]
1338 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1339 self.cx
1340 .update(|window, cx| self.handler.marked_text_range(window, cx))
1341 .ok()
1342 .flatten()
1343 }
1344
1345 #[cfg_attr(
1346 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1347 allow(dead_code)
1348 )]
1349 pub fn text_for_range(
1350 &mut self,
1351 range_utf16: Range<usize>,
1352 adjusted: &mut Option<Range<usize>>,
1353 ) -> Option<String> {
1354 self.cx
1355 .update(|window, cx| {
1356 self.handler
1357 .text_for_range(range_utf16, adjusted, window, cx)
1358 })
1359 .ok()
1360 .flatten()
1361 }
1362
1363 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1364 self.cx
1365 .update(|window, cx| {
1366 Self::mark_keyboard_input(window);
1367 self.handler
1368 .replace_text_in_range(replacement_range, text, window, cx);
1369 })
1370 .ok();
1371 }
1372
1373 pub fn replace_and_mark_text_in_range(
1374 &mut self,
1375 range_utf16: Option<Range<usize>>,
1376 new_text: &str,
1377 new_selected_range: Option<Range<usize>>,
1378 ) {
1379 self.cx
1380 .update(|window, cx| {
1381 Self::mark_keyboard_input(window);
1382 self.handler.replace_and_mark_text_in_range(
1383 range_utf16,
1384 new_text,
1385 new_selected_range,
1386 window,
1387 cx,
1388 )
1389 })
1390 .ok();
1391 }
1392
1393 #[cfg_attr(target_os = "windows", allow(dead_code))]
1394 pub fn unmark_text(&mut self) {
1395 self.cx
1396 .update(|window, cx| {
1397 Self::mark_keyboard_input(window);
1398 self.handler.unmark_text(window, cx)
1399 })
1400 .ok();
1401 }
1402
1403 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1404 self.cx
1405 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1406 .ok()
1407 .flatten()
1408 }
1409
1410 #[allow(dead_code)]
1411 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1412 self.handler.apple_press_and_hold_enabled()
1413 }
1414
1415 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1416 window.last_input_was_keyboard.set(true);
1419 self.handler.replace_text_in_range(None, input, window, cx);
1420 }
1421
1422 pub fn compute_ime_candidate_bounds(
1423 marked_range: Option<Range<usize>>,
1424 selection: &UTF16Selection,
1425 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1426 ) -> Option<Bounds<Pixels>> {
1427 if let Some(marked_range) = marked_range {
1428 let mut line_start = marked_range.start;
1429 let caret = selection.range.end;
1430 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1431 for i in (marked_range.start..caret).rev() {
1432 if let Some(bounds) = bounds_for_range(i..i)
1433 && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1434 {
1435 line_start = i + 1;
1436 break;
1437 }
1438 }
1439 }
1440 bounds_for_range(line_start..line_start)
1441 } else {
1442 let offset = if selection.reversed {
1443 selection.range.start
1444 } else {
1445 selection.range.end
1446 };
1447 bounds_for_range(offset..offset)
1448 }
1449 }
1450
1451 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1452 let marked_range = self.handler.marked_text_range(window, cx);
1453 let selection = self.handler.selected_text_range(true, window, cx)?;
1454 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1455 self.handler.bounds_for_range(range, window, cx)
1456 })
1457 }
1458
1459 #[allow(dead_code)]
1460 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1461 let marked_range = self.marked_text_range();
1462 let selection = self.selected_text_range(true)?;
1463 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1464 self.bounds_for_range(range)
1465 })
1466 }
1467
1468 #[allow(unused)]
1469 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1470 self.cx
1471 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1472 .ok()
1473 .flatten()
1474 }
1475
1476 #[allow(dead_code)]
1477 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1478 self.handler.accepts_text_input(window, cx)
1479 }
1480
1481 #[allow(dead_code)]
1482 pub fn query_accepts_text_input(&mut self) -> bool {
1483 self.cx
1484 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1485 .unwrap_or(true)
1486 }
1487}
1488
1489#[derive(Debug)]
1492pub struct UTF16Selection {
1493 pub range: Range<usize>,
1496 pub reversed: bool,
1499}
1500
1501pub trait InputHandler: 'static {
1506 fn selected_text_range(
1511 &mut self,
1512 ignore_disabled_input: bool,
1513 window: &mut Window,
1514 cx: &mut App,
1515 ) -> Option<UTF16Selection>;
1516
1517 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1522
1523 fn text_for_range(
1528 &mut self,
1529 range_utf16: Range<usize>,
1530 adjusted_range: &mut Option<Range<usize>>,
1531 window: &mut Window,
1532 cx: &mut App,
1533 ) -> Option<String>;
1534
1535 fn replace_text_in_range(
1540 &mut self,
1541 replacement_range: Option<Range<usize>>,
1542 text: &str,
1543 window: &mut Window,
1544 cx: &mut App,
1545 );
1546
1547 fn replace_and_mark_text_in_range(
1554 &mut self,
1555 range_utf16: Option<Range<usize>>,
1556 new_text: &str,
1557 new_selected_range: Option<Range<usize>>,
1558 window: &mut Window,
1559 cx: &mut App,
1560 );
1561
1562 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1565
1566 fn bounds_for_range(
1571 &mut self,
1572 range_utf16: Range<usize>,
1573 window: &mut Window,
1574 cx: &mut App,
1575 ) -> Option<Bounds<Pixels>>;
1576
1577 fn character_index_for_point(
1581 &mut self,
1582 point: Point<Pixels>,
1583 window: &mut Window,
1584 cx: &mut App,
1585 ) -> Option<usize>;
1586
1587 #[allow(dead_code)]
1592 fn apple_press_and_hold_enabled(&mut self) -> bool {
1593 true
1594 }
1595
1596 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1598 true
1599 }
1600}
1601
1602#[derive(Debug)]
1604pub struct WindowOptions {
1605 pub window_bounds: Option<WindowBounds>,
1609
1610 pub titlebar: Option<TitlebarOptions>,
1612
1613 pub focus: bool,
1615
1616 pub show: bool,
1618
1619 pub kind: WindowKind,
1621
1622 pub is_movable: bool,
1624
1625 pub app_owns_titlebar_drag: bool,
1630
1631 pub inactive_frame_interval: Option<Duration>,
1637
1638 pub is_resizable: bool,
1640
1641 pub is_minimizable: bool,
1643
1644 pub display_id: Option<DisplayId>,
1647
1648 pub window_background: WindowBackgroundAppearance,
1650
1651 pub app_id: Option<String>,
1653
1654 pub window_min_size: Option<Size<Pixels>>,
1656
1657 pub window_decorations: Option<WindowDecorations>,
1660
1661 pub tabbing_identifier: Option<String>,
1663
1664 pub mouse_passthrough: bool,
1666
1667 pub icon: Option<image::RgbaImage>,
1669}
1670
1671#[derive(Debug)]
1673#[cfg_attr(
1674 all(
1675 any(target_os = "linux", target_os = "freebsd"),
1676 not(any(feature = "x11", feature = "wayland"))
1677 ),
1678 allow(dead_code)
1679)]
1680#[allow(missing_docs)]
1681pub struct WindowParams {
1682 pub bounds: Bounds<Pixels>,
1683
1684 #[cfg_attr(feature = "wayland", allow(dead_code))]
1686 pub titlebar: Option<TitlebarOptions>,
1687
1688 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1690 pub kind: WindowKind,
1691
1692 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1694 pub is_movable: bool,
1695
1696 #[cfg_attr(
1698 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1699 allow(dead_code)
1700 )]
1701 pub app_owns_titlebar_drag: bool,
1702
1703 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1705 pub is_resizable: bool,
1706
1707 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1709 pub is_minimizable: bool,
1710
1711 #[cfg_attr(
1712 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1713 allow(dead_code)
1714 )]
1715 pub focus: bool,
1716
1717 #[cfg_attr(
1718 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1719 allow(dead_code)
1720 )]
1721 pub app_id: Option<String>,
1722
1723 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1724 pub show: bool,
1725
1726 #[cfg_attr(feature = "wayland", allow(dead_code))]
1727 pub display_id: Option<DisplayId>,
1728
1729 pub window_min_size: Option<Size<Pixels>>,
1730 #[cfg(target_os = "macos")]
1731 pub tabbing_identifier: Option<String>,
1732
1733 #[allow(dead_code)]
1734 pub mouse_passthrough: bool,
1735
1736 #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1738 pub icon: Option<image::RgbaImage>,
1739}
1740
1741#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1747pub enum ResizeAnchor {
1748 TopLeft,
1750 Top,
1752 TopRight,
1754 Left,
1756 Center,
1758 Right,
1760 BottomLeft,
1762 Bottom,
1764 BottomRight,
1766}
1767
1768#[derive(Debug, Copy, Clone, PartialEq)]
1770pub enum WindowBounds {
1771 Windowed(Bounds<Pixels>),
1773 Maximized(Bounds<Pixels>),
1776 Fullscreen(Bounds<Pixels>),
1779}
1780
1781impl Default for WindowBounds {
1782 fn default() -> Self {
1783 WindowBounds::Windowed(Bounds::default())
1784 }
1785}
1786
1787impl WindowBounds {
1788 pub fn get_bounds(&self) -> Bounds<Pixels> {
1790 match self {
1791 WindowBounds::Windowed(bounds) => *bounds,
1792 WindowBounds::Maximized(bounds) => *bounds,
1793 WindowBounds::Fullscreen(bounds) => *bounds,
1794 }
1795 }
1796
1797 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1802 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1803 }
1804}
1805
1806impl Default for WindowOptions {
1807 fn default() -> Self {
1808 Self {
1809 window_bounds: None,
1810 titlebar: Some(TitlebarOptions {
1811 title: Default::default(),
1812 appears_transparent: Default::default(),
1813 traffic_light_position: Default::default(),
1814 }),
1815 focus: true,
1816 show: true,
1817 kind: WindowKind::Normal,
1818 is_movable: true,
1819 app_owns_titlebar_drag: false,
1820 inactive_frame_interval: Some(Duration::from_micros(33_333)),
1821 is_resizable: true,
1822 is_minimizable: true,
1823 display_id: None,
1824 window_background: WindowBackgroundAppearance::default(),
1825 app_id: None,
1826 window_min_size: None,
1827 window_decorations: None,
1828 tabbing_identifier: None,
1829 mouse_passthrough: false,
1830 icon: None,
1831 }
1832 }
1833}
1834
1835#[cfg(test)]
1836mod platform_input_handler_tests {
1837 use super::*;
1838 use crate::{
1839 AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1840 };
1841
1842 struct RejectingEntityInputHandler;
1843
1844 impl EntityInputHandler for RejectingEntityInputHandler {
1845 fn text_for_range(
1846 &mut self,
1847 _range: Range<usize>,
1848 _adjusted_range: &mut Option<Range<usize>>,
1849 _window: &mut Window,
1850 _cx: &mut Context<Self>,
1851 ) -> Option<String> {
1852 None
1853 }
1854
1855 fn selected_text_range(
1856 &mut self,
1857 _ignore_disabled_input: bool,
1858 _window: &mut Window,
1859 _cx: &mut Context<Self>,
1860 ) -> Option<UTF16Selection> {
1861 None
1862 }
1863
1864 fn marked_text_range(
1865 &self,
1866 _window: &mut Window,
1867 _cx: &mut Context<Self>,
1868 ) -> Option<Range<usize>> {
1869 None
1870 }
1871
1872 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1873
1874 fn replace_text_in_range(
1875 &mut self,
1876 _range: Option<Range<usize>>,
1877 _text: &str,
1878 _window: &mut Window,
1879 _cx: &mut Context<Self>,
1880 ) {
1881 }
1882
1883 fn replace_and_mark_text_in_range(
1884 &mut self,
1885 _range: Option<Range<usize>>,
1886 _new_text: &str,
1887 _new_selected_range: Option<Range<usize>>,
1888 _window: &mut Window,
1889 _cx: &mut Context<Self>,
1890 ) {
1891 }
1892
1893 fn bounds_for_range(
1894 &mut self,
1895 _range_utf16: Range<usize>,
1896 _element_bounds: Bounds<Pixels>,
1897 _window: &mut Window,
1898 _cx: &mut Context<Self>,
1899 ) -> Option<Bounds<Pixels>> {
1900 None
1901 }
1902
1903 fn character_index_for_point(
1904 &mut self,
1905 _point: Point<Pixels>,
1906 _window: &mut Window,
1907 _cx: &mut Context<Self>,
1908 ) -> Option<usize> {
1909 None
1910 }
1911
1912 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1913 false
1914 }
1915 }
1916
1917 #[gpui_macros::test]
1918 fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1919 let entity = cx.new(|_| RejectingEntityInputHandler);
1920 let window = cx.add_window(|_, _| Empty);
1921
1922 cx.update_window(window.into(), |_, window, cx| {
1923 let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1924 assert!(!handler.accepts_text_input(window, cx));
1925 })
1926 .unwrap();
1927 }
1928
1929 #[gpui_macros::test]
1930 fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1931 let entity = cx.new(|_| RejectingEntityInputHandler);
1932 let window = cx.add_window(|_, _| Empty);
1933 let window = AnyWindowHandle::from(window);
1934 let async_cx = cx
1935 .update_window(window, |_, window, cx| window.to_async(cx))
1936 .unwrap();
1937
1938 cx.update_window(window, |_, window, _| window.remove_window())
1939 .unwrap();
1940 assert!(cx.update_window(window, |_, _, _| ()).is_err());
1941
1942 let mut handler = PlatformInputHandler::new(
1943 async_cx,
1944 Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1945 );
1946 assert!(handler.query_accepts_text_input());
1947 }
1948
1949 fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1950 Bounds::new(
1951 point(px(offset as f32 * 10.0), px(y)),
1952 size(px(1.0), px(20.0)),
1953 )
1954 }
1955
1956 #[test]
1957 fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1958 let selection = UTF16Selection {
1959 range: 3..5,
1960 reversed: false,
1961 };
1962 let mut requests = Vec::new();
1963 let bounds =
1964 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1965 requests.push(range.clone());
1966 Some(bounds_at(range.start, 0.0))
1967 })
1968 .unwrap();
1969
1970 assert_eq!(bounds.origin.x, px(50.0));
1971 assert_eq!(requests, vec![5..5]);
1972
1973 let selection = UTF16Selection {
1974 range: 3..5,
1975 reversed: true,
1976 };
1977 let bounds =
1978 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1979 Some(bounds_at(range.start, 0.0))
1980 })
1981 .unwrap();
1982
1983 assert_eq!(bounds.origin.x, px(30.0));
1984 }
1985
1986 #[test]
1987 fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
1988 let selection = UTF16Selection {
1989 range: 15..15,
1990 reversed: false,
1991 };
1992 let mut requests = Vec::new();
1993 let bounds =
1994 PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
1995 requests.push(range.clone());
1996 let y = if range.start >= 13 { 20.0 } else { 0.0 };
1997 Some(bounds_at(range.start, y))
1998 })
1999 .unwrap();
2000
2001 assert_eq!(bounds.origin.x, px(130.0));
2002 assert_eq!(requests.last(), Some(&(13..13)));
2003 }
2004}
2005
2006#[derive(Debug, Default)]
2008pub struct TitlebarOptions {
2009 pub title: Option<SharedString>,
2011
2012 pub appears_transparent: bool,
2015
2016 pub traffic_light_position: Option<Point<Pixels>>,
2018}
2019
2020#[derive(Clone, Debug, PartialEq)]
2022pub enum WindowKind {
2023 Normal,
2025
2026 PopUp,
2029
2030 Floating,
2032
2033 Overlay,
2035
2036 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
2039 LayerShell(layer_shell::LayerShellOptions),
2040}
2041
2042#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2047pub enum WindowAppearance {
2048 #[default]
2052 Light,
2053
2054 VibrantLight,
2058
2059 Dark,
2063
2064 VibrantDark,
2068}
2069
2070#[derive(Copy, Clone, Debug, Default, PartialEq)]
2073pub enum WindowBackgroundAppearance {
2074 #[default]
2082 Opaque,
2083 Transparent,
2085 Blurred,
2089}
2090
2091#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2093#[non_exhaustive]
2094pub enum TrayIconRenderingMode {
2095 #[default]
2099 Adaptive,
2100 Original,
2104}
2105
2106#[derive(Debug, Clone, PartialEq, Eq)]
2108pub enum TrayIconEvent {
2109 LeftClick,
2111 RightClick,
2113 DoubleClick,
2115}
2116
2117#[derive(Debug, Clone, PartialEq)]
2119pub struct TrayIconClickEvent {
2120 pub kind: TrayIconEvent,
2122 pub position: Option<Point<Pixels>>,
2129}
2130
2131impl TrayIconClickEvent {
2132 pub fn new(kind: TrayIconEvent) -> Self {
2134 Self {
2135 kind,
2136 position: None,
2137 }
2138 }
2139
2140 pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
2142 Self {
2143 kind,
2144 position: Some(position),
2145 }
2146 }
2147}
2148
2149#[allow(missing_docs)]
2150#[derive(Debug, Clone, PartialEq)]
2151pub struct TrayAnchor {
2152 pub display_id: DisplayId,
2153 pub bounds: Bounds<Pixels>,
2154}
2155
2156#[derive(Debug, Clone)]
2158pub enum TrayMenuItem {
2159 Action {
2161 label: SharedString,
2163 id: SharedString,
2165 },
2166 Separator,
2168 Submenu {
2170 label: SharedString,
2172 items: Vec<TrayMenuItem>,
2174 },
2175 Toggle {
2177 label: SharedString,
2179 checked: bool,
2181 id: SharedString,
2183 },
2184}
2185
2186#[derive(Debug, Clone)]
2188pub struct FocusedWindowInfo {
2189 pub app_name: String,
2191 pub window_title: String,
2193 pub bundle_id: Option<String>,
2195 pub pid: Option<u32>,
2197}
2198
2199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2201pub enum PermissionStatus {
2202 Granted,
2204 Denied,
2206 NotDetermined,
2208 Unavailable,
2210}
2211
2212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2214pub enum PermissionRequestStatus {
2215 Requested,
2217 Unavailable,
2219}
2220
2221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2223pub enum SystemPowerEvent {
2224 Suspend,
2226 Resume,
2228 LockScreen,
2230 UnlockScreen,
2232 Shutdown,
2234}
2235
2236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2238pub enum PowerSaveBlockerKind {
2239 PreventAppSuspension,
2241 PreventDisplaySleep,
2243}
2244
2245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2247pub enum NetworkStatus {
2248 Online,
2250 Offline,
2252}
2253
2254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2256pub enum MediaKeyEvent {
2257 Play,
2259 Pause,
2261 PlayPause,
2263 Stop,
2265 NextTrack,
2267 PreviousTrack,
2269}
2270
2271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2273pub enum AttentionType {
2274 Informational,
2276 Critical,
2278}
2279
2280#[derive(Debug, Clone, Copy, PartialEq)]
2282pub enum ProgressBarState {
2283 None,
2285 Indeterminate,
2287 Normal(f64),
2289 Error(f64),
2291 Paused(f64),
2293}
2294
2295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2297pub enum DialogKind {
2298 Info,
2300 Warning,
2302 Error,
2304}
2305
2306#[derive(Debug, Clone)]
2308pub struct DialogOptions {
2309 pub kind: DialogKind,
2311 pub title: SharedString,
2313 pub message: SharedString,
2315 pub detail: Option<SharedString>,
2317 pub buttons: Vec<SharedString>,
2319}
2320
2321#[derive(Debug, Clone)]
2323pub struct OsInfo {
2324 pub name: SharedString,
2326 pub version: SharedString,
2328 pub arch: SharedString,
2330 pub locale: SharedString,
2332 pub hostname: SharedString,
2334}
2335
2336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2338pub enum BiometricKind {
2339 TouchId,
2341 WindowsHello,
2343 Fingerprint,
2345}
2346
2347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2349pub enum BiometricStatus {
2350 Available(BiometricKind),
2352 Unavailable,
2354}
2355
2356#[derive(Debug, Clone, PartialEq)]
2358pub struct WindowState {
2359 pub bounds: WindowBounds,
2361 pub display_id: Option<DisplayId>,
2363 pub fullscreen: bool,
2365}
2366
2367#[derive(Debug, Clone, PartialEq)]
2369pub enum WindowPosition {
2370 Center,
2372 CenterOnDisplay(DisplayId),
2374 #[deprecated(
2381 since = "0.5.3",
2382 note = "Use `WindowPosition::TrayAnchored(TrayAnchor)` instead; the new variant carries `display_id` and fixes multi-monitor placement. This variant will be removed in a future release."
2383 )]
2384 TrayCenter(Bounds<Pixels>),
2385 TrayAnchored(TrayAnchor),
2392 TopRight {
2394 margin: Pixels,
2396 },
2397 BottomRight {
2399 margin: Pixels,
2401 },
2402 TopLeft {
2404 margin: Pixels,
2406 },
2407 BottomLeft {
2409 margin: Pixels,
2411 },
2412}
2413
2414#[derive(Debug, Clone)]
2416pub struct CrashReport {
2417 pub message: String,
2419 pub backtrace: String,
2421 pub os_info: OsInfo,
2423 pub app_version: Option<String>,
2425}
2426
2427#[derive(Clone, Debug)]
2429pub struct PathPromptOptions {
2430 pub files: bool,
2432 pub directories: bool,
2434 pub multiple: bool,
2436 pub prompt: Option<SharedString>,
2438}
2439
2440#[derive(Copy, Clone, Debug, PartialEq)]
2442pub enum PromptLevel {
2443 Info,
2445
2446 Warning,
2448
2449 Critical,
2451}
2452
2453#[derive(Clone, Debug, PartialEq)]
2455pub enum PromptButton {
2456 Ok(SharedString),
2458 Cancel(SharedString),
2460 Other(SharedString),
2462}
2463
2464impl PromptButton {
2465 pub fn new(label: impl Into<SharedString>) -> Self {
2467 PromptButton::Other(label.into())
2468 }
2469
2470 pub fn ok(label: impl Into<SharedString>) -> Self {
2472 PromptButton::Ok(label.into())
2473 }
2474
2475 pub fn cancel(label: impl Into<SharedString>) -> Self {
2477 PromptButton::Cancel(label.into())
2478 }
2479
2480 #[allow(dead_code)]
2481 #[doc(hidden)]
2482 pub fn is_cancel(&self) -> bool {
2483 matches!(self, PromptButton::Cancel(_))
2484 }
2485
2486 pub fn label(&self) -> &SharedString {
2488 match self {
2489 PromptButton::Ok(label) => label,
2490 PromptButton::Cancel(label) => label,
2491 PromptButton::Other(label) => label,
2492 }
2493 }
2494}
2495
2496impl From<&str> for PromptButton {
2497 fn from(value: &str) -> Self {
2498 match value.to_lowercase().as_str() {
2499 "ok" => PromptButton::Ok("Ok".into()),
2500 "cancel" => PromptButton::Cancel("Cancel".into()),
2501 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2502 }
2503 }
2504}
2505
2506#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2508pub enum CursorStyle {
2509 #[default]
2511 Arrow,
2512
2513 IBeam,
2516
2517 Crosshair,
2520
2521 ClosedHand,
2524
2525 OpenHand,
2528
2529 PointingHand,
2532
2533 ResizeLeft,
2536
2537 ResizeRight,
2540
2541 ResizeLeftRight,
2544
2545 ResizeUp,
2548
2549 ResizeDown,
2552
2553 ResizeUpDown,
2556
2557 ResizeUpLeftDownRight,
2560
2561 ResizeUpRightDownLeft,
2564
2565 ResizeColumn,
2568
2569 ResizeRow,
2572
2573 IBeamCursorForVerticalLayout,
2576
2577 OperationNotAllowed,
2580
2581 DragLink,
2584
2585 DragCopy,
2588
2589 ContextualMenu,
2592
2593 None,
2595}
2596
2597#[derive(Clone, Debug, Eq, PartialEq)]
2599pub struct ClipboardItem {
2600 entries: Vec<ClipboardEntry>,
2601}
2602
2603#[derive(Clone, Debug, Eq, PartialEq)]
2605pub enum ClipboardEntry {
2606 String(ClipboardString),
2608 Image(Image),
2610}
2611
2612impl ClipboardItem {
2613 pub fn new_string(text: String) -> Self {
2615 Self {
2616 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2617 }
2618 }
2619
2620 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2622 Self {
2623 entries: vec![ClipboardEntry::String(ClipboardString {
2624 text,
2625 metadata: Some(metadata),
2626 })],
2627 }
2628 }
2629
2630 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2632 Self {
2633 entries: vec![ClipboardEntry::String(
2634 ClipboardString::new(text).with_json_metadata(metadata),
2635 )],
2636 }
2637 }
2638
2639 pub fn new_image(image: &Image) -> Self {
2641 Self {
2642 entries: vec![ClipboardEntry::Image(image.clone())],
2643 }
2644 }
2645
2646 pub fn text(&self) -> Option<String> {
2649 let mut answer = String::new();
2650 let mut any_entries = false;
2651
2652 for entry in self.entries.iter() {
2653 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2654 answer.push_str(text);
2655 any_entries = true;
2656 }
2657 }
2658
2659 if any_entries { Some(answer) } else { None }
2660 }
2661
2662 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2664 pub fn metadata(&self) -> Option<&String> {
2665 match self.entries().first() {
2666 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2667 clipboard_string.metadata.as_ref()
2668 }
2669 _ => None,
2670 }
2671 }
2672
2673 pub fn entries(&self) -> &[ClipboardEntry] {
2675 &self.entries
2676 }
2677
2678 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2680 self.entries.into_iter()
2681 }
2682}
2683
2684impl From<ClipboardString> for ClipboardEntry {
2685 fn from(value: ClipboardString) -> Self {
2686 Self::String(value)
2687 }
2688}
2689
2690impl From<String> for ClipboardEntry {
2691 fn from(value: String) -> Self {
2692 Self::from(ClipboardString::from(value))
2693 }
2694}
2695
2696impl From<Image> for ClipboardEntry {
2697 fn from(value: Image) -> Self {
2698 Self::Image(value)
2699 }
2700}
2701
2702impl From<ClipboardEntry> for ClipboardItem {
2703 fn from(value: ClipboardEntry) -> Self {
2704 Self {
2705 entries: vec![value],
2706 }
2707 }
2708}
2709
2710impl From<String> for ClipboardItem {
2711 fn from(value: String) -> Self {
2712 Self::from(ClipboardEntry::from(value))
2713 }
2714}
2715
2716impl From<Image> for ClipboardItem {
2717 fn from(value: Image) -> Self {
2718 Self::from(ClipboardEntry::from(value))
2719 }
2720}
2721
2722#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2724pub enum ImageFormat {
2725 Png,
2730 Jpeg,
2732 Webp,
2734 Gif,
2736 Svg,
2738 Bmp,
2740 Tiff,
2742}
2743
2744impl ImageFormat {
2745 pub const fn mime_type(self) -> &'static str {
2747 match self {
2748 ImageFormat::Png => "image/png",
2749 ImageFormat::Jpeg => "image/jpeg",
2750 ImageFormat::Webp => "image/webp",
2751 ImageFormat::Gif => "image/gif",
2752 ImageFormat::Svg => "image/svg+xml",
2753 ImageFormat::Bmp => "image/bmp",
2754 ImageFormat::Tiff => "image/tiff",
2755 }
2756 }
2757
2758 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2760 match mime_type {
2761 "image/png" => Some(Self::Png),
2762 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2763 "image/webp" => Some(Self::Webp),
2764 "image/gif" => Some(Self::Gif),
2765 "image/svg+xml" => Some(Self::Svg),
2766 "image/bmp" => Some(Self::Bmp),
2767 "image/tiff" | "image/tif" => Some(Self::Tiff),
2768 _ => None,
2769 }
2770 }
2771}
2772
2773#[derive(Clone, Debug, PartialEq, Eq)]
2775pub struct Image {
2776 pub format: ImageFormat,
2778 pub bytes: Vec<u8>,
2780 id: u64,
2782}
2783
2784impl Image {
2785 pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2787 let id = crate::hash(&bytes);
2788 Self { format, bytes, id }
2789 }
2790}
2791
2792impl Hash for Image {
2793 fn hash<H: Hasher>(&self, state: &mut H) {
2794 state.write_u64(self.id);
2795 }
2796}
2797
2798pub(crate) fn decode_static_image(
2799 bytes: &[u8],
2800 format: image::ImageFormat,
2801) -> Result<SmallVec<[Frame; 1]>> {
2802 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2803 .into_decoder()
2804 .context("creating image decoder")?;
2805 decode_static_image_from_decoder(decoder)
2806}
2807
2808pub(crate) fn decode_static_image_from_decoder(
2809 mut decoder: impl image::ImageDecoder,
2810) -> Result<SmallVec<[Frame; 1]>> {
2811 let orientation = decoder.orientation().context("reading image orientation")?;
2812 let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2813 image.apply_orientation(orientation);
2814
2815 let mut data = image.into_rgba8();
2816 for pixel in data.chunks_exact_mut(4) {
2817 pixel.swap(0, 2);
2818 }
2819
2820 Ok(SmallVec::from_elem(Frame::new(data), 1))
2821}
2822
2823impl Image {
2824 pub fn empty() -> Self {
2826 Self::from_bytes(ImageFormat::Png, Vec::new())
2827 }
2828
2829 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2831 Self {
2832 id: hash(&bytes),
2833 format,
2834 bytes,
2835 }
2836 }
2837
2838 pub fn id(&self) -> u64 {
2840 self.id
2841 }
2842
2843 pub fn use_render_image(
2845 self: Arc<Self>,
2846 window: &mut Window,
2847 cx: &mut App,
2848 ) -> Option<Arc<RenderImage>> {
2849 ImageSource::Image(self)
2850 .use_data(None, window, cx)
2851 .and_then(|result| result.ok())
2852 }
2853
2854 pub fn get_render_image(
2856 self: Arc<Self>,
2857 window: &mut Window,
2858 cx: &mut App,
2859 ) -> Option<Arc<RenderImage>> {
2860 ImageSource::Image(self)
2861 .get_data(None, window, cx)
2862 .and_then(|result| result.ok())
2863 }
2864
2865 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2867 ImageSource::Image(self).remove_asset(cx);
2868 }
2869
2870 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2872 let frames = match self.format {
2873 #[cfg(feature = "image-format-gif")]
2874 ImageFormat::Gif => {
2875 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2876 let mut frames = SmallVec::new();
2877
2878 for frame in decoder.into_frames() {
2879 let mut frame = frame?;
2880 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2882 pixel.swap(0, 2);
2883 }
2884 frames.push(frame);
2885 }
2886
2887 frames
2888 }
2889 #[cfg(not(feature = "image-format-gif"))]
2890 ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2891 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2892 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2893 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2894 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2895 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2896 ImageFormat::Svg => {
2897 return svg_renderer
2898 .render_single_frame(&self.bytes, 1.0)
2899 .map_err(Into::into);
2900 }
2901 };
2902
2903 Ok(Arc::new(RenderImage::new(frames)))
2904 }
2905
2906 pub fn format(&self) -> ImageFormat {
2908 self.format
2909 }
2910
2911 pub fn bytes(&self) -> &[u8] {
2913 self.bytes.as_slice()
2914 }
2915}
2916
2917#[cfg(test)]
2918mod image_tests {
2919 use super::*;
2920
2921 #[test]
2922 fn image_to_image_data_applies_exif_orientation() {
2923 let image = Image::from_bytes(
2924 ImageFormat::Jpeg,
2925 include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2926 );
2927
2928 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2929
2930 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2931 let bytes = render_image.as_bytes(0).unwrap();
2932 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2933 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2934 }
2935}
2936
2937#[derive(Clone, Debug, Eq, PartialEq)]
2939pub struct ClipboardString {
2940 pub(crate) text: String,
2941 pub(crate) metadata: Option<String>,
2942}
2943
2944impl ClipboardString {
2945 pub fn new(text: String) -> Self {
2947 Self {
2948 text,
2949 metadata: None,
2950 }
2951 }
2952
2953 #[doc(hidden)]
2955 pub fn with_metadata(mut self, metadata: String) -> Self {
2956 self.metadata = Some(metadata);
2957 self
2958 }
2959
2960 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2963 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2964 self
2965 }
2966
2967 pub fn text(&self) -> &String {
2969 &self.text
2970 }
2971
2972 pub fn into_text(self) -> String {
2974 self.text
2975 }
2976
2977 pub fn metadata_json<T>(&self) -> Option<T>
2979 where
2980 T: for<'a> Deserialize<'a>,
2981 {
2982 self.metadata
2983 .as_ref()
2984 .and_then(|m| serde_json::from_str(m).ok())
2985 }
2986
2987 #[doc(hidden)]
2989 pub fn metadata(&self) -> Option<&str> {
2990 self.metadata.as_deref()
2991 }
2992
2993 #[doc(hidden)]
2994 pub fn text_hash(text: &str) -> u64 {
2995 let mut hasher = SeaHasher::new();
2996 text.hash(&mut hasher);
2997 hasher.finish()
2998 }
2999}
3000
3001impl From<String> for ClipboardString {
3002 fn from(value: String) -> Self {
3003 Self {
3004 text: value,
3005 metadata: None,
3006 }
3007 }
3008}
3009
3010#[cfg(test)]
3011mod clipboard_string_tests {
3012 use super::ClipboardString;
3013
3014 #[test]
3015 fn raw_metadata_preserves_the_clipboard_text() {
3016 let clipboard_string = ClipboardString::new("copied text".to_owned())
3017 .with_metadata("application/x-fc-gpui".to_owned());
3018
3019 assert_eq!(clipboard_string.text(), "copied text");
3020 assert_eq!(clipboard_string.metadata(), Some("application/x-fc-gpui"));
3021 }
3022}