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 schedule_frame(&self) {}
961 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
962 fn is_subpixel_rendering_supported(&self) -> bool;
963
964 fn get_title(&self) -> String {
966 String::new()
967 }
968 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
969 None
970 }
971 fn tab_bar_visible(&self) -> bool {
972 false
973 }
974 fn set_edited(&mut self, _edited: bool) {}
975 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
976 fn toggle_simple_fullscreen(&self) {}
977 fn is_simple_fullscreen(&self) -> bool {
978 false
979 }
980 #[cfg(target_os = "macos")]
981 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
982 fn show_character_palette(&self) {}
983 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
984 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
985 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
986 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
987 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
988 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
989 fn merge_all_windows(&self) {}
990 fn move_tab_to_new_window(&self) {}
991 fn toggle_window_tab_overview(&self) {}
992 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
993
994 fn native_window_state(&self) -> Option<Vec<u8>> {
995 None
996 }
997 fn restore_native_window_state(&self, _state: &[u8]) {}
998
999 #[cfg(target_os = "windows")]
1000 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1001
1002 fn inner_window_bounds(&self) -> WindowBounds {
1004 self.window_bounds()
1005 }
1006 fn request_decorations(&self, _decorations: WindowDecorations) {}
1007 fn show_window_menu(&self, _position: Point<Pixels>) {}
1008 fn start_window_move(&self) {}
1009 fn can_start_external_drag(&self) -> bool {
1010 false
1011 }
1012 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1013 false
1014 }
1015 fn start_window_resize(&self, _edge: ResizeEdge) {}
1016 fn set_exclusive_zone(&self, _zone: Pixels) {}
1017 #[cfg(all(target_os = "linux", feature = "wayland"))]
1018 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1019 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1020 fn window_decorations(&self) -> Decorations {
1021 Decorations::Server
1022 }
1023 fn set_app_id(&mut self, _app_id: &str) {}
1024 fn map_window(&mut self) -> anyhow::Result<()> {
1025 Ok(())
1026 }
1027 fn window_controls(&self) -> WindowControls {
1028 WindowControls::default()
1029 }
1030 fn set_client_inset(&self, _inset: Pixels) {}
1031 fn gpu_specs(&self) -> Option<GpuSpecs>;
1032
1033 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1034
1035 fn insets(&self) -> WindowInsets {
1040 WindowInsets::default()
1041 }
1042
1043 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1049
1050 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1053
1054 fn set_back_enabled(&self, _enabled: bool) {}
1057
1058 fn show_soft_keyboard(&self) {}
1060
1061 fn hide_soft_keyboard(&self) {}
1063
1064 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1066
1067 fn play_system_bell(&self) {}
1068
1069 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1071
1072 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1074
1075 fn a11y_update_window_bounds(&self) {}
1077
1078 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1079 fn as_test(&mut self) -> Option<&mut TestWindow> {
1080 None
1081 }
1082
1083 #[cfg(any(test, feature = "test-support"))]
1087 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1088 anyhow::bail!("render_to_image not implemented for this platform")
1089 }
1090}
1091
1092#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1094pub trait PlatformHeadlessRenderer {
1095 fn render_scene_to_image(
1097 &mut self,
1098 scene: &Scene,
1099 size: Size<DevicePixels>,
1100 ) -> Result<RgbaImage>;
1101
1102 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1108
1109 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1111}
1112
1113#[doc(hidden)]
1116pub type RunnableVariant = Runnable<RunnableMeta>;
1117
1118#[doc(hidden)]
1119pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1120
1121#[doc(hidden)]
1122pub enum TasksIncluded {
1123 OnlyCompleted,
1124 CompletedAndRunning,
1125}
1126
1127#[doc(hidden)]
1130pub trait PlatformDispatcher: Send + Sync {
1131 fn is_main_thread(&self) -> bool;
1132 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1133 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1134 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1135
1136 fn dispatch_on_main_thread_when_idle(
1137 &self,
1138 runnable: RunnableVariant,
1139 timeout: Option<Duration>,
1140 ) {
1141 let _ = timeout;
1142 self.dispatch_on_main_thread(runnable, Priority::Low);
1143 }
1144
1145 fn idle_time_remaining(&self) -> Option<Duration> {
1146 None
1147 }
1148
1149 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1150
1151 fn now(&self) -> Instant {
1152 Instant::now()
1153 }
1154
1155 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1156 gpui_util::defer(Box::new(|| {}))
1157 }
1158
1159 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1160 ActivityGuard::noop()
1161 }
1162
1163 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1164 fn as_test(&self) -> Option<&TestDispatcher> {
1165 None
1166 }
1167
1168 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1171 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1172 None
1173 }
1174}
1175
1176#[expect(missing_docs)]
1177pub trait PlatformTextSystem: Send + Sync {
1178 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1179 fn set_missing_glyph_sink(&self, _sink: Option<Arc<dyn MissingGlyphSink>>) {}
1181 fn all_font_names(&self) -> Vec<String>;
1183 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1185 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1187 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1189 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1191 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1193 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1195 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1197 fn rasterize_glyph(
1199 &self,
1200 params: &RenderGlyphParams,
1201 raster_bounds: Bounds<DevicePixels>,
1202 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1203 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1205 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1207 -> TextRenderingMode;
1208 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1210 0
1211 }
1212}
1213
1214#[expect(missing_docs)]
1215pub struct NoopTextSystem;
1216
1217#[expect(missing_docs)]
1218impl NoopTextSystem {
1219 #[allow(dead_code)]
1220 pub fn new() -> Self {
1221 Self
1222 }
1223}
1224
1225impl PlatformTextSystem for NoopTextSystem {
1226 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1227 Ok(())
1228 }
1229
1230 fn all_font_names(&self) -> Vec<String> {
1231 Vec::new()
1232 }
1233
1234 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1235 Ok(FontId(1))
1236 }
1237
1238 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1239 FontMetrics {
1240 units_per_em: 1000,
1241 ascent: 1025.0,
1242 descent: -275.0,
1243 line_gap: 0.0,
1244 underline_position: -95.0,
1245 underline_thickness: 60.0,
1246 cap_height: 698.0,
1247 x_height: 516.0,
1248 bounding_box: Bounds {
1249 origin: Point {
1250 x: -260.0,
1251 y: -245.0,
1252 },
1253 size: Size {
1254 width: 1501.0,
1255 height: 1364.0,
1256 },
1257 },
1258 }
1259 }
1260
1261 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1262 Ok(Bounds {
1263 origin: Point { x: 54.0, y: 0.0 },
1264 size: size(392.0, 528.0),
1265 })
1266 }
1267
1268 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1269 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1270 }
1271
1272 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1273 Some(GlyphId(ch.len_utf16() as u32))
1274 }
1275
1276 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1277 Ok(Default::default())
1278 }
1279
1280 fn rasterize_glyph(
1281 &self,
1282 _params: &RenderGlyphParams,
1283 raster_bounds: Bounds<DevicePixels>,
1284 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1285 Ok((raster_bounds.size, Vec::new()))
1286 }
1287
1288 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1289 let mut position = px(0.);
1290 let metrics = self.font_metrics(FontId(0));
1291 let em_width = font_size
1292 * self
1293 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1294 .unwrap()
1295 .width
1296 / metrics.units_per_em as f32;
1297 let mut glyphs = Vec::new();
1298 for (ix, c) in text.char_indices() {
1299 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1300 glyphs.push(ShapedGlyph {
1301 id: glyph,
1302 position: point(position, px(0.)),
1303 index: ix,
1304 is_emoji: glyph.0 == 2,
1305 });
1306 if glyph.0 == 2 {
1307 position += em_width * 2.0;
1308 } else {
1309 position += em_width;
1310 }
1311 } else {
1312 position += em_width
1313 }
1314 }
1315 let mut runs = Vec::default();
1316 if !glyphs.is_empty() {
1317 runs.push(ShapedRun {
1318 font_id: FontId(0),
1319 glyphs,
1320 });
1321 } else {
1322 position = px(0.);
1323 }
1324
1325 LineLayout {
1326 font_size,
1327 width: position,
1328 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1329 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1330 runs,
1331 len: text.len(),
1332 }
1333 }
1334
1335 fn recommended_rendering_mode(
1336 &self,
1337 _font_id: FontId,
1338 _font_size: Pixels,
1339 ) -> TextRenderingMode {
1340 TextRenderingMode::Grayscale
1341 }
1342}
1343
1344#[allow(dead_code)]
1349pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1350 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1351 [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], ];
1365
1366 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1367 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1368
1369 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1370 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1371
1372 [
1373 ratios[0] * NORM13,
1374 ratios[1] * NORM24,
1375 ratios[2] * NORM13,
1376 ratios[3] * NORM24,
1377 ]
1378}
1379
1380#[derive(PartialEq, Eq, Hash, Clone)]
1381#[expect(missing_docs)]
1382pub enum AtlasKey {
1383 Glyph(RenderGlyphParams),
1384 Svg(RenderSvgParams),
1385 Image(RenderImageParams),
1386}
1387
1388impl AtlasKey {
1389 pub fn texture_kind(&self) -> AtlasTextureKind {
1391 match self {
1392 AtlasKey::Glyph(params) => {
1393 if params.is_emoji {
1394 AtlasTextureKind::Polychrome
1395 } else if params.subpixel_rendering {
1396 AtlasTextureKind::Subpixel
1397 } else {
1398 AtlasTextureKind::Monochrome
1399 }
1400 }
1401 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1402 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1403 }
1404 }
1405}
1406
1407impl From<RenderGlyphParams> for AtlasKey {
1408 fn from(params: RenderGlyphParams) -> Self {
1409 Self::Glyph(params)
1410 }
1411}
1412
1413impl From<RenderSvgParams> for AtlasKey {
1414 fn from(params: RenderSvgParams) -> Self {
1415 Self::Svg(params)
1416 }
1417}
1418
1419impl From<RenderImageParams> for AtlasKey {
1420 fn from(params: RenderImageParams) -> Self {
1421 Self::Image(params)
1422 }
1423}
1424
1425#[expect(missing_docs)]
1426pub trait PlatformAtlas {
1427 fn get_or_insert_with<'a>(
1429 &self,
1430 key: AtlasKey,
1431 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1432 ) -> Result<Option<AtlasTile>>;
1433 fn remove(&self, key: &AtlasKey);
1434
1435 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1436 fn contains(&self, _key: &AtlasKey) -> bool {
1437 false
1438 }
1439}
1440
1441#[doc(hidden)]
1442pub trait AtlasBackend {
1443 fn insert(
1444 &mut self,
1445 kind: AtlasTextureKind,
1446 size: Size<DevicePixels>,
1447 bytes: &[u8],
1448 ) -> Result<AtlasTile>;
1449
1450 fn remove(&mut self, tile: AtlasTile);
1451}
1452
1453#[doc(hidden)]
1454pub struct AtlasState<Backend> {
1455 tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
1456 pub backend: Backend,
1457}
1458
1459impl<Backend> AtlasState<Backend> {
1460 pub fn new(backend: Backend) -> Self {
1461 Self {
1462 tiles_by_key: FxHashMap::default(),
1463 backend,
1464 }
1465 }
1466
1467 pub fn contains(&self, key: &AtlasKey) -> bool {
1468 self.tiles_by_key.contains_key(key)
1469 }
1470
1471 pub fn clear(&mut self, reset_backend: impl FnOnce(&mut Backend)) {
1472 self.tiles_by_key.clear();
1473 reset_backend(&mut self.backend);
1474 }
1475}
1476
1477impl<Backend: Default> Default for AtlasState<Backend> {
1478 fn default() -> Self {
1479 Self::new(Backend::default())
1480 }
1481}
1482
1483impl<Backend: AtlasBackend> AtlasState<Backend> {
1484 pub fn get_or_insert_with<'a>(
1485 &mut self,
1486 key: AtlasKey,
1487 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1488 ) -> Result<Option<AtlasTile>> {
1489 match self.tiles_by_key.entry(key) {
1490 Entry::Occupied(entry) => Ok(Some(*entry.get())),
1491 Entry::Vacant(entry) => {
1492 profiling::scope!("new tile");
1493 let Some((size, bytes)) = build()? else {
1494 return Ok(None);
1495 };
1496 let tile = self
1497 .backend
1498 .insert(entry.key().texture_kind(), size, &bytes)?;
1499 entry.insert(tile);
1500 Ok(Some(tile))
1501 }
1502 }
1503 }
1504
1505 pub fn remove(&mut self, key: &AtlasKey) {
1506 if let Some(tile) = self.tiles_by_key.remove(key) {
1507 self.backend.remove(tile);
1508 }
1509 }
1510}
1511
1512#[derive(Default)]
1516pub struct HeadlessAtlas(parking_lot::Mutex<AtlasState<HeadlessAtlasBackend>>);
1517
1518#[doc(hidden)]
1519#[derive(Default)]
1520pub struct HeadlessAtlasBackend {
1521 next_id: u32,
1522}
1523
1524impl AtlasBackend for HeadlessAtlasBackend {
1525 fn insert(
1526 &mut self,
1527 kind: AtlasTextureKind,
1528 size: Size<DevicePixels>,
1529 _bytes: &[u8],
1530 ) -> Result<AtlasTile> {
1531 self.next_id += 1;
1532 let texture_id = self.next_id;
1533 self.next_id += 1;
1534 let tile_id = self.next_id;
1535 Ok(AtlasTile {
1536 texture_id: AtlasTextureId {
1537 index: texture_id,
1538 kind,
1539 },
1540 tile_id: TileId(tile_id),
1541 padding: 0,
1542 bounds: Bounds {
1543 origin: Point::default(),
1544 size,
1545 },
1546 })
1547 }
1548
1549 fn remove(&mut self, _tile: AtlasTile) {}
1550}
1551
1552impl PlatformAtlas for HeadlessAtlas {
1553 fn get_or_insert_with<'a>(
1554 &self,
1555 key: AtlasKey,
1556 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1557 ) -> Result<Option<AtlasTile>> {
1558 self.0.lock().get_or_insert_with(key, build)
1559 }
1560
1561 fn remove(&self, key: &AtlasKey) {
1562 self.0.lock().remove(key);
1563 }
1564
1565 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1566 fn contains(&self, key: &AtlasKey) -> bool {
1567 self.0.lock().contains(key)
1568 }
1569}
1570
1571#[doc(hidden)]
1572pub struct AtlasTextureList<T> {
1573 pub textures: Vec<Option<T>>,
1574 pub free_list: Vec<usize>,
1575}
1576
1577impl<T> Default for AtlasTextureList<T> {
1578 fn default() -> Self {
1579 Self {
1580 textures: Vec::default(),
1581 free_list: Vec::default(),
1582 }
1583 }
1584}
1585
1586impl<T> ops::Index<usize> for AtlasTextureList<T> {
1587 type Output = Option<T>;
1588
1589 fn index(&self, index: usize) -> &Self::Output {
1590 &self.textures[index]
1591 }
1592}
1593
1594impl<T> AtlasTextureList<T> {
1595 #[allow(unused)]
1596 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1597 self.free_list.clear();
1598 self.textures.drain(..)
1599 }
1600
1601 #[allow(dead_code)]
1602 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1603 self.textures.iter_mut().flatten()
1604 }
1605}
1606
1607#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1608#[repr(C)]
1609#[expect(missing_docs)]
1610pub struct AtlasTile {
1611 pub texture_id: AtlasTextureId,
1613 pub tile_id: TileId,
1615 pub padding: u32,
1617 pub bounds: Bounds<DevicePixels>,
1619}
1620
1621#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1622#[repr(C)]
1623#[expect(missing_docs)]
1624pub struct AtlasTextureId {
1625 pub index: u32,
1628 pub kind: AtlasTextureKind,
1630}
1631
1632#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1633#[repr(C)]
1634#[cfg_attr(
1635 all(
1636 any(target_os = "linux", target_os = "freebsd"),
1637 not(any(feature = "x11", feature = "wayland"))
1638 ),
1639 allow(dead_code)
1640)]
1641#[expect(missing_docs)]
1642pub enum AtlasTextureKind {
1643 Monochrome = 0,
1644 Polychrome = 1,
1645 Subpixel = 2,
1646}
1647
1648#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1649#[repr(C)]
1650#[expect(missing_docs)]
1651pub struct TileId(pub u32);
1652
1653impl From<etagere::AllocId> for TileId {
1654 fn from(id: etagere::AllocId) -> Self {
1655 Self(id.serialize())
1656 }
1657}
1658
1659impl From<TileId> for etagere::AllocId {
1660 fn from(id: TileId) -> Self {
1661 Self::deserialize(id.0)
1662 }
1663}
1664
1665#[expect(missing_docs)]
1666pub struct PlatformInputHandler {
1667 cx: AsyncWindowContext,
1668 handler: Box<dyn InputHandler>,
1669}
1670
1671#[expect(missing_docs)]
1672#[cfg_attr(
1673 all(
1674 any(target_os = "linux", target_os = "freebsd"),
1675 not(any(feature = "x11", feature = "wayland"))
1676 ),
1677 allow(dead_code)
1678)]
1679impl PlatformInputHandler {
1680 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1681 Self { cx, handler }
1682 }
1683
1684 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1685 self.cx
1686 .update(|window, cx| {
1687 self.handler
1688 .selected_text_range(ignore_disabled_input, window, cx)
1689 })
1690 .ok()
1691 .flatten()
1692 }
1693
1694 #[cfg_attr(target_os = "windows", allow(dead_code))]
1695 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1696 self.cx
1697 .update(|window, cx| self.handler.marked_text_range(window, cx))
1698 .ok()
1699 .flatten()
1700 }
1701
1702 #[cfg_attr(
1703 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1704 allow(dead_code)
1705 )]
1706 pub fn text_for_range(
1707 &mut self,
1708 range_utf16: Range<usize>,
1709 adjusted: &mut Option<Range<usize>>,
1710 ) -> Option<String> {
1711 self.cx
1712 .update(|window, cx| {
1713 self.handler
1714 .text_for_range(range_utf16, adjusted, window, cx)
1715 })
1716 .ok()
1717 .flatten()
1718 }
1719
1720 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1721 self.cx
1722 .update(|window, cx| {
1723 self.handler
1724 .replace_text_in_range(replacement_range, text, window, cx);
1725 })
1726 .ok();
1727 }
1728
1729 pub fn replace_and_mark_text_in_range(
1730 &mut self,
1731 range_utf16: Option<Range<usize>>,
1732 new_text: &str,
1733 new_selected_range: Option<Range<usize>>,
1734 ) {
1735 self.cx
1736 .update(|window, cx| {
1737 self.handler.replace_and_mark_text_in_range(
1738 range_utf16,
1739 new_text,
1740 new_selected_range,
1741 window,
1742 cx,
1743 )
1744 })
1745 .ok();
1746 }
1747
1748 #[cfg_attr(target_os = "windows", allow(dead_code))]
1749 pub fn unmark_text(&mut self) {
1750 self.cx
1751 .update(|window, cx| self.handler.unmark_text(window, cx))
1752 .ok();
1753 }
1754
1755 pub fn paste(&mut self, item: ClipboardItem) {
1756 self.cx
1757 .update(|window, cx| self.handler.paste(item, window, cx))
1758 .ok();
1759 }
1760
1761 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1762 self.cx
1763 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1764 .ok()
1765 .flatten()
1766 }
1767
1768 #[allow(dead_code)]
1769 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1770 self.handler.apple_press_and_hold_enabled()
1771 }
1772
1773 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1774 self.handler.replace_text_in_range(None, input, window, cx);
1775 }
1776
1777 pub fn compute_ime_candidate_bounds(
1778 marked_range: Option<Range<usize>>,
1779 selection: &UTF16Selection,
1780 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1781 ) -> Option<Bounds<Pixels>> {
1782 if let Some(marked_range) = marked_range {
1783 let mut line_start = marked_range.start;
1785
1786 let caret = selection.range.end;
1790 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1791 for i in (marked_range.start..caret).rev() {
1792 if let Some(b) = bounds_for_range(i..i) {
1793 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1794 line_start = i + 1;
1795 break;
1796 }
1797 }
1798 }
1799 }
1800 bounds_for_range(line_start..line_start)
1801 } else {
1802 let offset = if selection.reversed {
1804 selection.range.start
1805 } else {
1806 selection.range.end
1807 };
1808 bounds_for_range(offset..offset)
1809 }
1810 }
1811
1812 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1813 let marked_range = self.handler.marked_text_range(window, cx);
1814 let selection = self.handler.selected_text_range(true, window, cx)?;
1815 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1816 self.handler.bounds_for_range(range, window, cx)
1817 })
1818 }
1819
1820 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1821 let marked_range = self.marked_text_range();
1822 let selection = self.selected_text_range(true)?;
1823 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1824 self.bounds_for_range(range)
1825 })
1826 }
1827
1828 #[allow(unused)]
1829 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1830 self.cx
1831 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1832 .ok()
1833 .flatten()
1834 }
1835
1836 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1838 self.cx
1839 .update(|window, cx| {
1840 self.handler
1841 .set_selected_text_range(range_utf16, window, cx)
1842 })
1843 .ok();
1844 }
1845
1846 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1848 self.cx
1849 .update(|window, cx| self.handler.element_bounds(window, cx))
1850 .ok()
1851 .flatten()
1852 }
1853
1854 pub fn text_length_utf16(&mut self) -> Option<usize> {
1856 self.cx
1857 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1858 .ok()
1859 .flatten()
1860 }
1861
1862 #[allow(dead_code)]
1863 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1864 self.handler.accepts_text_input(window, cx)
1865 }
1866
1867 #[allow(dead_code)]
1868 pub fn query_accepts_text_input(&mut self) -> bool {
1869 self.cx
1870 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1871 .unwrap_or(true)
1872 }
1873
1874 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1880 self.cx
1881 .update(|window, cx| {
1882 !window.has_pending_keystrokes()
1884 && self.handler.prefers_ime_for_printable_keys(window, cx)
1885 })
1886 .unwrap_or(false)
1887 }
1888
1889 pub fn text_input_configuration(
1891 &mut self,
1892 window: &mut Window,
1893 cx: &mut App,
1894 ) -> TextInputConfiguration {
1895 self.handler.text_input_configuration(window, cx)
1896 }
1897
1898 pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1900 self.cx
1901 .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1902 .ok()
1903 .flatten()
1904 }
1905}
1906
1907#[derive(Debug)]
1910pub struct UTF16Selection {
1911 pub range: Range<usize>,
1914 pub reversed: bool,
1917}
1918
1919pub trait InputHandler: 'static {
1924 fn selected_text_range(
1929 &mut self,
1930 ignore_disabled_input: bool,
1931 window: &mut Window,
1932 cx: &mut App,
1933 ) -> Option<UTF16Selection>;
1934
1935 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1940
1941 fn text_for_range(
1946 &mut self,
1947 range_utf16: Range<usize>,
1948 adjusted_range: &mut Option<Range<usize>>,
1949 window: &mut Window,
1950 cx: &mut App,
1951 ) -> Option<String>;
1952
1953 fn replace_text_in_range(
1958 &mut self,
1959 replacement_range: Option<Range<usize>>,
1960 text: &str,
1961 window: &mut Window,
1962 cx: &mut App,
1963 );
1964
1965 fn replace_and_mark_text_in_range(
1972 &mut self,
1973 range_utf16: Option<Range<usize>>,
1974 new_text: &str,
1975 new_selected_range: Option<Range<usize>>,
1976 window: &mut Window,
1977 cx: &mut App,
1978 );
1979
1980 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1983
1984 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1991 if let Some(text) = item.text() {
1992 self.replace_text_in_range(None, &text, window, cx);
1993 }
1994 }
1995
1996 fn bounds_for_range(
2001 &mut self,
2002 range_utf16: Range<usize>,
2003 window: &mut Window,
2004 cx: &mut App,
2005 ) -> Option<Bounds<Pixels>>;
2006
2007 fn character_index_for_point(
2011 &mut self,
2012 point: Point<Pixels>,
2013 window: &mut Window,
2014 cx: &mut App,
2015 ) -> Option<usize>;
2016
2017 fn set_selected_text_range(
2027 &mut self,
2028 _range_utf16: Range<usize>,
2029 _window: &mut Window,
2030 _cx: &mut App,
2031 ) {
2032 }
2033
2034 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2041 None
2042 }
2043
2044 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2046 None
2047 }
2048
2049 #[allow(dead_code)]
2054 fn apple_press_and_hold_enabled(&mut self) -> bool {
2055 true
2056 }
2057
2058 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2060 true
2061 }
2062
2063 fn text_input_editable_range(
2074 &mut self,
2075 _window: &mut Window,
2076 _cx: &mut App,
2077 ) -> Option<Range<usize>> {
2078 None
2079 }
2080
2081 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2090 false
2091 }
2092
2093 fn text_input_configuration(
2099 &mut self,
2100 _window: &mut Window,
2101 _cx: &mut App,
2102 ) -> TextInputConfiguration {
2103 TextInputConfiguration::default()
2104 }
2105}
2106
2107#[derive(Clone, Debug, Default, PartialEq, Eq)]
2117pub struct TextInputConfiguration {
2118 pub autocorrect: bool,
2120 pub autocapitalize: Autocapitalize,
2122 pub suggestions: bool,
2124 pub input_action: TextInputAction,
2126}
2127
2128#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2130pub enum Autocapitalize {
2131 #[default]
2133 None,
2134 Words,
2136 Sentences,
2138 Characters,
2140}
2141
2142#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2152pub enum TextInputAction {
2153 #[default]
2155 Unspecified,
2156 Enter,
2158 Done,
2160 Go,
2162 Next,
2164 Previous,
2166 Search,
2168 Send,
2170}
2171
2172#[derive(Debug)]
2174pub struct WindowOptions {
2175 pub window_bounds: Option<WindowBounds>,
2179
2180 pub titlebar: Option<TitlebarOptions>,
2182
2183 pub focus: bool,
2185
2186 pub show: bool,
2188
2189 pub kind: WindowKind,
2191
2192 pub is_movable: bool,
2196
2197 pub app_owns_titlebar_drag: bool,
2209
2210 pub inactive_frame_interval: Option<Duration>,
2214
2215 pub is_resizable: bool,
2217
2218 pub is_minimizable: bool,
2220
2221 pub display_id: Option<DisplayId>,
2224
2225 pub window_background: WindowBackgroundAppearance,
2227
2228 pub app_id: Option<String>,
2230
2231 pub window_min_size: Option<Size<Pixels>>,
2233
2234 pub window_decorations: Option<WindowDecorations>,
2237
2238 pub icon: Option<Arc<image::RgbaImage>>,
2240
2241 pub tabbing_identifier: Option<String>,
2243}
2244
2245#[derive(Debug)]
2247#[cfg_attr(
2248 all(
2249 any(target_os = "linux", target_os = "freebsd"),
2250 not(any(feature = "x11", feature = "wayland"))
2251 ),
2252 allow(dead_code)
2253)]
2254#[allow(missing_docs)]
2255pub struct WindowParams {
2256 pub bounds: Bounds<Pixels>,
2257
2258 #[cfg_attr(feature = "wayland", allow(dead_code))]
2260 pub titlebar: Option<TitlebarOptions>,
2261
2262 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2264 pub kind: WindowKind,
2265
2266 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2268 pub is_movable: bool,
2269
2270 #[cfg_attr(
2272 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2273 allow(dead_code)
2274 )]
2275 pub app_owns_titlebar_drag: bool,
2276
2277 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2279 pub is_resizable: bool,
2280
2281 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2283 pub is_minimizable: bool,
2284
2285 #[cfg_attr(
2286 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2287 allow(dead_code)
2288 )]
2289 pub focus: bool,
2290
2291 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2292 pub show: bool,
2293
2294 #[cfg_attr(feature = "wayland", allow(dead_code))]
2296 pub icon: Option<Arc<image::RgbaImage>>,
2297
2298 #[cfg_attr(feature = "wayland", allow(dead_code))]
2299 pub display_id: Option<DisplayId>,
2300
2301 #[cfg_attr(feature = "wayland", allow(dead_code))]
2302 pub app_id: Option<String>,
2303
2304 pub window_min_size: Option<Size<Pixels>>,
2305
2306 #[cfg(target_os = "macos")]
2307 pub tabbing_identifier: Option<String>,
2308}
2309
2310#[derive(Debug, Copy, Clone, PartialEq)]
2312pub enum WindowBounds {
2313 Windowed(Bounds<Pixels>),
2315 Maximized(Bounds<Pixels>),
2318 Fullscreen(Bounds<Pixels>),
2321}
2322
2323impl Default for WindowBounds {
2324 fn default() -> Self {
2325 WindowBounds::Windowed(Bounds::default())
2326 }
2327}
2328
2329impl WindowBounds {
2330 pub fn get_bounds(&self) -> Bounds<Pixels> {
2332 match self {
2333 WindowBounds::Windowed(bounds) => *bounds,
2334 WindowBounds::Maximized(bounds) => *bounds,
2335 WindowBounds::Fullscreen(bounds) => *bounds,
2336 }
2337 }
2338
2339 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2341 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2342 }
2343}
2344
2345impl Default for WindowOptions {
2346 fn default() -> Self {
2347 Self {
2348 window_bounds: None,
2349 titlebar: Some(TitlebarOptions {
2350 title: Default::default(),
2351 appears_transparent: Default::default(),
2352 traffic_light_position: Default::default(),
2353 }),
2354 focus: true,
2355 show: true,
2356 kind: WindowKind::Normal,
2357 is_movable: true,
2358 app_owns_titlebar_drag: false,
2359 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2360 is_resizable: true,
2361 is_minimizable: true,
2362 display_id: None,
2363 window_background: WindowBackgroundAppearance::default(),
2364 icon: None,
2365 app_id: None,
2366 window_min_size: None,
2367 window_decorations: None,
2368 tabbing_identifier: None,
2369 }
2370 }
2371}
2372
2373#[derive(Debug, Default)]
2375pub struct TitlebarOptions {
2376 pub title: Option<SharedString>,
2378
2379 pub appears_transparent: bool,
2382
2383 pub traffic_light_position: Option<Point<Pixels>>,
2385}
2386
2387#[derive(Clone, Debug, PartialEq, Eq)]
2389pub enum WindowKind {
2390 Normal,
2392
2393 PopUp,
2396
2397 AnchoredPopup(popup::PopupOptions),
2404
2405 Floating,
2407
2408 #[cfg(all(target_os = "linux", feature = "wayland"))]
2411 LayerShell(layer_shell::LayerShellOptions),
2412
2413 Dialog,
2416}
2417
2418#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2423pub enum WindowAppearance {
2424 #[default]
2428 Light,
2429
2430 VibrantLight,
2434
2435 Dark,
2439
2440 VibrantDark,
2444}
2445
2446#[derive(Copy, Clone, Debug, Default, PartialEq)]
2449pub enum WindowBackgroundAppearance {
2450 #[default]
2458 Opaque,
2459 Transparent,
2461 Blurred,
2465 MicaBackdrop,
2467 MicaAltBackdrop,
2469}
2470
2471#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2473pub enum TextRenderingMode {
2474 #[default]
2476 PlatformDefault,
2477 Subpixel,
2479 Grayscale,
2481}
2482
2483#[derive(Clone, Debug)]
2485pub struct PathPromptOptions {
2486 pub files: bool,
2488 pub directories: bool,
2490 pub multiple: bool,
2492 pub prompt: Option<SharedString>,
2494}
2495
2496#[derive(Copy, Clone, Debug, PartialEq)]
2498pub enum PromptLevel {
2499 Info,
2501
2502 Warning,
2504
2505 Critical,
2507}
2508
2509#[derive(Clone, Debug, PartialEq)]
2511pub enum PromptButton {
2512 Ok(SharedString),
2514 Cancel(SharedString),
2516 Other(SharedString),
2518}
2519
2520impl PromptButton {
2521 pub fn new(label: impl Into<SharedString>) -> Self {
2523 PromptButton::Other(label.into())
2524 }
2525
2526 pub fn ok(label: impl Into<SharedString>) -> Self {
2528 PromptButton::Ok(label.into())
2529 }
2530
2531 pub fn cancel(label: impl Into<SharedString>) -> Self {
2533 PromptButton::Cancel(label.into())
2534 }
2535
2536 #[allow(dead_code)]
2538 pub fn is_cancel(&self) -> bool {
2539 matches!(self, PromptButton::Cancel(_))
2540 }
2541
2542 pub fn label(&self) -> &SharedString {
2544 match self {
2545 PromptButton::Ok(label) => label,
2546 PromptButton::Cancel(label) => label,
2547 PromptButton::Other(label) => label,
2548 }
2549 }
2550}
2551
2552impl From<&str> for PromptButton {
2553 fn from(value: &str) -> Self {
2554 match value.to_lowercase().as_str() {
2555 "ok" => PromptButton::Ok("OK".into()),
2556 "cancel" => PromptButton::Cancel("Cancel".into()),
2557 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2558 }
2559 }
2560}
2561
2562#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2564pub enum CursorStyle {
2565 #[default]
2567 Arrow,
2568
2569 IBeam,
2572
2573 Crosshair,
2576
2577 ClosedHand,
2580
2581 OpenHand,
2584
2585 PointingHand,
2588
2589 ResizeLeft,
2592
2593 ResizeRight,
2596
2597 ResizeLeftRight,
2600
2601 ResizeUp,
2604
2605 ResizeDown,
2608
2609 ResizeUpDown,
2612
2613 ResizeUpLeftDownRight,
2616
2617 ResizeUpRightDownLeft,
2620
2621 ResizeColumn,
2624
2625 ResizeRow,
2628
2629 IBeamCursorForVerticalLayout,
2632
2633 OperationNotAllowed,
2636
2637 DragLink,
2640
2641 DragCopy,
2644
2645 ContextualMenu,
2648}
2649
2650#[derive(Clone, Debug, Eq, PartialEq)]
2652pub struct ClipboardItem {
2653 pub entries: Vec<ClipboardEntry>,
2655}
2656
2657#[derive(Clone, Debug, PartialEq, Eq)]
2662pub enum ClipboardReadError {
2663 Unavailable,
2667 Denied(String),
2670 UnsupportedContent,
2673}
2674
2675impl std::fmt::Display for ClipboardReadError {
2676 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2677 match self {
2678 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2679 Self::Denied(message) => {
2680 write!(formatter, "clipboard access was denied: {message}")
2681 }
2682 Self::UnsupportedContent => {
2683 formatter.write_str("the clipboard contents are unsupported")
2684 }
2685 }
2686 }
2687}
2688
2689impl std::error::Error for ClipboardReadError {}
2690
2691#[derive(Clone, Debug, Eq, PartialEq)]
2693pub enum ClipboardEntry {
2694 String(ClipboardString),
2696 Image(Image),
2698 ExternalPaths(crate::ExternalPaths),
2700}
2701
2702impl ClipboardItem {
2703 pub fn new_string(text: String) -> Self {
2705 Self {
2706 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2707 }
2708 }
2709
2710 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2712 Self {
2713 entries: vec![ClipboardEntry::String(ClipboardString {
2714 text,
2715 metadata: Some(metadata),
2716 })],
2717 }
2718 }
2719
2720 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2722 Self {
2723 entries: vec![ClipboardEntry::String(
2724 ClipboardString::new(text).with_json_metadata(metadata),
2725 )],
2726 }
2727 }
2728
2729 pub fn new_image(image: &Image) -> Self {
2731 Self {
2732 entries: vec![ClipboardEntry::Image(image.clone())],
2733 }
2734 }
2735
2736 pub fn text(&self) -> Option<String> {
2739 let mut answer = String::new();
2740
2741 for entry in self.entries.iter() {
2742 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2743 answer.push_str(text);
2744 }
2745 }
2746
2747 if answer.is_empty() {
2748 for entry in self.entries.iter() {
2749 if let ClipboardEntry::ExternalPaths(paths) = entry {
2750 for path in &paths.0 {
2751 use std::fmt::Write as _;
2752 _ = write!(answer, "{}", path.display());
2753 }
2754 }
2755 }
2756 }
2757
2758 if !answer.is_empty() {
2759 Some(answer)
2760 } else {
2761 None
2762 }
2763 }
2764
2765 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2767 pub fn metadata(&self) -> Option<&String> {
2768 match self.entries().first() {
2769 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2770 clipboard_string.metadata.as_ref()
2771 }
2772 _ => None,
2773 }
2774 }
2775
2776 pub fn entries(&self) -> &[ClipboardEntry] {
2778 &self.entries
2779 }
2780
2781 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2783 self.entries.into_iter()
2784 }
2785}
2786
2787impl From<ClipboardString> for ClipboardEntry {
2788 fn from(value: ClipboardString) -> Self {
2789 Self::String(value)
2790 }
2791}
2792
2793impl From<String> for ClipboardEntry {
2794 fn from(value: String) -> Self {
2795 Self::from(ClipboardString::from(value))
2796 }
2797}
2798
2799impl From<Image> for ClipboardEntry {
2800 fn from(value: Image) -> Self {
2801 Self::Image(value)
2802 }
2803}
2804
2805impl From<ClipboardEntry> for ClipboardItem {
2806 fn from(value: ClipboardEntry) -> Self {
2807 Self {
2808 entries: vec![value],
2809 }
2810 }
2811}
2812
2813impl From<String> for ClipboardItem {
2814 fn from(value: String) -> Self {
2815 Self::from(ClipboardEntry::from(value))
2816 }
2817}
2818
2819impl From<Image> for ClipboardItem {
2820 fn from(value: Image) -> Self {
2821 Self::from(ClipboardEntry::from(value))
2822 }
2823}
2824
2825#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2827pub enum ImageFormat {
2828 Png,
2833 Jpeg,
2835 Webp,
2837 Gif,
2839 Svg,
2841 Bmp,
2843 Tiff,
2845 Ico,
2847 Pnm,
2849}
2850
2851impl ImageFormat {
2852 pub const fn mime_type(self) -> &'static str {
2854 match self {
2855 ImageFormat::Png => "image/png",
2856 ImageFormat::Jpeg => "image/jpeg",
2857 ImageFormat::Webp => "image/webp",
2858 ImageFormat::Gif => "image/gif",
2859 ImageFormat::Svg => "image/svg+xml",
2860 ImageFormat::Bmp => "image/bmp",
2861 ImageFormat::Tiff => "image/tiff",
2862 ImageFormat::Ico => "image/ico",
2863 ImageFormat::Pnm => "image/x-portable-anymap",
2864 }
2865 }
2866
2867 pub const fn extension(self) -> &'static str {
2869 match self {
2870 ImageFormat::Png => "png",
2871 ImageFormat::Jpeg => "jpg",
2872 ImageFormat::Webp => "webp",
2873 ImageFormat::Gif => "gif",
2874 ImageFormat::Svg => "svg",
2875 ImageFormat::Bmp => "bmp",
2876 ImageFormat::Tiff => "tiff",
2877 ImageFormat::Ico => "ico",
2878 ImageFormat::Pnm => "pnm",
2879 }
2880 }
2881
2882 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2884 use strum::IntoEnumIterator;
2885 Self::iter()
2886 .find(|format| format.mime_type() == mime_type)
2887 .or_else(|| Self::from_mime_type_alias(mime_type))
2888 }
2889
2890 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2894 match mime_type {
2895 "image/jpg" => Some(Self::Jpeg),
2896 "image/tif" => Some(Self::Tiff),
2897 _ => None,
2898 }
2899 }
2900}
2901
2902#[derive(Clone, Debug, PartialEq, Eq)]
2904pub struct Image {
2905 pub format: ImageFormat,
2907 pub bytes: Vec<u8>,
2909 pub id: u64,
2911}
2912
2913pub(crate) fn decode_static_image(
2914 bytes: &[u8],
2915 format: image::ImageFormat,
2916) -> Result<SmallVec<[Frame; 1]>> {
2917 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2918 .into_decoder()
2919 .context("creating image decoder")?;
2920 decode_static_image_from_decoder(decoder)
2921}
2922
2923pub(crate) fn decode_static_image_from_decoder(
2924 mut decoder: impl image::ImageDecoder,
2925) -> Result<SmallVec<[Frame; 1]>> {
2926 let orientation = decoder
2927 .orientation()
2928 .context("reading decoder's orientation")?;
2929 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2930 image.apply_orientation(orientation);
2931
2932 let mut data = image.into_rgba8();
2933 for pixel in data.chunks_exact_mut(4) {
2934 pixel.swap(0, 2);
2935 }
2936
2937 Ok(SmallVec::from_elem(Frame::new(data), 1))
2938}
2939
2940impl Hash for Image {
2941 fn hash<H: Hasher>(&self, state: &mut H) {
2942 state.write_u64(self.id);
2943 }
2944}
2945
2946impl Image {
2947 pub fn empty() -> Self {
2949 Self::from_bytes(ImageFormat::Png, Vec::new())
2950 }
2951
2952 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2954 Self {
2955 id: hash(&bytes),
2956 format,
2957 bytes,
2958 }
2959 }
2960
2961 pub fn id(&self) -> u64 {
2963 self.id
2964 }
2965
2966 pub fn use_render_image(
2968 self: Arc<Self>,
2969 window: &mut Window,
2970 cx: &mut App,
2971 ) -> Option<Arc<RenderImage>> {
2972 ImageSource::Image(self)
2973 .use_data(None, window, cx)
2974 .and_then(|result| result.ok())
2975 }
2976
2977 pub fn get_render_image(
2979 self: Arc<Self>,
2980 window: &mut Window,
2981 cx: &mut App,
2982 ) -> Option<Arc<RenderImage>> {
2983 ImageSource::Image(self)
2984 .get_data(None, window, cx)
2985 .and_then(|result| result.ok())
2986 }
2987
2988 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2990 ImageSource::Image(self).remove_asset(cx);
2991 }
2992
2993 #[cfg(any(test, feature = "test-support"))]
2996 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2997 ImageSource::Image(self.clone()).is_asset_cached(cx)
2998 }
2999
3000 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
3002 let frames = match self.format {
3003 ImageFormat::Gif => {
3004 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
3005 let mut frames = SmallVec::new();
3006
3007 for frame in decoder.into_frames() {
3008 match frame {
3009 Ok(mut frame) => {
3010 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
3012 pixel.swap(0, 2);
3013 }
3014 frames.push(frame);
3015 }
3016 Err(err) => {
3017 log::debug!("Skipping GIF frame due to decode error: {err}");
3018 }
3019 }
3020 }
3021
3022 if frames.is_empty() {
3023 anyhow::bail!("GIF could not be decoded: all frames failed");
3024 }
3025
3026 frames
3027 }
3028 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
3029 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
3030 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
3031 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
3032 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
3033 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
3034 ImageFormat::Svg => {
3035 return svg_renderer
3036 .render_single_frame(&self.bytes, 1.0)
3037 .map_err(Into::into);
3038 }
3039 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
3040 };
3041
3042 Ok(Arc::new(RenderImage::new(frames)))
3043 }
3044
3045 pub fn format(&self) -> ImageFormat {
3047 self.format
3048 }
3049
3050 pub fn bytes(&self) -> &[u8] {
3052 self.bytes.as_slice()
3053 }
3054}
3055
3056#[derive(Clone, Debug, Eq, PartialEq)]
3058pub struct ClipboardString {
3059 pub text: String,
3061 pub metadata: Option<String>,
3063}
3064
3065impl ClipboardString {
3066 pub fn new(text: String) -> Self {
3068 Self {
3069 text,
3070 metadata: None,
3071 }
3072 }
3073
3074 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
3077 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
3078 self
3079 }
3080
3081 pub fn text(&self) -> &String {
3083 &self.text
3084 }
3085
3086 pub fn into_text(self) -> String {
3088 self.text
3089 }
3090
3091 pub fn metadata_json<T>(&self) -> Option<T>
3093 where
3094 T: for<'a> Deserialize<'a>,
3095 {
3096 self.metadata
3097 .as_ref()
3098 .and_then(|m| serde_json::from_str(m).ok())
3099 }
3100
3101 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
3102 pub fn text_hash(text: &str) -> u64 {
3104 let mut hasher = SeaHasher::new();
3105 text.hash(&mut hasher);
3106 hasher.finish()
3107 }
3108}
3109
3110impl From<String> for ClipboardString {
3111 fn from(value: String) -> Self {
3112 Self {
3113 text: value,
3114 metadata: None,
3115 }
3116 }
3117}
3118
3119#[cfg(test)]
3120mod image_tests {
3121 use super::*;
3122 use std::sync::Arc;
3123
3124 #[test]
3125 fn test_image_to_image_data_applies_exif_orientation() {
3126 let image = Image::from_bytes(
3127 ImageFormat::Jpeg,
3128 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
3129 );
3130
3131 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3132
3133 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3134
3135 let bytes = render_image.as_bytes(0).unwrap();
3136 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3137 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3138 }
3139
3140 #[test]
3141 fn test_svg_image_to_image_data_converts_to_bgra() {
3142 let image = Image::from_bytes(
3143 ImageFormat::Svg,
3144 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3145<rect width="1" height="1" fill="#38BDF8"/>
3146</svg>"##
3147 .to_vec(),
3148 );
3149
3150 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3151 let bytes = render_image.as_bytes(0).unwrap();
3152
3153 for pixel in bytes.chunks_exact(4) {
3154 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3155 }
3156 }
3157}
3158
3159#[cfg(test)]
3160mod atlas_tests {
3161 use super::*;
3162
3163 const TILE_SIZE: Size<DevicePixels> = Size {
3164 width: DevicePixels(1),
3165 height: DevicePixels(1),
3166 };
3167
3168 #[derive(Default)]
3169 struct RecordingAtlasBackend {
3170 insert_calls: u32,
3171 fail_next_insert: bool,
3172 removed_tiles: Vec<AtlasTile>,
3173 }
3174
3175 impl AtlasBackend for RecordingAtlasBackend {
3176 fn insert(
3177 &mut self,
3178 kind: AtlasTextureKind,
3179 size: Size<DevicePixels>,
3180 _bytes: &[u8],
3181 ) -> Result<AtlasTile> {
3182 self.insert_calls += 1;
3183 if std::mem::take(&mut self.fail_next_insert) {
3184 anyhow::bail!("backend failed");
3185 }
3186 Ok(AtlasTile {
3187 texture_id: AtlasTextureId { index: 0, kind },
3188 tile_id: TileId(self.insert_calls),
3189 padding: 0,
3190 bounds: Bounds {
3191 origin: Point::default(),
3192 size,
3193 },
3194 })
3195 }
3196
3197 fn remove(&mut self, tile: AtlasTile) {
3198 self.removed_tiles.push(tile);
3199 }
3200 }
3201
3202 fn image_key(image_id: usize) -> AtlasKey {
3203 AtlasKey::Image(RenderImageParams {
3204 image_id: crate::ImageId(image_id),
3205 frame_index: 0,
3206 })
3207 }
3208
3209 fn build_tile() -> Result<Option<(Size<DevicePixels>, Cow<'static, [u8]>)>> {
3210 Ok(Some((TILE_SIZE, Cow::Borrowed(&[0, 0, 0, 255]))))
3211 }
3212
3213 #[test]
3214 fn only_successful_inserts_are_cached() -> Result<()> {
3215 let mut state = AtlasState::new(RecordingAtlasBackend::default());
3216 let key = image_key(1);
3217
3218 assert_eq!(
3219 state.get_or_insert_with(key.clone(), &mut || Ok(None))?,
3220 None
3221 );
3222 state
3223 .get_or_insert_with(key.clone(), &mut || anyhow::bail!("builder failed"))
3224 .expect_err("builder error should propagate");
3225 assert!(!state.contains(&key));
3226 assert_eq!(state.backend.insert_calls, 0);
3227
3228 state.backend.fail_next_insert = true;
3229 state
3230 .get_or_insert_with(key.clone(), &mut build_tile)
3231 .expect_err("backend error should propagate");
3232 assert!(!state.contains(&key));
3233 assert_eq!(state.backend.insert_calls, 1);
3234
3235 let tile = state
3236 .get_or_insert_with(key.clone(), &mut build_tile)?
3237 .context("builder should produce a tile")?;
3238 assert_eq!(tile.texture_id.kind, key.texture_kind());
3239 assert_eq!(
3240 state.get_or_insert_with(key.clone(), &mut || {
3241 anyhow::bail!("cache hit must not call the builder")
3242 })?,
3243 Some(tile)
3244 );
3245 assert!(state.contains(&key));
3246 assert_eq!(state.backend.insert_calls, 2);
3247 Ok(())
3248 }
3249
3250 #[test]
3251 fn remove_and_clear_invalidate_keys() -> Result<()> {
3252 let mut state = AtlasState::new(RecordingAtlasBackend::default());
3253 let key = image_key(1);
3254 let other_key = image_key(2);
3255 let tile = state
3256 .get_or_insert_with(key.clone(), &mut build_tile)?
3257 .context("builder should produce a tile")?;
3258 state
3259 .get_or_insert_with(other_key.clone(), &mut build_tile)?
3260 .context("builder should produce another tile")?;
3261
3262 state.remove(&key);
3263 state.remove(&key);
3264 assert!(!state.contains(&key));
3265 assert!(state.contains(&other_key));
3266 assert_eq!(state.backend.removed_tiles, vec![tile]);
3267
3268 let mut reset_calls = 0;
3269 state.clear(|_| reset_calls += 1);
3270 assert_eq!(reset_calls, 1);
3271 assert!(!state.contains(&other_key));
3272 assert_eq!(state.backend.removed_tiles, vec![tile]);
3273 Ok(())
3274 }
3275}
3276
3277#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3278mod tests {
3279 use super::*;
3280 use std::collections::HashSet;
3281
3282 #[test]
3283 fn test_window_button_layout_parse_standard() {
3284 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3285 assert_eq!(
3286 layout.left,
3287 [
3288 Some(WindowButton::Close),
3289 Some(WindowButton::Minimize),
3290 None
3291 ]
3292 );
3293 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3294 }
3295
3296 #[test]
3297 fn test_window_button_layout_parse_right_only() {
3298 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3299 assert_eq!(layout.left, [None, None, None]);
3300 assert_eq!(
3301 layout.right,
3302 [
3303 Some(WindowButton::Minimize),
3304 Some(WindowButton::Maximize),
3305 Some(WindowButton::Close)
3306 ]
3307 );
3308 }
3309
3310 #[test]
3311 fn test_window_button_layout_parse_left_only() {
3312 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3313 assert_eq!(
3314 layout.left,
3315 [
3316 Some(WindowButton::Close),
3317 Some(WindowButton::Minimize),
3318 Some(WindowButton::Maximize)
3319 ]
3320 );
3321 assert_eq!(layout.right, [None, None, None]);
3322 }
3323
3324 #[test]
3325 fn test_window_button_layout_parse_with_whitespace() {
3326 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3327 assert_eq!(
3328 layout.left,
3329 [
3330 Some(WindowButton::Close),
3331 Some(WindowButton::Minimize),
3332 None
3333 ]
3334 );
3335 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3336 }
3337
3338 #[test]
3339 fn test_window_button_layout_parse_empty() {
3340 let layout = WindowButtonLayout::parse("").unwrap();
3341 assert_eq!(layout.left, [None, None, None]);
3342 assert_eq!(layout.right, [None, None, None]);
3343 }
3344
3345 #[test]
3346 fn test_window_button_layout_parse_intentionally_empty() {
3347 let layout = WindowButtonLayout::parse(":").unwrap();
3348 assert_eq!(layout.left, [None, None, None]);
3349 assert_eq!(layout.right, [None, None, None]);
3350 }
3351
3352 #[test]
3353 fn test_window_button_layout_parse_invalid_buttons() {
3354 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3355 assert_eq!(
3356 layout.left,
3357 [
3358 Some(WindowButton::Close),
3359 Some(WindowButton::Minimize),
3360 None
3361 ]
3362 );
3363 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3364 }
3365
3366 #[test]
3367 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3368 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3369 assert_eq!(
3370 layout.right,
3371 [
3372 Some(WindowButton::Close),
3373 Some(WindowButton::Minimize),
3374 None
3375 ]
3376 );
3377 assert_eq!(layout.format(), ":close,minimize");
3378 }
3379
3380 #[test]
3381 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3382 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3383 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3384 assert_eq!(
3385 layout.right,
3386 [
3387 Some(WindowButton::Maximize),
3388 Some(WindowButton::Minimize),
3389 None
3390 ]
3391 );
3392
3393 let button_ids: Vec<_> = layout
3394 .left
3395 .iter()
3396 .chain(layout.right.iter())
3397 .flatten()
3398 .map(WindowButton::id)
3399 .collect();
3400 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3401 assert_eq!(unique_button_ids.len(), button_ids.len());
3402 assert_eq!(layout.format(), "close:maximize,minimize");
3403 }
3404
3405 #[test]
3406 fn test_window_button_layout_parse_gnome_style() {
3407 let layout = WindowButtonLayout::parse("close").unwrap();
3408 assert_eq!(layout.left, [None, None, None]);
3409 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3410 }
3411
3412 #[test]
3413 fn test_window_button_layout_parse_elementary_style() {
3414 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3415 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3416 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3417 }
3418
3419 #[test]
3420 fn test_window_button_layout_round_trip() {
3421 let cases = [
3422 "close:minimize,maximize",
3423 "minimize,maximize,close:",
3424 ":close",
3425 "close:",
3426 "close:maximize",
3427 ":",
3428 ];
3429
3430 for case in cases {
3431 let layout = WindowButtonLayout::parse(case).unwrap();
3432 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3433 }
3434 }
3435
3436 #[test]
3437 fn test_window_button_layout_linux_default() {
3438 let layout = WindowButtonLayout::linux_default();
3439 assert_eq!(layout.left, [None, None, None]);
3440 assert_eq!(
3441 layout.right,
3442 [
3443 Some(WindowButton::Minimize),
3444 Some(WindowButton::Maximize),
3445 Some(WindowButton::Close)
3446 ]
3447 );
3448
3449 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3450 assert_eq!(round_tripped, layout);
3451 }
3452
3453 #[test]
3454 fn test_window_button_layout_parse_all_invalid() {
3455 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3456 }
3457}