1mod app_menu;
2mod keyboard;
3mod keystroke;
4#[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
6pub mod layer_shell;
7pub mod single_instance;
9pub mod window_positioner;
11
12#[cfg(all(
13 feature = "screen-capture",
14 any(target_os = "linux", target_os = "freebsd")
15))]
16type PlatformScreenCaptureFrame = scap::frame::Frame;
17#[cfg(all(
18 any(target_os = "linux", target_os = "freebsd"),
19 not(feature = "screen-capture")
20))]
21type PlatformScreenCaptureFrame = ();
22
23#[cfg(target_os = "macos")]
24type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
25
26#[cfg(all(target_os = "windows", feature = "screen-capture"))]
27type PlatformScreenCaptureFrame = scap::frame::Frame;
28#[cfg(all(target_os = "windows", not(feature = "screen-capture")))]
29type PlatformScreenCaptureFrame = ();
30
31#[cfg(any(test, feature = "test-support"))]
32#[doc(hidden)]
33#[allow(missing_docs)]
34pub mod render_image;
35
36#[cfg(any(test, feature = "test-support"))]
37mod test;
38#[cfg(any(test, feature = "test-support"))]
39mod threaded_dispatcher;
40
41#[cfg(all(
42 feature = "screen-capture",
43 any(
44 target_os = "windows",
45 all(
46 any(target_os = "linux", target_os = "freebsd"),
47 any(feature = "wayland", feature = "x11"),
48 )
49 )
50))]
51#[doc(hidden)]
53pub mod scap_screen_capture;
54
55use crate::{
56 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
57 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
58 ForegroundExecutor, GlyphId, GpuResourceBudget, GpuSpecs, Hsla, ImageSource, Keymap,
59 LineLayout, MissingGlyphSink, Pixels, PlatformInput, Point, QuitMode, RenderGlyphParams,
60 RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString,
61 Size, SvgRenderer, SystemWindowTab, Task, TaskLabel, Window, WindowControlArea, hash, point,
62 px, size,
63};
64use anyhow::{Context as _, Result};
65use async_task::Runnable;
66use futures::channel::oneshot;
67#[cfg(feature = "image-format-gif")]
68use image::AnimationDecoder as _;
69#[cfg(feature = "image-format-gif")]
70use image::codecs::gif::GifDecoder;
71use image::{DynamicImage, Frame};
72use parking::Unparker;
73use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
74use schemars::JsonSchema;
75use seahash::SeaHasher;
76use serde::{Deserialize, Serialize};
77use smallvec::SmallVec;
78use std::borrow::Cow;
79use std::hash::{Hash, Hasher};
80use std::io::Cursor;
81use std::ops;
82use std::time::{Duration, Instant};
83use std::{
84 ffi::OsString,
85 fmt::{self, Debug},
86 ops::Range,
87 path::{Path, PathBuf},
88 rc::Rc,
89 sync::Arc,
90};
91use strum::EnumIter;
92use uuid::Uuid;
93
94pub use app_menu::*;
95pub use keyboard::*;
96pub use keystroke::*;
97
98pub use semantic_version::SemanticVersion;
99#[cfg(any(test, feature = "test-support"))]
100pub(crate) use test::*;
101
102pub struct ActivityGuard {
104 _release: util::Deferred<Box<dyn FnOnce() + Send>>,
105}
106
107impl std::fmt::Debug for ActivityGuard {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("ActivityGuard").finish_non_exhaustive()
110 }
111}
112
113impl ActivityGuard {
114 pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
116 Self {
117 _release: util::defer(Box::new(release)),
118 }
119 }
120
121 pub fn noop() -> Self {
123 Self::new(|| {})
124 }
125}
126
127#[cfg(any(test, feature = "test-support"))]
128pub use test::{
129 TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream, VisualRenderArtifact,
130};
131#[cfg(any(test, feature = "test-support"))]
132pub use threaded_dispatcher::ThreadedDispatcher;
133
134#[cfg(all(
135 any(test, feature = "test-support"),
136 any(
137 target_os = "macos",
138 target_os = "linux",
139 target_os = "freebsd",
140 target_os = "windows"
141 )
142))]
143#[doc(hidden)]
144pub use test::VisualTestPlatform;
145
146#[cfg(any(target_os = "linux", target_os = "freebsd"))]
148#[inline]
149pub fn guess_compositor() -> &'static str {
150 if std::env::var_os("ZED_HEADLESS").is_some() {
151 return "Headless";
152 }
153
154 #[cfg(feature = "wayland")]
155 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
156 #[cfg(not(feature = "wayland"))]
157 let wayland_display: Option<std::ffi::OsString> = None;
158
159 #[cfg(feature = "x11")]
160 let x11_display = std::env::var_os("DISPLAY");
161 #[cfg(not(feature = "x11"))]
162 let x11_display: Option<std::ffi::OsString> = None;
163
164 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
165 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
166
167 if use_wayland {
168 "Wayland"
169 } else if use_x11 {
170 "X11"
171 } else {
172 "Headless"
173 }
174}
175
176#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
183#[non_exhaustive]
184pub struct RendererCacheStats {
185 pub idle_gpu_buffers: usize,
187 pub gpu_buffer_size_bytes: usize,
189}
190
191impl RendererCacheStats {
192 #[doc(hidden)]
194 pub fn new(idle_gpu_buffers: usize, gpu_buffer_size_bytes: usize) -> Self {
195 Self {
196 idle_gpu_buffers,
197 gpu_buffer_size_bytes,
198 }
199 }
200}
201
202#[allow(missing_docs)]
204pub trait Platform: 'static {
205 fn background_executor(&self) -> BackgroundExecutor;
206 fn foreground_executor(&self) -> ForegroundExecutor;
207 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
208
209 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
210 fn quit(&self);
211 fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
212 fn activate(&self, ignoring_other_apps: bool);
213 fn hide(&self);
214 fn hide_other_apps(&self);
215 fn unhide_other_apps(&self);
216
217 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
218 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
219 fn active_window(&self) -> Option<AnyWindowHandle>;
220 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
221 None
222 }
223
224 #[cfg(feature = "screen-capture")]
225 fn is_screen_capture_supported(&self) -> bool;
226 #[cfg(not(feature = "screen-capture"))]
227 fn is_screen_capture_supported(&self) -> bool {
228 false
229 }
230 #[cfg(feature = "screen-capture")]
231 fn screen_capture_sources(&self)
232 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
233 #[cfg(not(feature = "screen-capture"))]
234 fn screen_capture_sources(
235 &self,
236 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
237 let (sources_tx, sources_rx) = oneshot::channel();
238 sources_tx
239 .send(Err(anyhow::anyhow!(
240 "gpui was compiled without the screen-capture feature"
241 )))
242 .ok();
243 sources_rx
244 }
245
246 fn open_window(
247 &self,
248 handle: AnyWindowHandle,
249 options: WindowParams,
250 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
251
252 fn trim_renderer_caches(&self) {}
256
257 fn configure_gpu_resources(&self, _gpu: &GpuResourceBudget) {}
260
261 fn renderer_cache_stats(&self) -> RendererCacheStats {
265 RendererCacheStats::default()
266 }
267
268 fn window_appearance(&self) -> WindowAppearance;
270
271 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
280
281 fn open_url(&self, url: &str);
282 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
283 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
284
285 fn prompt_for_paths(
286 &self,
287 options: PathPromptOptions,
288 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
289 fn prompt_for_new_path(
290 &self,
291 directory: &Path,
292 suggested_name: Option<&str>,
293 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
294 fn can_select_mixed_files_and_dirs(&self) -> bool;
295 fn reveal_path(&self, path: &Path);
296 fn open_with_system(&self, path: &Path);
297
298 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
299 fn on_reopen(&self, callback: Box<dyn FnMut()>);
300
301 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
302 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
303 None
304 }
305
306 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
307 fn perform_dock_menu_action(&self, _action: usize) {}
308 fn add_recent_document(&self, _path: &Path) {}
309 fn update_jump_list(
310 &self,
311 _menus: Vec<MenuItem>,
312 _entries: Vec<SmallVec<[PathBuf; 2]>>,
313 ) -> Vec<SmallVec<[PathBuf; 2]>> {
314 Vec::new()
315 }
316 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
317 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
318 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
319
320 fn thermal_state(&self) -> ThermalState {
321 ThermalState::Nominal
322 }
323 fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
324
325 fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
329
330 fn compositor_name(&self) -> &'static str {
331 ""
332 }
333 fn app_path(&self) -> Result<PathBuf>;
334 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
335
336 fn set_cursor_style(&self, style: CursorStyle);
337 fn should_auto_hide_scrollbars(&self) -> bool;
338
339 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
340 fn write_to_primary(&self, item: ClipboardItem);
341 fn write_to_clipboard(&self, item: ClipboardItem);
342 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
343 fn read_from_primary(&self) -> Option<ClipboardItem>;
344 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
345
346 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
347 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
348 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
349
350 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
351 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
352 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
353
354 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
355 fn set_tray_icon_rendering_mode(&self, _rendering_mode: TrayIconRenderingMode) {}
356 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
357 fn set_tray_tooltip(&self, _tooltip: &str) {}
358 fn set_tray_panel_mode(&self, _enabled: bool) {}
359 fn get_tray_icon_anchor(&self) -> Option<TrayAnchor> {
360 None
361 }
362 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
363 None
364 }
365 fn on_tray_icon_event(&self, mut callback: Box<dyn FnMut(TrayIconEvent)>) {
366 self.on_tray_icon_click_event(Box::new(move |event| callback(event.kind)));
367 }
368 fn on_tray_icon_click_event(&self, _callback: Box<dyn FnMut(TrayIconClickEvent)>) {}
369 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
370
371 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
372 Err(anyhow::anyhow!(
373 "Global hotkeys not supported on this platform"
374 ))
375 }
376 fn unregister_global_hotkey(&self, _id: u32) {}
377 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
378
379 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
380 None
381 }
382
383 fn accessibility_status(&self) -> PermissionStatus {
384 PermissionStatus::Unavailable
385 }
386 fn request_accessibility_permission(&self) -> PermissionRequestStatus {
387 PermissionRequestStatus::Unavailable
388 }
389
390 fn microphone_status(&self) -> PermissionStatus {
391 PermissionStatus::Unavailable
392 }
393 fn request_microphone_permission(
394 &self,
395 callback: Box<dyn FnOnce(bool)>,
396 ) -> PermissionRequestStatus {
397 callback(false);
398 PermissionRequestStatus::Unavailable
399 }
400
401 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
402 Err(anyhow::anyhow!(
403 "Auto-launch not supported on this platform"
404 ))
405 }
406 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
407 false
408 }
409
410 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
411 Err(anyhow::anyhow!(
412 "Notifications not supported on this platform"
413 ))
414 }
415
416 fn set_quit_mode(&self, _mode: QuitMode) {}
423
424 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
425 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
426 None
427 }
428 fn stop_power_save_blocker(&self, _id: u32) {}
429 fn system_idle_time(&self) -> Option<Duration> {
430 None
431 }
432 fn network_status(&self) -> NetworkStatus {
433 NetworkStatus::Online
434 }
435 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
436 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
437 fn request_user_attention(&self, _attention_type: AttentionType) {}
438 fn cancel_user_attention(&self) {}
439 fn set_dock_badge(&self, _label: Option<&str>) {}
440 fn show_context_menu(
441 &self,
442 _position: Point<Pixels>,
443 _items: Vec<TrayMenuItem>,
444 _callback: Box<dyn FnMut(SharedString)>,
445 ) {
446 }
447 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
448 let (tx, rx) = oneshot::channel();
449 tx.send(0).ok();
450 rx
451 }
452 fn os_info(&self) -> OsInfo {
453 OsInfo {
454 name: std::env::consts::OS.into(),
455 arch: std::env::consts::ARCH.into(),
456 version: String::new().into(),
457 locale: String::new().into(),
458 hostname: String::new().into(),
459 }
460 }
461 fn biometric_status(&self) -> BiometricStatus {
462 BiometricStatus::Unavailable
463 }
464 fn authenticate_biometric(&self, _reason: &str, callback: Box<dyn FnOnce(bool) + Send>) {
465 callback(false);
466 }
467}
468
469pub trait PlatformDisplay: Send + Sync + Debug {
471 fn id(&self) -> DisplayId;
473
474 fn uuid(&self) -> Result<Uuid>;
477
478 fn bounds(&self) -> Bounds<Pixels>;
480
481 fn visible_bounds(&self) -> Bounds<Pixels> {
485 self.bounds()
486 }
487
488 fn default_bounds(&self) -> Bounds<Pixels> {
490 let bounds = self.visible_bounds();
491 let center = bounds.center();
492 let window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
493 let offset = window_size / 2.0;
494 let origin = point(center.x - offset.width, center.y - offset.height);
495 Bounds::new(origin, window_size)
496 }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ThermalState {
502 Nominal,
504 Fair,
506 Serious,
508 Critical,
510}
511
512#[derive(Clone)]
514pub struct SourceMetadata {
515 pub id: u64,
517 pub label: Option<SharedString>,
519 pub is_main: Option<bool>,
521 pub resolution: Size<DevicePixels>,
523}
524
525pub trait ScreenCaptureSource {
527 fn metadata(&self) -> Result<SourceMetadata>;
529
530 fn stream(
533 &self,
534 foreground_executor: &ForegroundExecutor,
535 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
536 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
537
538 fn stream_with_termination(
544 &self,
545 _foreground_executor: &ForegroundExecutor,
546 _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
547 _termination_callback: ScreenCaptureTerminationCallback,
548 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
549 let (mut sender, receiver) = oneshot::channel();
550 sender
551 .send(Err(anyhow::anyhow!(
552 "runtime termination notification is not supported by this screen capture source"
553 )))
554 .ok();
555 receiver
556 }
557}
558
559pub type ScreenCaptureTerminationCallback = Box<dyn FnOnce(ScreenCaptureStreamTermination) + Send>;
561
562#[derive(Debug, Clone, PartialEq, Eq)]
564pub enum ScreenCaptureStreamTermination {
565 Ended,
567 Cancelled,
569 Failed(SharedString),
571}
572
573pub trait ScreenCaptureStream {
575 fn metadata(&self) -> Result<SourceMetadata>;
577}
578
579pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
581
582#[derive(PartialEq, Eq, Hash, Copy, Clone)]
584pub struct DisplayId(pub(crate) u64);
585
586impl DisplayId {
587 #[doc(hidden)]
589 pub fn new(id: u64) -> Self {
590 Self(id)
591 }
592
593 #[doc(hidden)]
595 pub fn index(self) -> usize {
596 self.0 as usize
597 }
598}
599
600impl From<u32> for DisplayId {
601 fn from(id: u32) -> Self {
602 Self(u64::from(id))
603 }
604}
605
606impl From<DisplayId> for u32 {
607 fn from(id: DisplayId) -> Self {
608 id.0 as u32
609 }
610}
611
612impl From<DisplayId> for u64 {
613 fn from(id: DisplayId) -> Self {
614 id.0
615 }
616}
617
618impl Debug for DisplayId {
619 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620 write!(f, "DisplayId({})", self.0)
621 }
622}
623
624unsafe impl Send for DisplayId {}
625
626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
628pub enum ResizeEdge {
629 Top,
631 TopRight,
633 Right,
635 BottomRight,
637 Bottom,
639 BottomLeft,
641 Left,
643 TopLeft,
645}
646
647#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
649pub enum WindowDecorations {
650 #[default]
651 Server,
653 Client,
655}
656
657#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
659pub enum Decorations {
660 #[default]
662 Server,
663 Client {
665 tiling: Tiling,
667 },
668}
669
670#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
672pub struct WindowControls {
673 pub fullscreen: bool,
675 pub maximize: bool,
677 pub minimize: bool,
679 pub window_menu: bool,
681}
682
683impl Default for WindowControls {
684 fn default() -> Self {
685 Self {
687 fullscreen: true,
688 maximize: true,
689 minimize: true,
690 window_menu: true,
691 }
692 }
693}
694
695#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
697pub struct Tiling {
698 pub top: bool,
700 pub left: bool,
702 pub right: bool,
704 pub bottom: bool,
706}
707
708impl Tiling {
709 pub fn tiled() -> Self {
711 Self {
712 top: true,
713 left: true,
714 right: true,
715 bottom: true,
716 }
717 }
718
719 pub fn is_tiled(&self) -> bool {
721 self.top || self.left || self.right || self.bottom
722 }
723}
724
725#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
726#[allow(missing_docs)]
727pub struct RequestFrameOptions {
728 pub require_presentation: bool,
729 pub force_render: bool,
731}
732
733pub struct A11yCallbacks {
735 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
737 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
739 pub deactivation: Box<dyn Fn() + Send + 'static>,
741}
742
743#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764pub enum WindowVisibility {
765 Visible,
768 Hidden,
771}
772
773impl WindowVisibility {
774 pub fn is_visible(self) -> bool {
776 self == Self::Visible
777 }
778}
779
780#[allow(missing_docs)]
781pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
782 fn bounds(&self) -> Bounds<Pixels>;
783 fn is_maximized(&self) -> bool;
784 fn window_bounds(&self) -> WindowBounds;
785 fn content_size(&self) -> Size<Pixels>;
786 fn resize(&mut self, size: Size<Pixels>);
787 fn resize_anchored(&mut self, size: Size<Pixels>, _anchor: ResizeAnchor) {
798 self.resize(size);
799 }
800 fn scale_factor(&self) -> f32;
801 fn appearance(&self) -> WindowAppearance;
802 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
803 fn mouse_position(&self) -> Point<Pixels>;
804 fn modifiers(&self) -> Modifiers;
805 fn capslock(&self) -> Capslock;
806 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
807 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
808 fn prompt(
809 &self,
810 level: PromptLevel,
811 msg: &str,
812 detail: Option<&str>,
813 answers: &[PromptButton],
814 ) -> Option<oneshot::Receiver<usize>>;
815 fn activate(&self);
816 fn request_attention(&self) {}
818 fn is_active(&self) -> bool;
819 fn visibility(&self) -> WindowVisibility;
822 fn is_hovered(&self) -> bool;
823 fn set_title(&mut self, title: &str);
824 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
825 fn minimize(&self);
826 fn zoom(&self);
827 fn toggle_fullscreen(&self);
828 fn is_fullscreen(&self) -> bool;
829 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
830 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
831 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
832 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
836 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
837 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
838 fn on_moved(&self, callback: Box<dyn FnMut()>);
839 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
840 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
841 fn on_close(&self, callback: Box<dyn FnOnce()>);
842 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
843 fn draw(&self, scene: &Scene);
844 #[cfg(any(test, feature = "test-support"))]
845 fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
846 anyhow::bail!("render_to_image not implemented for this platform")
847 }
848 fn schedule_frame(&self) {}
849 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
850
851 fn get_title(&self) -> String {
853 String::new()
854 }
855 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
856 None
857 }
858 fn tab_bar_visible(&self) -> bool {
859 false
860 }
861 fn set_edited(&mut self, _edited: bool) {}
862 fn toggle_simple_fullscreen(&self) {}
863 fn is_simple_fullscreen(&self) -> bool {
864 false
865 }
866 fn show_character_palette(&self) {}
867 fn titlebar_double_click(&self) {}
868 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
869 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
870 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
871 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
872 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
873 fn merge_all_windows(&self) {}
874 fn move_tab_to_new_window(&self) {}
875 fn toggle_window_tab_overview(&self) {}
876 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
877
878 fn native_window_state(&self) -> Option<Vec<u8>> {
879 None
880 }
881 fn restore_native_window_state(&self, _state: &[u8]) {}
882
883 #[cfg(target_os = "windows")]
884 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
885
886 fn inner_window_bounds(&self) -> WindowBounds {
888 self.window_bounds()
889 }
890 fn request_decorations(&self, _decorations: WindowDecorations) {}
891 fn show_window_menu(&self, _position: Point<Pixels>) {}
892 fn start_window_move(&self) {}
893 fn start_window_resize(&self, _edge: ResizeEdge) {}
894 fn set_exclusive_zone(&self, _zone: Pixels) {}
895 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
896 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
897 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
898 fn window_decorations(&self) -> Decorations {
899 Decorations::Server
900 }
901 fn set_app_id(&mut self, _app_id: &str) {}
902 fn map_window(&mut self) -> anyhow::Result<()> {
903 Ok(())
904 }
905 fn window_controls(&self) -> WindowControls {
906 WindowControls::default()
907 }
908 fn set_client_inset(&self, _inset: Pixels) {}
909 fn gpu_specs(&self) -> Option<GpuSpecs>;
910
911 fn trim_renderer_caches(&self) {}
916
917 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
918
919 fn play_system_bell(&self) {}
920
921 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
923
924 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
926
927 fn a11y_update_window_bounds(&self) {}
929
930 fn show(&self) {}
931 fn hide(&self) {}
932 fn is_visible(&self) -> bool {
933 true
934 }
935 fn set_mouse_passthrough(&self, _passthrough: bool) {}
936 fn set_progress_bar(&self, _state: ProgressBarState) {}
937 #[allow(dead_code)]
938 fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
939
940 fn set_cursor_style(&self, _style: CursorStyle) {}
941
942 #[cfg(any(test, feature = "test-support"))]
943 fn as_test(&mut self) -> Option<&mut TestWindow> {
944 None
945 }
946}
947
948#[doc(hidden)]
951pub trait PlatformDispatcher: Send + Sync {
952 fn is_main_thread(&self) -> bool;
953 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
954 fn dispatch_on_main_thread(&self, runnable: Runnable);
955 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
956 fn park(&self, timeout: Option<Duration>) -> bool;
957 fn unparker(&self) -> Unparker;
958 fn now(&self) -> Instant {
959 Instant::now()
960 }
961
962 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
966 ActivityGuard::noop()
967 }
968
969 #[cfg(any(test, feature = "test-support"))]
970 fn as_test(&self) -> Option<&TestDispatcher> {
971 None
972 }
973
974 #[cfg(any(test, feature = "test-support"))]
975 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
976 None
977 }
978}
979
980#[allow(missing_docs)]
981pub trait PlatformTextSystem: Send + Sync {
982 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
983 fn all_font_names(&self) -> Vec<String>;
984 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
985 fn clear_glyph_raster_cache(&self) {}
987 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
989 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
990 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
991 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
992 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
993 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
994 fn rasterize_glyph(
995 &self,
996 params: &RenderGlyphParams,
997 raster_bounds: Bounds<DevicePixels>,
998 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
999 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1000
1001 fn set_missing_glyph_sink(&self, _sink: Arc<dyn MissingGlyphSink>) {}
1005
1006 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1007 0
1008 }
1009}
1010
1011#[allow(missing_docs)]
1012pub struct NoopTextSystem;
1013
1014#[allow(missing_docs)]
1015impl NoopTextSystem {
1016 #[allow(dead_code)]
1017 pub fn new() -> Self {
1018 Self
1019 }
1020}
1021
1022impl PlatformTextSystem for NoopTextSystem {
1023 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1024 Ok(())
1025 }
1026
1027 fn all_font_names(&self) -> Vec<String> {
1028 Vec::new()
1029 }
1030
1031 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1032 Ok(FontId(1))
1033 }
1034
1035 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1036 FontMetrics {
1037 units_per_em: 1000,
1038 ascent: 1025.0,
1039 descent: -275.0,
1040 line_gap: 0.0,
1041 underline_position: -95.0,
1042 underline_thickness: 60.0,
1043 cap_height: 698.0,
1044 x_height: 516.0,
1045 bounding_box: Bounds {
1046 origin: Point {
1047 x: -260.0,
1048 y: -245.0,
1049 },
1050 size: Size {
1051 width: 1501.0,
1052 height: 1364.0,
1053 },
1054 },
1055 }
1056 }
1057
1058 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1059 Ok(Bounds {
1060 origin: Point { x: 54.0, y: 0.0 },
1061 size: size(392.0, 528.0),
1062 })
1063 }
1064
1065 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1066 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1067 }
1068
1069 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1070 Some(GlyphId(ch.len_utf16() as u32))
1071 }
1072
1073 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1074 Ok(Default::default())
1075 }
1076
1077 fn rasterize_glyph(
1078 &self,
1079 _params: &RenderGlyphParams,
1080 raster_bounds: Bounds<DevicePixels>,
1081 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1082 Ok((raster_bounds.size, Vec::new()))
1083 }
1084
1085 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1086 let mut position = px(0.);
1087 let metrics = self.font_metrics(FontId(0));
1088 let em_width = font_size
1089 * self
1090 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1091 .unwrap()
1092 .width
1093 / metrics.units_per_em as f32;
1094 let mut glyphs = Vec::new();
1095 for (ix, c) in text.char_indices() {
1096 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1097 glyphs.push(ShapedGlyph {
1098 id: glyph,
1099 position: point(position, px(0.)),
1100 index: ix,
1101 is_emoji: glyph.0 == 2,
1102 });
1103 if glyph.0 == 2 {
1104 position += em_width * 2.0;
1105 } else {
1106 position += em_width;
1107 }
1108 } else {
1109 position += em_width
1110 }
1111 }
1112 let mut runs = Vec::default();
1113 if !glyphs.is_empty() {
1114 runs.push(ShapedRun {
1115 font_id: FontId(0),
1116 glyphs,
1117 });
1118 } else {
1119 position = px(0.);
1120 }
1121
1122 LineLayout {
1123 font_size,
1124 width: position,
1125 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1126 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1127 runs,
1128 len: text.len(),
1129 }
1130 }
1131}
1132
1133#[derive(PartialEq, Eq, Hash, Clone)]
1134#[allow(missing_docs)]
1135pub enum AtlasKey {
1136 Glyph(RenderGlyphParams),
1137 Svg(RenderSvgParams),
1138 Image(RenderImageParams),
1139}
1140
1141#[allow(missing_docs)]
1142impl AtlasKey {
1143 #[cfg_attr(
1144 all(
1145 any(target_os = "linux", target_os = "freebsd"),
1146 not(any(feature = "x11", feature = "wayland"))
1147 ),
1148 allow(dead_code)
1149 )]
1150 pub fn texture_kind(&self) -> AtlasTextureKind {
1151 match self {
1152 AtlasKey::Glyph(params) => {
1153 if params.is_emoji {
1154 AtlasTextureKind::Polychrome
1155 } else {
1156 AtlasTextureKind::Monochrome
1157 }
1158 }
1159 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1160 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1161 }
1162 }
1163}
1164
1165impl From<RenderGlyphParams> for AtlasKey {
1166 fn from(params: RenderGlyphParams) -> Self {
1167 Self::Glyph(params)
1168 }
1169}
1170
1171impl From<RenderSvgParams> for AtlasKey {
1172 fn from(params: RenderSvgParams) -> Self {
1173 Self::Svg(params)
1174 }
1175}
1176
1177impl From<RenderImageParams> for AtlasKey {
1178 fn from(params: RenderImageParams) -> Self {
1179 Self::Image(params)
1180 }
1181}
1182
1183#[allow(missing_docs)]
1184pub trait PlatformAtlas: Send + Sync {
1185 fn get_or_insert_with<'a>(
1186 &self,
1187 key: &AtlasKey,
1188 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1189 ) -> Result<Option<AtlasTile>>;
1190 fn remove(&self, key: &AtlasKey);
1191}
1192
1193#[doc(hidden)]
1194pub struct AtlasTextureList<T> {
1195 pub textures: Vec<Option<T>>,
1196 pub free_list: Vec<usize>,
1197}
1198
1199impl<T> Default for AtlasTextureList<T> {
1200 fn default() -> Self {
1201 Self {
1202 textures: Vec::default(),
1203 free_list: Vec::default(),
1204 }
1205 }
1206}
1207
1208impl<T> ops::Index<usize> for AtlasTextureList<T> {
1209 type Output = Option<T>;
1210
1211 fn index(&self, index: usize) -> &Self::Output {
1212 &self.textures[index]
1213 }
1214}
1215
1216#[allow(missing_docs)]
1217impl<T> AtlasTextureList<T> {
1218 #[allow(unused)]
1219 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1220 self.free_list.clear();
1221 self.textures.drain(..)
1222 }
1223
1224 #[allow(dead_code)]
1225 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1226 self.textures.iter_mut().flatten()
1227 }
1228}
1229
1230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1231#[repr(C)]
1232#[allow(missing_docs)]
1233pub struct AtlasTile {
1234 pub texture_id: AtlasTextureId,
1235 pub tile_id: TileId,
1236 pub padding: u32,
1237 pub bounds: Bounds<DevicePixels>,
1238}
1239
1240#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1241#[repr(C)]
1242#[allow(missing_docs)]
1243pub struct AtlasTextureId {
1244 pub index: u32,
1246 pub kind: AtlasTextureKind,
1247}
1248
1249#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1250#[repr(C)]
1251#[cfg_attr(
1252 all(
1253 any(target_os = "linux", target_os = "freebsd"),
1254 not(any(feature = "x11", feature = "wayland"))
1255 ),
1256 allow(dead_code)
1257)]
1258#[allow(missing_docs)]
1259pub enum AtlasTextureKind {
1260 Monochrome = 0,
1261 Polychrome = 1,
1262}
1263
1264#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1265#[repr(C)]
1266#[allow(missing_docs)]
1267pub struct TileId(pub u32);
1268
1269impl From<etagere::AllocId> for TileId {
1270 fn from(id: etagere::AllocId) -> Self {
1271 Self(id.serialize())
1272 }
1273}
1274
1275impl From<TileId> for etagere::AllocId {
1276 fn from(id: TileId) -> Self {
1277 Self::deserialize(id.0)
1278 }
1279}
1280
1281#[allow(missing_docs)]
1282pub struct PlatformInputHandler {
1283 cx: AsyncWindowContext,
1284 handler: Box<dyn InputHandler>,
1285}
1286
1287#[cfg_attr(
1288 all(
1289 any(target_os = "linux", target_os = "freebsd"),
1290 not(any(feature = "x11", feature = "wayland"))
1291 ),
1292 allow(dead_code)
1293)]
1294#[allow(missing_docs)]
1295impl PlatformInputHandler {
1296 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1297 Self { cx, handler }
1298 }
1299
1300 fn mark_keyboard_input(window: &Window) {
1301 window.input_rate_tracker.borrow_mut().record_input();
1302 window.last_input_was_keyboard.set(true);
1303 }
1304
1305 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1306 self.cx
1307 .update(|window, cx| {
1308 self.handler
1309 .selected_text_range(ignore_disabled_input, window, cx)
1310 })
1311 .ok()
1312 .flatten()
1313 }
1314
1315 #[cfg_attr(target_os = "windows", allow(dead_code))]
1316 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1317 self.cx
1318 .update(|window, cx| self.handler.marked_text_range(window, cx))
1319 .ok()
1320 .flatten()
1321 }
1322
1323 #[cfg_attr(
1324 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1325 allow(dead_code)
1326 )]
1327 pub fn text_for_range(
1328 &mut self,
1329 range_utf16: Range<usize>,
1330 adjusted: &mut Option<Range<usize>>,
1331 ) -> Option<String> {
1332 self.cx
1333 .update(|window, cx| {
1334 self.handler
1335 .text_for_range(range_utf16, adjusted, window, cx)
1336 })
1337 .ok()
1338 .flatten()
1339 }
1340
1341 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1342 self.cx
1343 .update(|window, cx| {
1344 Self::mark_keyboard_input(window);
1345 self.handler
1346 .replace_text_in_range(replacement_range, text, window, cx);
1347 })
1348 .ok();
1349 }
1350
1351 pub fn replace_and_mark_text_in_range(
1352 &mut self,
1353 range_utf16: Option<Range<usize>>,
1354 new_text: &str,
1355 new_selected_range: Option<Range<usize>>,
1356 ) {
1357 self.cx
1358 .update(|window, cx| {
1359 Self::mark_keyboard_input(window);
1360 self.handler.replace_and_mark_text_in_range(
1361 range_utf16,
1362 new_text,
1363 new_selected_range,
1364 window,
1365 cx,
1366 )
1367 })
1368 .ok();
1369 }
1370
1371 #[cfg_attr(target_os = "windows", allow(dead_code))]
1372 pub fn unmark_text(&mut self) {
1373 self.cx
1374 .update(|window, cx| {
1375 Self::mark_keyboard_input(window);
1376 self.handler.unmark_text(window, cx)
1377 })
1378 .ok();
1379 }
1380
1381 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1382 self.cx
1383 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1384 .ok()
1385 .flatten()
1386 }
1387
1388 #[allow(dead_code)]
1389 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1390 self.handler.apple_press_and_hold_enabled()
1391 }
1392
1393 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1394 window.last_input_was_keyboard.set(true);
1397 self.handler.replace_text_in_range(None, input, window, cx);
1398 }
1399
1400 pub fn compute_ime_candidate_bounds(
1401 marked_range: Option<Range<usize>>,
1402 selection: &UTF16Selection,
1403 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1404 ) -> Option<Bounds<Pixels>> {
1405 if let Some(marked_range) = marked_range {
1406 let mut line_start = marked_range.start;
1407 let caret = selection.range.end;
1408 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1409 for i in (marked_range.start..caret).rev() {
1410 if let Some(bounds) = bounds_for_range(i..i)
1411 && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1412 {
1413 line_start = i + 1;
1414 break;
1415 }
1416 }
1417 }
1418 bounds_for_range(line_start..line_start)
1419 } else {
1420 let offset = if selection.reversed {
1421 selection.range.start
1422 } else {
1423 selection.range.end
1424 };
1425 bounds_for_range(offset..offset)
1426 }
1427 }
1428
1429 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1430 let marked_range = self.handler.marked_text_range(window, cx);
1431 let selection = self.handler.selected_text_range(true, window, cx)?;
1432 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1433 self.handler.bounds_for_range(range, window, cx)
1434 })
1435 }
1436
1437 #[allow(dead_code)]
1438 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1439 let marked_range = self.marked_text_range();
1440 let selection = self.selected_text_range(true)?;
1441 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1442 self.bounds_for_range(range)
1443 })
1444 }
1445
1446 #[allow(unused)]
1447 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1448 self.cx
1449 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1450 .ok()
1451 .flatten()
1452 }
1453
1454 #[allow(dead_code)]
1455 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1456 self.handler.accepts_text_input(window, cx)
1457 }
1458
1459 #[allow(dead_code)]
1460 pub fn query_accepts_text_input(&mut self) -> bool {
1461 self.cx
1462 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1463 .unwrap_or(true)
1464 }
1465}
1466
1467#[derive(Debug)]
1470pub struct UTF16Selection {
1471 pub range: Range<usize>,
1474 pub reversed: bool,
1477}
1478
1479pub trait InputHandler: 'static {
1484 fn selected_text_range(
1489 &mut self,
1490 ignore_disabled_input: bool,
1491 window: &mut Window,
1492 cx: &mut App,
1493 ) -> Option<UTF16Selection>;
1494
1495 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1500
1501 fn text_for_range(
1506 &mut self,
1507 range_utf16: Range<usize>,
1508 adjusted_range: &mut Option<Range<usize>>,
1509 window: &mut Window,
1510 cx: &mut App,
1511 ) -> Option<String>;
1512
1513 fn replace_text_in_range(
1518 &mut self,
1519 replacement_range: Option<Range<usize>>,
1520 text: &str,
1521 window: &mut Window,
1522 cx: &mut App,
1523 );
1524
1525 fn replace_and_mark_text_in_range(
1532 &mut self,
1533 range_utf16: Option<Range<usize>>,
1534 new_text: &str,
1535 new_selected_range: Option<Range<usize>>,
1536 window: &mut Window,
1537 cx: &mut App,
1538 );
1539
1540 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1543
1544 fn bounds_for_range(
1549 &mut self,
1550 range_utf16: Range<usize>,
1551 window: &mut Window,
1552 cx: &mut App,
1553 ) -> Option<Bounds<Pixels>>;
1554
1555 fn character_index_for_point(
1559 &mut self,
1560 point: Point<Pixels>,
1561 window: &mut Window,
1562 cx: &mut App,
1563 ) -> Option<usize>;
1564
1565 #[allow(dead_code)]
1570 fn apple_press_and_hold_enabled(&mut self) -> bool {
1571 true
1572 }
1573
1574 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1576 true
1577 }
1578}
1579
1580#[derive(Debug)]
1582pub struct WindowOptions {
1583 pub window_bounds: Option<WindowBounds>,
1587
1588 pub titlebar: Option<TitlebarOptions>,
1590
1591 pub focus: bool,
1593
1594 pub show: bool,
1596
1597 pub kind: WindowKind,
1599
1600 pub is_movable: bool,
1602
1603 pub app_owns_titlebar_drag: bool,
1608
1609 pub inactive_frame_interval: Option<Duration>,
1615
1616 pub is_resizable: bool,
1618
1619 pub is_minimizable: bool,
1621
1622 pub display_id: Option<DisplayId>,
1625
1626 pub window_background: WindowBackgroundAppearance,
1628
1629 pub app_id: Option<String>,
1631
1632 pub window_min_size: Option<Size<Pixels>>,
1634
1635 pub window_decorations: Option<WindowDecorations>,
1638
1639 pub tabbing_identifier: Option<String>,
1641
1642 pub mouse_passthrough: bool,
1644
1645 pub icon: Option<image::RgbaImage>,
1647}
1648
1649#[derive(Debug)]
1651#[cfg_attr(
1652 all(
1653 any(target_os = "linux", target_os = "freebsd"),
1654 not(any(feature = "x11", feature = "wayland"))
1655 ),
1656 allow(dead_code)
1657)]
1658#[allow(missing_docs)]
1659pub struct WindowParams {
1660 pub bounds: Bounds<Pixels>,
1661
1662 #[cfg_attr(feature = "wayland", allow(dead_code))]
1664 pub titlebar: Option<TitlebarOptions>,
1665
1666 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1668 pub kind: WindowKind,
1669
1670 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1672 pub is_movable: bool,
1673
1674 #[cfg_attr(
1676 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1677 allow(dead_code)
1678 )]
1679 pub app_owns_titlebar_drag: bool,
1680
1681 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1683 pub is_resizable: bool,
1684
1685 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1687 pub is_minimizable: bool,
1688
1689 #[cfg_attr(
1690 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1691 allow(dead_code)
1692 )]
1693 pub focus: bool,
1694
1695 #[cfg_attr(
1696 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1697 allow(dead_code)
1698 )]
1699 pub app_id: Option<String>,
1700
1701 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1702 pub show: bool,
1703
1704 #[cfg_attr(feature = "wayland", allow(dead_code))]
1705 pub display_id: Option<DisplayId>,
1706
1707 pub window_min_size: Option<Size<Pixels>>,
1708 #[cfg(target_os = "macos")]
1709 pub tabbing_identifier: Option<String>,
1710
1711 #[allow(dead_code)]
1712 pub mouse_passthrough: bool,
1713
1714 #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1716 pub icon: Option<image::RgbaImage>,
1717}
1718
1719#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1725pub enum ResizeAnchor {
1726 TopLeft,
1728 Top,
1730 TopRight,
1732 Left,
1734 Center,
1736 Right,
1738 BottomLeft,
1740 Bottom,
1742 BottomRight,
1744}
1745
1746#[derive(Debug, Copy, Clone, PartialEq)]
1748pub enum WindowBounds {
1749 Windowed(Bounds<Pixels>),
1751 Maximized(Bounds<Pixels>),
1754 Fullscreen(Bounds<Pixels>),
1757}
1758
1759impl Default for WindowBounds {
1760 fn default() -> Self {
1761 WindowBounds::Windowed(Bounds::default())
1762 }
1763}
1764
1765impl WindowBounds {
1766 pub fn get_bounds(&self) -> Bounds<Pixels> {
1768 match self {
1769 WindowBounds::Windowed(bounds) => *bounds,
1770 WindowBounds::Maximized(bounds) => *bounds,
1771 WindowBounds::Fullscreen(bounds) => *bounds,
1772 }
1773 }
1774
1775 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1780 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1781 }
1782}
1783
1784impl Default for WindowOptions {
1785 fn default() -> Self {
1786 Self {
1787 window_bounds: None,
1788 titlebar: Some(TitlebarOptions {
1789 title: Default::default(),
1790 appears_transparent: Default::default(),
1791 traffic_light_position: Default::default(),
1792 }),
1793 focus: true,
1794 show: true,
1795 kind: WindowKind::Normal,
1796 is_movable: true,
1797 app_owns_titlebar_drag: false,
1798 inactive_frame_interval: Some(Duration::from_micros(33_333)),
1799 is_resizable: true,
1800 is_minimizable: true,
1801 display_id: None,
1802 window_background: WindowBackgroundAppearance::default(),
1803 app_id: None,
1804 window_min_size: None,
1805 window_decorations: None,
1806 tabbing_identifier: None,
1807 mouse_passthrough: false,
1808 icon: None,
1809 }
1810 }
1811}
1812
1813#[cfg(test)]
1814mod platform_input_handler_tests {
1815 use super::*;
1816 use crate::{
1817 AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1818 };
1819
1820 struct RejectingEntityInputHandler;
1821
1822 impl EntityInputHandler for RejectingEntityInputHandler {
1823 fn text_for_range(
1824 &mut self,
1825 _range: Range<usize>,
1826 _adjusted_range: &mut Option<Range<usize>>,
1827 _window: &mut Window,
1828 _cx: &mut Context<Self>,
1829 ) -> Option<String> {
1830 None
1831 }
1832
1833 fn selected_text_range(
1834 &mut self,
1835 _ignore_disabled_input: bool,
1836 _window: &mut Window,
1837 _cx: &mut Context<Self>,
1838 ) -> Option<UTF16Selection> {
1839 None
1840 }
1841
1842 fn marked_text_range(
1843 &self,
1844 _window: &mut Window,
1845 _cx: &mut Context<Self>,
1846 ) -> Option<Range<usize>> {
1847 None
1848 }
1849
1850 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1851
1852 fn replace_text_in_range(
1853 &mut self,
1854 _range: Option<Range<usize>>,
1855 _text: &str,
1856 _window: &mut Window,
1857 _cx: &mut Context<Self>,
1858 ) {
1859 }
1860
1861 fn replace_and_mark_text_in_range(
1862 &mut self,
1863 _range: Option<Range<usize>>,
1864 _new_text: &str,
1865 _new_selected_range: Option<Range<usize>>,
1866 _window: &mut Window,
1867 _cx: &mut Context<Self>,
1868 ) {
1869 }
1870
1871 fn bounds_for_range(
1872 &mut self,
1873 _range_utf16: Range<usize>,
1874 _element_bounds: Bounds<Pixels>,
1875 _window: &mut Window,
1876 _cx: &mut Context<Self>,
1877 ) -> Option<Bounds<Pixels>> {
1878 None
1879 }
1880
1881 fn character_index_for_point(
1882 &mut self,
1883 _point: Point<Pixels>,
1884 _window: &mut Window,
1885 _cx: &mut Context<Self>,
1886 ) -> Option<usize> {
1887 None
1888 }
1889
1890 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1891 false
1892 }
1893 }
1894
1895 #[gpui_macros::test]
1896 fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1897 let entity = cx.new(|_| RejectingEntityInputHandler);
1898 let window = cx.add_window(|_, _| Empty);
1899
1900 cx.update_window(window.into(), |_, window, cx| {
1901 let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1902 assert!(!handler.accepts_text_input(window, cx));
1903 })
1904 .unwrap();
1905 }
1906
1907 #[gpui_macros::test]
1908 fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1909 let entity = cx.new(|_| RejectingEntityInputHandler);
1910 let window = cx.add_window(|_, _| Empty);
1911 let window = AnyWindowHandle::from(window);
1912 let async_cx = cx
1913 .update_window(window, |_, window, cx| window.to_async(cx))
1914 .unwrap();
1915
1916 cx.update_window(window, |_, window, _| window.remove_window())
1917 .unwrap();
1918 assert!(cx.update_window(window, |_, _, _| ()).is_err());
1919
1920 let mut handler = PlatformInputHandler::new(
1921 async_cx,
1922 Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1923 );
1924 assert!(handler.query_accepts_text_input());
1925 }
1926
1927 fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1928 Bounds::new(
1929 point(px(offset as f32 * 10.0), px(y)),
1930 size(px(1.0), px(20.0)),
1931 )
1932 }
1933
1934 #[test]
1935 fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1936 let selection = UTF16Selection {
1937 range: 3..5,
1938 reversed: false,
1939 };
1940 let mut requests = Vec::new();
1941 let bounds =
1942 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1943 requests.push(range.clone());
1944 Some(bounds_at(range.start, 0.0))
1945 })
1946 .unwrap();
1947
1948 assert_eq!(bounds.origin.x, px(50.0));
1949 assert_eq!(requests, vec![5..5]);
1950
1951 let selection = UTF16Selection {
1952 range: 3..5,
1953 reversed: true,
1954 };
1955 let bounds =
1956 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1957 Some(bounds_at(range.start, 0.0))
1958 })
1959 .unwrap();
1960
1961 assert_eq!(bounds.origin.x, px(30.0));
1962 }
1963
1964 #[test]
1965 fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
1966 let selection = UTF16Selection {
1967 range: 15..15,
1968 reversed: false,
1969 };
1970 let mut requests = Vec::new();
1971 let bounds =
1972 PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
1973 requests.push(range.clone());
1974 let y = if range.start >= 13 { 20.0 } else { 0.0 };
1975 Some(bounds_at(range.start, y))
1976 })
1977 .unwrap();
1978
1979 assert_eq!(bounds.origin.x, px(130.0));
1980 assert_eq!(requests.last(), Some(&(13..13)));
1981 }
1982}
1983
1984#[derive(Debug, Default)]
1986pub struct TitlebarOptions {
1987 pub title: Option<SharedString>,
1989
1990 pub appears_transparent: bool,
1993
1994 pub traffic_light_position: Option<Point<Pixels>>,
1996}
1997
1998#[derive(Clone, Debug, PartialEq)]
2000pub enum WindowKind {
2001 Normal,
2003
2004 PopUp,
2007
2008 Floating,
2010
2011 Overlay,
2013
2014 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
2017 LayerShell(layer_shell::LayerShellOptions),
2018}
2019
2020#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2025pub enum WindowAppearance {
2026 #[default]
2030 Light,
2031
2032 VibrantLight,
2036
2037 Dark,
2041
2042 VibrantDark,
2046}
2047
2048#[derive(Copy, Clone, Debug, Default, PartialEq)]
2051pub enum WindowBackgroundAppearance {
2052 #[default]
2060 Opaque,
2061 Transparent,
2063 Blurred,
2067}
2068
2069#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2071#[non_exhaustive]
2072pub enum TrayIconRenderingMode {
2073 #[default]
2077 Adaptive,
2078 Original,
2082}
2083
2084#[derive(Debug, Clone, PartialEq, Eq)]
2086pub enum TrayIconEvent {
2087 LeftClick,
2089 RightClick,
2091 DoubleClick,
2093}
2094
2095#[derive(Debug, Clone, PartialEq)]
2097pub struct TrayIconClickEvent {
2098 pub kind: TrayIconEvent,
2100 pub position: Option<Point<Pixels>>,
2107}
2108
2109impl TrayIconClickEvent {
2110 pub fn new(kind: TrayIconEvent) -> Self {
2112 Self {
2113 kind,
2114 position: None,
2115 }
2116 }
2117
2118 pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
2120 Self {
2121 kind,
2122 position: Some(position),
2123 }
2124 }
2125}
2126
2127#[allow(missing_docs)]
2128#[derive(Debug, Clone, PartialEq)]
2129pub struct TrayAnchor {
2130 pub display_id: DisplayId,
2131 pub bounds: Bounds<Pixels>,
2132}
2133
2134#[derive(Debug, Clone)]
2136pub enum TrayMenuItem {
2137 Action {
2139 label: SharedString,
2141 id: SharedString,
2143 },
2144 Separator,
2146 Submenu {
2148 label: SharedString,
2150 items: Vec<TrayMenuItem>,
2152 },
2153 Toggle {
2155 label: SharedString,
2157 checked: bool,
2159 id: SharedString,
2161 },
2162}
2163
2164#[derive(Debug, Clone)]
2166pub struct FocusedWindowInfo {
2167 pub app_name: String,
2169 pub window_title: String,
2171 pub bundle_id: Option<String>,
2173 pub pid: Option<u32>,
2175}
2176
2177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2179pub enum PermissionStatus {
2180 Granted,
2182 Denied,
2184 NotDetermined,
2186 Unavailable,
2188}
2189
2190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2192pub enum PermissionRequestStatus {
2193 Requested,
2195 Unavailable,
2197}
2198
2199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2201pub enum SystemPowerEvent {
2202 Suspend,
2204 Resume,
2206 LockScreen,
2208 UnlockScreen,
2210 Shutdown,
2212}
2213
2214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2216pub enum PowerSaveBlockerKind {
2217 PreventAppSuspension,
2219 PreventDisplaySleep,
2221}
2222
2223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2225pub enum NetworkStatus {
2226 Online,
2228 Offline,
2230}
2231
2232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2234pub enum MediaKeyEvent {
2235 Play,
2237 Pause,
2239 PlayPause,
2241 Stop,
2243 NextTrack,
2245 PreviousTrack,
2247}
2248
2249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2251pub enum AttentionType {
2252 Informational,
2254 Critical,
2256}
2257
2258#[derive(Debug, Clone, Copy, PartialEq)]
2260pub enum ProgressBarState {
2261 None,
2263 Indeterminate,
2265 Normal(f64),
2267 Error(f64),
2269 Paused(f64),
2271}
2272
2273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2275pub enum DialogKind {
2276 Info,
2278 Warning,
2280 Error,
2282}
2283
2284#[derive(Debug, Clone)]
2286pub struct DialogOptions {
2287 pub kind: DialogKind,
2289 pub title: SharedString,
2291 pub message: SharedString,
2293 pub detail: Option<SharedString>,
2295 pub buttons: Vec<SharedString>,
2297}
2298
2299#[derive(Debug, Clone)]
2301pub struct OsInfo {
2302 pub name: SharedString,
2304 pub version: SharedString,
2306 pub arch: SharedString,
2308 pub locale: SharedString,
2310 pub hostname: SharedString,
2312}
2313
2314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2316pub enum BiometricKind {
2317 TouchId,
2319 WindowsHello,
2321 Fingerprint,
2323}
2324
2325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2327pub enum BiometricStatus {
2328 Available(BiometricKind),
2330 Unavailable,
2332}
2333
2334#[derive(Debug, Clone, PartialEq)]
2336pub struct WindowState {
2337 pub bounds: WindowBounds,
2339 pub display_id: Option<DisplayId>,
2341 pub fullscreen: bool,
2343}
2344
2345#[derive(Debug, Clone, PartialEq)]
2347pub enum WindowPosition {
2348 Center,
2350 CenterOnDisplay(DisplayId),
2352 #[deprecated(
2359 since = "0.5.3",
2360 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."
2361 )]
2362 TrayCenter(Bounds<Pixels>),
2363 TrayAnchored(TrayAnchor),
2370 TopRight {
2372 margin: Pixels,
2374 },
2375 BottomRight {
2377 margin: Pixels,
2379 },
2380 TopLeft {
2382 margin: Pixels,
2384 },
2385 BottomLeft {
2387 margin: Pixels,
2389 },
2390}
2391
2392#[derive(Debug, Clone)]
2394pub struct CrashReport {
2395 pub message: String,
2397 pub backtrace: String,
2399 pub os_info: OsInfo,
2401 pub app_version: Option<String>,
2403}
2404
2405#[derive(Clone, Debug)]
2407pub struct PathPromptOptions {
2408 pub files: bool,
2410 pub directories: bool,
2412 pub multiple: bool,
2414 pub prompt: Option<SharedString>,
2416}
2417
2418#[derive(Copy, Clone, Debug, PartialEq)]
2420pub enum PromptLevel {
2421 Info,
2423
2424 Warning,
2426
2427 Critical,
2429}
2430
2431#[derive(Clone, Debug, PartialEq)]
2433pub enum PromptButton {
2434 Ok(SharedString),
2436 Cancel(SharedString),
2438 Other(SharedString),
2440}
2441
2442impl PromptButton {
2443 pub fn new(label: impl Into<SharedString>) -> Self {
2445 PromptButton::Other(label.into())
2446 }
2447
2448 pub fn ok(label: impl Into<SharedString>) -> Self {
2450 PromptButton::Ok(label.into())
2451 }
2452
2453 pub fn cancel(label: impl Into<SharedString>) -> Self {
2455 PromptButton::Cancel(label.into())
2456 }
2457
2458 #[allow(dead_code)]
2459 #[doc(hidden)]
2460 pub fn is_cancel(&self) -> bool {
2461 matches!(self, PromptButton::Cancel(_))
2462 }
2463
2464 pub fn label(&self) -> &SharedString {
2466 match self {
2467 PromptButton::Ok(label) => label,
2468 PromptButton::Cancel(label) => label,
2469 PromptButton::Other(label) => label,
2470 }
2471 }
2472}
2473
2474impl From<&str> for PromptButton {
2475 fn from(value: &str) -> Self {
2476 match value.to_lowercase().as_str() {
2477 "ok" => PromptButton::Ok("Ok".into()),
2478 "cancel" => PromptButton::Cancel("Cancel".into()),
2479 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2480 }
2481 }
2482}
2483
2484#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2486pub enum CursorStyle {
2487 #[default]
2489 Arrow,
2490
2491 IBeam,
2494
2495 Crosshair,
2498
2499 ClosedHand,
2502
2503 OpenHand,
2506
2507 PointingHand,
2510
2511 ResizeLeft,
2514
2515 ResizeRight,
2518
2519 ResizeLeftRight,
2522
2523 ResizeUp,
2526
2527 ResizeDown,
2530
2531 ResizeUpDown,
2534
2535 ResizeUpLeftDownRight,
2538
2539 ResizeUpRightDownLeft,
2542
2543 ResizeColumn,
2546
2547 ResizeRow,
2550
2551 IBeamCursorForVerticalLayout,
2554
2555 OperationNotAllowed,
2558
2559 DragLink,
2562
2563 DragCopy,
2566
2567 ContextualMenu,
2570
2571 None,
2573}
2574
2575#[derive(Clone, Debug, Eq, PartialEq)]
2577pub struct ClipboardItem {
2578 entries: Vec<ClipboardEntry>,
2579}
2580
2581#[derive(Clone, Debug, Eq, PartialEq)]
2583pub enum ClipboardEntry {
2584 String(ClipboardString),
2586 Image(Image),
2588}
2589
2590impl ClipboardItem {
2591 pub fn new_string(text: String) -> Self {
2593 Self {
2594 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2595 }
2596 }
2597
2598 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2600 Self {
2601 entries: vec![ClipboardEntry::String(ClipboardString {
2602 text,
2603 metadata: Some(metadata),
2604 })],
2605 }
2606 }
2607
2608 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2610 Self {
2611 entries: vec![ClipboardEntry::String(
2612 ClipboardString::new(text).with_json_metadata(metadata),
2613 )],
2614 }
2615 }
2616
2617 pub fn new_image(image: &Image) -> Self {
2619 Self {
2620 entries: vec![ClipboardEntry::Image(image.clone())],
2621 }
2622 }
2623
2624 pub fn text(&self) -> Option<String> {
2627 let mut answer = String::new();
2628 let mut any_entries = false;
2629
2630 for entry in self.entries.iter() {
2631 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2632 answer.push_str(text);
2633 any_entries = true;
2634 }
2635 }
2636
2637 if any_entries { Some(answer) } else { None }
2638 }
2639
2640 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2642 pub fn metadata(&self) -> Option<&String> {
2643 match self.entries().first() {
2644 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2645 clipboard_string.metadata.as_ref()
2646 }
2647 _ => None,
2648 }
2649 }
2650
2651 pub fn entries(&self) -> &[ClipboardEntry] {
2653 &self.entries
2654 }
2655
2656 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2658 self.entries.into_iter()
2659 }
2660}
2661
2662impl From<ClipboardString> for ClipboardEntry {
2663 fn from(value: ClipboardString) -> Self {
2664 Self::String(value)
2665 }
2666}
2667
2668impl From<String> for ClipboardEntry {
2669 fn from(value: String) -> Self {
2670 Self::from(ClipboardString::from(value))
2671 }
2672}
2673
2674impl From<Image> for ClipboardEntry {
2675 fn from(value: Image) -> Self {
2676 Self::Image(value)
2677 }
2678}
2679
2680impl From<ClipboardEntry> for ClipboardItem {
2681 fn from(value: ClipboardEntry) -> Self {
2682 Self {
2683 entries: vec![value],
2684 }
2685 }
2686}
2687
2688impl From<String> for ClipboardItem {
2689 fn from(value: String) -> Self {
2690 Self::from(ClipboardEntry::from(value))
2691 }
2692}
2693
2694impl From<Image> for ClipboardItem {
2695 fn from(value: Image) -> Self {
2696 Self::from(ClipboardEntry::from(value))
2697 }
2698}
2699
2700#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2702pub enum ImageFormat {
2703 Png,
2708 Jpeg,
2710 Webp,
2712 Gif,
2714 Svg,
2716 Bmp,
2718 Tiff,
2720}
2721
2722impl ImageFormat {
2723 pub const fn mime_type(self) -> &'static str {
2725 match self {
2726 ImageFormat::Png => "image/png",
2727 ImageFormat::Jpeg => "image/jpeg",
2728 ImageFormat::Webp => "image/webp",
2729 ImageFormat::Gif => "image/gif",
2730 ImageFormat::Svg => "image/svg+xml",
2731 ImageFormat::Bmp => "image/bmp",
2732 ImageFormat::Tiff => "image/tiff",
2733 }
2734 }
2735
2736 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2738 match mime_type {
2739 "image/png" => Some(Self::Png),
2740 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2741 "image/webp" => Some(Self::Webp),
2742 "image/gif" => Some(Self::Gif),
2743 "image/svg+xml" => Some(Self::Svg),
2744 "image/bmp" => Some(Self::Bmp),
2745 "image/tiff" | "image/tif" => Some(Self::Tiff),
2746 _ => None,
2747 }
2748 }
2749}
2750
2751#[derive(Clone, Debug, PartialEq, Eq)]
2753pub struct Image {
2754 pub format: ImageFormat,
2756 pub bytes: Vec<u8>,
2758 id: u64,
2760}
2761
2762impl Image {
2763 pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2765 let id = crate::hash(&bytes);
2766 Self { format, bytes, id }
2767 }
2768}
2769
2770impl Hash for Image {
2771 fn hash<H: Hasher>(&self, state: &mut H) {
2772 state.write_u64(self.id);
2773 }
2774}
2775
2776pub(crate) fn decode_static_image(
2777 bytes: &[u8],
2778 format: image::ImageFormat,
2779) -> Result<SmallVec<[Frame; 1]>> {
2780 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2781 .into_decoder()
2782 .context("creating image decoder")?;
2783 decode_static_image_from_decoder(decoder)
2784}
2785
2786pub(crate) fn decode_static_image_from_decoder(
2787 mut decoder: impl image::ImageDecoder,
2788) -> Result<SmallVec<[Frame; 1]>> {
2789 let orientation = decoder.orientation().context("reading image orientation")?;
2790 let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2791 image.apply_orientation(orientation);
2792
2793 let mut data = image.into_rgba8();
2794 for pixel in data.chunks_exact_mut(4) {
2795 pixel.swap(0, 2);
2796 }
2797
2798 Ok(SmallVec::from_elem(Frame::new(data), 1))
2799}
2800
2801impl Image {
2802 pub fn empty() -> Self {
2804 Self::from_bytes(ImageFormat::Png, Vec::new())
2805 }
2806
2807 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2809 Self {
2810 id: hash(&bytes),
2811 format,
2812 bytes,
2813 }
2814 }
2815
2816 pub fn id(&self) -> u64 {
2818 self.id
2819 }
2820
2821 pub fn use_render_image(
2823 self: Arc<Self>,
2824 window: &mut Window,
2825 cx: &mut App,
2826 ) -> Option<Arc<RenderImage>> {
2827 ImageSource::Image(self)
2828 .use_data(None, window, cx)
2829 .and_then(|result| result.ok())
2830 }
2831
2832 pub fn get_render_image(
2834 self: Arc<Self>,
2835 window: &mut Window,
2836 cx: &mut App,
2837 ) -> Option<Arc<RenderImage>> {
2838 ImageSource::Image(self)
2839 .get_data(None, window, cx)
2840 .and_then(|result| result.ok())
2841 }
2842
2843 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2845 ImageSource::Image(self).remove_asset(cx);
2846 }
2847
2848 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2850 let frames = match self.format {
2851 #[cfg(feature = "image-format-gif")]
2852 ImageFormat::Gif => {
2853 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2854 let mut frames = SmallVec::new();
2855
2856 for frame in decoder.into_frames() {
2857 let mut frame = frame?;
2858 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2860 pixel.swap(0, 2);
2861 }
2862 frames.push(frame);
2863 }
2864
2865 frames
2866 }
2867 #[cfg(not(feature = "image-format-gif"))]
2868 ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2869 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2870 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2871 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2872 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2873 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2874 ImageFormat::Svg => {
2875 return svg_renderer
2876 .render_single_frame(&self.bytes, 1.0)
2877 .map_err(Into::into);
2878 }
2879 };
2880
2881 Ok(Arc::new(RenderImage::new(frames)))
2882 }
2883
2884 pub fn format(&self) -> ImageFormat {
2886 self.format
2887 }
2888
2889 pub fn bytes(&self) -> &[u8] {
2891 self.bytes.as_slice()
2892 }
2893}
2894
2895#[cfg(test)]
2896mod image_tests {
2897 use super::*;
2898
2899 #[test]
2900 fn image_to_image_data_applies_exif_orientation() {
2901 let image = Image::from_bytes(
2902 ImageFormat::Jpeg,
2903 include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2904 );
2905
2906 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2907
2908 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2909 let bytes = render_image.as_bytes(0).unwrap();
2910 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2911 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2912 }
2913}
2914
2915#[derive(Clone, Debug, Eq, PartialEq)]
2917pub struct ClipboardString {
2918 pub(crate) text: String,
2919 pub(crate) metadata: Option<String>,
2920}
2921
2922impl ClipboardString {
2923 pub fn new(text: String) -> Self {
2925 Self {
2926 text,
2927 metadata: None,
2928 }
2929 }
2930
2931 #[doc(hidden)]
2933 pub fn with_metadata(mut self, metadata: String) -> Self {
2934 self.metadata = Some(metadata);
2935 self
2936 }
2937
2938 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2941 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2942 self
2943 }
2944
2945 pub fn text(&self) -> &String {
2947 &self.text
2948 }
2949
2950 pub fn into_text(self) -> String {
2952 self.text
2953 }
2954
2955 pub fn metadata_json<T>(&self) -> Option<T>
2957 where
2958 T: for<'a> Deserialize<'a>,
2959 {
2960 self.metadata
2961 .as_ref()
2962 .and_then(|m| serde_json::from_str(m).ok())
2963 }
2964
2965 #[doc(hidden)]
2967 pub fn metadata(&self) -> Option<&str> {
2968 self.metadata.as_deref()
2969 }
2970
2971 #[doc(hidden)]
2972 pub fn text_hash(text: &str) -> u64 {
2973 let mut hasher = SeaHasher::new();
2974 text.hash(&mut hasher);
2975 hasher.finish()
2976 }
2977}
2978
2979impl From<String> for ClipboardString {
2980 fn from(value: String) -> Self {
2981 Self {
2982 text: value,
2983 metadata: None,
2984 }
2985 }
2986}
2987
2988#[cfg(test)]
2989mod clipboard_string_tests {
2990 use super::ClipboardString;
2991
2992 #[test]
2993 fn raw_metadata_preserves_the_clipboard_text() {
2994 let clipboard_string = ClipboardString::new("copied text".to_owned())
2995 .with_metadata("application/x-fc-gpui".to_owned());
2996
2997 assert_eq!(clipboard_string.text(), "copied text");
2998 assert_eq!(clipboard_string.metadata(), Some("application/x-fc-gpui"));
2999 }
3000}