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 scale_factor(&self) -> f32;
788 fn appearance(&self) -> WindowAppearance;
789 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
790 fn mouse_position(&self) -> Point<Pixels>;
791 fn modifiers(&self) -> Modifiers;
792 fn capslock(&self) -> Capslock;
793 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
794 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
795 fn prompt(
796 &self,
797 level: PromptLevel,
798 msg: &str,
799 detail: Option<&str>,
800 answers: &[PromptButton],
801 ) -> Option<oneshot::Receiver<usize>>;
802 fn activate(&self);
803 fn request_attention(&self) {}
805 fn is_active(&self) -> bool;
806 fn visibility(&self) -> WindowVisibility;
809 fn is_hovered(&self) -> bool;
810 fn set_title(&mut self, title: &str);
811 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
812 fn minimize(&self);
813 fn zoom(&self);
814 fn toggle_fullscreen(&self);
815 fn is_fullscreen(&self) -> bool;
816 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
817 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
818 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
819 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
823 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
824 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
825 fn on_moved(&self, callback: Box<dyn FnMut()>);
826 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
827 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
828 fn on_close(&self, callback: Box<dyn FnOnce()>);
829 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
830 fn draw(&self, scene: &Scene);
831 #[cfg(any(test, feature = "test-support"))]
832 fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
833 anyhow::bail!("render_to_image not implemented for this platform")
834 }
835 fn schedule_frame(&self) {}
836 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
837
838 fn get_title(&self) -> String {
840 String::new()
841 }
842 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
843 None
844 }
845 fn tab_bar_visible(&self) -> bool {
846 false
847 }
848 fn set_edited(&mut self, _edited: bool) {}
849 fn toggle_simple_fullscreen(&self) {}
850 fn is_simple_fullscreen(&self) -> bool {
851 false
852 }
853 fn show_character_palette(&self) {}
854 fn titlebar_double_click(&self) {}
855 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
856 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
857 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
858 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
859 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
860 fn merge_all_windows(&self) {}
861 fn move_tab_to_new_window(&self) {}
862 fn toggle_window_tab_overview(&self) {}
863 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
864
865 fn native_window_state(&self) -> Option<Vec<u8>> {
866 None
867 }
868 fn restore_native_window_state(&self, _state: &[u8]) {}
869
870 #[cfg(target_os = "windows")]
871 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
872
873 fn inner_window_bounds(&self) -> WindowBounds {
875 self.window_bounds()
876 }
877 fn request_decorations(&self, _decorations: WindowDecorations) {}
878 fn show_window_menu(&self, _position: Point<Pixels>) {}
879 fn start_window_move(&self) {}
880 fn start_window_resize(&self, _edge: ResizeEdge) {}
881 fn set_exclusive_zone(&self, _zone: Pixels) {}
882 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
883 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
884 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
885 fn window_decorations(&self) -> Decorations {
886 Decorations::Server
887 }
888 fn set_app_id(&mut self, _app_id: &str) {}
889 fn map_window(&mut self) -> anyhow::Result<()> {
890 Ok(())
891 }
892 fn window_controls(&self) -> WindowControls {
893 WindowControls::default()
894 }
895 fn set_client_inset(&self, _inset: Pixels) {}
896 fn gpu_specs(&self) -> Option<GpuSpecs>;
897
898 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
899
900 fn play_system_bell(&self) {}
901
902 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
904
905 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
907
908 fn a11y_update_window_bounds(&self) {}
910
911 fn show(&self) {}
912 fn hide(&self) {}
913 fn is_visible(&self) -> bool {
914 true
915 }
916 fn set_mouse_passthrough(&self, _passthrough: bool) {}
917 fn set_progress_bar(&self, _state: ProgressBarState) {}
918 #[allow(dead_code)]
919 fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
920
921 fn set_cursor_style(&self, _style: CursorStyle) {}
922
923 #[cfg(any(test, feature = "test-support"))]
924 fn as_test(&mut self) -> Option<&mut TestWindow> {
925 None
926 }
927}
928
929#[doc(hidden)]
932pub trait PlatformDispatcher: Send + Sync {
933 fn is_main_thread(&self) -> bool;
934 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
935 fn dispatch_on_main_thread(&self, runnable: Runnable);
936 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
937 fn park(&self, timeout: Option<Duration>) -> bool;
938 fn unparker(&self) -> Unparker;
939 fn now(&self) -> Instant {
940 Instant::now()
941 }
942
943 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
947 ActivityGuard::noop()
948 }
949
950 #[cfg(any(test, feature = "test-support"))]
951 fn as_test(&self) -> Option<&TestDispatcher> {
952 None
953 }
954
955 #[cfg(any(test, feature = "test-support"))]
956 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
957 None
958 }
959}
960
961#[allow(missing_docs)]
962pub trait PlatformTextSystem: Send + Sync {
963 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
964 fn all_font_names(&self) -> Vec<String>;
965 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
966 fn clear_glyph_raster_cache(&self) {}
968 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
970 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
971 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
972 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
973 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
974 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
975 fn rasterize_glyph(
976 &self,
977 params: &RenderGlyphParams,
978 raster_bounds: Bounds<DevicePixels>,
979 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
980 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
981
982 fn set_missing_glyph_sink(&self, _sink: Arc<dyn MissingGlyphSink>) {}
986
987 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
988 0
989 }
990}
991
992#[allow(missing_docs)]
993pub struct NoopTextSystem;
994
995#[allow(missing_docs)]
996impl NoopTextSystem {
997 #[allow(dead_code)]
998 pub fn new() -> Self {
999 Self
1000 }
1001}
1002
1003impl PlatformTextSystem for NoopTextSystem {
1004 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1005 Ok(())
1006 }
1007
1008 fn all_font_names(&self) -> Vec<String> {
1009 Vec::new()
1010 }
1011
1012 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1013 Ok(FontId(1))
1014 }
1015
1016 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1017 FontMetrics {
1018 units_per_em: 1000,
1019 ascent: 1025.0,
1020 descent: -275.0,
1021 line_gap: 0.0,
1022 underline_position: -95.0,
1023 underline_thickness: 60.0,
1024 cap_height: 698.0,
1025 x_height: 516.0,
1026 bounding_box: Bounds {
1027 origin: Point {
1028 x: -260.0,
1029 y: -245.0,
1030 },
1031 size: Size {
1032 width: 1501.0,
1033 height: 1364.0,
1034 },
1035 },
1036 }
1037 }
1038
1039 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1040 Ok(Bounds {
1041 origin: Point { x: 54.0, y: 0.0 },
1042 size: size(392.0, 528.0),
1043 })
1044 }
1045
1046 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1047 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1048 }
1049
1050 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1051 Some(GlyphId(ch.len_utf16() as u32))
1052 }
1053
1054 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1055 Ok(Default::default())
1056 }
1057
1058 fn rasterize_glyph(
1059 &self,
1060 _params: &RenderGlyphParams,
1061 raster_bounds: Bounds<DevicePixels>,
1062 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1063 Ok((raster_bounds.size, Vec::new()))
1064 }
1065
1066 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1067 let mut position = px(0.);
1068 let metrics = self.font_metrics(FontId(0));
1069 let em_width = font_size
1070 * self
1071 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1072 .unwrap()
1073 .width
1074 / metrics.units_per_em as f32;
1075 let mut glyphs = Vec::new();
1076 for (ix, c) in text.char_indices() {
1077 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1078 glyphs.push(ShapedGlyph {
1079 id: glyph,
1080 position: point(position, px(0.)),
1081 index: ix,
1082 is_emoji: glyph.0 == 2,
1083 });
1084 if glyph.0 == 2 {
1085 position += em_width * 2.0;
1086 } else {
1087 position += em_width;
1088 }
1089 } else {
1090 position += em_width
1091 }
1092 }
1093 let mut runs = Vec::default();
1094 if !glyphs.is_empty() {
1095 runs.push(ShapedRun {
1096 font_id: FontId(0),
1097 glyphs,
1098 });
1099 } else {
1100 position = px(0.);
1101 }
1102
1103 LineLayout {
1104 font_size,
1105 width: position,
1106 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1107 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1108 runs,
1109 len: text.len(),
1110 }
1111 }
1112}
1113
1114#[derive(PartialEq, Eq, Hash, Clone)]
1115#[allow(missing_docs)]
1116pub enum AtlasKey {
1117 Glyph(RenderGlyphParams),
1118 Svg(RenderSvgParams),
1119 Image(RenderImageParams),
1120}
1121
1122#[allow(missing_docs)]
1123impl AtlasKey {
1124 #[cfg_attr(
1125 all(
1126 any(target_os = "linux", target_os = "freebsd"),
1127 not(any(feature = "x11", feature = "wayland"))
1128 ),
1129 allow(dead_code)
1130 )]
1131 pub fn texture_kind(&self) -> AtlasTextureKind {
1132 match self {
1133 AtlasKey::Glyph(params) => {
1134 if params.is_emoji {
1135 AtlasTextureKind::Polychrome
1136 } else {
1137 AtlasTextureKind::Monochrome
1138 }
1139 }
1140 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1141 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1142 }
1143 }
1144}
1145
1146impl From<RenderGlyphParams> for AtlasKey {
1147 fn from(params: RenderGlyphParams) -> Self {
1148 Self::Glyph(params)
1149 }
1150}
1151
1152impl From<RenderSvgParams> for AtlasKey {
1153 fn from(params: RenderSvgParams) -> Self {
1154 Self::Svg(params)
1155 }
1156}
1157
1158impl From<RenderImageParams> for AtlasKey {
1159 fn from(params: RenderImageParams) -> Self {
1160 Self::Image(params)
1161 }
1162}
1163
1164#[allow(missing_docs)]
1165pub trait PlatformAtlas: Send + Sync {
1166 fn get_or_insert_with<'a>(
1167 &self,
1168 key: &AtlasKey,
1169 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1170 ) -> Result<Option<AtlasTile>>;
1171 fn remove(&self, key: &AtlasKey);
1172}
1173
1174#[doc(hidden)]
1175pub struct AtlasTextureList<T> {
1176 pub textures: Vec<Option<T>>,
1177 pub free_list: Vec<usize>,
1178}
1179
1180impl<T> Default for AtlasTextureList<T> {
1181 fn default() -> Self {
1182 Self {
1183 textures: Vec::default(),
1184 free_list: Vec::default(),
1185 }
1186 }
1187}
1188
1189impl<T> ops::Index<usize> for AtlasTextureList<T> {
1190 type Output = Option<T>;
1191
1192 fn index(&self, index: usize) -> &Self::Output {
1193 &self.textures[index]
1194 }
1195}
1196
1197#[allow(missing_docs)]
1198impl<T> AtlasTextureList<T> {
1199 #[allow(unused)]
1200 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1201 self.free_list.clear();
1202 self.textures.drain(..)
1203 }
1204
1205 #[allow(dead_code)]
1206 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1207 self.textures.iter_mut().flatten()
1208 }
1209}
1210
1211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1212#[repr(C)]
1213#[allow(missing_docs)]
1214pub struct AtlasTile {
1215 pub texture_id: AtlasTextureId,
1216 pub tile_id: TileId,
1217 pub padding: u32,
1218 pub bounds: Bounds<DevicePixels>,
1219}
1220
1221#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1222#[repr(C)]
1223#[allow(missing_docs)]
1224pub struct AtlasTextureId {
1225 pub index: u32,
1227 pub kind: AtlasTextureKind,
1228}
1229
1230#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1231#[repr(C)]
1232#[cfg_attr(
1233 all(
1234 any(target_os = "linux", target_os = "freebsd"),
1235 not(any(feature = "x11", feature = "wayland"))
1236 ),
1237 allow(dead_code)
1238)]
1239#[allow(missing_docs)]
1240pub enum AtlasTextureKind {
1241 Monochrome = 0,
1242 Polychrome = 1,
1243}
1244
1245#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1246#[repr(C)]
1247#[allow(missing_docs)]
1248pub struct TileId(pub u32);
1249
1250impl From<etagere::AllocId> for TileId {
1251 fn from(id: etagere::AllocId) -> Self {
1252 Self(id.serialize())
1253 }
1254}
1255
1256impl From<TileId> for etagere::AllocId {
1257 fn from(id: TileId) -> Self {
1258 Self::deserialize(id.0)
1259 }
1260}
1261
1262#[allow(missing_docs)]
1263pub struct PlatformInputHandler {
1264 cx: AsyncWindowContext,
1265 handler: Box<dyn InputHandler>,
1266}
1267
1268#[cfg_attr(
1269 all(
1270 any(target_os = "linux", target_os = "freebsd"),
1271 not(any(feature = "x11", feature = "wayland"))
1272 ),
1273 allow(dead_code)
1274)]
1275#[allow(missing_docs)]
1276impl PlatformInputHandler {
1277 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1278 Self { cx, handler }
1279 }
1280
1281 fn mark_keyboard_input(window: &Window) {
1282 window.input_rate_tracker.borrow_mut().record_input();
1283 window.last_input_was_keyboard.set(true);
1284 }
1285
1286 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1287 self.cx
1288 .update(|window, cx| {
1289 self.handler
1290 .selected_text_range(ignore_disabled_input, window, cx)
1291 })
1292 .ok()
1293 .flatten()
1294 }
1295
1296 #[cfg_attr(target_os = "windows", allow(dead_code))]
1297 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1298 self.cx
1299 .update(|window, cx| self.handler.marked_text_range(window, cx))
1300 .ok()
1301 .flatten()
1302 }
1303
1304 #[cfg_attr(
1305 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1306 allow(dead_code)
1307 )]
1308 pub fn text_for_range(
1309 &mut self,
1310 range_utf16: Range<usize>,
1311 adjusted: &mut Option<Range<usize>>,
1312 ) -> Option<String> {
1313 self.cx
1314 .update(|window, cx| {
1315 self.handler
1316 .text_for_range(range_utf16, adjusted, window, cx)
1317 })
1318 .ok()
1319 .flatten()
1320 }
1321
1322 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1323 self.cx
1324 .update(|window, cx| {
1325 Self::mark_keyboard_input(window);
1326 self.handler
1327 .replace_text_in_range(replacement_range, text, window, cx);
1328 })
1329 .ok();
1330 }
1331
1332 pub fn replace_and_mark_text_in_range(
1333 &mut self,
1334 range_utf16: Option<Range<usize>>,
1335 new_text: &str,
1336 new_selected_range: Option<Range<usize>>,
1337 ) {
1338 self.cx
1339 .update(|window, cx| {
1340 Self::mark_keyboard_input(window);
1341 self.handler.replace_and_mark_text_in_range(
1342 range_utf16,
1343 new_text,
1344 new_selected_range,
1345 window,
1346 cx,
1347 )
1348 })
1349 .ok();
1350 }
1351
1352 #[cfg_attr(target_os = "windows", allow(dead_code))]
1353 pub fn unmark_text(&mut self) {
1354 self.cx
1355 .update(|window, cx| {
1356 Self::mark_keyboard_input(window);
1357 self.handler.unmark_text(window, cx)
1358 })
1359 .ok();
1360 }
1361
1362 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1363 self.cx
1364 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1365 .ok()
1366 .flatten()
1367 }
1368
1369 #[allow(dead_code)]
1370 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1371 self.handler.apple_press_and_hold_enabled()
1372 }
1373
1374 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1375 window.last_input_was_keyboard.set(true);
1378 self.handler.replace_text_in_range(None, input, window, cx);
1379 }
1380
1381 pub fn compute_ime_candidate_bounds(
1382 marked_range: Option<Range<usize>>,
1383 selection: &UTF16Selection,
1384 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1385 ) -> Option<Bounds<Pixels>> {
1386 if let Some(marked_range) = marked_range {
1387 let mut line_start = marked_range.start;
1388 let caret = selection.range.end;
1389 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1390 for i in (marked_range.start..caret).rev() {
1391 if let Some(bounds) = bounds_for_range(i..i)
1392 && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1393 {
1394 line_start = i + 1;
1395 break;
1396 }
1397 }
1398 }
1399 bounds_for_range(line_start..line_start)
1400 } else {
1401 let offset = if selection.reversed {
1402 selection.range.start
1403 } else {
1404 selection.range.end
1405 };
1406 bounds_for_range(offset..offset)
1407 }
1408 }
1409
1410 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1411 let marked_range = self.handler.marked_text_range(window, cx);
1412 let selection = self.handler.selected_text_range(true, window, cx)?;
1413 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1414 self.handler.bounds_for_range(range, window, cx)
1415 })
1416 }
1417
1418 #[allow(dead_code)]
1419 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1420 let marked_range = self.marked_text_range();
1421 let selection = self.selected_text_range(true)?;
1422 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1423 self.bounds_for_range(range)
1424 })
1425 }
1426
1427 #[allow(unused)]
1428 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1429 self.cx
1430 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1431 .ok()
1432 .flatten()
1433 }
1434
1435 #[allow(dead_code)]
1436 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1437 self.handler.accepts_text_input(window, cx)
1438 }
1439
1440 #[allow(dead_code)]
1441 pub fn query_accepts_text_input(&mut self) -> bool {
1442 self.cx
1443 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1444 .unwrap_or(true)
1445 }
1446}
1447
1448#[derive(Debug)]
1451pub struct UTF16Selection {
1452 pub range: Range<usize>,
1455 pub reversed: bool,
1458}
1459
1460pub trait InputHandler: 'static {
1465 fn selected_text_range(
1470 &mut self,
1471 ignore_disabled_input: bool,
1472 window: &mut Window,
1473 cx: &mut App,
1474 ) -> Option<UTF16Selection>;
1475
1476 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1481
1482 fn text_for_range(
1487 &mut self,
1488 range_utf16: Range<usize>,
1489 adjusted_range: &mut Option<Range<usize>>,
1490 window: &mut Window,
1491 cx: &mut App,
1492 ) -> Option<String>;
1493
1494 fn replace_text_in_range(
1499 &mut self,
1500 replacement_range: Option<Range<usize>>,
1501 text: &str,
1502 window: &mut Window,
1503 cx: &mut App,
1504 );
1505
1506 fn replace_and_mark_text_in_range(
1513 &mut self,
1514 range_utf16: Option<Range<usize>>,
1515 new_text: &str,
1516 new_selected_range: Option<Range<usize>>,
1517 window: &mut Window,
1518 cx: &mut App,
1519 );
1520
1521 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1524
1525 fn bounds_for_range(
1530 &mut self,
1531 range_utf16: Range<usize>,
1532 window: &mut Window,
1533 cx: &mut App,
1534 ) -> Option<Bounds<Pixels>>;
1535
1536 fn character_index_for_point(
1540 &mut self,
1541 point: Point<Pixels>,
1542 window: &mut Window,
1543 cx: &mut App,
1544 ) -> Option<usize>;
1545
1546 #[allow(dead_code)]
1551 fn apple_press_and_hold_enabled(&mut self) -> bool {
1552 true
1553 }
1554
1555 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1557 true
1558 }
1559}
1560
1561#[derive(Debug)]
1563pub struct WindowOptions {
1564 pub window_bounds: Option<WindowBounds>,
1568
1569 pub titlebar: Option<TitlebarOptions>,
1571
1572 pub focus: bool,
1574
1575 pub show: bool,
1577
1578 pub kind: WindowKind,
1580
1581 pub is_movable: bool,
1583
1584 pub app_owns_titlebar_drag: bool,
1589
1590 pub inactive_frame_interval: Option<Duration>,
1596
1597 pub is_resizable: bool,
1599
1600 pub is_minimizable: bool,
1602
1603 pub display_id: Option<DisplayId>,
1606
1607 pub window_background: WindowBackgroundAppearance,
1609
1610 pub app_id: Option<String>,
1612
1613 pub window_min_size: Option<Size<Pixels>>,
1615
1616 pub window_decorations: Option<WindowDecorations>,
1619
1620 pub tabbing_identifier: Option<String>,
1622
1623 pub mouse_passthrough: bool,
1625
1626 pub icon: Option<image::RgbaImage>,
1628}
1629
1630#[derive(Debug)]
1632#[cfg_attr(
1633 all(
1634 any(target_os = "linux", target_os = "freebsd"),
1635 not(any(feature = "x11", feature = "wayland"))
1636 ),
1637 allow(dead_code)
1638)]
1639#[allow(missing_docs)]
1640pub struct WindowParams {
1641 pub bounds: Bounds<Pixels>,
1642
1643 #[cfg_attr(feature = "wayland", allow(dead_code))]
1645 pub titlebar: Option<TitlebarOptions>,
1646
1647 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1649 pub kind: WindowKind,
1650
1651 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1653 pub is_movable: bool,
1654
1655 #[cfg_attr(
1657 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1658 allow(dead_code)
1659 )]
1660 pub app_owns_titlebar_drag: bool,
1661
1662 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1664 pub is_resizable: bool,
1665
1666 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1668 pub is_minimizable: bool,
1669
1670 #[cfg_attr(
1671 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1672 allow(dead_code)
1673 )]
1674 pub focus: bool,
1675
1676 #[cfg_attr(
1677 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1678 allow(dead_code)
1679 )]
1680 pub app_id: Option<String>,
1681
1682 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1683 pub show: bool,
1684
1685 #[cfg_attr(feature = "wayland", allow(dead_code))]
1686 pub display_id: Option<DisplayId>,
1687
1688 pub window_min_size: Option<Size<Pixels>>,
1689 #[cfg(target_os = "macos")]
1690 pub tabbing_identifier: Option<String>,
1691
1692 #[allow(dead_code)]
1693 pub mouse_passthrough: bool,
1694
1695 #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1697 pub icon: Option<image::RgbaImage>,
1698}
1699
1700#[derive(Debug, Copy, Clone, PartialEq)]
1702pub enum WindowBounds {
1703 Windowed(Bounds<Pixels>),
1705 Maximized(Bounds<Pixels>),
1708 Fullscreen(Bounds<Pixels>),
1711}
1712
1713impl Default for WindowBounds {
1714 fn default() -> Self {
1715 WindowBounds::Windowed(Bounds::default())
1716 }
1717}
1718
1719impl WindowBounds {
1720 pub fn get_bounds(&self) -> Bounds<Pixels> {
1722 match self {
1723 WindowBounds::Windowed(bounds) => *bounds,
1724 WindowBounds::Maximized(bounds) => *bounds,
1725 WindowBounds::Fullscreen(bounds) => *bounds,
1726 }
1727 }
1728
1729 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1734 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1735 }
1736}
1737
1738impl Default for WindowOptions {
1739 fn default() -> Self {
1740 Self {
1741 window_bounds: None,
1742 titlebar: Some(TitlebarOptions {
1743 title: Default::default(),
1744 appears_transparent: Default::default(),
1745 traffic_light_position: Default::default(),
1746 }),
1747 focus: true,
1748 show: true,
1749 kind: WindowKind::Normal,
1750 is_movable: true,
1751 app_owns_titlebar_drag: false,
1752 inactive_frame_interval: Some(Duration::from_micros(33_333)),
1753 is_resizable: true,
1754 is_minimizable: true,
1755 display_id: None,
1756 window_background: WindowBackgroundAppearance::default(),
1757 app_id: None,
1758 window_min_size: None,
1759 window_decorations: None,
1760 tabbing_identifier: None,
1761 mouse_passthrough: false,
1762 icon: None,
1763 }
1764 }
1765}
1766
1767#[cfg(test)]
1768mod platform_input_handler_tests {
1769 use super::*;
1770 use crate::{
1771 AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1772 };
1773
1774 struct RejectingEntityInputHandler;
1775
1776 impl EntityInputHandler for RejectingEntityInputHandler {
1777 fn text_for_range(
1778 &mut self,
1779 _range: Range<usize>,
1780 _adjusted_range: &mut Option<Range<usize>>,
1781 _window: &mut Window,
1782 _cx: &mut Context<Self>,
1783 ) -> Option<String> {
1784 None
1785 }
1786
1787 fn selected_text_range(
1788 &mut self,
1789 _ignore_disabled_input: bool,
1790 _window: &mut Window,
1791 _cx: &mut Context<Self>,
1792 ) -> Option<UTF16Selection> {
1793 None
1794 }
1795
1796 fn marked_text_range(
1797 &self,
1798 _window: &mut Window,
1799 _cx: &mut Context<Self>,
1800 ) -> Option<Range<usize>> {
1801 None
1802 }
1803
1804 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1805
1806 fn replace_text_in_range(
1807 &mut self,
1808 _range: Option<Range<usize>>,
1809 _text: &str,
1810 _window: &mut Window,
1811 _cx: &mut Context<Self>,
1812 ) {
1813 }
1814
1815 fn replace_and_mark_text_in_range(
1816 &mut self,
1817 _range: Option<Range<usize>>,
1818 _new_text: &str,
1819 _new_selected_range: Option<Range<usize>>,
1820 _window: &mut Window,
1821 _cx: &mut Context<Self>,
1822 ) {
1823 }
1824
1825 fn bounds_for_range(
1826 &mut self,
1827 _range_utf16: Range<usize>,
1828 _element_bounds: Bounds<Pixels>,
1829 _window: &mut Window,
1830 _cx: &mut Context<Self>,
1831 ) -> Option<Bounds<Pixels>> {
1832 None
1833 }
1834
1835 fn character_index_for_point(
1836 &mut self,
1837 _point: Point<Pixels>,
1838 _window: &mut Window,
1839 _cx: &mut Context<Self>,
1840 ) -> Option<usize> {
1841 None
1842 }
1843
1844 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1845 false
1846 }
1847 }
1848
1849 #[gpui_macros::test]
1850 fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1851 let entity = cx.new(|_| RejectingEntityInputHandler);
1852 let window = cx.add_window(|_, _| Empty);
1853
1854 cx.update_window(window.into(), |_, window, cx| {
1855 let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1856 assert!(!handler.accepts_text_input(window, cx));
1857 })
1858 .unwrap();
1859 }
1860
1861 #[gpui_macros::test]
1862 fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1863 let entity = cx.new(|_| RejectingEntityInputHandler);
1864 let window = cx.add_window(|_, _| Empty);
1865 let window = AnyWindowHandle::from(window);
1866 let async_cx = cx
1867 .update_window(window, |_, window, cx| window.to_async(cx))
1868 .unwrap();
1869
1870 cx.update_window(window, |_, window, _| window.remove_window())
1871 .unwrap();
1872 assert!(cx.update_window(window, |_, _, _| ()).is_err());
1873
1874 let mut handler = PlatformInputHandler::new(
1875 async_cx,
1876 Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1877 );
1878 assert!(handler.query_accepts_text_input());
1879 }
1880
1881 fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1882 Bounds::new(
1883 point(px(offset as f32 * 10.0), px(y)),
1884 size(px(1.0), px(20.0)),
1885 )
1886 }
1887
1888 #[test]
1889 fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1890 let selection = UTF16Selection {
1891 range: 3..5,
1892 reversed: false,
1893 };
1894 let mut requests = Vec::new();
1895 let bounds =
1896 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1897 requests.push(range.clone());
1898 Some(bounds_at(range.start, 0.0))
1899 })
1900 .unwrap();
1901
1902 assert_eq!(bounds.origin.x, px(50.0));
1903 assert_eq!(requests, vec![5..5]);
1904
1905 let selection = UTF16Selection {
1906 range: 3..5,
1907 reversed: true,
1908 };
1909 let bounds =
1910 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1911 Some(bounds_at(range.start, 0.0))
1912 })
1913 .unwrap();
1914
1915 assert_eq!(bounds.origin.x, px(30.0));
1916 }
1917
1918 #[test]
1919 fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
1920 let selection = UTF16Selection {
1921 range: 15..15,
1922 reversed: false,
1923 };
1924 let mut requests = Vec::new();
1925 let bounds =
1926 PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
1927 requests.push(range.clone());
1928 let y = if range.start >= 13 { 20.0 } else { 0.0 };
1929 Some(bounds_at(range.start, y))
1930 })
1931 .unwrap();
1932
1933 assert_eq!(bounds.origin.x, px(130.0));
1934 assert_eq!(requests.last(), Some(&(13..13)));
1935 }
1936}
1937
1938#[derive(Debug, Default)]
1940pub struct TitlebarOptions {
1941 pub title: Option<SharedString>,
1943
1944 pub appears_transparent: bool,
1947
1948 pub traffic_light_position: Option<Point<Pixels>>,
1950}
1951
1952#[derive(Clone, Debug, PartialEq)]
1954pub enum WindowKind {
1955 Normal,
1957
1958 PopUp,
1961
1962 Floating,
1964
1965 Overlay,
1967
1968 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
1971 LayerShell(layer_shell::LayerShellOptions),
1972}
1973
1974#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1979pub enum WindowAppearance {
1980 #[default]
1984 Light,
1985
1986 VibrantLight,
1990
1991 Dark,
1995
1996 VibrantDark,
2000}
2001
2002#[derive(Copy, Clone, Debug, Default, PartialEq)]
2005pub enum WindowBackgroundAppearance {
2006 #[default]
2014 Opaque,
2015 Transparent,
2017 Blurred,
2021}
2022
2023#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2025#[non_exhaustive]
2026pub enum TrayIconRenderingMode {
2027 #[default]
2031 Adaptive,
2032 Original,
2036}
2037
2038#[derive(Debug, Clone, PartialEq, Eq)]
2040pub enum TrayIconEvent {
2041 LeftClick,
2043 RightClick,
2045 DoubleClick,
2047}
2048
2049#[derive(Debug, Clone, PartialEq)]
2051pub struct TrayIconClickEvent {
2052 pub kind: TrayIconEvent,
2054 pub position: Option<Point<Pixels>>,
2061}
2062
2063impl TrayIconClickEvent {
2064 pub fn new(kind: TrayIconEvent) -> Self {
2066 Self {
2067 kind,
2068 position: None,
2069 }
2070 }
2071
2072 pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
2074 Self {
2075 kind,
2076 position: Some(position),
2077 }
2078 }
2079}
2080
2081#[allow(missing_docs)]
2082#[derive(Debug, Clone, PartialEq)]
2083pub struct TrayAnchor {
2084 pub display_id: DisplayId,
2085 pub bounds: Bounds<Pixels>,
2086}
2087
2088#[derive(Debug, Clone)]
2090pub enum TrayMenuItem {
2091 Action {
2093 label: SharedString,
2095 id: SharedString,
2097 },
2098 Separator,
2100 Submenu {
2102 label: SharedString,
2104 items: Vec<TrayMenuItem>,
2106 },
2107 Toggle {
2109 label: SharedString,
2111 checked: bool,
2113 id: SharedString,
2115 },
2116}
2117
2118#[derive(Debug, Clone)]
2120pub struct FocusedWindowInfo {
2121 pub app_name: String,
2123 pub window_title: String,
2125 pub bundle_id: Option<String>,
2127 pub pid: Option<u32>,
2129}
2130
2131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2133pub enum PermissionStatus {
2134 Granted,
2136 Denied,
2138 NotDetermined,
2140 Unavailable,
2142}
2143
2144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2146pub enum PermissionRequestStatus {
2147 Requested,
2149 Unavailable,
2151}
2152
2153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2155pub enum SystemPowerEvent {
2156 Suspend,
2158 Resume,
2160 LockScreen,
2162 UnlockScreen,
2164 Shutdown,
2166}
2167
2168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2170pub enum PowerSaveBlockerKind {
2171 PreventAppSuspension,
2173 PreventDisplaySleep,
2175}
2176
2177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2179pub enum NetworkStatus {
2180 Online,
2182 Offline,
2184}
2185
2186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2188pub enum MediaKeyEvent {
2189 Play,
2191 Pause,
2193 PlayPause,
2195 Stop,
2197 NextTrack,
2199 PreviousTrack,
2201}
2202
2203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2205pub enum AttentionType {
2206 Informational,
2208 Critical,
2210}
2211
2212#[derive(Debug, Clone, Copy, PartialEq)]
2214pub enum ProgressBarState {
2215 None,
2217 Indeterminate,
2219 Normal(f64),
2221 Error(f64),
2223 Paused(f64),
2225}
2226
2227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2229pub enum DialogKind {
2230 Info,
2232 Warning,
2234 Error,
2236}
2237
2238#[derive(Debug, Clone)]
2240pub struct DialogOptions {
2241 pub kind: DialogKind,
2243 pub title: SharedString,
2245 pub message: SharedString,
2247 pub detail: Option<SharedString>,
2249 pub buttons: Vec<SharedString>,
2251}
2252
2253#[derive(Debug, Clone)]
2255pub struct OsInfo {
2256 pub name: SharedString,
2258 pub version: SharedString,
2260 pub arch: SharedString,
2262 pub locale: SharedString,
2264 pub hostname: SharedString,
2266}
2267
2268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2270pub enum BiometricKind {
2271 TouchId,
2273 WindowsHello,
2275 Fingerprint,
2277}
2278
2279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2281pub enum BiometricStatus {
2282 Available(BiometricKind),
2284 Unavailable,
2286}
2287
2288#[derive(Debug, Clone, PartialEq)]
2290pub struct WindowState {
2291 pub bounds: WindowBounds,
2293 pub display_id: Option<DisplayId>,
2295 pub fullscreen: bool,
2297}
2298
2299#[derive(Debug, Clone, PartialEq)]
2301pub enum WindowPosition {
2302 Center,
2304 CenterOnDisplay(DisplayId),
2306 #[deprecated(
2313 since = "0.5.3",
2314 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."
2315 )]
2316 TrayCenter(Bounds<Pixels>),
2317 TrayAnchored(TrayAnchor),
2324 TopRight {
2326 margin: Pixels,
2328 },
2329 BottomRight {
2331 margin: Pixels,
2333 },
2334 TopLeft {
2336 margin: Pixels,
2338 },
2339 BottomLeft {
2341 margin: Pixels,
2343 },
2344}
2345
2346#[derive(Debug, Clone)]
2348pub struct CrashReport {
2349 pub message: String,
2351 pub backtrace: String,
2353 pub os_info: OsInfo,
2355 pub app_version: Option<String>,
2357}
2358
2359#[derive(Clone, Debug)]
2361pub struct PathPromptOptions {
2362 pub files: bool,
2364 pub directories: bool,
2366 pub multiple: bool,
2368 pub prompt: Option<SharedString>,
2370}
2371
2372#[derive(Copy, Clone, Debug, PartialEq)]
2374pub enum PromptLevel {
2375 Info,
2377
2378 Warning,
2380
2381 Critical,
2383}
2384
2385#[derive(Clone, Debug, PartialEq)]
2387pub enum PromptButton {
2388 Ok(SharedString),
2390 Cancel(SharedString),
2392 Other(SharedString),
2394}
2395
2396impl PromptButton {
2397 pub fn new(label: impl Into<SharedString>) -> Self {
2399 PromptButton::Other(label.into())
2400 }
2401
2402 pub fn ok(label: impl Into<SharedString>) -> Self {
2404 PromptButton::Ok(label.into())
2405 }
2406
2407 pub fn cancel(label: impl Into<SharedString>) -> Self {
2409 PromptButton::Cancel(label.into())
2410 }
2411
2412 #[allow(dead_code)]
2413 #[doc(hidden)]
2414 pub fn is_cancel(&self) -> bool {
2415 matches!(self, PromptButton::Cancel(_))
2416 }
2417
2418 pub fn label(&self) -> &SharedString {
2420 match self {
2421 PromptButton::Ok(label) => label,
2422 PromptButton::Cancel(label) => label,
2423 PromptButton::Other(label) => label,
2424 }
2425 }
2426}
2427
2428impl From<&str> for PromptButton {
2429 fn from(value: &str) -> Self {
2430 match value.to_lowercase().as_str() {
2431 "ok" => PromptButton::Ok("Ok".into()),
2432 "cancel" => PromptButton::Cancel("Cancel".into()),
2433 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2434 }
2435 }
2436}
2437
2438#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2440pub enum CursorStyle {
2441 #[default]
2443 Arrow,
2444
2445 IBeam,
2448
2449 Crosshair,
2452
2453 ClosedHand,
2456
2457 OpenHand,
2460
2461 PointingHand,
2464
2465 ResizeLeft,
2468
2469 ResizeRight,
2472
2473 ResizeLeftRight,
2476
2477 ResizeUp,
2480
2481 ResizeDown,
2484
2485 ResizeUpDown,
2488
2489 ResizeUpLeftDownRight,
2492
2493 ResizeUpRightDownLeft,
2496
2497 ResizeColumn,
2500
2501 ResizeRow,
2504
2505 IBeamCursorForVerticalLayout,
2508
2509 OperationNotAllowed,
2512
2513 DragLink,
2516
2517 DragCopy,
2520
2521 ContextualMenu,
2524
2525 None,
2527}
2528
2529#[derive(Clone, Debug, Eq, PartialEq)]
2531pub struct ClipboardItem {
2532 entries: Vec<ClipboardEntry>,
2533}
2534
2535#[derive(Clone, Debug, Eq, PartialEq)]
2537pub enum ClipboardEntry {
2538 String(ClipboardString),
2540 Image(Image),
2542}
2543
2544impl ClipboardItem {
2545 pub fn new_string(text: String) -> Self {
2547 Self {
2548 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2549 }
2550 }
2551
2552 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2554 Self {
2555 entries: vec![ClipboardEntry::String(ClipboardString {
2556 text,
2557 metadata: Some(metadata),
2558 })],
2559 }
2560 }
2561
2562 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2564 Self {
2565 entries: vec![ClipboardEntry::String(
2566 ClipboardString::new(text).with_json_metadata(metadata),
2567 )],
2568 }
2569 }
2570
2571 pub fn new_image(image: &Image) -> Self {
2573 Self {
2574 entries: vec![ClipboardEntry::Image(image.clone())],
2575 }
2576 }
2577
2578 pub fn text(&self) -> Option<String> {
2581 let mut answer = String::new();
2582 let mut any_entries = false;
2583
2584 for entry in self.entries.iter() {
2585 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2586 answer.push_str(text);
2587 any_entries = true;
2588 }
2589 }
2590
2591 if any_entries { Some(answer) } else { None }
2592 }
2593
2594 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2596 pub fn metadata(&self) -> Option<&String> {
2597 match self.entries().first() {
2598 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2599 clipboard_string.metadata.as_ref()
2600 }
2601 _ => None,
2602 }
2603 }
2604
2605 pub fn entries(&self) -> &[ClipboardEntry] {
2607 &self.entries
2608 }
2609
2610 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2612 self.entries.into_iter()
2613 }
2614}
2615
2616impl From<ClipboardString> for ClipboardEntry {
2617 fn from(value: ClipboardString) -> Self {
2618 Self::String(value)
2619 }
2620}
2621
2622impl From<String> for ClipboardEntry {
2623 fn from(value: String) -> Self {
2624 Self::from(ClipboardString::from(value))
2625 }
2626}
2627
2628impl From<Image> for ClipboardEntry {
2629 fn from(value: Image) -> Self {
2630 Self::Image(value)
2631 }
2632}
2633
2634impl From<ClipboardEntry> for ClipboardItem {
2635 fn from(value: ClipboardEntry) -> Self {
2636 Self {
2637 entries: vec![value],
2638 }
2639 }
2640}
2641
2642impl From<String> for ClipboardItem {
2643 fn from(value: String) -> Self {
2644 Self::from(ClipboardEntry::from(value))
2645 }
2646}
2647
2648impl From<Image> for ClipboardItem {
2649 fn from(value: Image) -> Self {
2650 Self::from(ClipboardEntry::from(value))
2651 }
2652}
2653
2654#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2656pub enum ImageFormat {
2657 Png,
2662 Jpeg,
2664 Webp,
2666 Gif,
2668 Svg,
2670 Bmp,
2672 Tiff,
2674}
2675
2676impl ImageFormat {
2677 pub const fn mime_type(self) -> &'static str {
2679 match self {
2680 ImageFormat::Png => "image/png",
2681 ImageFormat::Jpeg => "image/jpeg",
2682 ImageFormat::Webp => "image/webp",
2683 ImageFormat::Gif => "image/gif",
2684 ImageFormat::Svg => "image/svg+xml",
2685 ImageFormat::Bmp => "image/bmp",
2686 ImageFormat::Tiff => "image/tiff",
2687 }
2688 }
2689
2690 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2692 match mime_type {
2693 "image/png" => Some(Self::Png),
2694 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2695 "image/webp" => Some(Self::Webp),
2696 "image/gif" => Some(Self::Gif),
2697 "image/svg+xml" => Some(Self::Svg),
2698 "image/bmp" => Some(Self::Bmp),
2699 "image/tiff" | "image/tif" => Some(Self::Tiff),
2700 _ => None,
2701 }
2702 }
2703}
2704
2705#[derive(Clone, Debug, PartialEq, Eq)]
2707pub struct Image {
2708 pub format: ImageFormat,
2710 pub bytes: Vec<u8>,
2712 id: u64,
2714}
2715
2716impl Image {
2717 pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2719 let id = crate::hash(&bytes);
2720 Self { format, bytes, id }
2721 }
2722}
2723
2724impl Hash for Image {
2725 fn hash<H: Hasher>(&self, state: &mut H) {
2726 state.write_u64(self.id);
2727 }
2728}
2729
2730pub(crate) fn decode_static_image(
2731 bytes: &[u8],
2732 format: image::ImageFormat,
2733) -> Result<SmallVec<[Frame; 1]>> {
2734 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2735 .into_decoder()
2736 .context("creating image decoder")?;
2737 decode_static_image_from_decoder(decoder)
2738}
2739
2740pub(crate) fn decode_static_image_from_decoder(
2741 mut decoder: impl image::ImageDecoder,
2742) -> Result<SmallVec<[Frame; 1]>> {
2743 let orientation = decoder.orientation().context("reading image orientation")?;
2744 let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2745 image.apply_orientation(orientation);
2746
2747 let mut data = image.into_rgba8();
2748 for pixel in data.chunks_exact_mut(4) {
2749 pixel.swap(0, 2);
2750 }
2751
2752 Ok(SmallVec::from_elem(Frame::new(data), 1))
2753}
2754
2755impl Image {
2756 pub fn empty() -> Self {
2758 Self::from_bytes(ImageFormat::Png, Vec::new())
2759 }
2760
2761 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2763 Self {
2764 id: hash(&bytes),
2765 format,
2766 bytes,
2767 }
2768 }
2769
2770 pub fn id(&self) -> u64 {
2772 self.id
2773 }
2774
2775 pub fn use_render_image(
2777 self: Arc<Self>,
2778 window: &mut Window,
2779 cx: &mut App,
2780 ) -> Option<Arc<RenderImage>> {
2781 ImageSource::Image(self)
2782 .use_data(None, window, cx)
2783 .and_then(|result| result.ok())
2784 }
2785
2786 pub fn get_render_image(
2788 self: Arc<Self>,
2789 window: &mut Window,
2790 cx: &mut App,
2791 ) -> Option<Arc<RenderImage>> {
2792 ImageSource::Image(self)
2793 .get_data(None, window, cx)
2794 .and_then(|result| result.ok())
2795 }
2796
2797 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2799 ImageSource::Image(self).remove_asset(cx);
2800 }
2801
2802 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2804 let frames = match self.format {
2805 #[cfg(feature = "image-format-gif")]
2806 ImageFormat::Gif => {
2807 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2808 let mut frames = SmallVec::new();
2809
2810 for frame in decoder.into_frames() {
2811 let mut frame = frame?;
2812 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2814 pixel.swap(0, 2);
2815 }
2816 frames.push(frame);
2817 }
2818
2819 frames
2820 }
2821 #[cfg(not(feature = "image-format-gif"))]
2822 ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2823 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2824 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2825 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2826 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2827 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2828 ImageFormat::Svg => {
2829 return svg_renderer
2830 .render_single_frame(&self.bytes, 1.0)
2831 .map_err(Into::into);
2832 }
2833 };
2834
2835 Ok(Arc::new(RenderImage::new(frames)))
2836 }
2837
2838 pub fn format(&self) -> ImageFormat {
2840 self.format
2841 }
2842
2843 pub fn bytes(&self) -> &[u8] {
2845 self.bytes.as_slice()
2846 }
2847}
2848
2849#[cfg(test)]
2850mod image_tests {
2851 use super::*;
2852
2853 #[test]
2854 fn image_to_image_data_applies_exif_orientation() {
2855 let image = Image::from_bytes(
2856 ImageFormat::Jpeg,
2857 include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2858 );
2859
2860 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2861
2862 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2863 let bytes = render_image.as_bytes(0).unwrap();
2864 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2865 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2866 }
2867}
2868
2869#[derive(Clone, Debug, Eq, PartialEq)]
2871pub struct ClipboardString {
2872 pub(crate) text: String,
2873 pub(crate) metadata: Option<String>,
2874}
2875
2876impl ClipboardString {
2877 pub fn new(text: String) -> Self {
2879 Self {
2880 text,
2881 metadata: None,
2882 }
2883 }
2884
2885 #[doc(hidden)]
2887 pub fn with_metadata(mut self, metadata: String) -> Self {
2888 self.metadata = Some(metadata);
2889 self
2890 }
2891
2892 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2895 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2896 self
2897 }
2898
2899 pub fn text(&self) -> &String {
2901 &self.text
2902 }
2903
2904 pub fn into_text(self) -> String {
2906 self.text
2907 }
2908
2909 pub fn metadata_json<T>(&self) -> Option<T>
2911 where
2912 T: for<'a> Deserialize<'a>,
2913 {
2914 self.metadata
2915 .as_ref()
2916 .and_then(|m| serde_json::from_str(m).ok())
2917 }
2918
2919 #[doc(hidden)]
2921 pub fn metadata(&self) -> Option<&str> {
2922 self.metadata.as_deref()
2923 }
2924
2925 #[doc(hidden)]
2926 pub fn text_hash(text: &str) -> u64 {
2927 let mut hasher = SeaHasher::new();
2928 text.hash(&mut hasher);
2929 hasher.finish()
2930 }
2931}
2932
2933impl From<String> for ClipboardString {
2934 fn from(value: String) -> Self {
2935 Self {
2936 text: value,
2937 metadata: None,
2938 }
2939 }
2940}
2941
2942#[cfg(test)]
2943mod clipboard_string_tests {
2944 use super::ClipboardString;
2945
2946 #[test]
2947 fn raw_metadata_preserves_the_clipboard_text() {
2948 let clipboard_string = ClipboardString::new("copied text".to_owned())
2949 .with_metadata("application/x-adabraka".to_owned());
2950
2951 assert_eq!(clipboard_string.text(), "copied text");
2952 assert_eq!(clipboard_string.metadata(), Some("application/x-adabraka"));
2953 }
2954}