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, Corners,
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 power_mode(&self) -> PowerMode {
334 PowerMode::Unknown
335 }
336 fn on_power_mode_change(&self, _callback: Box<dyn FnMut()>) {}
338
339 fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
343
344 fn compositor_name(&self) -> &'static str {
345 ""
346 }
347 fn app_path(&self) -> Result<PathBuf>;
348 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
349
350 fn set_cursor_style(&self, style: CursorStyle);
351 fn should_auto_hide_scrollbars(&self) -> bool;
352
353 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
354 fn write_to_primary(&self, item: ClipboardItem);
355 fn write_to_clipboard(&self, item: ClipboardItem);
356 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
357 fn read_from_primary(&self) -> Option<ClipboardItem>;
358 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
359
360 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
361
362 #[cfg(target_os = "macos")]
366 fn write_credentials_if_not_exists(
367 &self,
368 _url: &str,
369 _username: &str,
370 _password: &[u8],
371 ) -> Task<Result<bool>> {
372 Task::ready(Err(anyhow::anyhow!(
373 "this platform does not support atomic credential writes"
374 )))
375 }
376
377 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
378 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
379
380 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
381 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
382 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
383
384 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
385 fn set_tray_icon_rendering_mode(&self, _rendering_mode: TrayIconRenderingMode) {}
386 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
387 fn set_tray_tooltip(&self, _tooltip: &str) {}
388 fn set_tray_panel_mode(&self, _enabled: bool) {}
389 fn get_tray_icon_anchor(&self) -> Option<TrayAnchor> {
390 None
391 }
392 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
393 None
394 }
395 fn on_tray_icon_event(&self, mut callback: Box<dyn FnMut(TrayIconEvent)>) {
396 self.on_tray_icon_click_event(Box::new(move |event| callback(event.kind)));
397 }
398 fn on_tray_icon_click_event(&self, _callback: Box<dyn FnMut(TrayIconClickEvent)>) {}
399 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
400
401 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
402 Err(anyhow::anyhow!(
403 "Global hotkeys not supported on this platform"
404 ))
405 }
406 fn unregister_global_hotkey(&self, _id: u32) {}
407 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
408
409 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
410 None
411 }
412
413 fn accessibility_status(&self) -> PermissionStatus {
414 PermissionStatus::Unavailable
415 }
416 fn request_accessibility_permission(&self) -> PermissionRequestStatus {
417 PermissionRequestStatus::Unavailable
418 }
419
420 fn microphone_status(&self) -> PermissionStatus {
421 PermissionStatus::Unavailable
422 }
423 fn request_microphone_permission(
424 &self,
425 callback: Box<dyn FnOnce(bool)>,
426 ) -> PermissionRequestStatus {
427 callback(false);
428 PermissionRequestStatus::Unavailable
429 }
430
431 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
432 Err(anyhow::anyhow!(
433 "Auto-launch not supported on this platform"
434 ))
435 }
436 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
437 false
438 }
439
440 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
441 Err(anyhow::anyhow!(
442 "Notifications not supported on this platform"
443 ))
444 }
445
446 fn set_quit_mode(&self, _mode: QuitMode) {}
453
454 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
455 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
456 None
457 }
458 fn stop_power_save_blocker(&self, _id: u32) {}
459 fn system_idle_time(&self) -> Option<Duration> {
460 None
461 }
462 fn network_status(&self) -> NetworkStatus {
463 NetworkStatus::Online
464 }
465 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
466 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
467 fn request_user_attention(&self, _attention_type: AttentionType) {}
468 fn cancel_user_attention(&self) {}
469 fn set_dock_badge(&self, _label: Option<&str>) {}
470 fn show_context_menu(
471 &self,
472 _position: Point<Pixels>,
473 _items: Vec<TrayMenuItem>,
474 _callback: Box<dyn FnMut(SharedString)>,
475 ) {
476 }
477 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
478 let (tx, rx) = oneshot::channel();
479 tx.send(0).ok();
480 rx
481 }
482 fn os_info(&self) -> OsInfo {
483 OsInfo {
484 name: std::env::consts::OS.into(),
485 arch: std::env::consts::ARCH.into(),
486 version: String::new().into(),
487 locale: String::new().into(),
488 hostname: String::new().into(),
489 }
490 }
491 fn biometric_status(&self) -> BiometricStatus {
492 BiometricStatus::Unavailable
493 }
494 fn authenticate_biometric(&self, _reason: &str, callback: Box<dyn FnOnce(bool) + Send>) {
495 callback(false);
496 }
497}
498
499pub trait PlatformDisplay: Send + Sync + Debug {
501 fn id(&self) -> DisplayId;
503
504 fn uuid(&self) -> Result<Uuid>;
507
508 fn bounds(&self) -> Bounds<Pixels>;
510
511 fn visible_bounds(&self) -> Bounds<Pixels> {
515 self.bounds()
516 }
517
518 fn default_bounds(&self) -> Bounds<Pixels> {
520 let bounds = self.visible_bounds();
521 let center = bounds.center();
522 let window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
523 let offset = window_size / 2.0;
524 let origin = point(center.x - offset.width, center.y - offset.height);
525 Bounds::new(origin, window_size)
526 }
527}
528
529#[derive(Debug, Clone, Copy, PartialEq, Eq)]
531pub enum PowerMode {
532 Unknown,
535 LowPower,
537 Balanced,
539 HighPower,
541}
542
543#[derive(Debug, Clone, Copy, PartialEq, Eq)]
545pub enum ThermalState {
546 Nominal,
548 Fair,
550 Serious,
552 Critical,
554}
555
556#[derive(Clone)]
558pub struct SourceMetadata {
559 pub id: u64,
561 pub label: Option<SharedString>,
563 pub is_main: Option<bool>,
565 pub resolution: Size<DevicePixels>,
567}
568
569pub trait ScreenCaptureSource {
571 fn metadata(&self) -> Result<SourceMetadata>;
573
574 fn stream(
577 &self,
578 foreground_executor: &ForegroundExecutor,
579 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
580 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
581
582 fn stream_with_termination(
588 &self,
589 _foreground_executor: &ForegroundExecutor,
590 _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
591 _termination_callback: ScreenCaptureTerminationCallback,
592 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
593 let (mut sender, receiver) = oneshot::channel();
594 sender
595 .send(Err(anyhow::anyhow!(
596 "runtime termination notification is not supported by this screen capture source"
597 )))
598 .ok();
599 receiver
600 }
601}
602
603pub type ScreenCaptureTerminationCallback = Box<dyn FnOnce(ScreenCaptureStreamTermination) + Send>;
605
606#[derive(Debug, Clone, PartialEq, Eq)]
608pub enum ScreenCaptureStreamTermination {
609 Ended,
611 Cancelled,
613 Failed(SharedString),
615}
616
617pub trait ScreenCaptureStream {
619 fn metadata(&self) -> Result<SourceMetadata>;
621}
622
623pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
625
626#[derive(PartialEq, Eq, Hash, Copy, Clone)]
628pub struct DisplayId(pub(crate) u64);
629
630impl DisplayId {
631 #[doc(hidden)]
633 pub fn new(id: u64) -> Self {
634 Self(id)
635 }
636
637 #[doc(hidden)]
639 pub fn index(self) -> usize {
640 self.0 as usize
641 }
642}
643
644impl From<u32> for DisplayId {
645 fn from(id: u32) -> Self {
646 Self(u64::from(id))
647 }
648}
649
650impl From<DisplayId> for u32 {
651 fn from(id: DisplayId) -> Self {
652 id.0 as u32
653 }
654}
655
656impl From<DisplayId> for u64 {
657 fn from(id: DisplayId) -> Self {
658 id.0
659 }
660}
661
662impl Debug for DisplayId {
663 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
664 write!(f, "DisplayId({})", self.0)
665 }
666}
667
668unsafe impl Send for DisplayId {}
669
670#[derive(Debug, Clone, Copy, PartialEq, Eq)]
672pub enum ResizeEdge {
673 Top,
675 TopRight,
677 Right,
679 BottomRight,
681 Bottom,
683 BottomLeft,
685 Left,
687 TopLeft,
689}
690
691#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
693pub enum WindowDecorations {
694 #[default]
695 Server,
697 Client,
699}
700
701#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
703pub enum Decorations {
704 #[default]
706 Server,
707 Client {
709 tiling: Tiling,
711 },
712}
713
714#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
716pub struct WindowControls {
717 pub fullscreen: bool,
719 pub maximize: bool,
721 pub minimize: bool,
723 pub window_menu: bool,
725}
726
727impl Default for WindowControls {
728 fn default() -> Self {
729 Self {
731 fullscreen: true,
732 maximize: true,
733 minimize: true,
734 window_menu: true,
735 }
736 }
737}
738
739#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
741pub struct Tiling {
742 pub top: bool,
744 pub left: bool,
746 pub right: bool,
748 pub bottom: bool,
750}
751
752impl Tiling {
753 pub fn tiled() -> Self {
755 Self {
756 top: true,
757 left: true,
758 right: true,
759 bottom: true,
760 }
761 }
762
763 pub fn is_tiled(&self) -> bool {
765 self.top || self.left || self.right || self.bottom
766 }
767}
768
769#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
770#[allow(missing_docs)]
771pub struct RequestFrameOptions {
772 pub require_presentation: bool,
773 pub force_render: bool,
775}
776
777pub struct A11yCallbacks {
779 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
781 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
783 pub deactivation: Box<dyn Fn() + Send + 'static>,
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Eq)]
808pub enum WindowVisibility {
809 Visible,
812 Hidden,
815}
816
817impl WindowVisibility {
818 pub fn is_visible(self) -> bool {
820 self == Self::Visible
821 }
822}
823
824#[allow(missing_docs)]
825pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
826 fn bounds(&self) -> Bounds<Pixels>;
827 fn is_maximized(&self) -> bool;
828 fn window_bounds(&self) -> WindowBounds;
829 fn content_size(&self) -> Size<Pixels>;
830 fn resize(&mut self, size: Size<Pixels>);
831 fn resize_anchored(&mut self, size: Size<Pixels>, _anchor: ResizeAnchor) {
842 self.resize(size);
843 }
844 fn scale_factor(&self) -> f32;
845 fn appearance(&self) -> WindowAppearance;
846 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
847 fn mouse_position(&self) -> Point<Pixels>;
848 fn modifiers(&self) -> Modifiers;
849 fn capslock(&self) -> Capslock;
850 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
851 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
852 fn prompt(
853 &self,
854 level: PromptLevel,
855 msg: &str,
856 detail: Option<&str>,
857 answers: &[PromptButton],
858 ) -> Option<oneshot::Receiver<usize>>;
859 fn activate(&self);
860 fn request_attention(&self) {}
862 fn is_active(&self) -> bool;
863 fn visibility(&self) -> WindowVisibility;
866 fn is_hovered(&self) -> bool;
867 fn set_title(&mut self, title: &str);
868 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
869 fn minimize(&self);
870 fn zoom(&self);
871 fn toggle_fullscreen(&self);
872 fn is_fullscreen(&self) -> bool;
873 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
874 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
875 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
876 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
880 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
881 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
882 fn on_moved(&self, callback: Box<dyn FnMut()>);
883 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
884 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
885 fn on_close(&self, callback: Box<dyn FnOnce()>);
886 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
887 fn draw(&self, scene: &Scene);
888 #[cfg(any(test, feature = "test-support"))]
889 fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
890 anyhow::bail!("render_to_image not implemented for this platform")
891 }
892 fn schedule_frame(&self) {}
893 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
894
895 fn get_title(&self) -> String {
897 String::new()
898 }
899 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
900 None
901 }
902 fn tab_bar_visible(&self) -> bool {
903 false
904 }
905 fn set_edited(&mut self, _edited: bool) {}
906 fn toggle_simple_fullscreen(&self) {}
907 fn is_simple_fullscreen(&self) -> bool {
908 false
909 }
910 fn show_character_palette(&self) {}
911 fn titlebar_double_click(&self) {}
912 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
913 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
914 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
915 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
916 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
917 fn merge_all_windows(&self) {}
918 fn move_tab_to_new_window(&self) {}
919 fn toggle_window_tab_overview(&self) {}
920 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
921
922 fn native_window_state(&self) -> Option<Vec<u8>> {
923 None
924 }
925 fn restore_native_window_state(&self, _state: &[u8]) {}
926
927 #[cfg(target_os = "windows")]
928 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
929
930 fn inner_window_bounds(&self) -> WindowBounds {
932 self.window_bounds()
933 }
934 fn request_decorations(&self, _decorations: WindowDecorations) {}
935 fn show_window_menu(&self, _position: Point<Pixels>) {}
936 fn start_window_move(&self) {}
937 fn start_window_resize(&self, _edge: ResizeEdge) {}
938 fn set_exclusive_zone(&self, _zone: Pixels) {}
939 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
940 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
941 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
942 fn window_decorations(&self) -> Decorations {
943 Decorations::Server
944 }
945 fn set_app_id(&mut self, _app_id: &str) {}
946 fn map_window(&mut self) -> anyhow::Result<()> {
947 Ok(())
948 }
949 fn window_controls(&self) -> WindowControls {
950 WindowControls::default()
951 }
952 fn set_client_inset(&self, _inset: Pixels) {}
953 fn set_client_corner_radii(&self, _radii: Corners<Pixels>) {}
954 fn gpu_specs(&self) -> Option<GpuSpecs>;
955
956 fn trim_renderer_caches(&self) {}
961
962 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
963
964 fn play_system_bell(&self) {}
965
966 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
968
969 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
971
972 fn a11y_update_window_bounds(&self) {}
974
975 fn show(&self) {}
976 fn hide(&self) {}
977 fn is_visible(&self) -> bool {
978 true
979 }
980 fn set_mouse_passthrough(&self, _passthrough: bool) {}
981 fn set_progress_bar(&self, _state: ProgressBarState) {}
982 #[allow(dead_code)]
983 fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
984
985 fn set_cursor_style(&self, _style: CursorStyle) {}
986
987 #[cfg(any(test, feature = "test-support"))]
988 fn as_test(&mut self) -> Option<&mut TestWindow> {
989 None
990 }
991}
992
993#[doc(hidden)]
996pub trait PlatformDispatcher: Send + Sync {
997 fn is_main_thread(&self) -> bool;
998 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
999 fn dispatch_on_main_thread(&self, runnable: Runnable);
1000 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
1001 fn park(&self, timeout: Option<Duration>) -> bool;
1002 fn unparker(&self) -> Unparker;
1003 fn now(&self) -> Instant {
1004 Instant::now()
1005 }
1006
1007 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1011 ActivityGuard::noop()
1012 }
1013
1014 #[cfg(any(test, feature = "test-support"))]
1015 fn as_test(&self) -> Option<&TestDispatcher> {
1016 None
1017 }
1018
1019 #[cfg(any(test, feature = "test-support"))]
1020 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1021 None
1022 }
1023}
1024
1025#[allow(missing_docs)]
1026pub trait PlatformTextSystem: Send + Sync {
1027 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1028 fn all_font_names(&self) -> Vec<String>;
1029 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1030 fn clear_glyph_raster_cache(&self) {}
1032 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1034 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1035 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1036 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1037 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1038 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1039 fn rasterize_glyph(
1040 &self,
1041 params: &RenderGlyphParams,
1042 raster_bounds: Bounds<DevicePixels>,
1043 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1044 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1045
1046 fn set_missing_glyph_sink(&self, _sink: Arc<dyn MissingGlyphSink>) {}
1050
1051 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1052 0
1053 }
1054}
1055
1056#[allow(missing_docs)]
1057pub struct NoopTextSystem;
1058
1059#[allow(missing_docs)]
1060impl NoopTextSystem {
1061 #[allow(dead_code)]
1062 pub fn new() -> Self {
1063 Self
1064 }
1065}
1066
1067impl PlatformTextSystem for NoopTextSystem {
1068 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1069 Ok(())
1070 }
1071
1072 fn all_font_names(&self) -> Vec<String> {
1073 Vec::new()
1074 }
1075
1076 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1077 Ok(FontId(1))
1078 }
1079
1080 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1081 FontMetrics {
1082 units_per_em: 1000,
1083 ascent: 1025.0,
1084 descent: -275.0,
1085 line_gap: 0.0,
1086 underline_position: -95.0,
1087 underline_thickness: 60.0,
1088 cap_height: 698.0,
1089 x_height: 516.0,
1090 bounding_box: Bounds {
1091 origin: Point {
1092 x: -260.0,
1093 y: -245.0,
1094 },
1095 size: Size {
1096 width: 1501.0,
1097 height: 1364.0,
1098 },
1099 },
1100 }
1101 }
1102
1103 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1104 Ok(Bounds {
1105 origin: Point { x: 54.0, y: 0.0 },
1106 size: size(392.0, 528.0),
1107 })
1108 }
1109
1110 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1111 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1112 }
1113
1114 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1115 Some(GlyphId(ch.len_utf16() as u32))
1116 }
1117
1118 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1119 Ok(Default::default())
1120 }
1121
1122 fn rasterize_glyph(
1123 &self,
1124 _params: &RenderGlyphParams,
1125 raster_bounds: Bounds<DevicePixels>,
1126 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1127 Ok((raster_bounds.size, Vec::new()))
1128 }
1129
1130 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1131 let mut position = px(0.);
1132 let metrics = self.font_metrics(FontId(0));
1133 let em_width = font_size
1134 * self
1135 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1136 .unwrap()
1137 .width
1138 / metrics.units_per_em as f32;
1139 let mut glyphs = Vec::new();
1140 for (ix, c) in text.char_indices() {
1141 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1142 glyphs.push(ShapedGlyph {
1143 id: glyph,
1144 position: point(position, px(0.)),
1145 index: ix,
1146 is_emoji: glyph.0 == 2,
1147 });
1148 if glyph.0 == 2 {
1149 position += em_width * 2.0;
1150 } else {
1151 position += em_width;
1152 }
1153 } else {
1154 position += em_width
1155 }
1156 }
1157 let mut runs = Vec::default();
1158 if !glyphs.is_empty() {
1159 runs.push(ShapedRun {
1160 font_id: FontId(0),
1161 glyphs,
1162 });
1163 } else {
1164 position = px(0.);
1165 }
1166
1167 LineLayout {
1168 font_size,
1169 width: position,
1170 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1171 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1172 runs,
1173 len: text.len(),
1174 }
1175 }
1176}
1177
1178#[derive(PartialEq, Eq, Hash, Clone)]
1179#[allow(missing_docs)]
1180pub enum AtlasKey {
1181 Glyph(RenderGlyphParams),
1182 Svg(RenderSvgParams),
1183 Image(RenderImageParams),
1184}
1185
1186#[allow(missing_docs)]
1187impl AtlasKey {
1188 #[cfg_attr(
1189 all(
1190 any(target_os = "linux", target_os = "freebsd"),
1191 not(any(feature = "x11", feature = "wayland"))
1192 ),
1193 allow(dead_code)
1194 )]
1195 pub fn texture_kind(&self) -> AtlasTextureKind {
1196 match self {
1197 AtlasKey::Glyph(params) => {
1198 if params.is_emoji {
1199 AtlasTextureKind::Polychrome
1200 } else {
1201 AtlasTextureKind::Monochrome
1202 }
1203 }
1204 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1205 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1206 }
1207 }
1208}
1209
1210impl From<RenderGlyphParams> for AtlasKey {
1211 fn from(params: RenderGlyphParams) -> Self {
1212 Self::Glyph(params)
1213 }
1214}
1215
1216impl From<RenderSvgParams> for AtlasKey {
1217 fn from(params: RenderSvgParams) -> Self {
1218 Self::Svg(params)
1219 }
1220}
1221
1222impl From<RenderImageParams> for AtlasKey {
1223 fn from(params: RenderImageParams) -> Self {
1224 Self::Image(params)
1225 }
1226}
1227
1228#[allow(missing_docs)]
1229pub trait PlatformAtlas: Send + Sync {
1230 fn get_or_insert_with<'a>(
1231 &self,
1232 key: &AtlasKey,
1233 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1234 ) -> Result<Option<AtlasTile>>;
1235 fn remove(&self, key: &AtlasKey);
1236}
1237
1238#[doc(hidden)]
1239pub struct AtlasTextureList<T> {
1240 pub textures: Vec<Option<T>>,
1241 pub free_list: Vec<usize>,
1242}
1243
1244impl<T> Default for AtlasTextureList<T> {
1245 fn default() -> Self {
1246 Self {
1247 textures: Vec::default(),
1248 free_list: Vec::default(),
1249 }
1250 }
1251}
1252
1253impl<T> ops::Index<usize> for AtlasTextureList<T> {
1254 type Output = Option<T>;
1255
1256 fn index(&self, index: usize) -> &Self::Output {
1257 &self.textures[index]
1258 }
1259}
1260
1261#[allow(missing_docs)]
1262impl<T> AtlasTextureList<T> {
1263 #[allow(unused)]
1264 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1265 self.free_list.clear();
1266 self.textures.drain(..)
1267 }
1268
1269 #[allow(dead_code)]
1270 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1271 self.textures.iter_mut().flatten()
1272 }
1273}
1274
1275#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1276#[repr(C)]
1277#[allow(missing_docs)]
1278pub struct AtlasTile {
1279 pub texture_id: AtlasTextureId,
1280 pub tile_id: TileId,
1281 pub padding: u32,
1282 pub bounds: Bounds<DevicePixels>,
1283}
1284
1285#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1286#[repr(C)]
1287#[allow(missing_docs)]
1288pub struct AtlasTextureId {
1289 pub index: u32,
1291 pub kind: AtlasTextureKind,
1292}
1293
1294#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1295#[repr(C)]
1296#[cfg_attr(
1297 all(
1298 any(target_os = "linux", target_os = "freebsd"),
1299 not(any(feature = "x11", feature = "wayland"))
1300 ),
1301 allow(dead_code)
1302)]
1303#[allow(missing_docs)]
1304pub enum AtlasTextureKind {
1305 Monochrome = 0,
1306 Polychrome = 1,
1307}
1308
1309#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1310#[repr(C)]
1311#[allow(missing_docs)]
1312pub struct TileId(pub u32);
1313
1314impl From<etagere::AllocId> for TileId {
1315 fn from(id: etagere::AllocId) -> Self {
1316 Self(id.serialize())
1317 }
1318}
1319
1320impl From<TileId> for etagere::AllocId {
1321 fn from(id: TileId) -> Self {
1322 Self::deserialize(id.0)
1323 }
1324}
1325
1326#[allow(missing_docs)]
1327pub struct PlatformInputHandler {
1328 cx: AsyncWindowContext,
1329 handler: Box<dyn InputHandler>,
1330}
1331
1332#[cfg_attr(
1333 all(
1334 any(target_os = "linux", target_os = "freebsd"),
1335 not(any(feature = "x11", feature = "wayland"))
1336 ),
1337 allow(dead_code)
1338)]
1339#[allow(missing_docs)]
1340impl PlatformInputHandler {
1341 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1342 Self { cx, handler }
1343 }
1344
1345 fn mark_keyboard_input(window: &Window) {
1346 window.input_rate_tracker.borrow_mut().record_input();
1347 window.last_input_was_keyboard.set(true);
1348 }
1349
1350 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1351 self.cx
1352 .update(|window, cx| {
1353 self.handler
1354 .selected_text_range(ignore_disabled_input, window, cx)
1355 })
1356 .ok()
1357 .flatten()
1358 }
1359
1360 #[cfg_attr(target_os = "windows", allow(dead_code))]
1361 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1362 self.cx
1363 .update(|window, cx| self.handler.marked_text_range(window, cx))
1364 .ok()
1365 .flatten()
1366 }
1367
1368 #[cfg_attr(
1369 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1370 allow(dead_code)
1371 )]
1372 pub fn text_for_range(
1373 &mut self,
1374 range_utf16: Range<usize>,
1375 adjusted: &mut Option<Range<usize>>,
1376 ) -> Option<String> {
1377 self.cx
1378 .update(|window, cx| {
1379 self.handler
1380 .text_for_range(range_utf16, adjusted, window, cx)
1381 })
1382 .ok()
1383 .flatten()
1384 }
1385
1386 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1387 self.cx
1388 .update(|window, cx| {
1389 Self::mark_keyboard_input(window);
1390 self.handler
1391 .replace_text_in_range(replacement_range, text, window, cx);
1392 })
1393 .ok();
1394 }
1395
1396 pub fn replace_and_mark_text_in_range(
1397 &mut self,
1398 range_utf16: Option<Range<usize>>,
1399 new_text: &str,
1400 new_selected_range: Option<Range<usize>>,
1401 ) {
1402 self.cx
1403 .update(|window, cx| {
1404 Self::mark_keyboard_input(window);
1405 self.handler.replace_and_mark_text_in_range(
1406 range_utf16,
1407 new_text,
1408 new_selected_range,
1409 window,
1410 cx,
1411 )
1412 })
1413 .ok();
1414 }
1415
1416 #[cfg_attr(target_os = "windows", allow(dead_code))]
1417 pub fn unmark_text(&mut self) {
1418 self.cx
1419 .update(|window, cx| {
1420 Self::mark_keyboard_input(window);
1421 self.handler.unmark_text(window, cx)
1422 })
1423 .ok();
1424 }
1425
1426 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1427 self.cx
1428 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1429 .ok()
1430 .flatten()
1431 }
1432
1433 #[allow(dead_code)]
1434 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1435 self.handler.apple_press_and_hold_enabled()
1436 }
1437
1438 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1439 window.last_input_was_keyboard.set(true);
1442 self.handler.replace_text_in_range(None, input, window, cx);
1443 }
1444
1445 pub fn compute_ime_candidate_bounds(
1446 marked_range: Option<Range<usize>>,
1447 selection: &UTF16Selection,
1448 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1449 ) -> Option<Bounds<Pixels>> {
1450 if let Some(marked_range) = marked_range {
1451 let mut line_start = marked_range.start;
1452 let caret = selection.range.end;
1453 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1454 for i in (marked_range.start..caret).rev() {
1455 if let Some(bounds) = bounds_for_range(i..i)
1456 && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1457 {
1458 line_start = i + 1;
1459 break;
1460 }
1461 }
1462 }
1463 bounds_for_range(line_start..line_start)
1464 } else {
1465 let offset = if selection.reversed {
1466 selection.range.start
1467 } else {
1468 selection.range.end
1469 };
1470 bounds_for_range(offset..offset)
1471 }
1472 }
1473
1474 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1475 let marked_range = self.handler.marked_text_range(window, cx);
1476 let selection = self.handler.selected_text_range(true, window, cx)?;
1477 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1478 self.handler.bounds_for_range(range, window, cx)
1479 })
1480 }
1481
1482 #[allow(dead_code)]
1483 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1484 let marked_range = self.marked_text_range();
1485 let selection = self.selected_text_range(true)?;
1486 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1487 self.bounds_for_range(range)
1488 })
1489 }
1490
1491 #[allow(unused)]
1492 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1493 self.cx
1494 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1495 .ok()
1496 .flatten()
1497 }
1498
1499 #[allow(dead_code)]
1500 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1501 self.handler.accepts_text_input(window, cx)
1502 }
1503
1504 #[allow(dead_code)]
1505 pub fn query_accepts_text_input(&mut self) -> bool {
1506 self.cx
1507 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1508 .unwrap_or(true)
1509 }
1510}
1511
1512#[derive(Debug)]
1515pub struct UTF16Selection {
1516 pub range: Range<usize>,
1519 pub reversed: bool,
1522}
1523
1524pub trait InputHandler: 'static {
1529 fn selected_text_range(
1534 &mut self,
1535 ignore_disabled_input: bool,
1536 window: &mut Window,
1537 cx: &mut App,
1538 ) -> Option<UTF16Selection>;
1539
1540 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1545
1546 fn text_for_range(
1551 &mut self,
1552 range_utf16: Range<usize>,
1553 adjusted_range: &mut Option<Range<usize>>,
1554 window: &mut Window,
1555 cx: &mut App,
1556 ) -> Option<String>;
1557
1558 fn replace_text_in_range(
1563 &mut self,
1564 replacement_range: Option<Range<usize>>,
1565 text: &str,
1566 window: &mut Window,
1567 cx: &mut App,
1568 );
1569
1570 fn replace_and_mark_text_in_range(
1577 &mut self,
1578 range_utf16: Option<Range<usize>>,
1579 new_text: &str,
1580 new_selected_range: Option<Range<usize>>,
1581 window: &mut Window,
1582 cx: &mut App,
1583 );
1584
1585 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1588
1589 fn bounds_for_range(
1594 &mut self,
1595 range_utf16: Range<usize>,
1596 window: &mut Window,
1597 cx: &mut App,
1598 ) -> Option<Bounds<Pixels>>;
1599
1600 fn character_index_for_point(
1604 &mut self,
1605 point: Point<Pixels>,
1606 window: &mut Window,
1607 cx: &mut App,
1608 ) -> Option<usize>;
1609
1610 #[allow(dead_code)]
1615 fn apple_press_and_hold_enabled(&mut self) -> bool {
1616 true
1617 }
1618
1619 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1621 true
1622 }
1623}
1624
1625#[derive(Debug)]
1627pub struct WindowOptions {
1628 pub window_bounds: Option<WindowBounds>,
1632
1633 pub titlebar: Option<TitlebarOptions>,
1635
1636 pub focus: bool,
1638
1639 pub show: bool,
1641
1642 pub kind: WindowKind,
1644
1645 pub is_movable: bool,
1647
1648 pub app_owns_titlebar_drag: bool,
1653
1654 pub inactive_frame_interval: Option<Duration>,
1660
1661 pub is_resizable: bool,
1663
1664 pub is_minimizable: bool,
1666
1667 pub display_id: Option<DisplayId>,
1670
1671 pub window_background: WindowBackgroundAppearance,
1673
1674 pub app_id: Option<String>,
1676
1677 pub window_min_size: Option<Size<Pixels>>,
1679
1680 pub window_decorations: Option<WindowDecorations>,
1683
1684 pub tabbing_identifier: Option<String>,
1686
1687 pub mouse_passthrough: bool,
1689
1690 pub icon: Option<image::RgbaImage>,
1692}
1693
1694#[derive(Debug)]
1696#[cfg_attr(
1697 all(
1698 any(target_os = "linux", target_os = "freebsd"),
1699 not(any(feature = "x11", feature = "wayland"))
1700 ),
1701 allow(dead_code)
1702)]
1703#[allow(missing_docs)]
1704pub struct WindowParams {
1705 pub bounds: Bounds<Pixels>,
1706
1707 #[cfg_attr(feature = "wayland", allow(dead_code))]
1709 pub titlebar: Option<TitlebarOptions>,
1710
1711 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1713 pub kind: WindowKind,
1714
1715 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1717 pub is_movable: bool,
1718
1719 #[cfg_attr(
1721 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1722 allow(dead_code)
1723 )]
1724 pub app_owns_titlebar_drag: bool,
1725
1726 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1728 pub is_resizable: bool,
1729
1730 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1732 pub is_minimizable: bool,
1733
1734 #[cfg_attr(
1735 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1736 allow(dead_code)
1737 )]
1738 pub focus: bool,
1739
1740 #[cfg_attr(
1741 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1742 allow(dead_code)
1743 )]
1744 pub app_id: Option<String>,
1745
1746 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1747 pub show: bool,
1748
1749 #[cfg_attr(feature = "wayland", allow(dead_code))]
1750 pub display_id: Option<DisplayId>,
1751
1752 pub window_min_size: Option<Size<Pixels>>,
1753 #[cfg(target_os = "macos")]
1754 pub tabbing_identifier: Option<String>,
1755
1756 #[allow(dead_code)]
1757 pub mouse_passthrough: bool,
1758
1759 #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1761 pub icon: Option<image::RgbaImage>,
1762}
1763
1764#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1770pub enum ResizeAnchor {
1771 TopLeft,
1773 Top,
1775 TopRight,
1777 Left,
1779 Center,
1781 Right,
1783 BottomLeft,
1785 Bottom,
1787 BottomRight,
1789}
1790
1791#[derive(Debug, Copy, Clone, PartialEq)]
1793pub enum WindowBounds {
1794 Windowed(Bounds<Pixels>),
1796 Maximized(Bounds<Pixels>),
1799 Fullscreen(Bounds<Pixels>),
1802}
1803
1804impl Default for WindowBounds {
1805 fn default() -> Self {
1806 WindowBounds::Windowed(Bounds::default())
1807 }
1808}
1809
1810impl WindowBounds {
1811 pub fn get_bounds(&self) -> Bounds<Pixels> {
1813 match self {
1814 WindowBounds::Windowed(bounds) => *bounds,
1815 WindowBounds::Maximized(bounds) => *bounds,
1816 WindowBounds::Fullscreen(bounds) => *bounds,
1817 }
1818 }
1819
1820 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1825 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1826 }
1827}
1828
1829impl Default for WindowOptions {
1830 fn default() -> Self {
1831 Self {
1832 window_bounds: None,
1833 titlebar: Some(TitlebarOptions {
1834 title: Default::default(),
1835 appears_transparent: Default::default(),
1836 traffic_light_position: Default::default(),
1837 }),
1838 focus: true,
1839 show: true,
1840 kind: WindowKind::Normal,
1841 is_movable: true,
1842 app_owns_titlebar_drag: false,
1843 inactive_frame_interval: Some(Duration::from_micros(33_333)),
1844 is_resizable: true,
1845 is_minimizable: true,
1846 display_id: None,
1847 window_background: WindowBackgroundAppearance::default(),
1848 app_id: None,
1849 window_min_size: None,
1850 window_decorations: None,
1851 tabbing_identifier: None,
1852 mouse_passthrough: false,
1853 icon: None,
1854 }
1855 }
1856}
1857
1858#[cfg(test)]
1859mod platform_input_handler_tests {
1860 use super::*;
1861 use crate::{
1862 AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1863 };
1864
1865 struct RejectingEntityInputHandler;
1866
1867 impl EntityInputHandler for RejectingEntityInputHandler {
1868 fn text_for_range(
1869 &mut self,
1870 _range: Range<usize>,
1871 _adjusted_range: &mut Option<Range<usize>>,
1872 _window: &mut Window,
1873 _cx: &mut Context<Self>,
1874 ) -> Option<String> {
1875 None
1876 }
1877
1878 fn selected_text_range(
1879 &mut self,
1880 _ignore_disabled_input: bool,
1881 _window: &mut Window,
1882 _cx: &mut Context<Self>,
1883 ) -> Option<UTF16Selection> {
1884 None
1885 }
1886
1887 fn marked_text_range(
1888 &self,
1889 _window: &mut Window,
1890 _cx: &mut Context<Self>,
1891 ) -> Option<Range<usize>> {
1892 None
1893 }
1894
1895 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1896
1897 fn replace_text_in_range(
1898 &mut self,
1899 _range: Option<Range<usize>>,
1900 _text: &str,
1901 _window: &mut Window,
1902 _cx: &mut Context<Self>,
1903 ) {
1904 }
1905
1906 fn replace_and_mark_text_in_range(
1907 &mut self,
1908 _range: Option<Range<usize>>,
1909 _new_text: &str,
1910 _new_selected_range: Option<Range<usize>>,
1911 _window: &mut Window,
1912 _cx: &mut Context<Self>,
1913 ) {
1914 }
1915
1916 fn bounds_for_range(
1917 &mut self,
1918 _range_utf16: Range<usize>,
1919 _element_bounds: Bounds<Pixels>,
1920 _window: &mut Window,
1921 _cx: &mut Context<Self>,
1922 ) -> Option<Bounds<Pixels>> {
1923 None
1924 }
1925
1926 fn character_index_for_point(
1927 &mut self,
1928 _point: Point<Pixels>,
1929 _window: &mut Window,
1930 _cx: &mut Context<Self>,
1931 ) -> Option<usize> {
1932 None
1933 }
1934
1935 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1936 false
1937 }
1938 }
1939
1940 #[gpui_macros::test]
1941 fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1942 let entity = cx.new(|_| RejectingEntityInputHandler);
1943 let window = cx.add_window(|_, _| Empty);
1944
1945 cx.update_window(window.into(), |_, window, cx| {
1946 let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1947 assert!(!handler.accepts_text_input(window, cx));
1948 })
1949 .unwrap();
1950 }
1951
1952 #[gpui_macros::test]
1953 fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1954 let entity = cx.new(|_| RejectingEntityInputHandler);
1955 let window = cx.add_window(|_, _| Empty);
1956 let window = AnyWindowHandle::from(window);
1957 let async_cx = cx
1958 .update_window(window, |_, window, cx| window.to_async(cx))
1959 .unwrap();
1960
1961 cx.update_window(window, |_, window, _| window.remove_window())
1962 .unwrap();
1963 assert!(cx.update_window(window, |_, _, _| ()).is_err());
1964
1965 let mut handler = PlatformInputHandler::new(
1966 async_cx,
1967 Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1968 );
1969 assert!(handler.query_accepts_text_input());
1970 }
1971
1972 fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1973 Bounds::new(
1974 point(px(offset as f32 * 10.0), px(y)),
1975 size(px(1.0), px(20.0)),
1976 )
1977 }
1978
1979 #[test]
1980 fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1981 let selection = UTF16Selection {
1982 range: 3..5,
1983 reversed: false,
1984 };
1985 let mut requests = Vec::new();
1986 let bounds =
1987 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1988 requests.push(range.clone());
1989 Some(bounds_at(range.start, 0.0))
1990 })
1991 .unwrap();
1992
1993 assert_eq!(bounds.origin.x, px(50.0));
1994 assert_eq!(requests, vec![5..5]);
1995
1996 let selection = UTF16Selection {
1997 range: 3..5,
1998 reversed: true,
1999 };
2000 let bounds =
2001 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
2002 Some(bounds_at(range.start, 0.0))
2003 })
2004 .unwrap();
2005
2006 assert_eq!(bounds.origin.x, px(30.0));
2007 }
2008
2009 #[test]
2010 fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
2011 let selection = UTF16Selection {
2012 range: 15..15,
2013 reversed: false,
2014 };
2015 let mut requests = Vec::new();
2016 let bounds =
2017 PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
2018 requests.push(range.clone());
2019 let y = if range.start >= 13 { 20.0 } else { 0.0 };
2020 Some(bounds_at(range.start, y))
2021 })
2022 .unwrap();
2023
2024 assert_eq!(bounds.origin.x, px(130.0));
2025 assert_eq!(requests.last(), Some(&(13..13)));
2026 }
2027}
2028
2029#[derive(Debug, Default)]
2031pub struct TitlebarOptions {
2032 pub title: Option<SharedString>,
2034
2035 pub appears_transparent: bool,
2038
2039 pub traffic_light_position: Option<Point<Pixels>>,
2041}
2042
2043#[derive(Clone, Debug, PartialEq)]
2045pub enum WindowKind {
2046 Normal,
2048
2049 PopUp,
2052
2053 Floating,
2055
2056 Overlay,
2058
2059 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
2062 LayerShell(layer_shell::LayerShellOptions),
2063}
2064
2065#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2070pub enum WindowAppearance {
2071 #[default]
2075 Light,
2076
2077 VibrantLight,
2081
2082 Dark,
2086
2087 VibrantDark,
2091}
2092
2093#[derive(Copy, Clone, Debug, Default, PartialEq)]
2096pub enum WindowBackgroundAppearance {
2097 #[default]
2105 Opaque,
2106 Transparent,
2108 Blurred,
2112}
2113
2114#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2116#[non_exhaustive]
2117pub enum TrayIconRenderingMode {
2118 #[default]
2122 Adaptive,
2123 Original,
2127}
2128
2129#[derive(Debug, Clone, PartialEq, Eq)]
2131pub enum TrayIconEvent {
2132 LeftClick,
2134 RightClick,
2136 DoubleClick,
2138}
2139
2140#[derive(Debug, Clone, PartialEq)]
2142pub struct TrayIconClickEvent {
2143 pub kind: TrayIconEvent,
2145 pub position: Option<Point<Pixels>>,
2152}
2153
2154impl TrayIconClickEvent {
2155 pub fn new(kind: TrayIconEvent) -> Self {
2157 Self {
2158 kind,
2159 position: None,
2160 }
2161 }
2162
2163 pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
2165 Self {
2166 kind,
2167 position: Some(position),
2168 }
2169 }
2170}
2171
2172#[allow(missing_docs)]
2173#[derive(Debug, Clone, PartialEq)]
2174pub struct TrayAnchor {
2175 pub display_id: DisplayId,
2176 pub bounds: Bounds<Pixels>,
2177}
2178
2179#[derive(Debug, Clone)]
2181pub enum TrayMenuItem {
2182 Action {
2184 label: SharedString,
2186 id: SharedString,
2188 },
2189 Separator,
2191 Submenu {
2193 label: SharedString,
2195 items: Vec<TrayMenuItem>,
2197 },
2198 Toggle {
2200 label: SharedString,
2202 checked: bool,
2204 id: SharedString,
2206 },
2207}
2208
2209#[derive(Debug, Clone)]
2211pub struct FocusedWindowInfo {
2212 pub app_name: String,
2214 pub window_title: String,
2216 pub bundle_id: Option<String>,
2218 pub pid: Option<u32>,
2220}
2221
2222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2224pub enum PermissionStatus {
2225 Granted,
2227 Denied,
2229 NotDetermined,
2231 Unavailable,
2233}
2234
2235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2237pub enum PermissionRequestStatus {
2238 Requested,
2240 Unavailable,
2242}
2243
2244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2246pub enum SystemPowerEvent {
2247 Suspend,
2249 Resume,
2251 LockScreen,
2253 UnlockScreen,
2255 Shutdown,
2257}
2258
2259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2261pub enum PowerSaveBlockerKind {
2262 PreventAppSuspension,
2264 PreventDisplaySleep,
2266}
2267
2268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2270pub enum NetworkStatus {
2271 Online,
2273 Offline,
2275}
2276
2277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2279pub enum MediaKeyEvent {
2280 Play,
2282 Pause,
2284 PlayPause,
2286 Stop,
2288 NextTrack,
2290 PreviousTrack,
2292}
2293
2294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2296pub enum AttentionType {
2297 Informational,
2299 Critical,
2301}
2302
2303#[derive(Debug, Clone, Copy, PartialEq)]
2305pub enum ProgressBarState {
2306 None,
2308 Indeterminate,
2310 Normal(f64),
2312 Error(f64),
2314 Paused(f64),
2316}
2317
2318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2320pub enum DialogKind {
2321 Info,
2323 Warning,
2325 Error,
2327}
2328
2329#[derive(Debug, Clone)]
2331pub struct DialogOptions {
2332 pub kind: DialogKind,
2334 pub title: SharedString,
2336 pub message: SharedString,
2338 pub detail: Option<SharedString>,
2340 pub buttons: Vec<SharedString>,
2342}
2343
2344#[derive(Debug, Clone)]
2346pub struct OsInfo {
2347 pub name: SharedString,
2349 pub version: SharedString,
2351 pub arch: SharedString,
2353 pub locale: SharedString,
2355 pub hostname: SharedString,
2357}
2358
2359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2361pub enum BiometricKind {
2362 TouchId,
2364 WindowsHello,
2366 Fingerprint,
2368}
2369
2370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2372pub enum BiometricStatus {
2373 Available(BiometricKind),
2375 Unavailable,
2377}
2378
2379#[derive(Debug, Clone, PartialEq)]
2381pub struct WindowState {
2382 pub bounds: WindowBounds,
2384 pub display_id: Option<DisplayId>,
2386 pub fullscreen: bool,
2388}
2389
2390#[derive(Debug, Clone, PartialEq)]
2392pub enum WindowPosition {
2393 Center,
2395 CenterOnDisplay(DisplayId),
2397 #[deprecated(
2404 since = "0.5.3",
2405 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."
2406 )]
2407 TrayCenter(Bounds<Pixels>),
2408 TrayAnchored(TrayAnchor),
2415 TopRight {
2417 margin: Pixels,
2419 },
2420 BottomRight {
2422 margin: Pixels,
2424 },
2425 TopLeft {
2427 margin: Pixels,
2429 },
2430 BottomLeft {
2432 margin: Pixels,
2434 },
2435}
2436
2437#[derive(Debug, Clone)]
2439pub struct CrashReport {
2440 pub message: String,
2442 pub backtrace: String,
2444 pub os_info: OsInfo,
2446 pub app_version: Option<String>,
2448}
2449
2450#[derive(Clone, Debug)]
2452pub struct PathPromptOptions {
2453 pub files: bool,
2455 pub directories: bool,
2457 pub multiple: bool,
2459 pub prompt: Option<SharedString>,
2461}
2462
2463#[derive(Copy, Clone, Debug, PartialEq)]
2465pub enum PromptLevel {
2466 Info,
2468
2469 Warning,
2471
2472 Critical,
2474}
2475
2476#[derive(Clone, Debug, PartialEq)]
2478pub enum PromptButton {
2479 Ok(SharedString),
2481 Cancel(SharedString),
2483 Other(SharedString),
2485}
2486
2487impl PromptButton {
2488 pub fn new(label: impl Into<SharedString>) -> Self {
2490 PromptButton::Other(label.into())
2491 }
2492
2493 pub fn ok(label: impl Into<SharedString>) -> Self {
2495 PromptButton::Ok(label.into())
2496 }
2497
2498 pub fn cancel(label: impl Into<SharedString>) -> Self {
2500 PromptButton::Cancel(label.into())
2501 }
2502
2503 #[allow(dead_code)]
2504 #[doc(hidden)]
2505 pub fn is_cancel(&self) -> bool {
2506 matches!(self, PromptButton::Cancel(_))
2507 }
2508
2509 pub fn label(&self) -> &SharedString {
2511 match self {
2512 PromptButton::Ok(label) => label,
2513 PromptButton::Cancel(label) => label,
2514 PromptButton::Other(label) => label,
2515 }
2516 }
2517}
2518
2519impl From<&str> for PromptButton {
2520 fn from(value: &str) -> Self {
2521 match value.to_lowercase().as_str() {
2522 "ok" => PromptButton::Ok("Ok".into()),
2523 "cancel" => PromptButton::Cancel("Cancel".into()),
2524 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2525 }
2526 }
2527}
2528
2529#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2531pub enum CursorStyle {
2532 #[default]
2534 Arrow,
2535
2536 IBeam,
2539
2540 Crosshair,
2543
2544 ClosedHand,
2547
2548 OpenHand,
2551
2552 PointingHand,
2555
2556 ResizeLeft,
2559
2560 ResizeRight,
2563
2564 ResizeLeftRight,
2567
2568 ResizeUp,
2571
2572 ResizeDown,
2575
2576 ResizeUpDown,
2579
2580 ResizeUpLeftDownRight,
2583
2584 ResizeUpRightDownLeft,
2587
2588 ResizeColumn,
2591
2592 ResizeRow,
2595
2596 IBeamCursorForVerticalLayout,
2599
2600 OperationNotAllowed,
2603
2604 DragLink,
2607
2608 DragCopy,
2611
2612 ContextualMenu,
2615
2616 None,
2618}
2619
2620#[derive(Clone, Debug, Eq, PartialEq)]
2622pub struct ClipboardItem {
2623 entries: Vec<ClipboardEntry>,
2624}
2625
2626#[derive(Clone, Debug, Eq, PartialEq)]
2628pub enum ClipboardEntry {
2629 String(ClipboardString),
2631 Image(Image),
2633}
2634
2635impl ClipboardItem {
2636 pub fn new_string(text: String) -> Self {
2638 Self {
2639 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2640 }
2641 }
2642
2643 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2645 Self {
2646 entries: vec![ClipboardEntry::String(ClipboardString {
2647 text,
2648 metadata: Some(metadata),
2649 })],
2650 }
2651 }
2652
2653 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2655 Self {
2656 entries: vec![ClipboardEntry::String(
2657 ClipboardString::new(text).with_json_metadata(metadata),
2658 )],
2659 }
2660 }
2661
2662 pub fn new_image(image: &Image) -> Self {
2664 Self {
2665 entries: vec![ClipboardEntry::Image(image.clone())],
2666 }
2667 }
2668
2669 pub fn text(&self) -> Option<String> {
2672 let mut answer = String::new();
2673 let mut any_entries = false;
2674
2675 for entry in self.entries.iter() {
2676 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2677 answer.push_str(text);
2678 any_entries = true;
2679 }
2680 }
2681
2682 if any_entries { Some(answer) } else { None }
2683 }
2684
2685 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2687 pub fn metadata(&self) -> Option<&String> {
2688 match self.entries().first() {
2689 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2690 clipboard_string.metadata.as_ref()
2691 }
2692 _ => None,
2693 }
2694 }
2695
2696 pub fn entries(&self) -> &[ClipboardEntry] {
2698 &self.entries
2699 }
2700
2701 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2703 self.entries.into_iter()
2704 }
2705}
2706
2707impl From<ClipboardString> for ClipboardEntry {
2708 fn from(value: ClipboardString) -> Self {
2709 Self::String(value)
2710 }
2711}
2712
2713impl From<String> for ClipboardEntry {
2714 fn from(value: String) -> Self {
2715 Self::from(ClipboardString::from(value))
2716 }
2717}
2718
2719impl From<Image> for ClipboardEntry {
2720 fn from(value: Image) -> Self {
2721 Self::Image(value)
2722 }
2723}
2724
2725impl From<ClipboardEntry> for ClipboardItem {
2726 fn from(value: ClipboardEntry) -> Self {
2727 Self {
2728 entries: vec![value],
2729 }
2730 }
2731}
2732
2733impl From<String> for ClipboardItem {
2734 fn from(value: String) -> Self {
2735 Self::from(ClipboardEntry::from(value))
2736 }
2737}
2738
2739impl From<Image> for ClipboardItem {
2740 fn from(value: Image) -> Self {
2741 Self::from(ClipboardEntry::from(value))
2742 }
2743}
2744
2745#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2747pub enum ImageFormat {
2748 Png,
2753 Jpeg,
2755 Webp,
2757 Gif,
2759 Svg,
2761 Bmp,
2763 Tiff,
2765}
2766
2767impl ImageFormat {
2768 pub const fn mime_type(self) -> &'static str {
2770 match self {
2771 ImageFormat::Png => "image/png",
2772 ImageFormat::Jpeg => "image/jpeg",
2773 ImageFormat::Webp => "image/webp",
2774 ImageFormat::Gif => "image/gif",
2775 ImageFormat::Svg => "image/svg+xml",
2776 ImageFormat::Bmp => "image/bmp",
2777 ImageFormat::Tiff => "image/tiff",
2778 }
2779 }
2780
2781 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2783 match mime_type {
2784 "image/png" => Some(Self::Png),
2785 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2786 "image/webp" => Some(Self::Webp),
2787 "image/gif" => Some(Self::Gif),
2788 "image/svg+xml" => Some(Self::Svg),
2789 "image/bmp" => Some(Self::Bmp),
2790 "image/tiff" | "image/tif" => Some(Self::Tiff),
2791 _ => None,
2792 }
2793 }
2794}
2795
2796#[derive(Clone, Debug, PartialEq, Eq)]
2798pub struct Image {
2799 pub format: ImageFormat,
2801 pub bytes: Vec<u8>,
2803 id: u64,
2805}
2806
2807impl Image {
2808 pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2810 let id = crate::hash(&bytes);
2811 Self { format, bytes, id }
2812 }
2813}
2814
2815impl Hash for Image {
2816 fn hash<H: Hasher>(&self, state: &mut H) {
2817 state.write_u64(self.id);
2818 }
2819}
2820
2821pub(crate) fn decode_static_image(
2822 bytes: &[u8],
2823 format: image::ImageFormat,
2824) -> Result<SmallVec<[Frame; 1]>> {
2825 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2826 .into_decoder()
2827 .context("creating image decoder")?;
2828 decode_static_image_from_decoder(decoder)
2829}
2830
2831pub(crate) fn decode_static_image_from_decoder(
2832 mut decoder: impl image::ImageDecoder,
2833) -> Result<SmallVec<[Frame; 1]>> {
2834 let orientation = decoder.orientation().context("reading image orientation")?;
2835 let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2836 image.apply_orientation(orientation);
2837
2838 let mut data = image.into_rgba8();
2839 for pixel in data.chunks_exact_mut(4) {
2840 pixel.swap(0, 2);
2841 }
2842
2843 Ok(SmallVec::from_elem(Frame::new(data), 1))
2844}
2845
2846impl Image {
2847 pub fn empty() -> Self {
2849 Self::from_bytes(ImageFormat::Png, Vec::new())
2850 }
2851
2852 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2854 Self {
2855 id: hash(&bytes),
2856 format,
2857 bytes,
2858 }
2859 }
2860
2861 pub fn id(&self) -> u64 {
2863 self.id
2864 }
2865
2866 pub fn use_render_image(
2868 self: Arc<Self>,
2869 window: &mut Window,
2870 cx: &mut App,
2871 ) -> Option<Arc<RenderImage>> {
2872 ImageSource::Image(self)
2873 .use_data(None, window, cx)
2874 .and_then(|result| result.ok())
2875 }
2876
2877 pub fn get_render_image(
2879 self: Arc<Self>,
2880 window: &mut Window,
2881 cx: &mut App,
2882 ) -> Option<Arc<RenderImage>> {
2883 ImageSource::Image(self)
2884 .get_data(None, window, cx)
2885 .and_then(|result| result.ok())
2886 }
2887
2888 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2890 ImageSource::Image(self).remove_asset(cx);
2891 }
2892
2893 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2895 let frames = match self.format {
2896 #[cfg(feature = "image-format-gif")]
2897 ImageFormat::Gif => {
2898 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2899 let mut frames = SmallVec::new();
2900
2901 for frame in decoder.into_frames() {
2902 let mut frame = frame?;
2903 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2905 pixel.swap(0, 2);
2906 }
2907 frames.push(frame);
2908 }
2909
2910 frames
2911 }
2912 #[cfg(not(feature = "image-format-gif"))]
2913 ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2914 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2915 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2916 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2917 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2918 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2919 ImageFormat::Svg => {
2920 return svg_renderer
2921 .render_single_frame(&self.bytes, 1.0)
2922 .map_err(Into::into);
2923 }
2924 };
2925
2926 Ok(Arc::new(RenderImage::new(frames)))
2927 }
2928
2929 pub fn format(&self) -> ImageFormat {
2931 self.format
2932 }
2933
2934 pub fn bytes(&self) -> &[u8] {
2936 self.bytes.as_slice()
2937 }
2938}
2939
2940#[cfg(test)]
2941mod image_tests {
2942 use super::*;
2943
2944 #[test]
2945 fn image_to_image_data_applies_exif_orientation() {
2946 let image = Image::from_bytes(
2947 ImageFormat::Jpeg,
2948 include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2949 );
2950
2951 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2952
2953 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2954 let bytes = render_image.as_bytes(0).unwrap();
2955 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2956 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2957 }
2958}
2959
2960#[derive(Clone, Debug, Eq, PartialEq)]
2962pub struct ClipboardString {
2963 pub(crate) text: String,
2964 pub(crate) metadata: Option<String>,
2965}
2966
2967impl ClipboardString {
2968 pub fn new(text: String) -> Self {
2970 Self {
2971 text,
2972 metadata: None,
2973 }
2974 }
2975
2976 #[doc(hidden)]
2978 pub fn with_metadata(mut self, metadata: String) -> Self {
2979 self.metadata = Some(metadata);
2980 self
2981 }
2982
2983 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2986 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2987 self
2988 }
2989
2990 pub fn text(&self) -> &String {
2992 &self.text
2993 }
2994
2995 pub fn into_text(self) -> String {
2997 self.text
2998 }
2999
3000 pub fn metadata_json<T>(&self) -> Option<T>
3002 where
3003 T: for<'a> Deserialize<'a>,
3004 {
3005 self.metadata
3006 .as_ref()
3007 .and_then(|m| serde_json::from_str(m).ok())
3008 }
3009
3010 #[doc(hidden)]
3012 pub fn metadata(&self) -> Option<&str> {
3013 self.metadata.as_deref()
3014 }
3015
3016 #[doc(hidden)]
3017 pub fn text_hash(text: &str) -> u64 {
3018 let mut hasher = SeaHasher::new();
3019 text.hash(&mut hasher);
3020 hasher.finish()
3021 }
3022}
3023
3024impl From<String> for ClipboardString {
3025 fn from(value: String) -> Self {
3026 Self {
3027 text: value,
3028 metadata: None,
3029 }
3030 }
3031}
3032
3033#[cfg(test)]
3034mod clipboard_string_tests {
3035 use super::ClipboardString;
3036
3037 #[test]
3038 fn raw_metadata_preserves_the_clipboard_text() {
3039 let clipboard_string = ClipboardString::new("copied text".to_owned())
3040 .with_metadata("application/x-fc-gpui".to_owned());
3041
3042 assert_eq!(clipboard_string.text(), "copied text");
3043 assert_eq!(clipboard_string.metadata(), Some("application/x-fc-gpui"));
3044 }
3045}