1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(all(target_os = "linux", feature = "wayland"))]
6#[expect(missing_docs)]
7pub mod layer_shell;
8
9pub mod popup;
11
12#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
13mod threaded_dispatcher;
14
15#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
16mod test;
17
18#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
19mod visual_test;
20
21#[cfg(all(
22 feature = "screen-capture",
23 any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
24))]
25pub mod scap_screen_capture;
26
27#[cfg(all(
28 any(target_os = "windows", target_os = "linux"),
29 feature = "screen-capture"
30))]
31pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
32#[cfg(not(feature = "screen-capture"))]
33pub(crate) type PlatformScreenCaptureFrame = ();
34#[cfg(all(target_os = "macos", feature = "screen-capture"))]
35pub(crate) type PlatformScreenCaptureFrame =
36 objc2_core_foundation::CFRetained<objc2_core_video::CVImageBuffer>;
37
38use crate::{
39 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
40 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Edges, ExternalDragPayload, Font,
41 FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap,
42 LineLayout, MissingGlyphSink, Pixels, PlatformGestures, PlatformInput, Point, Priority,
43 RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
44 ShapedRun, SharedString, Size, SvgRenderer, SystemWindowTab, Task, Window, WindowControlArea,
45 hash, point, px, size,
46};
47#[cfg(any(target_os = "linux", target_os = "freebsd"))]
48use anyhow::bail;
49use anyhow::{Context as _, Result};
50use async_task::Runnable;
51use collections::FxHashMap;
52use futures::channel::oneshot;
53#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
54use image::RgbaImage;
55use image::codecs::gif::GifDecoder;
56use image::{AnimationDecoder as _, DynamicImage, Frame};
57use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
58use scheduler::Instant;
59pub use scheduler::RunnableMeta;
60use schemars::JsonSchema;
61use seahash::SeaHasher;
62use serde::{Deserialize, Serialize};
63use smallvec::SmallVec;
64use std::borrow::Cow;
65use std::collections::hash_map::Entry;
66use std::hash::{Hash, Hasher};
67use std::io::Cursor;
68use std::ops;
69use std::time::Duration;
70use std::{
71 ffi::OsString,
72 fmt::{self, Debug},
73 ops::Range,
74 path::{Path, PathBuf},
75 rc::Rc,
76 sync::Arc,
77};
78use strum::EnumIter;
79use uuid::Uuid;
80
81pub use app_menu::*;
82pub use keyboard::*;
83pub use keystroke::*;
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum WindowVisibility {
107 Visible,
110 Hidden,
113}
114
115impl WindowVisibility {
116 pub fn is_visible(self) -> bool {
118 self == Self::Visible
119 }
120}
121
122#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
123pub(crate) use test::*;
124
125#[cfg(any(test, feature = "test-support"))]
126pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
127
128#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
129pub use threaded_dispatcher::ThreadedDispatcher;
130
131#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
132pub use visual_test::VisualTestPlatform;
133
134pub struct ActivityGuard {
136 _release: gpui_util::Deferred<Box<dyn FnOnce() + Send>>,
137}
138
139impl ActivityGuard {
140 pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
142 Self {
143 _release: gpui_util::defer(Box::new(release)),
144 }
145 }
146
147 pub fn noop() -> Self {
149 Self::new(|| {})
150 }
151}
152
153#[cfg(any(target_os = "linux", target_os = "freebsd"))]
157#[inline]
158pub fn guess_compositor() -> &'static str {
159 if std::env::var_os("ZED_HEADLESS").is_some() {
160 return "Headless";
161 }
162
163 #[cfg(feature = "wayland")]
164 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
165 #[cfg(not(feature = "wayland"))]
166 let wayland_display: Option<std::ffi::OsString> = None;
167
168 #[cfg(feature = "x11")]
169 let x11_display = std::env::var_os("DISPLAY");
170 #[cfg(not(feature = "x11"))]
171 let x11_display: Option<std::ffi::OsString> = None;
172
173 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
174 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
175
176 if use_wayland {
177 "Wayland"
178 } else if use_x11 {
179 "X11"
180 } else {
181 "Headless"
182 }
183}
184
185#[expect(missing_docs)]
186pub trait Platform: 'static {
187 fn background_executor(&self) -> BackgroundExecutor;
188 fn foreground_executor(&self) -> ForegroundExecutor;
189 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
190
191 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
192 fn quit(&self);
193 fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
194 fn activate(&self, ignoring_other_apps: bool);
195 fn hide(&self);
196 fn hide_other_apps(&self);
197 fn unhide_other_apps(&self);
198
199 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
200 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
201 fn active_window(&self) -> Option<AnyWindowHandle>;
202 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
203 None
204 }
205
206 fn is_screen_capture_supported(&self) -> bool {
207 false
208 }
209
210 fn screen_capture_sources(
211 &self,
212 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
213 let (sources_tx, sources_rx) = oneshot::channel();
214 sources_tx
215 .send(Err(anyhow::anyhow!(
216 "gpui was compiled without the screen-capture feature"
217 )))
218 .ok();
219 sources_rx
220 }
221
222 fn open_window(
223 &self,
224 handle: AnyWindowHandle,
225 options: WindowParams,
226 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
227
228 fn window_appearance(&self) -> WindowAppearance;
230
231 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
240
241 fn button_layout(&self) -> Option<WindowButtonLayout> {
243 None
244 }
245
246 fn open_url(&self, url: &str);
247 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
248 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
249
250 fn prompt_for_paths(
251 &self,
252 options: PathPromptOptions,
253 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
254 fn prompt_for_new_path(
255 &self,
256 directory: &Path,
257 suggested_name: Option<&str>,
258 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
259 fn can_select_mixed_files_and_dirs(&self) -> bool;
260 fn reveal_path(&self, path: &Path);
261 fn open_with_system(&self, path: &Path);
262
263 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
264 fn on_reopen(&self, callback: Box<dyn FnMut()>);
265 fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
267 fn on_system_wake(&self, callback: Box<dyn FnMut()>);
269
270 fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
280
281 fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
286
287 fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
291 None
292 }
293
294 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
295 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
296 None
297 }
298
299 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
300 fn perform_dock_menu_action(&self, _action: usize) {}
301 fn add_recent_document(&self, _path: &Path) {}
302 fn update_jump_list(
303 &self,
304 _menus: Vec<MenuItem>,
305 _entries: Vec<SmallVec<[PathBuf; 2]>>,
306 ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
307 Task::ready(Vec::new())
308 }
309 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
310 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
311 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
312
313 fn thermal_state(&self) -> ThermalState;
314 fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
315 fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
316
317 fn set_app_identity(&self, identifier: &str, name: &str) {
324 _ = (identifier, name);
325 }
326
327 fn show_system_notification(&self, notification: SystemNotification) {
334 _ = notification;
335 }
336
337 fn dismiss_system_notification(&self, tag: &str) {
342 _ = tag;
343 }
344
345 fn on_system_notification_response(
351 &self,
352 callback: Box<dyn FnMut(SystemNotificationResponse)>,
353 ) {
354 _ = callback;
355 }
356
357 fn compositor_name(&self) -> &'static str {
358 ""
359 }
360 fn app_path(&self) -> Result<PathBuf>;
361 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
362
363 fn set_cursor_style(&self, style: CursorStyle);
364
365 fn hide_cursor_until_mouse_moves(&self);
368
369 fn is_cursor_visible(&self) -> bool;
371
372 fn should_auto_hide_scrollbars(&self) -> bool;
373
374 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
375 fn write_to_clipboard(&self, item: ClipboardItem);
376
377 fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
385 Task::ready(Ok(self.read_from_clipboard()))
386 }
387
388 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
389 fn read_from_primary(&self) -> Option<ClipboardItem>;
390 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
391 fn write_to_primary(&self, item: ClipboardItem);
392
393 #[cfg(target_os = "macos")]
394 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
395 #[cfg(target_os = "macos")]
396 fn write_to_find_pasteboard(&self, item: ClipboardItem);
397
398 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
399 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
400 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
401
402 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
403 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
404 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
405}
406
407pub trait PlatformDisplay: Debug {
409 fn id(&self) -> DisplayId;
411
412 fn uuid(&self) -> Result<Uuid>;
415
416 fn bounds(&self) -> Bounds<Pixels>;
418
419 fn visible_bounds(&self) -> Bounds<Pixels> {
423 self.bounds()
424 }
425
426 fn default_bounds(&self) -> Bounds<Pixels> {
428 let bounds = self.bounds();
429 let center = bounds.center();
430 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
431
432 let offset = clipped_window_size / 2.0;
433 let origin = point(center.x - offset.width, center.y - offset.height);
434 Bounds::new(origin, clipped_window_size)
435 }
436}
437
438#[derive(Clone, Debug, PartialEq, Eq)]
441pub struct SystemNotification {
442 pub tag: SharedString,
446 pub title: SharedString,
448 pub body: SharedString,
450 pub actions: Vec<SystemNotificationAction>,
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Hash)]
457pub struct SystemNotificationAction {
458 pub id: SharedString,
461 pub label: SharedString,
463}
464
465#[derive(Clone, Debug, PartialEq, Eq)]
467pub struct SystemNotificationResponse {
468 pub tag: SharedString,
470 pub action_id: Option<SharedString>,
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
477pub enum ThermalState {
478 Nominal,
480 Fair,
482 Serious,
484 Critical,
486}
487
488#[derive(Clone)]
490pub struct SourceMetadata {
491 pub id: u64,
493 pub label: Option<SharedString>,
495 pub is_main: Option<bool>,
497 pub resolution: Size<DevicePixels>,
499}
500
501pub trait ScreenCaptureSource {
503 fn metadata(&self) -> Result<SourceMetadata>;
505
506 fn stream(
509 &self,
510 foreground_executor: &ForegroundExecutor,
511 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
512 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
513}
514
515pub trait ScreenCaptureStream {
517 fn metadata(&self) -> Result<SourceMetadata>;
519}
520
521pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
523
524#[derive(PartialEq, Eq, Hash, Copy, Clone)]
526pub struct DisplayId(pub(crate) u64);
527
528impl DisplayId {
529 pub fn new(id: u64) -> Self {
531 Self(id)
532 }
533}
534
535impl From<u64> for DisplayId {
536 fn from(id: u64) -> Self {
537 Self(id)
538 }
539}
540
541impl From<DisplayId> for u64 {
542 fn from(id: DisplayId) -> Self {
543 id.0
544 }
545}
546
547impl Debug for DisplayId {
548 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549 write!(f, "DisplayId({})", self.0)
550 }
551}
552
553#[derive(Debug, Clone, Copy, PartialEq, Eq)]
555pub enum ResizeEdge {
556 Top,
558 TopRight,
560 Right,
562 BottomRight,
564 Bottom,
566 BottomLeft,
568 Left,
570 TopLeft,
572}
573
574#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
576pub enum WindowDecorations {
577 #[default]
578 Server,
580 Client,
582}
583
584#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
586pub enum Decorations {
587 #[default]
589 Server,
590 Client {
592 tiling: Tiling,
594 },
595}
596
597#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
599pub struct WindowControls {
600 pub fullscreen: bool,
602 pub maximize: bool,
604 pub minimize: bool,
606 pub window_menu: bool,
608}
609
610impl Default for WindowControls {
611 fn default() -> Self {
612 Self {
614 fullscreen: true,
615 maximize: true,
616 minimize: true,
617 window_menu: true,
618 }
619 }
620}
621
622#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
624pub enum WindowButton {
625 Minimize,
627 Maximize,
629 Close,
631}
632
633impl WindowButton {
634 pub fn id(&self) -> &'static str {
636 match self {
637 WindowButton::Minimize => "minimize",
638 WindowButton::Maximize => "maximize",
639 WindowButton::Close => "close",
640 }
641 }
642
643 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
644 fn index(&self) -> usize {
645 match self {
646 WindowButton::Minimize => 0,
647 WindowButton::Maximize => 1,
648 WindowButton::Close => 2,
649 }
650 }
651}
652
653pub const MAX_BUTTONS_PER_SIDE: usize = 3;
655
656#[derive(Debug, Clone, Copy, PartialEq, Eq)]
661pub struct WindowButtonLayout {
662 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
664 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
666}
667
668#[cfg(any(target_os = "linux", target_os = "freebsd"))]
669impl WindowButtonLayout {
670 pub fn linux_default() -> Self {
672 Self {
673 left: [None; MAX_BUTTONS_PER_SIDE],
674 right: [
675 Some(WindowButton::Minimize),
676 Some(WindowButton::Maximize),
677 Some(WindowButton::Close),
678 ],
679 }
680 }
681
682 pub fn parse(layout_string: &str) -> Result<Self> {
684 fn parse_side(
685 s: &str,
686 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
687 unrecognized: &mut Vec<String>,
688 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
689 let mut result = [None; MAX_BUTTONS_PER_SIDE];
690 let mut i = 0;
691 for name in s.split(',') {
692 let trimmed = name.trim();
693 if trimmed.is_empty() {
694 continue;
695 }
696 let button = match trimmed {
697 "minimize" => Some(WindowButton::Minimize),
698 "maximize" => Some(WindowButton::Maximize),
699 "close" => Some(WindowButton::Close),
700 other => {
701 unrecognized.push(other.to_string());
702 None
703 }
704 };
705 if let Some(button) = button {
706 if seen_buttons[button.index()] {
707 continue;
708 }
709 if let Some(slot) = result.get_mut(i) {
710 *slot = Some(button);
711 seen_buttons[button.index()] = true;
712 i += 1;
713 }
714 }
715 }
716 result
717 }
718
719 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
720 let mut unrecognized = Vec::new();
721 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
722 let layout = Self {
723 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
724 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
725 };
726
727 if !unrecognized.is_empty()
728 && layout.left.iter().all(Option::is_none)
729 && layout.right.iter().all(Option::is_none)
730 {
731 bail!(
732 "button layout string {:?} contains no valid buttons (unrecognized: {})",
733 layout_string,
734 unrecognized.join(", ")
735 );
736 }
737
738 Ok(layout)
739 }
740
741 #[cfg(test)]
743 pub fn format(&self) -> String {
744 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
745 buttons
746 .iter()
747 .flatten()
748 .map(|button| match button {
749 WindowButton::Minimize => "minimize",
750 WindowButton::Maximize => "maximize",
751 WindowButton::Close => "close",
752 })
753 .collect::<Vec<_>>()
754 .join(",")
755 }
756
757 format!("{}:{}", format_side(&self.left), format_side(&self.right))
758 }
759}
760
761#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
763pub struct Tiling {
764 pub top: bool,
766 pub left: bool,
768 pub right: bool,
770 pub bottom: bool,
772}
773
774impl Tiling {
775 pub fn tiled() -> Self {
777 Self {
778 top: true,
779 left: true,
780 right: true,
781 bottom: true,
782 }
783 }
784
785 pub fn is_tiled(&self) -> bool {
787 self.top || self.left || self.right || self.bottom
788 }
789}
790
791pub struct A11yCallbacks {
793 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
795 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
797 pub deactivation: Box<dyn Fn() + Send + 'static>,
799}
800
801#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
802#[expect(missing_docs)]
803pub struct RequestFrameOptions {
804 pub require_presentation: bool,
806 pub force_render: bool,
808}
809
810#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
823pub enum AppLifecyclePhase {
824 Active,
826 Inactive,
828 Background,
831 Foreground,
833}
834
835#[derive(Debug, Clone, Default, PartialEq)]
842pub struct WindowInsets {
843 pub safe_area: Edges<Pixels>,
848 pub ime: Edges<Pixels>,
852}
853
854impl WindowInsets {
855 pub fn effective(&self) -> Edges<Pixels> {
857 Edges {
858 top: self.safe_area.top.max(self.ime.top),
859 right: self.safe_area.right.max(self.ime.right),
860 bottom: self.safe_area.bottom.max(self.ime.bottom),
861 left: self.safe_area.left.max(self.ime.left),
862 }
863 }
864}
865
866#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
868pub enum TextInputStateChange {
869 FocusGained,
871 FocusLost,
873 SelectionChanged,
875 ContentChanged,
877}
878
879#[expect(missing_docs)]
880pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
881 fn bounds(&self) -> Bounds<Pixels>;
882 fn is_maximized(&self) -> bool;
883 fn window_bounds(&self) -> WindowBounds;
884 fn content_size(&self) -> Size<Pixels>;
885 fn visual_viewport_bounds(&self) -> Bounds<Pixels> {
891 Bounds::new(Point::default(), self.content_size())
892 }
893 fn on_visual_viewport_changed(&self, _callback: Box<dyn FnMut()>) {}
898 fn prepare_frame(&self) -> bool {
903 false
904 }
905 fn resize(&mut self, size: Size<Pixels>);
906 fn scale_factor(&self) -> f32;
907 fn appearance(&self) -> WindowAppearance;
908 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
909 fn mouse_position(&self) -> Point<Pixels>;
910 fn modifiers(&self) -> Modifiers;
911 fn capslock(&self) -> Capslock;
912 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
913 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
914 fn set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
919 fn prompt(
920 &self,
921 level: PromptLevel,
922 msg: &str,
923 detail: Option<&str>,
924 answers: &[PromptButton],
925 ) -> Option<oneshot::Receiver<usize>>;
926 fn activate(&self);
927 fn request_attention(&self) {}
929 fn is_active(&self) -> bool;
930 fn visibility(&self) -> WindowVisibility;
933 fn is_hovered(&self) -> bool;
934 fn background_appearance(&self) -> WindowBackgroundAppearance;
935 fn set_title(&mut self, title: &str);
936 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
937 fn minimize(&self);
938 fn zoom(&self);
939 fn toggle_fullscreen(&self);
940 fn is_fullscreen(&self) -> bool;
941 fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
942 None
943 }
944 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
945 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
946 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
947 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
951 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
952 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
953 fn on_moved(&self, callback: Box<dyn FnMut()>);
954 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
955 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
956 fn on_close(&self, callback: Box<dyn FnOnce()>);
957 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
958 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
959 fn draw(&self, scene: &Scene);
960 fn gpu_time(&self) -> Option<Duration> {
964 None
965 }
966 fn schedule_frame(&self) {}
967 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
968 fn is_subpixel_rendering_supported(&self) -> bool;
969
970 fn get_title(&self) -> String {
972 String::new()
973 }
974 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
975 None
976 }
977 fn tab_bar_visible(&self) -> bool {
978 false
979 }
980 fn set_edited(&mut self, _edited: bool) {}
981 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
982 fn toggle_simple_fullscreen(&self) {}
983 fn is_simple_fullscreen(&self) -> bool {
984 false
985 }
986 #[cfg(target_os = "macos")]
987 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
988 fn show_character_palette(&self) {}
989 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
990 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
991 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
992 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
993 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
994 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
995 fn merge_all_windows(&self) {}
996 fn move_tab_to_new_window(&self) {}
997 fn toggle_window_tab_overview(&self) {}
998 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
999
1000 fn native_window_state(&self) -> Option<Vec<u8>> {
1001 None
1002 }
1003 fn restore_native_window_state(&self, _state: &[u8]) {}
1004
1005 #[cfg(target_os = "windows")]
1006 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1007
1008 fn inner_window_bounds(&self) -> WindowBounds {
1010 self.window_bounds()
1011 }
1012 fn request_decorations(&self, _decorations: WindowDecorations) {}
1013 fn show_window_menu(&self, _position: Point<Pixels>) {}
1014 fn start_window_move(&self) {}
1015 fn can_start_external_drag(&self) -> bool {
1016 false
1017 }
1018 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1019 false
1020 }
1021 fn start_window_resize(&self, _edge: ResizeEdge) {}
1022 fn set_exclusive_zone(&self, _zone: Pixels) {}
1023 #[cfg(all(target_os = "linux", feature = "wayland"))]
1024 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1025 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1026 fn window_decorations(&self) -> Decorations {
1027 Decorations::Server
1028 }
1029 fn set_app_id(&mut self, _app_id: &str) {}
1030 fn map_window(&mut self) -> anyhow::Result<()> {
1031 Ok(())
1032 }
1033 fn window_controls(&self) -> WindowControls {
1034 WindowControls::default()
1035 }
1036 fn set_client_inset(&self, _inset: Pixels) {}
1037 fn gpu_specs(&self) -> Option<GpuSpecs>;
1038
1039 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1040
1041 fn insets(&self) -> WindowInsets {
1046 WindowInsets::default()
1047 }
1048
1049 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1055
1056 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1059
1060 fn set_back_enabled(&self, _enabled: bool) {}
1063
1064 fn show_soft_keyboard(&self) {}
1066
1067 fn hide_soft_keyboard(&self) {}
1069
1070 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1072
1073 fn play_system_bell(&self) {}
1074
1075 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1077
1078 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1080
1081 fn a11y_update_window_bounds(&self) {}
1083
1084 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1085 fn as_test(&mut self) -> Option<&mut TestWindow> {
1086 None
1087 }
1088
1089 #[cfg(any(test, feature = "test-support"))]
1093 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1094 anyhow::bail!("render_to_image not implemented for this platform")
1095 }
1096}
1097
1098#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1100pub trait PlatformHeadlessRenderer {
1101 fn render_scene_to_image(
1103 &mut self,
1104 scene: &Scene,
1105 size: Size<DevicePixels>,
1106 ) -> Result<RgbaImage>;
1107
1108 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1114
1115 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1117}
1118
1119#[doc(hidden)]
1122pub type RunnableVariant = Runnable<RunnableMeta>;
1123
1124#[doc(hidden)]
1125pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1126
1127#[doc(hidden)]
1128pub enum TasksIncluded {
1129 OnlyCompleted,
1130 CompletedAndRunning,
1131}
1132
1133#[doc(hidden)]
1136pub trait PlatformDispatcher: Send + Sync {
1137 fn is_main_thread(&self) -> bool;
1138 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1139 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1140 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1141
1142 fn dispatch_on_main_thread_when_idle(
1143 &self,
1144 runnable: RunnableVariant,
1145 timeout: Option<Duration>,
1146 ) {
1147 let _ = timeout;
1148 self.dispatch_on_main_thread(runnable, Priority::Low);
1149 }
1150
1151 fn idle_time_remaining(&self) -> Option<Duration> {
1152 None
1153 }
1154
1155 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1156
1157 fn now(&self) -> Instant {
1158 Instant::now()
1159 }
1160
1161 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1162 gpui_util::defer(Box::new(|| {}))
1163 }
1164
1165 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1166 ActivityGuard::noop()
1167 }
1168
1169 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1170 fn as_test(&self) -> Option<&TestDispatcher> {
1171 None
1172 }
1173
1174 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1177 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1178 None
1179 }
1180}
1181
1182#[expect(missing_docs)]
1183pub trait PlatformTextSystem: Send + Sync {
1184 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1185 fn set_missing_glyph_sink(&self, _sink: Option<Arc<dyn MissingGlyphSink>>) {}
1187 fn all_font_names(&self) -> Vec<String>;
1189 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1191 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1193 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1195 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1197 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1199 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1201 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1203 fn rasterize_glyph(
1205 &self,
1206 params: &RenderGlyphParams,
1207 raster_bounds: Bounds<DevicePixels>,
1208 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1209 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1211 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1213 -> TextRenderingMode;
1214 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1216 0
1217 }
1218}
1219
1220#[expect(missing_docs)]
1221pub struct NoopTextSystem;
1222
1223#[expect(missing_docs)]
1224impl NoopTextSystem {
1225 #[allow(dead_code)]
1226 pub fn new() -> Self {
1227 Self
1228 }
1229}
1230
1231impl PlatformTextSystem for NoopTextSystem {
1232 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1233 Ok(())
1234 }
1235
1236 fn all_font_names(&self) -> Vec<String> {
1237 Vec::new()
1238 }
1239
1240 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1241 Ok(FontId(1))
1242 }
1243
1244 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1245 FontMetrics {
1246 units_per_em: 1000,
1247 ascent: 1025.0,
1248 descent: -275.0,
1249 line_gap: 0.0,
1250 underline_position: -95.0,
1251 underline_thickness: 60.0,
1252 cap_height: 698.0,
1253 x_height: 516.0,
1254 bounding_box: Bounds {
1255 origin: Point {
1256 x: -260.0,
1257 y: -245.0,
1258 },
1259 size: Size {
1260 width: 1501.0,
1261 height: 1364.0,
1262 },
1263 },
1264 }
1265 }
1266
1267 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1268 Ok(Bounds {
1269 origin: Point { x: 54.0, y: 0.0 },
1270 size: size(392.0, 528.0),
1271 })
1272 }
1273
1274 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1275 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1276 }
1277
1278 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1279 Some(GlyphId(ch.len_utf16() as u32))
1280 }
1281
1282 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1283 Ok(Default::default())
1284 }
1285
1286 fn rasterize_glyph(
1287 &self,
1288 _params: &RenderGlyphParams,
1289 raster_bounds: Bounds<DevicePixels>,
1290 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1291 Ok((raster_bounds.size, Vec::new()))
1292 }
1293
1294 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1295 let mut position = px(0.);
1296 let metrics = self.font_metrics(FontId(0));
1297 let em_width = font_size
1298 * self
1299 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1300 .unwrap()
1301 .width
1302 / metrics.units_per_em as f32;
1303 let mut glyphs = Vec::new();
1304 for (ix, c) in text.char_indices() {
1305 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1306 glyphs.push(ShapedGlyph {
1307 id: glyph,
1308 position: point(position, px(0.)),
1309 index: ix,
1310 is_emoji: glyph.0 == 2,
1311 });
1312 if glyph.0 == 2 {
1313 position += em_width * 2.0;
1314 } else {
1315 position += em_width;
1316 }
1317 } else {
1318 position += em_width
1319 }
1320 }
1321 let mut runs = Vec::default();
1322 if !glyphs.is_empty() {
1323 runs.push(ShapedRun {
1324 font_id: FontId(0),
1325 glyphs,
1326 });
1327 } else {
1328 position = px(0.);
1329 }
1330
1331 LineLayout {
1332 font_size,
1333 width: position,
1334 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1335 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1336 runs,
1337 len: text.len(),
1338 }
1339 }
1340
1341 fn recommended_rendering_mode(
1342 &self,
1343 _font_id: FontId,
1344 _font_size: Pixels,
1345 ) -> TextRenderingMode {
1346 TextRenderingMode::Grayscale
1347 }
1348}
1349
1350#[allow(dead_code)]
1355pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1356 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1357 [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], ];
1371
1372 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1373 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1374
1375 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1376 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1377
1378 [
1379 ratios[0] * NORM13,
1380 ratios[1] * NORM24,
1381 ratios[2] * NORM13,
1382 ratios[3] * NORM24,
1383 ]
1384}
1385
1386#[derive(PartialEq, Eq, Hash, Clone)]
1387#[expect(missing_docs)]
1388pub enum AtlasKey {
1389 Glyph(RenderGlyphParams),
1390 Svg(RenderSvgParams),
1391 Image(RenderImageParams),
1392}
1393
1394impl AtlasKey {
1395 pub fn texture_kind(&self) -> AtlasTextureKind {
1397 match self {
1398 AtlasKey::Glyph(params) => {
1399 if params.is_emoji {
1400 AtlasTextureKind::Polychrome
1401 } else if params.subpixel_rendering {
1402 AtlasTextureKind::Subpixel
1403 } else {
1404 AtlasTextureKind::Monochrome
1405 }
1406 }
1407 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1408 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1409 }
1410 }
1411}
1412
1413impl From<RenderGlyphParams> for AtlasKey {
1414 fn from(params: RenderGlyphParams) -> Self {
1415 Self::Glyph(params)
1416 }
1417}
1418
1419impl From<RenderSvgParams> for AtlasKey {
1420 fn from(params: RenderSvgParams) -> Self {
1421 Self::Svg(params)
1422 }
1423}
1424
1425impl From<RenderImageParams> for AtlasKey {
1426 fn from(params: RenderImageParams) -> Self {
1427 Self::Image(params)
1428 }
1429}
1430
1431#[expect(missing_docs)]
1432pub trait PlatformAtlas {
1433 fn get_or_insert_with<'a>(
1435 &self,
1436 key: AtlasKey,
1437 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1438 ) -> Result<Option<AtlasTile>>;
1439 fn remove(&self, key: &AtlasKey);
1440
1441 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1442 fn contains(&self, _key: &AtlasKey) -> bool {
1443 false
1444 }
1445}
1446
1447#[doc(hidden)]
1448pub trait AtlasBackend {
1449 fn insert(
1450 &mut self,
1451 kind: AtlasTextureKind,
1452 size: Size<DevicePixels>,
1453 bytes: &[u8],
1454 ) -> Result<AtlasTile>;
1455
1456 fn remove(&mut self, tile: AtlasTile);
1457}
1458
1459#[doc(hidden)]
1460pub struct AtlasState<Backend> {
1461 tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
1462 pub backend: Backend,
1463}
1464
1465impl<Backend> AtlasState<Backend> {
1466 pub fn new(backend: Backend) -> Self {
1467 Self {
1468 tiles_by_key: FxHashMap::default(),
1469 backend,
1470 }
1471 }
1472
1473 pub fn contains(&self, key: &AtlasKey) -> bool {
1474 self.tiles_by_key.contains_key(key)
1475 }
1476
1477 pub fn clear(&mut self, reset_backend: impl FnOnce(&mut Backend)) {
1478 self.tiles_by_key.clear();
1479 reset_backend(&mut self.backend);
1480 }
1481}
1482
1483impl<Backend: Default> Default for AtlasState<Backend> {
1484 fn default() -> Self {
1485 Self::new(Backend::default())
1486 }
1487}
1488
1489impl<Backend: AtlasBackend> AtlasState<Backend> {
1490 pub fn get_or_insert_with<'a>(
1491 &mut self,
1492 key: AtlasKey,
1493 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1494 ) -> Result<Option<AtlasTile>> {
1495 match self.tiles_by_key.entry(key) {
1496 Entry::Occupied(entry) => Ok(Some(*entry.get())),
1497 Entry::Vacant(entry) => {
1498 profiling::scope!("new tile");
1499 let Some((size, bytes)) = build()? else {
1500 return Ok(None);
1501 };
1502 let tile = self
1503 .backend
1504 .insert(entry.key().texture_kind(), size, &bytes)?;
1505 entry.insert(tile);
1506 Ok(Some(tile))
1507 }
1508 }
1509 }
1510
1511 pub fn remove(&mut self, key: &AtlasKey) {
1512 if let Some(tile) = self.tiles_by_key.remove(key) {
1513 self.backend.remove(tile);
1514 }
1515 }
1516}
1517
1518#[derive(Default)]
1522pub struct HeadlessAtlas(parking_lot::Mutex<AtlasState<HeadlessAtlasBackend>>);
1523
1524#[doc(hidden)]
1525#[derive(Default)]
1526pub struct HeadlessAtlasBackend {
1527 next_id: u32,
1528}
1529
1530impl AtlasBackend for HeadlessAtlasBackend {
1531 fn insert(
1532 &mut self,
1533 kind: AtlasTextureKind,
1534 size: Size<DevicePixels>,
1535 _bytes: &[u8],
1536 ) -> Result<AtlasTile> {
1537 self.next_id += 1;
1538 let texture_id = self.next_id;
1539 self.next_id += 1;
1540 let tile_id = self.next_id;
1541 Ok(AtlasTile {
1542 texture_id: AtlasTextureId {
1543 index: texture_id,
1544 kind,
1545 },
1546 tile_id: TileId(tile_id),
1547 padding: 0,
1548 bounds: Bounds {
1549 origin: Point::default(),
1550 size,
1551 },
1552 })
1553 }
1554
1555 fn remove(&mut self, _tile: AtlasTile) {}
1556}
1557
1558impl PlatformAtlas for HeadlessAtlas {
1559 fn get_or_insert_with<'a>(
1560 &self,
1561 key: AtlasKey,
1562 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1563 ) -> Result<Option<AtlasTile>> {
1564 self.0.lock().get_or_insert_with(key, build)
1565 }
1566
1567 fn remove(&self, key: &AtlasKey) {
1568 self.0.lock().remove(key);
1569 }
1570
1571 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1572 fn contains(&self, key: &AtlasKey) -> bool {
1573 self.0.lock().contains(key)
1574 }
1575}
1576
1577#[doc(hidden)]
1578pub struct AtlasTextureList<T> {
1579 pub textures: Vec<Option<T>>,
1580 pub free_list: Vec<usize>,
1581}
1582
1583impl<T> Default for AtlasTextureList<T> {
1584 fn default() -> Self {
1585 Self {
1586 textures: Vec::default(),
1587 free_list: Vec::default(),
1588 }
1589 }
1590}
1591
1592impl<T> ops::Index<usize> for AtlasTextureList<T> {
1593 type Output = Option<T>;
1594
1595 fn index(&self, index: usize) -> &Self::Output {
1596 &self.textures[index]
1597 }
1598}
1599
1600impl<T> AtlasTextureList<T> {
1601 #[allow(unused)]
1602 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1603 self.free_list.clear();
1604 self.textures.drain(..)
1605 }
1606
1607 #[allow(dead_code)]
1608 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1609 self.textures.iter_mut().flatten()
1610 }
1611}
1612
1613#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1614#[repr(C)]
1615#[expect(missing_docs)]
1616pub struct AtlasTile {
1617 pub texture_id: AtlasTextureId,
1619 pub tile_id: TileId,
1621 pub padding: u32,
1623 pub bounds: Bounds<DevicePixels>,
1625}
1626
1627#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1628#[repr(C)]
1629#[expect(missing_docs)]
1630pub struct AtlasTextureId {
1631 pub index: u32,
1634 pub kind: AtlasTextureKind,
1636}
1637
1638#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1639#[repr(C)]
1640#[cfg_attr(
1641 all(
1642 any(target_os = "linux", target_os = "freebsd"),
1643 not(any(feature = "x11", feature = "wayland"))
1644 ),
1645 allow(dead_code)
1646)]
1647#[expect(missing_docs)]
1648pub enum AtlasTextureKind {
1649 Monochrome = 0,
1650 Polychrome = 1,
1651 Subpixel = 2,
1652}
1653
1654#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1655#[repr(C)]
1656#[expect(missing_docs)]
1657pub struct TileId(pub u32);
1658
1659impl From<etagere::AllocId> for TileId {
1660 fn from(id: etagere::AllocId) -> Self {
1661 Self(id.serialize())
1662 }
1663}
1664
1665impl From<TileId> for etagere::AllocId {
1666 fn from(id: TileId) -> Self {
1667 Self::deserialize(id.0)
1668 }
1669}
1670
1671#[expect(missing_docs)]
1672pub struct PlatformInputHandler {
1673 cx: AsyncWindowContext,
1674 handler: Box<dyn InputHandler>,
1675}
1676
1677#[expect(missing_docs)]
1678#[cfg_attr(
1679 all(
1680 any(target_os = "linux", target_os = "freebsd"),
1681 not(any(feature = "x11", feature = "wayland"))
1682 ),
1683 allow(dead_code)
1684)]
1685impl PlatformInputHandler {
1686 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1687 Self { cx, handler }
1688 }
1689
1690 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1691 self.cx
1692 .update(|window, cx| {
1693 self.handler
1694 .selected_text_range(ignore_disabled_input, window, cx)
1695 })
1696 .ok()
1697 .flatten()
1698 }
1699
1700 #[cfg_attr(target_os = "windows", allow(dead_code))]
1701 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1702 self.cx
1703 .update(|window, cx| self.handler.marked_text_range(window, cx))
1704 .ok()
1705 .flatten()
1706 }
1707
1708 #[cfg_attr(
1709 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1710 allow(dead_code)
1711 )]
1712 pub fn text_for_range(
1713 &mut self,
1714 range_utf16: Range<usize>,
1715 adjusted: &mut Option<Range<usize>>,
1716 ) -> Option<String> {
1717 self.cx
1718 .update(|window, cx| {
1719 self.handler
1720 .text_for_range(range_utf16, adjusted, window, cx)
1721 })
1722 .ok()
1723 .flatten()
1724 }
1725
1726 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1727 self.cx
1728 .update(|window, cx| {
1729 self.handler
1730 .replace_text_in_range(replacement_range, text, window, cx);
1731 })
1732 .ok();
1733 }
1734
1735 pub fn replace_and_mark_text_in_range(
1736 &mut self,
1737 range_utf16: Option<Range<usize>>,
1738 new_text: &str,
1739 new_selected_range: Option<Range<usize>>,
1740 ) {
1741 self.cx
1742 .update(|window, cx| {
1743 self.handler.replace_and_mark_text_in_range(
1744 range_utf16,
1745 new_text,
1746 new_selected_range,
1747 window,
1748 cx,
1749 )
1750 })
1751 .ok();
1752 }
1753
1754 #[cfg_attr(target_os = "windows", allow(dead_code))]
1755 pub fn unmark_text(&mut self) {
1756 self.cx
1757 .update(|window, cx| self.handler.unmark_text(window, cx))
1758 .ok();
1759 }
1760
1761 pub fn paste(&mut self, item: ClipboardItem) {
1762 self.cx
1763 .update(|window, cx| self.handler.paste(item, window, cx))
1764 .ok();
1765 }
1766
1767 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1768 self.cx
1769 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1770 .ok()
1771 .flatten()
1772 }
1773
1774 #[allow(dead_code)]
1775 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1776 self.handler.apple_press_and_hold_enabled()
1777 }
1778
1779 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1780 self.handler.replace_text_in_range(None, input, window, cx);
1781 }
1782
1783 pub fn compute_ime_candidate_bounds(
1784 marked_range: Option<Range<usize>>,
1785 selection: &UTF16Selection,
1786 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1787 ) -> Option<Bounds<Pixels>> {
1788 if let Some(marked_range) = marked_range {
1789 let mut line_start = marked_range.start;
1791
1792 let caret = selection.range.end;
1796 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1797 for i in (marked_range.start..caret).rev() {
1798 if let Some(b) = bounds_for_range(i..i) {
1799 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1800 line_start = i + 1;
1801 break;
1802 }
1803 }
1804 }
1805 }
1806 bounds_for_range(line_start..line_start)
1807 } else {
1808 let offset = if selection.reversed {
1810 selection.range.start
1811 } else {
1812 selection.range.end
1813 };
1814 bounds_for_range(offset..offset)
1815 }
1816 }
1817
1818 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1819 let marked_range = self.handler.marked_text_range(window, cx);
1820 let selection = self.handler.selected_text_range(true, window, cx)?;
1821 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1822 self.handler.bounds_for_range(range, window, cx)
1823 })
1824 }
1825
1826 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1827 let marked_range = self.marked_text_range();
1828 let selection = self.selected_text_range(true)?;
1829 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1830 self.bounds_for_range(range)
1831 })
1832 }
1833
1834 #[allow(unused)]
1835 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1836 self.cx
1837 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1838 .ok()
1839 .flatten()
1840 }
1841
1842 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1844 self.cx
1845 .update(|window, cx| {
1846 self.handler
1847 .set_selected_text_range(range_utf16, window, cx)
1848 })
1849 .ok();
1850 }
1851
1852 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1854 self.cx
1855 .update(|window, cx| self.handler.element_bounds(window, cx))
1856 .ok()
1857 .flatten()
1858 }
1859
1860 pub fn text_length_utf16(&mut self) -> Option<usize> {
1862 self.cx
1863 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1864 .ok()
1865 .flatten()
1866 }
1867
1868 #[allow(dead_code)]
1869 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1870 self.handler.accepts_text_input(window, cx)
1871 }
1872
1873 #[allow(dead_code)]
1874 pub fn query_accepts_text_input(&mut self) -> bool {
1875 self.cx
1876 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1877 .unwrap_or(true)
1878 }
1879
1880 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1886 self.cx
1887 .update(|window, cx| {
1888 !window.has_pending_keystrokes()
1890 && self.handler.prefers_ime_for_printable_keys(window, cx)
1891 })
1892 .unwrap_or(false)
1893 }
1894
1895 pub fn text_input_configuration(
1897 &mut self,
1898 window: &mut Window,
1899 cx: &mut App,
1900 ) -> TextInputConfiguration {
1901 self.handler.text_input_configuration(window, cx)
1902 }
1903
1904 pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1906 self.cx
1907 .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1908 .ok()
1909 .flatten()
1910 }
1911}
1912
1913#[derive(Debug)]
1916pub struct UTF16Selection {
1917 pub range: Range<usize>,
1920 pub reversed: bool,
1923}
1924
1925pub trait InputHandler: 'static {
1930 fn selected_text_range(
1935 &mut self,
1936 ignore_disabled_input: bool,
1937 window: &mut Window,
1938 cx: &mut App,
1939 ) -> Option<UTF16Selection>;
1940
1941 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1946
1947 fn text_for_range(
1952 &mut self,
1953 range_utf16: Range<usize>,
1954 adjusted_range: &mut Option<Range<usize>>,
1955 window: &mut Window,
1956 cx: &mut App,
1957 ) -> Option<String>;
1958
1959 fn replace_text_in_range(
1964 &mut self,
1965 replacement_range: Option<Range<usize>>,
1966 text: &str,
1967 window: &mut Window,
1968 cx: &mut App,
1969 );
1970
1971 fn replace_and_mark_text_in_range(
1978 &mut self,
1979 range_utf16: Option<Range<usize>>,
1980 new_text: &str,
1981 new_selected_range: Option<Range<usize>>,
1982 window: &mut Window,
1983 cx: &mut App,
1984 );
1985
1986 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1989
1990 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1997 if let Some(text) = item.text() {
1998 self.replace_text_in_range(None, &text, window, cx);
1999 }
2000 }
2001
2002 fn bounds_for_range(
2007 &mut self,
2008 range_utf16: Range<usize>,
2009 window: &mut Window,
2010 cx: &mut App,
2011 ) -> Option<Bounds<Pixels>>;
2012
2013 fn character_index_for_point(
2017 &mut self,
2018 point: Point<Pixels>,
2019 window: &mut Window,
2020 cx: &mut App,
2021 ) -> Option<usize>;
2022
2023 fn set_selected_text_range(
2033 &mut self,
2034 _range_utf16: Range<usize>,
2035 _window: &mut Window,
2036 _cx: &mut App,
2037 ) {
2038 }
2039
2040 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2047 None
2048 }
2049
2050 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2052 None
2053 }
2054
2055 #[allow(dead_code)]
2060 fn apple_press_and_hold_enabled(&mut self) -> bool {
2061 true
2062 }
2063
2064 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2066 true
2067 }
2068
2069 fn text_input_editable_range(
2080 &mut self,
2081 _window: &mut Window,
2082 _cx: &mut App,
2083 ) -> Option<Range<usize>> {
2084 None
2085 }
2086
2087 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2096 false
2097 }
2098
2099 fn text_input_configuration(
2105 &mut self,
2106 _window: &mut Window,
2107 _cx: &mut App,
2108 ) -> TextInputConfiguration {
2109 TextInputConfiguration::default()
2110 }
2111}
2112
2113#[derive(Clone, Debug, Default, PartialEq, Eq)]
2123pub struct TextInputConfiguration {
2124 pub autocorrect: bool,
2126 pub autocapitalize: Autocapitalize,
2128 pub suggestions: bool,
2130 pub input_action: TextInputAction,
2132}
2133
2134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2136pub enum Autocapitalize {
2137 #[default]
2139 None,
2140 Words,
2142 Sentences,
2144 Characters,
2146}
2147
2148#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2158pub enum TextInputAction {
2159 #[default]
2161 Unspecified,
2162 Enter,
2164 Done,
2166 Go,
2168 Next,
2170 Previous,
2172 Search,
2174 Send,
2176}
2177
2178#[derive(Debug)]
2180pub struct WindowOptions {
2181 pub window_bounds: Option<WindowBounds>,
2185
2186 pub titlebar: Option<TitlebarOptions>,
2188
2189 pub focus: bool,
2191
2192 pub show: bool,
2194
2195 pub kind: WindowKind,
2197
2198 pub is_movable: bool,
2202
2203 pub app_owns_titlebar_drag: bool,
2215
2216 pub inactive_frame_interval: Option<Duration>,
2220
2221 pub is_resizable: bool,
2223
2224 pub is_minimizable: bool,
2226
2227 pub display_id: Option<DisplayId>,
2230
2231 pub window_background: WindowBackgroundAppearance,
2233
2234 pub app_id: Option<String>,
2236
2237 pub window_min_size: Option<Size<Pixels>>,
2239
2240 pub window_decorations: Option<WindowDecorations>,
2243
2244 pub icon: Option<Arc<image::RgbaImage>>,
2246
2247 pub tabbing_identifier: Option<String>,
2249}
2250
2251#[derive(Debug)]
2253#[cfg_attr(
2254 all(
2255 any(target_os = "linux", target_os = "freebsd"),
2256 not(any(feature = "x11", feature = "wayland"))
2257 ),
2258 allow(dead_code)
2259)]
2260#[allow(missing_docs)]
2261pub struct WindowParams {
2262 pub bounds: Bounds<Pixels>,
2263
2264 #[cfg_attr(feature = "wayland", allow(dead_code))]
2266 pub titlebar: Option<TitlebarOptions>,
2267
2268 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2270 pub kind: WindowKind,
2271
2272 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2274 pub is_movable: bool,
2275
2276 #[cfg_attr(
2278 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2279 allow(dead_code)
2280 )]
2281 pub app_owns_titlebar_drag: bool,
2282
2283 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2285 pub is_resizable: bool,
2286
2287 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2289 pub is_minimizable: bool,
2290
2291 #[cfg_attr(
2292 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2293 allow(dead_code)
2294 )]
2295 pub focus: bool,
2296
2297 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2298 pub show: bool,
2299
2300 #[cfg_attr(feature = "wayland", allow(dead_code))]
2302 pub icon: Option<Arc<image::RgbaImage>>,
2303
2304 #[cfg_attr(feature = "wayland", allow(dead_code))]
2305 pub display_id: Option<DisplayId>,
2306
2307 #[cfg_attr(feature = "wayland", allow(dead_code))]
2308 pub app_id: Option<String>,
2309
2310 pub window_min_size: Option<Size<Pixels>>,
2311
2312 #[cfg(target_os = "macos")]
2313 pub tabbing_identifier: Option<String>,
2314}
2315
2316#[derive(Debug, Copy, Clone, PartialEq)]
2318pub enum WindowBounds {
2319 Windowed(Bounds<Pixels>),
2321 Maximized(Bounds<Pixels>),
2324 Fullscreen(Bounds<Pixels>),
2327}
2328
2329impl Default for WindowBounds {
2330 fn default() -> Self {
2331 WindowBounds::Windowed(Bounds::default())
2332 }
2333}
2334
2335impl WindowBounds {
2336 pub fn get_bounds(&self) -> Bounds<Pixels> {
2338 match self {
2339 WindowBounds::Windowed(bounds) => *bounds,
2340 WindowBounds::Maximized(bounds) => *bounds,
2341 WindowBounds::Fullscreen(bounds) => *bounds,
2342 }
2343 }
2344
2345 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2347 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2348 }
2349}
2350
2351impl Default for WindowOptions {
2352 fn default() -> Self {
2353 Self {
2354 window_bounds: None,
2355 titlebar: Some(TitlebarOptions {
2356 title: Default::default(),
2357 appears_transparent: Default::default(),
2358 traffic_light_position: Default::default(),
2359 }),
2360 focus: true,
2361 show: true,
2362 kind: WindowKind::Normal,
2363 is_movable: true,
2364 app_owns_titlebar_drag: false,
2365 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2366 is_resizable: true,
2367 is_minimizable: true,
2368 display_id: None,
2369 window_background: WindowBackgroundAppearance::default(),
2370 icon: None,
2371 app_id: None,
2372 window_min_size: None,
2373 window_decorations: None,
2374 tabbing_identifier: None,
2375 }
2376 }
2377}
2378
2379#[derive(Debug, Default)]
2381pub struct TitlebarOptions {
2382 pub title: Option<SharedString>,
2384
2385 pub appears_transparent: bool,
2388
2389 pub traffic_light_position: Option<Point<Pixels>>,
2391}
2392
2393#[derive(Clone, Debug, PartialEq, Eq)]
2395pub enum WindowKind {
2396 Normal,
2398
2399 PopUp,
2402
2403 AnchoredPopup(popup::PopupOptions),
2410
2411 Floating,
2413
2414 #[cfg(all(target_os = "linux", feature = "wayland"))]
2417 LayerShell(layer_shell::LayerShellOptions),
2418
2419 Dialog,
2422}
2423
2424#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2429pub enum WindowAppearance {
2430 #[default]
2434 Light,
2435
2436 VibrantLight,
2440
2441 Dark,
2445
2446 VibrantDark,
2450}
2451
2452#[derive(Copy, Clone, Debug, Default, PartialEq)]
2455pub enum WindowBackgroundAppearance {
2456 #[default]
2464 Opaque,
2465 Transparent,
2467 Blurred,
2471 MicaBackdrop,
2473 MicaAltBackdrop,
2475}
2476
2477#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2479pub enum TextRenderingMode {
2480 #[default]
2482 PlatformDefault,
2483 Subpixel,
2485 Grayscale,
2487}
2488
2489#[derive(Clone, Debug)]
2491pub struct PathPromptOptions {
2492 pub files: bool,
2494 pub directories: bool,
2496 pub multiple: bool,
2498 pub prompt: Option<SharedString>,
2500}
2501
2502#[derive(Copy, Clone, Debug, PartialEq)]
2504pub enum PromptLevel {
2505 Info,
2507
2508 Warning,
2510
2511 Critical,
2513}
2514
2515#[derive(Clone, Debug, PartialEq)]
2517pub enum PromptButton {
2518 Ok(SharedString),
2520 Cancel(SharedString),
2522 Other(SharedString),
2524}
2525
2526impl PromptButton {
2527 pub fn new(label: impl Into<SharedString>) -> Self {
2529 PromptButton::Other(label.into())
2530 }
2531
2532 pub fn ok(label: impl Into<SharedString>) -> Self {
2534 PromptButton::Ok(label.into())
2535 }
2536
2537 pub fn cancel(label: impl Into<SharedString>) -> Self {
2539 PromptButton::Cancel(label.into())
2540 }
2541
2542 #[allow(dead_code)]
2544 pub fn is_cancel(&self) -> bool {
2545 matches!(self, PromptButton::Cancel(_))
2546 }
2547
2548 pub fn label(&self) -> &SharedString {
2550 match self {
2551 PromptButton::Ok(label) => label,
2552 PromptButton::Cancel(label) => label,
2553 PromptButton::Other(label) => label,
2554 }
2555 }
2556}
2557
2558impl From<&str> for PromptButton {
2559 fn from(value: &str) -> Self {
2560 match value.to_lowercase().as_str() {
2561 "ok" => PromptButton::Ok("OK".into()),
2562 "cancel" => PromptButton::Cancel("Cancel".into()),
2563 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2564 }
2565 }
2566}
2567
2568#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2570pub enum CursorStyle {
2571 #[default]
2573 Arrow,
2574
2575 IBeam,
2578
2579 Crosshair,
2582
2583 ClosedHand,
2586
2587 OpenHand,
2590
2591 PointingHand,
2594
2595 ResizeLeft,
2598
2599 ResizeRight,
2602
2603 ResizeLeftRight,
2606
2607 ResizeUp,
2610
2611 ResizeDown,
2614
2615 ResizeUpDown,
2618
2619 ResizeUpLeftDownRight,
2622
2623 ResizeUpRightDownLeft,
2626
2627 ResizeColumn,
2630
2631 ResizeRow,
2634
2635 IBeamCursorForVerticalLayout,
2638
2639 OperationNotAllowed,
2642
2643 DragLink,
2646
2647 DragCopy,
2650
2651 ContextualMenu,
2654}
2655
2656#[derive(Clone, Debug, Eq, PartialEq)]
2658pub struct ClipboardItem {
2659 pub entries: Vec<ClipboardEntry>,
2661}
2662
2663#[derive(Clone, Debug, PartialEq, Eq)]
2668pub enum ClipboardReadError {
2669 Unavailable,
2673 Denied(String),
2676 UnsupportedContent,
2679}
2680
2681impl std::fmt::Display for ClipboardReadError {
2682 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2683 match self {
2684 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2685 Self::Denied(message) => {
2686 write!(formatter, "clipboard access was denied: {message}")
2687 }
2688 Self::UnsupportedContent => {
2689 formatter.write_str("the clipboard contents are unsupported")
2690 }
2691 }
2692 }
2693}
2694
2695impl std::error::Error for ClipboardReadError {}
2696
2697#[derive(Clone, Debug, Eq, PartialEq)]
2699pub enum ClipboardEntry {
2700 String(ClipboardString),
2702 Image(Image),
2704 ExternalPaths(crate::ExternalPaths),
2706}
2707
2708impl ClipboardItem {
2709 pub fn new_string(text: String) -> Self {
2711 Self {
2712 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2713 }
2714 }
2715
2716 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2718 Self {
2719 entries: vec![ClipboardEntry::String(ClipboardString {
2720 text,
2721 metadata: Some(metadata),
2722 })],
2723 }
2724 }
2725
2726 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2728 Self {
2729 entries: vec![ClipboardEntry::String(
2730 ClipboardString::new(text).with_json_metadata(metadata),
2731 )],
2732 }
2733 }
2734
2735 pub fn new_image(image: &Image) -> Self {
2737 Self {
2738 entries: vec![ClipboardEntry::Image(image.clone())],
2739 }
2740 }
2741
2742 pub fn text(&self) -> Option<String> {
2745 let mut answer = String::new();
2746
2747 for entry in self.entries.iter() {
2748 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2749 answer.push_str(text);
2750 }
2751 }
2752
2753 if answer.is_empty() {
2754 for entry in self.entries.iter() {
2755 if let ClipboardEntry::ExternalPaths(paths) = entry {
2756 for path in &paths.0 {
2757 use std::fmt::Write as _;
2758 _ = write!(answer, "{}", path.display());
2759 }
2760 }
2761 }
2762 }
2763
2764 if !answer.is_empty() {
2765 Some(answer)
2766 } else {
2767 None
2768 }
2769 }
2770
2771 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2773 pub fn metadata(&self) -> Option<&String> {
2774 match self.entries().first() {
2775 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2776 clipboard_string.metadata.as_ref()
2777 }
2778 _ => None,
2779 }
2780 }
2781
2782 pub fn entries(&self) -> &[ClipboardEntry] {
2784 &self.entries
2785 }
2786
2787 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2789 self.entries.into_iter()
2790 }
2791}
2792
2793impl From<ClipboardString> for ClipboardEntry {
2794 fn from(value: ClipboardString) -> Self {
2795 Self::String(value)
2796 }
2797}
2798
2799impl From<String> for ClipboardEntry {
2800 fn from(value: String) -> Self {
2801 Self::from(ClipboardString::from(value))
2802 }
2803}
2804
2805impl From<Image> for ClipboardEntry {
2806 fn from(value: Image) -> Self {
2807 Self::Image(value)
2808 }
2809}
2810
2811impl From<ClipboardEntry> for ClipboardItem {
2812 fn from(value: ClipboardEntry) -> Self {
2813 Self {
2814 entries: vec![value],
2815 }
2816 }
2817}
2818
2819impl From<String> for ClipboardItem {
2820 fn from(value: String) -> Self {
2821 Self::from(ClipboardEntry::from(value))
2822 }
2823}
2824
2825impl From<Image> for ClipboardItem {
2826 fn from(value: Image) -> Self {
2827 Self::from(ClipboardEntry::from(value))
2828 }
2829}
2830
2831#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2833pub enum ImageFormat {
2834 Png,
2839 Jpeg,
2841 Webp,
2843 Gif,
2845 Svg,
2847 Bmp,
2849 Tiff,
2851 Ico,
2853 Pnm,
2855}
2856
2857impl ImageFormat {
2858 pub const fn mime_type(self) -> &'static str {
2860 match self {
2861 ImageFormat::Png => "image/png",
2862 ImageFormat::Jpeg => "image/jpeg",
2863 ImageFormat::Webp => "image/webp",
2864 ImageFormat::Gif => "image/gif",
2865 ImageFormat::Svg => "image/svg+xml",
2866 ImageFormat::Bmp => "image/bmp",
2867 ImageFormat::Tiff => "image/tiff",
2868 ImageFormat::Ico => "image/ico",
2869 ImageFormat::Pnm => "image/x-portable-anymap",
2870 }
2871 }
2872
2873 pub const fn extension(self) -> &'static str {
2875 match self {
2876 ImageFormat::Png => "png",
2877 ImageFormat::Jpeg => "jpg",
2878 ImageFormat::Webp => "webp",
2879 ImageFormat::Gif => "gif",
2880 ImageFormat::Svg => "svg",
2881 ImageFormat::Bmp => "bmp",
2882 ImageFormat::Tiff => "tiff",
2883 ImageFormat::Ico => "ico",
2884 ImageFormat::Pnm => "pnm",
2885 }
2886 }
2887
2888 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2890 use strum::IntoEnumIterator;
2891 Self::iter()
2892 .find(|format| format.mime_type() == mime_type)
2893 .or_else(|| Self::from_mime_type_alias(mime_type))
2894 }
2895
2896 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2900 match mime_type {
2901 "image/jpg" => Some(Self::Jpeg),
2902 "image/tif" => Some(Self::Tiff),
2903 _ => None,
2904 }
2905 }
2906}
2907
2908#[derive(Clone, Debug, PartialEq, Eq)]
2910pub struct Image {
2911 pub format: ImageFormat,
2913 pub bytes: Vec<u8>,
2915 pub id: u64,
2917}
2918
2919pub(crate) fn decode_static_image(
2920 bytes: &[u8],
2921 format: image::ImageFormat,
2922) -> Result<SmallVec<[Frame; 1]>> {
2923 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2924 .into_decoder()
2925 .context("creating image decoder")?;
2926 decode_static_image_from_decoder(decoder)
2927}
2928
2929pub(crate) fn decode_static_image_from_decoder(
2930 mut decoder: impl image::ImageDecoder,
2931) -> Result<SmallVec<[Frame; 1]>> {
2932 let orientation = decoder
2933 .orientation()
2934 .context("reading decoder's orientation")?;
2935 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2936 image.apply_orientation(orientation);
2937
2938 let mut data = image.into_rgba8();
2939 for pixel in data.chunks_exact_mut(4) {
2940 pixel.swap(0, 2);
2941 }
2942
2943 Ok(SmallVec::from_elem(Frame::new(data), 1))
2944}
2945
2946impl Hash for Image {
2947 fn hash<H: Hasher>(&self, state: &mut H) {
2948 state.write_u64(self.id);
2949 }
2950}
2951
2952impl Image {
2953 pub fn empty() -> Self {
2955 Self::from_bytes(ImageFormat::Png, Vec::new())
2956 }
2957
2958 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2960 Self {
2961 id: hash(&bytes),
2962 format,
2963 bytes,
2964 }
2965 }
2966
2967 pub fn id(&self) -> u64 {
2969 self.id
2970 }
2971
2972 pub fn use_render_image(
2974 self: Arc<Self>,
2975 window: &mut Window,
2976 cx: &mut App,
2977 ) -> Option<Arc<RenderImage>> {
2978 ImageSource::Image(self)
2979 .use_data(None, window, cx)
2980 .and_then(|result| result.ok())
2981 }
2982
2983 pub fn get_render_image(
2985 self: Arc<Self>,
2986 window: &mut Window,
2987 cx: &mut App,
2988 ) -> Option<Arc<RenderImage>> {
2989 ImageSource::Image(self)
2990 .get_data(None, window, cx)
2991 .and_then(|result| result.ok())
2992 }
2993
2994 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2996 ImageSource::Image(self).remove_asset(cx);
2997 }
2998
2999 #[cfg(any(test, feature = "test-support"))]
3002 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
3003 ImageSource::Image(self.clone()).is_asset_cached(cx)
3004 }
3005
3006 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
3008 let frames = match self.format {
3009 ImageFormat::Gif => {
3010 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
3011 let mut frames = SmallVec::new();
3012
3013 for frame in decoder.into_frames() {
3014 match frame {
3015 Ok(mut frame) => {
3016 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
3018 pixel.swap(0, 2);
3019 }
3020 frames.push(frame);
3021 }
3022 Err(err) => {
3023 log::debug!("Skipping GIF frame due to decode error: {err}");
3024 }
3025 }
3026 }
3027
3028 if frames.is_empty() {
3029 anyhow::bail!("GIF could not be decoded: all frames failed");
3030 }
3031
3032 frames
3033 }
3034 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
3035 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
3036 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
3037 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
3038 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
3039 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
3040 ImageFormat::Svg => {
3041 return svg_renderer
3042 .render_single_frame(&self.bytes, 1.0)
3043 .map_err(Into::into);
3044 }
3045 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
3046 };
3047
3048 Ok(Arc::new(RenderImage::new(frames)))
3049 }
3050
3051 pub fn format(&self) -> ImageFormat {
3053 self.format
3054 }
3055
3056 pub fn bytes(&self) -> &[u8] {
3058 self.bytes.as_slice()
3059 }
3060}
3061
3062#[derive(Clone, Debug, Eq, PartialEq)]
3064pub struct ClipboardString {
3065 pub text: String,
3067 pub metadata: Option<String>,
3069}
3070
3071impl ClipboardString {
3072 pub fn new(text: String) -> Self {
3074 Self {
3075 text,
3076 metadata: None,
3077 }
3078 }
3079
3080 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
3083 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
3084 self
3085 }
3086
3087 pub fn text(&self) -> &String {
3089 &self.text
3090 }
3091
3092 pub fn into_text(self) -> String {
3094 self.text
3095 }
3096
3097 pub fn metadata_json<T>(&self) -> Option<T>
3099 where
3100 T: for<'a> Deserialize<'a>,
3101 {
3102 self.metadata
3103 .as_ref()
3104 .and_then(|m| serde_json::from_str(m).ok())
3105 }
3106
3107 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3108 pub fn text_hash(text: &str) -> u64 {
3110 let mut hasher = SeaHasher::new();
3111 text.hash(&mut hasher);
3112 hasher.finish()
3113 }
3114}
3115
3116impl From<String> for ClipboardString {
3117 fn from(value: String) -> Self {
3118 Self {
3119 text: value,
3120 metadata: None,
3121 }
3122 }
3123}
3124
3125#[cfg(test)]
3126mod image_tests {
3127 use super::*;
3128 use std::sync::Arc;
3129
3130 #[test]
3131 fn test_image_to_image_data_applies_exif_orientation() {
3132 let image = Image::from_bytes(
3133 ImageFormat::Jpeg,
3134 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
3135 );
3136
3137 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3138
3139 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3140
3141 let bytes = render_image.as_bytes(0).unwrap();
3142 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3143 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3144 }
3145
3146 #[test]
3147 fn test_svg_image_to_image_data_converts_to_bgra() {
3148 let image = Image::from_bytes(
3149 ImageFormat::Svg,
3150 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3151<rect width="1" height="1" fill="#38BDF8"/>
3152</svg>"##
3153 .to_vec(),
3154 );
3155
3156 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3157 let bytes = render_image.as_bytes(0).unwrap();
3158
3159 for pixel in bytes.chunks_exact(4) {
3160 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3161 }
3162 }
3163}
3164
3165#[cfg(test)]
3166mod atlas_tests {
3167 use super::*;
3168
3169 const TILE_SIZE: Size<DevicePixels> = Size {
3170 width: DevicePixels(1),
3171 height: DevicePixels(1),
3172 };
3173
3174 #[derive(Default)]
3175 struct RecordingAtlasBackend {
3176 insert_calls: u32,
3177 fail_next_insert: bool,
3178 removed_tiles: Vec<AtlasTile>,
3179 }
3180
3181 impl AtlasBackend for RecordingAtlasBackend {
3182 fn insert(
3183 &mut self,
3184 kind: AtlasTextureKind,
3185 size: Size<DevicePixels>,
3186 _bytes: &[u8],
3187 ) -> Result<AtlasTile> {
3188 self.insert_calls += 1;
3189 if std::mem::take(&mut self.fail_next_insert) {
3190 anyhow::bail!("backend failed");
3191 }
3192 Ok(AtlasTile {
3193 texture_id: AtlasTextureId { index: 0, kind },
3194 tile_id: TileId(self.insert_calls),
3195 padding: 0,
3196 bounds: Bounds {
3197 origin: Point::default(),
3198 size,
3199 },
3200 })
3201 }
3202
3203 fn remove(&mut self, tile: AtlasTile) {
3204 self.removed_tiles.push(tile);
3205 }
3206 }
3207
3208 fn image_key(image_id: usize) -> AtlasKey {
3209 AtlasKey::Image(RenderImageParams {
3210 image_id: crate::ImageId(image_id),
3211 frame_index: 0,
3212 })
3213 }
3214
3215 fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
3216 Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
3217 }
3218
3219 #[test]
3220 fn only_successful_inserts_are_cached() -> Result<()> {
3221 let mut state = AtlasState::new(RecordingAtlasBackend::default());
3222 let key = image_key(1);
3223
3224 assert_eq!(
3225 state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
3226 None
3227 );
3228 state
3229 .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
3230 .expect_err("builder error should propagate");
3231 assert!(!state.contains(&key));
3232 assert_eq!(state.backend.insert_calls, 0);
3233
3234 state.backend.fail_next_insert = true;
3235 state
3236 .get_or_insert_with(key.clone(), &mut build_tile)
3237 .expect_err("backend error should propagate");
3238 assert!(!state.contains(&key));
3239 assert_eq!(state.backend.insert_calls, 1);
3240
3241 let tile = state
3242 .get_or_insert_with(key.clone(), &mut build_tile)?
3243 .context("builder should produce a tile")?;
3244 assert_eq!(tile.texture_id.kind, key.texture_kind());
3245 assert_eq!(
3246 state.get_or_insert_with(key.clone(), &mut || {
3247 anyhow::bail!("cache hit must not call the builder")
3248 })?,
3249 Some(tile)
3250 );
3251 assert!(state.contains(&key));
3252 assert_eq!(state.backend.insert_calls, 2);
3253 Ok(())
3254 }
3255
3256 #[test]
3257 fn remove_and_clear_invalidate_keys() -> Result<()> {
3258 let mut state = AtlasState::new(RecordingAtlasBackend::default());
3259 let key = image_key(1);
3260 let other_key = image_key(2);
3261 let tile = state
3262 .get_or_insert_with(key.clone(), &mut build_tile)?
3263 .context("builder should produce a tile")?;
3264 state
3265 .get_or_insert_with(other_key.clone(), &mut build_tile)?
3266 .context("builder should produce another tile")?;
3267
3268 state.remove(&key);
3269 state.remove(&key);
3270 assert!(!state.contains(&key));
3271 assert!(state.contains(&other_key));
3272 assert_eq!(state.backend.removed_tiles, vec![tile]);
3273
3274 let mut reset_calls = 0;
3275 state.clear(|_| reset_calls += 1);
3276 assert_eq!(reset_calls, 1);
3277 assert!(!state.contains(&other_key));
3278 assert_eq!(state.backend.removed_tiles, vec![tile]);
3279 Ok(())
3280 }
3281}
3282
3283#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3284mod tests {
3285 use super::*;
3286 use std::collections::HashSet;
3287
3288 #[test]
3289 fn test_window_button_layout_parse_standard() {
3290 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3291 assert_eq!(
3292 layout.left,
3293 [
3294 Some(WindowButton::Close),
3295 Some(WindowButton::Minimize),
3296 None
3297 ]
3298 );
3299 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3300 }
3301
3302 #[test]
3303 fn test_window_button_layout_parse_right_only() {
3304 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3305 assert_eq!(layout.left, [None, None, None]);
3306 assert_eq!(
3307 layout.right,
3308 [
3309 Some(WindowButton::Minimize),
3310 Some(WindowButton::Maximize),
3311 Some(WindowButton::Close)
3312 ]
3313 );
3314 }
3315
3316 #[test]
3317 fn test_window_button_layout_parse_left_only() {
3318 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3319 assert_eq!(
3320 layout.left,
3321 [
3322 Some(WindowButton::Close),
3323 Some(WindowButton::Minimize),
3324 Some(WindowButton::Maximize)
3325 ]
3326 );
3327 assert_eq!(layout.right, [None, None, None]);
3328 }
3329
3330 #[test]
3331 fn test_window_button_layout_parse_with_whitespace() {
3332 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3333 assert_eq!(
3334 layout.left,
3335 [
3336 Some(WindowButton::Close),
3337 Some(WindowButton::Minimize),
3338 None
3339 ]
3340 );
3341 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3342 }
3343
3344 #[test]
3345 fn test_window_button_layout_parse_empty() {
3346 let layout = WindowButtonLayout::parse("").unwrap();
3347 assert_eq!(layout.left, [None, None, None]);
3348 assert_eq!(layout.right, [None, None, None]);
3349 }
3350
3351 #[test]
3352 fn test_window_button_layout_parse_intentionally_empty() {
3353 let layout = WindowButtonLayout::parse(":").unwrap();
3354 assert_eq!(layout.left, [None, None, None]);
3355 assert_eq!(layout.right, [None, None, None]);
3356 }
3357
3358 #[test]
3359 fn test_window_button_layout_parse_invalid_buttons() {
3360 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3361 assert_eq!(
3362 layout.left,
3363 [
3364 Some(WindowButton::Close),
3365 Some(WindowButton::Minimize),
3366 None
3367 ]
3368 );
3369 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3370 }
3371
3372 #[test]
3373 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3374 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3375 assert_eq!(
3376 layout.right,
3377 [
3378 Some(WindowButton::Close),
3379 Some(WindowButton::Minimize),
3380 None
3381 ]
3382 );
3383 assert_eq!(layout.format(), ":close,minimize");
3384 }
3385
3386 #[test]
3387 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3388 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3389 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3390 assert_eq!(
3391 layout.right,
3392 [
3393 Some(WindowButton::Maximize),
3394 Some(WindowButton::Minimize),
3395 None
3396 ]
3397 );
3398
3399 let button_ids: Vec<_> = layout
3400 .left
3401 .iter()
3402 .chain(layout.right.iter())
3403 .flatten()
3404 .map(WindowButton::id)
3405 .collect();
3406 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3407 assert_eq!(unique_button_ids.len(), button_ids.len());
3408 assert_eq!(layout.format(), "close:maximize,minimize");
3409 }
3410
3411 #[test]
3412 fn test_window_button_layout_parse_gnome_style() {
3413 let layout = WindowButtonLayout::parse("close").unwrap();
3414 assert_eq!(layout.left, [None, None, None]);
3415 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3416 }
3417
3418 #[test]
3419 fn test_window_button_layout_parse_elementary_style() {
3420 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3421 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3422 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3423 }
3424
3425 #[test]
3426 fn test_window_button_layout_round_trip() {
3427 let cases = [
3428 "close:minimize,maximize",
3429 "minimize,maximize,close:",
3430 ":close",
3431 "close:",
3432 "close:maximize",
3433 ":",
3434 ];
3435
3436 for case in cases {
3437 let layout = WindowButtonLayout::parse(case).unwrap();
3438 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3439 }
3440 }
3441
3442 #[test]
3443 fn test_window_button_layout_linux_default() {
3444 let layout = WindowButtonLayout::linux_default();
3445 assert_eq!(layout.left, [None, None, None]);
3446 assert_eq!(
3447 layout.right,
3448 [
3449 Some(WindowButton::Minimize),
3450 Some(WindowButton::Maximize),
3451 Some(WindowButton::Close)
3452 ]
3453 );
3454
3455 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3456 assert_eq!(round_tripped, layout);
3457 }
3458
3459 #[test]
3460 fn test_window_button_layout_parse_all_invalid() {
3461 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3462 }
3463}