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 = core_video::image_buffer::CVImageBuffer;
36
37use crate::{
38 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
39 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Edges, ExternalDragPayload, Font,
40 FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap,
41 LineLayout, Pixels, PlatformGestures, PlatformInput, Point, Priority, RenderGlyphParams,
42 RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString,
43 Size, SvgRenderer, SystemWindowTab, Task, Window, WindowControlArea, hash, point, px, size,
44};
45#[cfg(any(target_os = "linux", target_os = "freebsd"))]
46use anyhow::bail;
47use anyhow::{Context as _, Result};
48use async_task::Runnable;
49use futures::channel::oneshot;
50#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
51use image::RgbaImage;
52use image::codecs::gif::GifDecoder;
53use image::{AnimationDecoder as _, DynamicImage, Frame};
54use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
55use scheduler::Instant;
56pub use scheduler::RunnableMeta;
57use schemars::JsonSchema;
58use seahash::SeaHasher;
59use serde::{Deserialize, Serialize};
60use smallvec::SmallVec;
61use std::borrow::Cow;
62use std::hash::{Hash, Hasher};
63use std::io::Cursor;
64use std::ops;
65use std::time::Duration;
66use std::{
67 ffi::OsString,
68 fmt::{self, Debug},
69 ops::Range,
70 path::{Path, PathBuf},
71 rc::Rc,
72 sync::Arc,
73};
74use strum::EnumIter;
75use uuid::Uuid;
76
77pub use app_menu::*;
78pub use keyboard::*;
79pub use keystroke::*;
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum WindowVisibility {
103 Visible,
106 Hidden,
109}
110
111impl WindowVisibility {
112 pub fn is_visible(self) -> bool {
114 self == Self::Visible
115 }
116}
117
118#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
119pub(crate) use test::*;
120
121#[cfg(any(test, feature = "test-support"))]
122pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
123
124#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
125pub use threaded_dispatcher::ThreadedDispatcher;
126
127#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
128pub use visual_test::VisualTestPlatform;
129
130pub struct ActivityGuard {
132 _release: gpui_util::Deferred<Box<dyn FnOnce() + Send>>,
133}
134
135impl ActivityGuard {
136 pub fn new(release: impl FnOnce() + Send + 'static) -> Self {
138 Self {
139 _release: gpui_util::defer(Box::new(release)),
140 }
141 }
142
143 pub fn noop() -> Self {
145 Self::new(|| {})
146 }
147}
148
149#[cfg(any(target_os = "linux", target_os = "freebsd"))]
153#[inline]
154pub fn guess_compositor() -> &'static str {
155 if std::env::var_os("ZED_HEADLESS").is_some() {
156 return "Headless";
157 }
158
159 #[cfg(feature = "wayland")]
160 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
161 #[cfg(not(feature = "wayland"))]
162 let wayland_display: Option<std::ffi::OsString> = None;
163
164 #[cfg(feature = "x11")]
165 let x11_display = std::env::var_os("DISPLAY");
166 #[cfg(not(feature = "x11"))]
167 let x11_display: Option<std::ffi::OsString> = None;
168
169 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
170 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
171
172 if use_wayland {
173 "Wayland"
174 } else if use_x11 {
175 "X11"
176 } else {
177 "Headless"
178 }
179}
180
181#[expect(missing_docs)]
182pub trait Platform: 'static {
183 fn background_executor(&self) -> BackgroundExecutor;
184 fn foreground_executor(&self) -> ForegroundExecutor;
185 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
186
187 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
188 fn quit(&self);
189 fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
190 fn activate(&self, ignoring_other_apps: bool);
191 fn hide(&self);
192 fn hide_other_apps(&self);
193 fn unhide_other_apps(&self);
194
195 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
196 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
197 fn active_window(&self) -> Option<AnyWindowHandle>;
198 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
199 None
200 }
201
202 fn is_screen_capture_supported(&self) -> bool {
203 false
204 }
205
206 fn screen_capture_sources(
207 &self,
208 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
209 let (sources_tx, sources_rx) = oneshot::channel();
210 sources_tx
211 .send(Err(anyhow::anyhow!(
212 "gpui was compiled without the screen-capture feature"
213 )))
214 .ok();
215 sources_rx
216 }
217
218 fn open_window(
219 &self,
220 handle: AnyWindowHandle,
221 options: WindowParams,
222 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
223
224 fn window_appearance(&self) -> WindowAppearance;
226
227 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
236
237 fn button_layout(&self) -> Option<WindowButtonLayout> {
239 None
240 }
241
242 fn open_url(&self, url: &str);
243 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
244 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
245
246 fn prompt_for_paths(
247 &self,
248 options: PathPromptOptions,
249 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
250 fn prompt_for_new_path(
251 &self,
252 directory: &Path,
253 suggested_name: Option<&str>,
254 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
255 fn can_select_mixed_files_and_dirs(&self) -> bool;
256 fn reveal_path(&self, path: &Path);
257 fn open_with_system(&self, path: &Path);
258
259 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
260 fn on_reopen(&self, callback: Box<dyn FnMut()>);
261 fn on_system_sleep(&self, callback: Box<dyn FnMut()>);
263 fn on_system_wake(&self, callback: Box<dyn FnMut()>);
265
266 fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
276
277 fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
282
283 fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
287 None
288 }
289
290 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
291 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
292 None
293 }
294
295 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
296 fn perform_dock_menu_action(&self, _action: usize) {}
297 fn add_recent_document(&self, _path: &Path) {}
298 fn update_jump_list(
299 &self,
300 _menus: Vec<MenuItem>,
301 _entries: Vec<SmallVec<[PathBuf; 2]>>,
302 ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
303 Task::ready(Vec::new())
304 }
305 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
306 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
307 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
308
309 fn thermal_state(&self) -> ThermalState;
310 fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
311 fn prevent_idle_sleep(&self, reason: &str) -> Task<Result<ActivityGuard>>;
312
313 fn set_app_identity(&self, identifier: &str, name: &str) {
320 _ = (identifier, name);
321 }
322
323 fn show_system_notification(&self, notification: SystemNotification) {
330 _ = notification;
331 }
332
333 fn dismiss_system_notification(&self, tag: &str) {
338 _ = tag;
339 }
340
341 fn on_system_notification_response(
347 &self,
348 callback: Box<dyn FnMut(SystemNotificationResponse)>,
349 ) {
350 _ = callback;
351 }
352
353 fn compositor_name(&self) -> &'static str {
354 ""
355 }
356 fn app_path(&self) -> Result<PathBuf>;
357 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
358
359 fn set_cursor_style(&self, style: CursorStyle);
360
361 fn hide_cursor_until_mouse_moves(&self);
364
365 fn is_cursor_visible(&self) -> bool;
367
368 fn should_auto_hide_scrollbars(&self) -> bool;
369
370 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
371 fn write_to_clipboard(&self, item: ClipboardItem);
372
373 fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
381 Task::ready(Ok(self.read_from_clipboard()))
382 }
383
384 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
385 fn read_from_primary(&self) -> Option<ClipboardItem>;
386 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
387 fn write_to_primary(&self, item: ClipboardItem);
388
389 #[cfg(target_os = "macos")]
390 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
391 #[cfg(target_os = "macos")]
392 fn write_to_find_pasteboard(&self, item: ClipboardItem);
393
394 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
395 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
396 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
397
398 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
399 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
400 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
401}
402
403pub trait PlatformDisplay: Debug {
405 fn id(&self) -> DisplayId;
407
408 fn uuid(&self) -> Result<Uuid>;
411
412 fn bounds(&self) -> Bounds<Pixels>;
414
415 fn visible_bounds(&self) -> Bounds<Pixels> {
419 self.bounds()
420 }
421
422 fn default_bounds(&self) -> Bounds<Pixels> {
424 let bounds = self.bounds();
425 let center = bounds.center();
426 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
427
428 let offset = clipped_window_size / 2.0;
429 let origin = point(center.x - offset.width, center.y - offset.height);
430 Bounds::new(origin, clipped_window_size)
431 }
432}
433
434#[derive(Clone, Debug, PartialEq, Eq)]
437pub struct SystemNotification {
438 pub tag: SharedString,
442 pub title: SharedString,
444 pub body: SharedString,
446 pub actions: Vec<SystemNotificationAction>,
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Hash)]
453pub struct SystemNotificationAction {
454 pub id: SharedString,
457 pub label: SharedString,
459}
460
461#[derive(Clone, Debug, PartialEq, Eq)]
463pub struct SystemNotificationResponse {
464 pub tag: SharedString,
466 pub action_id: Option<SharedString>,
469}
470
471#[derive(Debug, Clone, Copy, PartialEq, Eq)]
473pub enum ThermalState {
474 Nominal,
476 Fair,
478 Serious,
480 Critical,
482}
483
484#[derive(Clone)]
486pub struct SourceMetadata {
487 pub id: u64,
489 pub label: Option<SharedString>,
491 pub is_main: Option<bool>,
493 pub resolution: Size<DevicePixels>,
495}
496
497pub trait ScreenCaptureSource {
499 fn metadata(&self) -> Result<SourceMetadata>;
501
502 fn stream(
505 &self,
506 foreground_executor: &ForegroundExecutor,
507 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
508 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
509}
510
511pub trait ScreenCaptureStream {
513 fn metadata(&self) -> Result<SourceMetadata>;
515}
516
517pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
519
520#[derive(PartialEq, Eq, Hash, Copy, Clone)]
522pub struct DisplayId(pub(crate) u64);
523
524impl DisplayId {
525 pub fn new(id: u64) -> Self {
527 Self(id)
528 }
529}
530
531impl From<u64> for DisplayId {
532 fn from(id: u64) -> Self {
533 Self(id)
534 }
535}
536
537impl From<DisplayId> for u64 {
538 fn from(id: DisplayId) -> Self {
539 id.0
540 }
541}
542
543impl Debug for DisplayId {
544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545 write!(f, "DisplayId({})", self.0)
546 }
547}
548
549#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum ResizeEdge {
552 Top,
554 TopRight,
556 Right,
558 BottomRight,
560 Bottom,
562 BottomLeft,
564 Left,
566 TopLeft,
568}
569
570#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
572pub enum WindowDecorations {
573 #[default]
574 Server,
576 Client,
578}
579
580#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
582pub enum Decorations {
583 #[default]
585 Server,
586 Client {
588 tiling: Tiling,
590 },
591}
592
593#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
595pub struct WindowControls {
596 pub fullscreen: bool,
598 pub maximize: bool,
600 pub minimize: bool,
602 pub window_menu: bool,
604}
605
606impl Default for WindowControls {
607 fn default() -> Self {
608 Self {
610 fullscreen: true,
611 maximize: true,
612 minimize: true,
613 window_menu: true,
614 }
615 }
616}
617
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
620pub enum WindowButton {
621 Minimize,
623 Maximize,
625 Close,
627}
628
629impl WindowButton {
630 pub fn id(&self) -> &'static str {
632 match self {
633 WindowButton::Minimize => "minimize",
634 WindowButton::Maximize => "maximize",
635 WindowButton::Close => "close",
636 }
637 }
638
639 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
640 fn index(&self) -> usize {
641 match self {
642 WindowButton::Minimize => 0,
643 WindowButton::Maximize => 1,
644 WindowButton::Close => 2,
645 }
646 }
647}
648
649pub const MAX_BUTTONS_PER_SIDE: usize = 3;
651
652#[derive(Debug, Clone, Copy, PartialEq, Eq)]
657pub struct WindowButtonLayout {
658 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
660 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
662}
663
664#[cfg(any(target_os = "linux", target_os = "freebsd"))]
665impl WindowButtonLayout {
666 pub fn linux_default() -> Self {
668 Self {
669 left: [None; MAX_BUTTONS_PER_SIDE],
670 right: [
671 Some(WindowButton::Minimize),
672 Some(WindowButton::Maximize),
673 Some(WindowButton::Close),
674 ],
675 }
676 }
677
678 pub fn parse(layout_string: &str) -> Result<Self> {
680 fn parse_side(
681 s: &str,
682 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
683 unrecognized: &mut Vec<String>,
684 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
685 let mut result = [None; MAX_BUTTONS_PER_SIDE];
686 let mut i = 0;
687 for name in s.split(',') {
688 let trimmed = name.trim();
689 if trimmed.is_empty() {
690 continue;
691 }
692 let button = match trimmed {
693 "minimize" => Some(WindowButton::Minimize),
694 "maximize" => Some(WindowButton::Maximize),
695 "close" => Some(WindowButton::Close),
696 other => {
697 unrecognized.push(other.to_string());
698 None
699 }
700 };
701 if let Some(button) = button {
702 if seen_buttons[button.index()] {
703 continue;
704 }
705 if let Some(slot) = result.get_mut(i) {
706 *slot = Some(button);
707 seen_buttons[button.index()] = true;
708 i += 1;
709 }
710 }
711 }
712 result
713 }
714
715 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
716 let mut unrecognized = Vec::new();
717 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
718 let layout = Self {
719 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
720 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
721 };
722
723 if !unrecognized.is_empty()
724 && layout.left.iter().all(Option::is_none)
725 && layout.right.iter().all(Option::is_none)
726 {
727 bail!(
728 "button layout string {:?} contains no valid buttons (unrecognized: {})",
729 layout_string,
730 unrecognized.join(", ")
731 );
732 }
733
734 Ok(layout)
735 }
736
737 #[cfg(test)]
739 pub fn format(&self) -> String {
740 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
741 buttons
742 .iter()
743 .flatten()
744 .map(|button| match button {
745 WindowButton::Minimize => "minimize",
746 WindowButton::Maximize => "maximize",
747 WindowButton::Close => "close",
748 })
749 .collect::<Vec<_>>()
750 .join(",")
751 }
752
753 format!("{}:{}", format_side(&self.left), format_side(&self.right))
754 }
755}
756
757#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
759pub struct Tiling {
760 pub top: bool,
762 pub left: bool,
764 pub right: bool,
766 pub bottom: bool,
768}
769
770impl Tiling {
771 pub fn tiled() -> Self {
773 Self {
774 top: true,
775 left: true,
776 right: true,
777 bottom: true,
778 }
779 }
780
781 pub fn is_tiled(&self) -> bool {
783 self.top || self.left || self.right || self.bottom
784 }
785}
786
787pub struct A11yCallbacks {
789 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
791 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
793 pub deactivation: Box<dyn Fn() + Send + 'static>,
795}
796
797#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
798#[expect(missing_docs)]
799pub struct RequestFrameOptions {
800 pub require_presentation: bool,
802 pub force_render: bool,
804}
805
806#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
819pub enum AppLifecyclePhase {
820 Active,
822 Inactive,
824 Background,
827 Foreground,
829}
830
831#[derive(Debug, Clone, Default, PartialEq)]
838pub struct WindowInsets {
839 pub safe_area: Edges<Pixels>,
844 pub ime: Edges<Pixels>,
848}
849
850impl WindowInsets {
851 pub fn effective(&self) -> Edges<Pixels> {
853 Edges {
854 top: self.safe_area.top.max(self.ime.top),
855 right: self.safe_area.right.max(self.ime.right),
856 bottom: self.safe_area.bottom.max(self.ime.bottom),
857 left: self.safe_area.left.max(self.ime.left),
858 }
859 }
860}
861
862#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
864pub enum TextInputStateChange {
865 FocusGained,
867 FocusLost,
869 SelectionChanged,
871 ContentChanged,
873}
874
875#[expect(missing_docs)]
876pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
877 fn bounds(&self) -> Bounds<Pixels>;
878 fn is_maximized(&self) -> bool;
879 fn window_bounds(&self) -> WindowBounds;
880 fn content_size(&self) -> Size<Pixels>;
881 fn visual_viewport_bounds(&self) -> Bounds<Pixels> {
887 Bounds::new(Point::default(), self.content_size())
888 }
889 fn on_visual_viewport_changed(&self, _callback: Box<dyn FnMut()>) {}
894 fn prepare_frame(&self) -> bool {
899 false
900 }
901 fn resize(&mut self, size: Size<Pixels>);
902 fn scale_factor(&self) -> f32;
903 fn appearance(&self) -> WindowAppearance;
904 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
905 fn mouse_position(&self) -> Point<Pixels>;
906 fn modifiers(&self) -> Modifiers;
907 fn capslock(&self) -> Capslock;
908 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
909 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
910 fn set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
915 fn prompt(
916 &self,
917 level: PromptLevel,
918 msg: &str,
919 detail: Option<&str>,
920 answers: &[PromptButton],
921 ) -> Option<oneshot::Receiver<usize>>;
922 fn activate(&self);
923 fn request_attention(&self) {}
925 fn is_active(&self) -> bool;
926 fn visibility(&self) -> WindowVisibility;
929 fn is_hovered(&self) -> bool;
930 fn background_appearance(&self) -> WindowBackgroundAppearance;
931 fn set_title(&mut self, title: &str);
932 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
933 fn minimize(&self);
934 fn zoom(&self);
935 fn toggle_fullscreen(&self);
936 fn is_fullscreen(&self) -> bool;
937 fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
938 None
939 }
940 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
941 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
942 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
943 fn on_visibility_change(&self, callback: Box<dyn FnMut(WindowVisibility)>);
947 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
948 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
949 fn on_moved(&self, callback: Box<dyn FnMut()>);
950 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
951 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
952 fn on_close(&self, callback: Box<dyn FnOnce()>);
953 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
954 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
955 fn draw(&self, scene: &Scene);
956 fn schedule_frame(&self) {}
957 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
958 fn is_subpixel_rendering_supported(&self) -> bool;
959
960 fn get_title(&self) -> String {
962 String::new()
963 }
964 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
965 None
966 }
967 fn tab_bar_visible(&self) -> bool {
968 false
969 }
970 fn set_edited(&mut self, _edited: bool) {}
971 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
972 fn toggle_simple_fullscreen(&self) {}
973 fn is_simple_fullscreen(&self) -> bool {
974 false
975 }
976 #[cfg(target_os = "macos")]
977 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
978 fn show_character_palette(&self) {}
979 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
980 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
981 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
982 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
983 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
984 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
985 fn merge_all_windows(&self) {}
986 fn move_tab_to_new_window(&self) {}
987 fn toggle_window_tab_overview(&self) {}
988 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
989
990 fn native_window_state(&self) -> Option<Vec<u8>> {
991 None
992 }
993 fn restore_native_window_state(&self, _state: &[u8]) {}
994
995 #[cfg(target_os = "windows")]
996 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
997
998 fn inner_window_bounds(&self) -> WindowBounds {
1000 self.window_bounds()
1001 }
1002 fn request_decorations(&self, _decorations: WindowDecorations) {}
1003 fn show_window_menu(&self, _position: Point<Pixels>) {}
1004 fn start_window_move(&self) {}
1005 fn can_start_external_drag(&self) -> bool {
1006 false
1007 }
1008 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
1009 false
1010 }
1011 fn start_window_resize(&self, _edge: ResizeEdge) {}
1012 fn set_exclusive_zone(&self, _zone: Pixels) {}
1013 #[cfg(all(target_os = "linux", feature = "wayland"))]
1014 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1015 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1016 fn window_decorations(&self) -> Decorations {
1017 Decorations::Server
1018 }
1019 fn set_app_id(&mut self, _app_id: &str) {}
1020 fn map_window(&mut self) -> anyhow::Result<()> {
1021 Ok(())
1022 }
1023 fn window_controls(&self) -> WindowControls {
1024 WindowControls::default()
1025 }
1026 fn set_client_inset(&self, _inset: Pixels) {}
1027 fn gpu_specs(&self) -> Option<GpuSpecs>;
1028
1029 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1030
1031 fn insets(&self) -> WindowInsets {
1036 WindowInsets::default()
1037 }
1038
1039 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
1045
1046 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
1049
1050 fn set_back_enabled(&self, _enabled: bool) {}
1053
1054 fn show_soft_keyboard(&self) {}
1056
1057 fn hide_soft_keyboard(&self) {}
1059
1060 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1062
1063 fn play_system_bell(&self) {}
1064
1065 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1067
1068 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1070
1071 fn a11y_update_window_bounds(&self) {}
1073
1074 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1075 fn as_test(&mut self) -> Option<&mut TestWindow> {
1076 None
1077 }
1078
1079 #[cfg(any(test, feature = "test-support"))]
1083 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1084 anyhow::bail!("render_to_image not implemented for this platform")
1085 }
1086}
1087
1088#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1090pub trait PlatformHeadlessRenderer {
1091 fn render_scene_to_image(
1093 &mut self,
1094 scene: &Scene,
1095 size: Size<DevicePixels>,
1096 ) -> Result<RgbaImage>;
1097
1098 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1104
1105 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1107}
1108
1109#[doc(hidden)]
1112pub type RunnableVariant = Runnable<RunnableMeta>;
1113
1114#[doc(hidden)]
1115pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1116
1117#[doc(hidden)]
1118pub enum TasksIncluded {
1119 OnlyCompleted,
1120 CompletedAndRunning,
1121}
1122
1123#[doc(hidden)]
1126pub trait PlatformDispatcher: Send + Sync {
1127 fn is_main_thread(&self) -> bool;
1128 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1129 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1130 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1131
1132 fn dispatch_on_main_thread_when_idle(
1133 &self,
1134 runnable: RunnableVariant,
1135 timeout: Option<Duration>,
1136 ) {
1137 let _ = timeout;
1138 self.dispatch_on_main_thread(runnable, Priority::Low);
1139 }
1140
1141 fn idle_time_remaining(&self) -> Option<Duration> {
1142 None
1143 }
1144
1145 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1146
1147 fn now(&self) -> Instant {
1148 Instant::now()
1149 }
1150
1151 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1152 gpui_util::defer(Box::new(|| {}))
1153 }
1154
1155 fn prevent_app_nap(&self, _reason: &str) -> ActivityGuard {
1156 ActivityGuard::noop()
1157 }
1158
1159 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1160 fn as_test(&self) -> Option<&TestDispatcher> {
1161 None
1162 }
1163
1164 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1167 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1168 None
1169 }
1170}
1171
1172#[expect(missing_docs)]
1173pub trait PlatformTextSystem: Send + Sync {
1174 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1175 fn all_font_names(&self) -> Vec<String>;
1177 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1179 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1181 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1183 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1185 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1187 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1189 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1191 fn rasterize_glyph(
1193 &self,
1194 params: &RenderGlyphParams,
1195 raster_bounds: Bounds<DevicePixels>,
1196 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1197 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1199 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1201 -> TextRenderingMode;
1202 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1204 0
1205 }
1206}
1207
1208#[expect(missing_docs)]
1209pub struct NoopTextSystem;
1210
1211#[expect(missing_docs)]
1212impl NoopTextSystem {
1213 #[allow(dead_code)]
1214 pub fn new() -> Self {
1215 Self
1216 }
1217}
1218
1219impl PlatformTextSystem for NoopTextSystem {
1220 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1221 Ok(())
1222 }
1223
1224 fn all_font_names(&self) -> Vec<String> {
1225 Vec::new()
1226 }
1227
1228 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1229 Ok(FontId(1))
1230 }
1231
1232 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1233 FontMetrics {
1234 units_per_em: 1000,
1235 ascent: 1025.0,
1236 descent: -275.0,
1237 line_gap: 0.0,
1238 underline_position: -95.0,
1239 underline_thickness: 60.0,
1240 cap_height: 698.0,
1241 x_height: 516.0,
1242 bounding_box: Bounds {
1243 origin: Point {
1244 x: -260.0,
1245 y: -245.0,
1246 },
1247 size: Size {
1248 width: 1501.0,
1249 height: 1364.0,
1250 },
1251 },
1252 }
1253 }
1254
1255 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1256 Ok(Bounds {
1257 origin: Point { x: 54.0, y: 0.0 },
1258 size: size(392.0, 528.0),
1259 })
1260 }
1261
1262 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1263 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1264 }
1265
1266 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1267 Some(GlyphId(ch.len_utf16() as u32))
1268 }
1269
1270 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1271 Ok(Default::default())
1272 }
1273
1274 fn rasterize_glyph(
1275 &self,
1276 _params: &RenderGlyphParams,
1277 raster_bounds: Bounds<DevicePixels>,
1278 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1279 Ok((raster_bounds.size, Vec::new()))
1280 }
1281
1282 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1283 let mut position = px(0.);
1284 let metrics = self.font_metrics(FontId(0));
1285 let em_width = font_size
1286 * self
1287 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1288 .unwrap()
1289 .width
1290 / metrics.units_per_em as f32;
1291 let mut glyphs = Vec::new();
1292 for (ix, c) in text.char_indices() {
1293 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1294 glyphs.push(ShapedGlyph {
1295 id: glyph,
1296 position: point(position, px(0.)),
1297 index: ix,
1298 is_emoji: glyph.0 == 2,
1299 });
1300 if glyph.0 == 2 {
1301 position += em_width * 2.0;
1302 } else {
1303 position += em_width;
1304 }
1305 } else {
1306 position += em_width
1307 }
1308 }
1309 let mut runs = Vec::default();
1310 if !glyphs.is_empty() {
1311 runs.push(ShapedRun {
1312 font_id: FontId(0),
1313 glyphs,
1314 });
1315 } else {
1316 position = px(0.);
1317 }
1318
1319 LineLayout {
1320 font_size,
1321 width: position,
1322 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1323 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1324 runs,
1325 len: text.len(),
1326 }
1327 }
1328
1329 fn recommended_rendering_mode(
1330 &self,
1331 _font_id: FontId,
1332 _font_size: Pixels,
1333 ) -> TextRenderingMode {
1334 TextRenderingMode::Grayscale
1335 }
1336}
1337
1338#[allow(dead_code)]
1343pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1344 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1345 [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], ];
1359
1360 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1361 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1362
1363 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1364 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1365
1366 [
1367 ratios[0] * NORM13,
1368 ratios[1] * NORM24,
1369 ratios[2] * NORM13,
1370 ratios[3] * NORM24,
1371 ]
1372}
1373
1374#[derive(PartialEq, Eq, Hash, Clone)]
1375#[expect(missing_docs)]
1376pub enum AtlasKey {
1377 Glyph(RenderGlyphParams),
1378 Svg(RenderSvgParams),
1379 Image(RenderImageParams),
1380}
1381
1382impl AtlasKey {
1383 #[cfg_attr(
1384 all(
1385 any(target_os = "linux", target_os = "freebsd"),
1386 not(any(feature = "x11", feature = "wayland"))
1387 ),
1388 allow(dead_code)
1389 )]
1390 pub fn texture_kind(&self) -> AtlasTextureKind {
1392 match self {
1393 AtlasKey::Glyph(params) => {
1394 if params.is_emoji {
1395 AtlasTextureKind::Polychrome
1396 } else if params.subpixel_rendering {
1397 AtlasTextureKind::Subpixel
1398 } else {
1399 AtlasTextureKind::Monochrome
1400 }
1401 }
1402 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1403 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1404 }
1405 }
1406}
1407
1408impl From<RenderGlyphParams> for AtlasKey {
1409 fn from(params: RenderGlyphParams) -> Self {
1410 Self::Glyph(params)
1411 }
1412}
1413
1414impl From<RenderSvgParams> for AtlasKey {
1415 fn from(params: RenderSvgParams) -> Self {
1416 Self::Svg(params)
1417 }
1418}
1419
1420impl From<RenderImageParams> for AtlasKey {
1421 fn from(params: RenderImageParams) -> Self {
1422 Self::Image(params)
1423 }
1424}
1425
1426#[expect(missing_docs)]
1427pub trait PlatformAtlas {
1428 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 struct AtlasTextureList<T> {
1443 pub textures: Vec<Option<T>>,
1444 pub free_list: Vec<usize>,
1445}
1446
1447impl<T> Default for AtlasTextureList<T> {
1448 fn default() -> Self {
1449 Self {
1450 textures: Vec::default(),
1451 free_list: Vec::default(),
1452 }
1453 }
1454}
1455
1456impl<T> ops::Index<usize> for AtlasTextureList<T> {
1457 type Output = Option<T>;
1458
1459 fn index(&self, index: usize) -> &Self::Output {
1460 &self.textures[index]
1461 }
1462}
1463
1464impl<T> AtlasTextureList<T> {
1465 #[allow(unused)]
1466 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1467 self.free_list.clear();
1468 self.textures.drain(..)
1469 }
1470
1471 #[allow(dead_code)]
1472 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1473 self.textures.iter_mut().flatten()
1474 }
1475}
1476
1477#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1478#[repr(C)]
1479#[expect(missing_docs)]
1480pub struct AtlasTile {
1481 pub texture_id: AtlasTextureId,
1483 pub tile_id: TileId,
1485 pub padding: u32,
1487 pub bounds: Bounds<DevicePixels>,
1489}
1490
1491#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1492#[repr(C)]
1493#[expect(missing_docs)]
1494pub struct AtlasTextureId {
1495 pub index: u32,
1498 pub kind: AtlasTextureKind,
1500}
1501
1502#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1503#[repr(C)]
1504#[cfg_attr(
1505 all(
1506 any(target_os = "linux", target_os = "freebsd"),
1507 not(any(feature = "x11", feature = "wayland"))
1508 ),
1509 allow(dead_code)
1510)]
1511#[expect(missing_docs)]
1512pub enum AtlasTextureKind {
1513 Monochrome = 0,
1514 Polychrome = 1,
1515 Subpixel = 2,
1516}
1517
1518#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1519#[repr(C)]
1520#[expect(missing_docs)]
1521pub struct TileId(pub u32);
1522
1523impl From<etagere::AllocId> for TileId {
1524 fn from(id: etagere::AllocId) -> Self {
1525 Self(id.serialize())
1526 }
1527}
1528
1529impl From<TileId> for etagere::AllocId {
1530 fn from(id: TileId) -> Self {
1531 Self::deserialize(id.0)
1532 }
1533}
1534
1535#[expect(missing_docs)]
1536pub struct PlatformInputHandler {
1537 cx: AsyncWindowContext,
1538 handler: Box<dyn InputHandler>,
1539}
1540
1541#[expect(missing_docs)]
1542#[cfg_attr(
1543 all(
1544 any(target_os = "linux", target_os = "freebsd"),
1545 not(any(feature = "x11", feature = "wayland"))
1546 ),
1547 allow(dead_code)
1548)]
1549impl PlatformInputHandler {
1550 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1551 Self { cx, handler }
1552 }
1553
1554 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1555 self.cx
1556 .update(|window, cx| {
1557 self.handler
1558 .selected_text_range(ignore_disabled_input, window, cx)
1559 })
1560 .ok()
1561 .flatten()
1562 }
1563
1564 #[cfg_attr(target_os = "windows", allow(dead_code))]
1565 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1566 self.cx
1567 .update(|window, cx| self.handler.marked_text_range(window, cx))
1568 .ok()
1569 .flatten()
1570 }
1571
1572 #[cfg_attr(
1573 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1574 allow(dead_code)
1575 )]
1576 pub fn text_for_range(
1577 &mut self,
1578 range_utf16: Range<usize>,
1579 adjusted: &mut Option<Range<usize>>,
1580 ) -> Option<String> {
1581 self.cx
1582 .update(|window, cx| {
1583 self.handler
1584 .text_for_range(range_utf16, adjusted, window, cx)
1585 })
1586 .ok()
1587 .flatten()
1588 }
1589
1590 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1591 self.cx
1592 .update(|window, cx| {
1593 self.handler
1594 .replace_text_in_range(replacement_range, text, window, cx);
1595 })
1596 .ok();
1597 }
1598
1599 pub fn replace_and_mark_text_in_range(
1600 &mut self,
1601 range_utf16: Option<Range<usize>>,
1602 new_text: &str,
1603 new_selected_range: Option<Range<usize>>,
1604 ) {
1605 self.cx
1606 .update(|window, cx| {
1607 self.handler.replace_and_mark_text_in_range(
1608 range_utf16,
1609 new_text,
1610 new_selected_range,
1611 window,
1612 cx,
1613 )
1614 })
1615 .ok();
1616 }
1617
1618 #[cfg_attr(target_os = "windows", allow(dead_code))]
1619 pub fn unmark_text(&mut self) {
1620 self.cx
1621 .update(|window, cx| self.handler.unmark_text(window, cx))
1622 .ok();
1623 }
1624
1625 pub fn paste(&mut self, item: ClipboardItem) {
1626 self.cx
1627 .update(|window, cx| self.handler.paste(item, window, cx))
1628 .ok();
1629 }
1630
1631 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1632 self.cx
1633 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1634 .ok()
1635 .flatten()
1636 }
1637
1638 #[allow(dead_code)]
1639 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1640 self.handler.apple_press_and_hold_enabled()
1641 }
1642
1643 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1644 self.handler.replace_text_in_range(None, input, window, cx);
1645 }
1646
1647 pub fn compute_ime_candidate_bounds(
1648 marked_range: Option<Range<usize>>,
1649 selection: &UTF16Selection,
1650 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1651 ) -> Option<Bounds<Pixels>> {
1652 if let Some(marked_range) = marked_range {
1653 let mut line_start = marked_range.start;
1655
1656 let caret = selection.range.end;
1660 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1661 for i in (marked_range.start..caret).rev() {
1662 if let Some(b) = bounds_for_range(i..i) {
1663 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1664 line_start = i + 1;
1665 break;
1666 }
1667 }
1668 }
1669 }
1670 bounds_for_range(line_start..line_start)
1671 } else {
1672 let offset = if selection.reversed {
1674 selection.range.start
1675 } else {
1676 selection.range.end
1677 };
1678 bounds_for_range(offset..offset)
1679 }
1680 }
1681
1682 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1683 let marked_range = self.handler.marked_text_range(window, cx);
1684 let selection = self.handler.selected_text_range(true, window, cx)?;
1685 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1686 self.handler.bounds_for_range(range, window, cx)
1687 })
1688 }
1689
1690 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1691 let marked_range = self.marked_text_range();
1692 let selection = self.selected_text_range(true)?;
1693 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1694 self.bounds_for_range(range)
1695 })
1696 }
1697
1698 #[allow(unused)]
1699 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1700 self.cx
1701 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1702 .ok()
1703 .flatten()
1704 }
1705
1706 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1708 self.cx
1709 .update(|window, cx| {
1710 self.handler
1711 .set_selected_text_range(range_utf16, window, cx)
1712 })
1713 .ok();
1714 }
1715
1716 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1718 self.cx
1719 .update(|window, cx| self.handler.element_bounds(window, cx))
1720 .ok()
1721 .flatten()
1722 }
1723
1724 pub fn text_length_utf16(&mut self) -> Option<usize> {
1726 self.cx
1727 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1728 .ok()
1729 .flatten()
1730 }
1731
1732 #[allow(dead_code)]
1733 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1734 self.handler.accepts_text_input(window, cx)
1735 }
1736
1737 #[allow(dead_code)]
1738 pub fn query_accepts_text_input(&mut self) -> bool {
1739 self.cx
1740 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1741 .unwrap_or(true)
1742 }
1743
1744 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1750 self.cx
1751 .update(|window, cx| {
1752 !window.has_pending_keystrokes()
1754 && self.handler.prefers_ime_for_printable_keys(window, cx)
1755 })
1756 .unwrap_or(false)
1757 }
1758
1759 pub fn text_input_configuration(
1761 &mut self,
1762 window: &mut Window,
1763 cx: &mut App,
1764 ) -> TextInputConfiguration {
1765 self.handler.text_input_configuration(window, cx)
1766 }
1767
1768 pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1770 self.cx
1771 .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1772 .ok()
1773 .flatten()
1774 }
1775}
1776
1777#[derive(Debug)]
1780pub struct UTF16Selection {
1781 pub range: Range<usize>,
1784 pub reversed: bool,
1787}
1788
1789pub trait InputHandler: 'static {
1794 fn selected_text_range(
1799 &mut self,
1800 ignore_disabled_input: bool,
1801 window: &mut Window,
1802 cx: &mut App,
1803 ) -> Option<UTF16Selection>;
1804
1805 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1810
1811 fn text_for_range(
1816 &mut self,
1817 range_utf16: Range<usize>,
1818 adjusted_range: &mut Option<Range<usize>>,
1819 window: &mut Window,
1820 cx: &mut App,
1821 ) -> Option<String>;
1822
1823 fn replace_text_in_range(
1828 &mut self,
1829 replacement_range: Option<Range<usize>>,
1830 text: &str,
1831 window: &mut Window,
1832 cx: &mut App,
1833 );
1834
1835 fn replace_and_mark_text_in_range(
1842 &mut self,
1843 range_utf16: Option<Range<usize>>,
1844 new_text: &str,
1845 new_selected_range: Option<Range<usize>>,
1846 window: &mut Window,
1847 cx: &mut App,
1848 );
1849
1850 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1853
1854 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1861 if let Some(text) = item.text() {
1862 self.replace_text_in_range(None, &text, window, cx);
1863 }
1864 }
1865
1866 fn bounds_for_range(
1871 &mut self,
1872 range_utf16: Range<usize>,
1873 window: &mut Window,
1874 cx: &mut App,
1875 ) -> Option<Bounds<Pixels>>;
1876
1877 fn character_index_for_point(
1881 &mut self,
1882 point: Point<Pixels>,
1883 window: &mut Window,
1884 cx: &mut App,
1885 ) -> Option<usize>;
1886
1887 fn set_selected_text_range(
1897 &mut self,
1898 _range_utf16: Range<usize>,
1899 _window: &mut Window,
1900 _cx: &mut App,
1901 ) {
1902 }
1903
1904 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1911 None
1912 }
1913
1914 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1916 None
1917 }
1918
1919 #[allow(dead_code)]
1924 fn apple_press_and_hold_enabled(&mut self) -> bool {
1925 true
1926 }
1927
1928 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1930 true
1931 }
1932
1933 fn text_input_editable_range(
1944 &mut self,
1945 _window: &mut Window,
1946 _cx: &mut App,
1947 ) -> Option<Range<usize>> {
1948 None
1949 }
1950
1951 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1960 false
1961 }
1962
1963 fn text_input_configuration(
1969 &mut self,
1970 _window: &mut Window,
1971 _cx: &mut App,
1972 ) -> TextInputConfiguration {
1973 TextInputConfiguration::default()
1974 }
1975}
1976
1977#[derive(Clone, Debug, Default, PartialEq, Eq)]
1987pub struct TextInputConfiguration {
1988 pub autocorrect: bool,
1990 pub autocapitalize: Autocapitalize,
1992 pub suggestions: bool,
1994 pub input_action: TextInputAction,
1996}
1997
1998#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2000pub enum Autocapitalize {
2001 #[default]
2003 None,
2004 Words,
2006 Sentences,
2008 Characters,
2010}
2011
2012#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2022pub enum TextInputAction {
2023 #[default]
2025 Unspecified,
2026 Enter,
2028 Done,
2030 Go,
2032 Next,
2034 Previous,
2036 Search,
2038 Send,
2040}
2041
2042#[derive(Debug)]
2044pub struct WindowOptions {
2045 pub window_bounds: Option<WindowBounds>,
2049
2050 pub titlebar: Option<TitlebarOptions>,
2052
2053 pub focus: bool,
2055
2056 pub show: bool,
2058
2059 pub kind: WindowKind,
2061
2062 pub is_movable: bool,
2066
2067 pub app_owns_titlebar_drag: bool,
2079
2080 pub inactive_frame_interval: Option<Duration>,
2084
2085 pub is_resizable: bool,
2087
2088 pub is_minimizable: bool,
2090
2091 pub display_id: Option<DisplayId>,
2094
2095 pub window_background: WindowBackgroundAppearance,
2097
2098 pub app_id: Option<String>,
2100
2101 pub window_min_size: Option<Size<Pixels>>,
2103
2104 pub window_decorations: Option<WindowDecorations>,
2107
2108 pub icon: Option<Arc<image::RgbaImage>>,
2110
2111 pub tabbing_identifier: Option<String>,
2113}
2114
2115#[derive(Debug)]
2117#[cfg_attr(
2118 all(
2119 any(target_os = "linux", target_os = "freebsd"),
2120 not(any(feature = "x11", feature = "wayland"))
2121 ),
2122 allow(dead_code)
2123)]
2124#[allow(missing_docs)]
2125pub struct WindowParams {
2126 pub bounds: Bounds<Pixels>,
2127
2128 #[cfg_attr(feature = "wayland", allow(dead_code))]
2130 pub titlebar: Option<TitlebarOptions>,
2131
2132 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2134 pub kind: WindowKind,
2135
2136 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2138 pub is_movable: bool,
2139
2140 #[cfg_attr(
2142 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2143 allow(dead_code)
2144 )]
2145 pub app_owns_titlebar_drag: bool,
2146
2147 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2149 pub is_resizable: bool,
2150
2151 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2153 pub is_minimizable: bool,
2154
2155 #[cfg_attr(
2156 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2157 allow(dead_code)
2158 )]
2159 pub focus: bool,
2160
2161 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2162 pub show: bool,
2163
2164 #[cfg_attr(feature = "wayland", allow(dead_code))]
2166 pub icon: Option<Arc<image::RgbaImage>>,
2167
2168 #[cfg_attr(feature = "wayland", allow(dead_code))]
2169 pub display_id: Option<DisplayId>,
2170
2171 #[cfg_attr(feature = "wayland", allow(dead_code))]
2172 pub app_id: Option<String>,
2173
2174 pub window_min_size: Option<Size<Pixels>>,
2175
2176 #[cfg(target_os = "macos")]
2177 pub tabbing_identifier: Option<String>,
2178}
2179
2180#[derive(Debug, Copy, Clone, PartialEq)]
2182pub enum WindowBounds {
2183 Windowed(Bounds<Pixels>),
2185 Maximized(Bounds<Pixels>),
2188 Fullscreen(Bounds<Pixels>),
2191}
2192
2193impl Default for WindowBounds {
2194 fn default() -> Self {
2195 WindowBounds::Windowed(Bounds::default())
2196 }
2197}
2198
2199impl WindowBounds {
2200 pub fn get_bounds(&self) -> Bounds<Pixels> {
2202 match self {
2203 WindowBounds::Windowed(bounds) => *bounds,
2204 WindowBounds::Maximized(bounds) => *bounds,
2205 WindowBounds::Fullscreen(bounds) => *bounds,
2206 }
2207 }
2208
2209 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2211 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2212 }
2213}
2214
2215impl Default for WindowOptions {
2216 fn default() -> Self {
2217 Self {
2218 window_bounds: None,
2219 titlebar: Some(TitlebarOptions {
2220 title: Default::default(),
2221 appears_transparent: Default::default(),
2222 traffic_light_position: Default::default(),
2223 }),
2224 focus: true,
2225 show: true,
2226 kind: WindowKind::Normal,
2227 is_movable: true,
2228 app_owns_titlebar_drag: false,
2229 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2230 is_resizable: true,
2231 is_minimizable: true,
2232 display_id: None,
2233 window_background: WindowBackgroundAppearance::default(),
2234 icon: None,
2235 app_id: None,
2236 window_min_size: None,
2237 window_decorations: None,
2238 tabbing_identifier: None,
2239 }
2240 }
2241}
2242
2243#[derive(Debug, Default)]
2245pub struct TitlebarOptions {
2246 pub title: Option<SharedString>,
2248
2249 pub appears_transparent: bool,
2252
2253 pub traffic_light_position: Option<Point<Pixels>>,
2255}
2256
2257#[derive(Clone, Debug, PartialEq, Eq)]
2259pub enum WindowKind {
2260 Normal,
2262
2263 PopUp,
2266
2267 AnchoredPopup(popup::PopupOptions),
2274
2275 Floating,
2277
2278 #[cfg(all(target_os = "linux", feature = "wayland"))]
2281 LayerShell(layer_shell::LayerShellOptions),
2282
2283 Dialog,
2286}
2287
2288#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2293pub enum WindowAppearance {
2294 #[default]
2298 Light,
2299
2300 VibrantLight,
2304
2305 Dark,
2309
2310 VibrantDark,
2314}
2315
2316#[derive(Copy, Clone, Debug, Default, PartialEq)]
2319pub enum WindowBackgroundAppearance {
2320 #[default]
2328 Opaque,
2329 Transparent,
2331 Blurred,
2335 MicaBackdrop,
2337 MicaAltBackdrop,
2339}
2340
2341#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2343pub enum TextRenderingMode {
2344 #[default]
2346 PlatformDefault,
2347 Subpixel,
2349 Grayscale,
2351}
2352
2353#[derive(Clone, Debug)]
2355pub struct PathPromptOptions {
2356 pub files: bool,
2358 pub directories: bool,
2360 pub multiple: bool,
2362 pub prompt: Option<SharedString>,
2364}
2365
2366#[derive(Copy, Clone, Debug, PartialEq)]
2368pub enum PromptLevel {
2369 Info,
2371
2372 Warning,
2374
2375 Critical,
2377}
2378
2379#[derive(Clone, Debug, PartialEq)]
2381pub enum PromptButton {
2382 Ok(SharedString),
2384 Cancel(SharedString),
2386 Other(SharedString),
2388}
2389
2390impl PromptButton {
2391 pub fn new(label: impl Into<SharedString>) -> Self {
2393 PromptButton::Other(label.into())
2394 }
2395
2396 pub fn ok(label: impl Into<SharedString>) -> Self {
2398 PromptButton::Ok(label.into())
2399 }
2400
2401 pub fn cancel(label: impl Into<SharedString>) -> Self {
2403 PromptButton::Cancel(label.into())
2404 }
2405
2406 #[allow(dead_code)]
2408 pub fn is_cancel(&self) -> bool {
2409 matches!(self, PromptButton::Cancel(_))
2410 }
2411
2412 pub fn label(&self) -> &SharedString {
2414 match self {
2415 PromptButton::Ok(label) => label,
2416 PromptButton::Cancel(label) => label,
2417 PromptButton::Other(label) => label,
2418 }
2419 }
2420}
2421
2422impl From<&str> for PromptButton {
2423 fn from(value: &str) -> Self {
2424 match value.to_lowercase().as_str() {
2425 "ok" => PromptButton::Ok("OK".into()),
2426 "cancel" => PromptButton::Cancel("Cancel".into()),
2427 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2428 }
2429 }
2430}
2431
2432#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2434pub enum CursorStyle {
2435 #[default]
2437 Arrow,
2438
2439 IBeam,
2442
2443 Crosshair,
2446
2447 ClosedHand,
2450
2451 OpenHand,
2454
2455 PointingHand,
2458
2459 ResizeLeft,
2462
2463 ResizeRight,
2466
2467 ResizeLeftRight,
2470
2471 ResizeUp,
2474
2475 ResizeDown,
2478
2479 ResizeUpDown,
2482
2483 ResizeUpLeftDownRight,
2486
2487 ResizeUpRightDownLeft,
2490
2491 ResizeColumn,
2494
2495 ResizeRow,
2498
2499 IBeamCursorForVerticalLayout,
2502
2503 OperationNotAllowed,
2506
2507 DragLink,
2510
2511 DragCopy,
2514
2515 ContextualMenu,
2518}
2519
2520#[derive(Clone, Debug, Eq, PartialEq)]
2522pub struct ClipboardItem {
2523 pub entries: Vec<ClipboardEntry>,
2525}
2526
2527#[derive(Clone, Debug, PartialEq, Eq)]
2532pub enum ClipboardReadError {
2533 Unavailable,
2537 Denied(String),
2540 UnsupportedContent,
2543}
2544
2545impl std::fmt::Display for ClipboardReadError {
2546 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2547 match self {
2548 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2549 Self::Denied(message) => {
2550 write!(formatter, "clipboard access was denied: {message}")
2551 }
2552 Self::UnsupportedContent => {
2553 formatter.write_str("the clipboard contents are unsupported")
2554 }
2555 }
2556 }
2557}
2558
2559impl std::error::Error for ClipboardReadError {}
2560
2561#[derive(Clone, Debug, Eq, PartialEq)]
2563pub enum ClipboardEntry {
2564 String(ClipboardString),
2566 Image(Image),
2568 ExternalPaths(crate::ExternalPaths),
2570}
2571
2572impl ClipboardItem {
2573 pub fn new_string(text: String) -> Self {
2575 Self {
2576 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2577 }
2578 }
2579
2580 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2582 Self {
2583 entries: vec![ClipboardEntry::String(ClipboardString {
2584 text,
2585 metadata: Some(metadata),
2586 })],
2587 }
2588 }
2589
2590 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2592 Self {
2593 entries: vec![ClipboardEntry::String(
2594 ClipboardString::new(text).with_json_metadata(metadata),
2595 )],
2596 }
2597 }
2598
2599 pub fn new_image(image: &Image) -> Self {
2601 Self {
2602 entries: vec![ClipboardEntry::Image(image.clone())],
2603 }
2604 }
2605
2606 pub fn text(&self) -> Option<String> {
2609 let mut answer = String::new();
2610
2611 for entry in self.entries.iter() {
2612 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2613 answer.push_str(text);
2614 }
2615 }
2616
2617 if answer.is_empty() {
2618 for entry in self.entries.iter() {
2619 if let ClipboardEntry::ExternalPaths(paths) = entry {
2620 for path in &paths.0 {
2621 use std::fmt::Write as _;
2622 _ = write!(answer, "{}", path.display());
2623 }
2624 }
2625 }
2626 }
2627
2628 if !answer.is_empty() {
2629 Some(answer)
2630 } else {
2631 None
2632 }
2633 }
2634
2635 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2637 pub fn metadata(&self) -> Option<&String> {
2638 match self.entries().first() {
2639 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2640 clipboard_string.metadata.as_ref()
2641 }
2642 _ => None,
2643 }
2644 }
2645
2646 pub fn entries(&self) -> &[ClipboardEntry] {
2648 &self.entries
2649 }
2650
2651 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2653 self.entries.into_iter()
2654 }
2655}
2656
2657impl From<ClipboardString> for ClipboardEntry {
2658 fn from(value: ClipboardString) -> Self {
2659 Self::String(value)
2660 }
2661}
2662
2663impl From<String> for ClipboardEntry {
2664 fn from(value: String) -> Self {
2665 Self::from(ClipboardString::from(value))
2666 }
2667}
2668
2669impl From<Image> for ClipboardEntry {
2670 fn from(value: Image) -> Self {
2671 Self::Image(value)
2672 }
2673}
2674
2675impl From<ClipboardEntry> for ClipboardItem {
2676 fn from(value: ClipboardEntry) -> Self {
2677 Self {
2678 entries: vec![value],
2679 }
2680 }
2681}
2682
2683impl From<String> for ClipboardItem {
2684 fn from(value: String) -> Self {
2685 Self::from(ClipboardEntry::from(value))
2686 }
2687}
2688
2689impl From<Image> for ClipboardItem {
2690 fn from(value: Image) -> Self {
2691 Self::from(ClipboardEntry::from(value))
2692 }
2693}
2694
2695#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2697pub enum ImageFormat {
2698 Png,
2703 Jpeg,
2705 Webp,
2707 Gif,
2709 Svg,
2711 Bmp,
2713 Tiff,
2715 Ico,
2717 Pnm,
2719}
2720
2721impl ImageFormat {
2722 pub const fn mime_type(self) -> &'static str {
2724 match self {
2725 ImageFormat::Png => "image/png",
2726 ImageFormat::Jpeg => "image/jpeg",
2727 ImageFormat::Webp => "image/webp",
2728 ImageFormat::Gif => "image/gif",
2729 ImageFormat::Svg => "image/svg+xml",
2730 ImageFormat::Bmp => "image/bmp",
2731 ImageFormat::Tiff => "image/tiff",
2732 ImageFormat::Ico => "image/ico",
2733 ImageFormat::Pnm => "image/x-portable-anymap",
2734 }
2735 }
2736
2737 pub const fn extension(self) -> &'static str {
2739 match self {
2740 ImageFormat::Png => "png",
2741 ImageFormat::Jpeg => "jpg",
2742 ImageFormat::Webp => "webp",
2743 ImageFormat::Gif => "gif",
2744 ImageFormat::Svg => "svg",
2745 ImageFormat::Bmp => "bmp",
2746 ImageFormat::Tiff => "tiff",
2747 ImageFormat::Ico => "ico",
2748 ImageFormat::Pnm => "pnm",
2749 }
2750 }
2751
2752 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2754 use strum::IntoEnumIterator;
2755 Self::iter()
2756 .find(|format| format.mime_type() == mime_type)
2757 .or_else(|| Self::from_mime_type_alias(mime_type))
2758 }
2759
2760 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2764 match mime_type {
2765 "image/jpg" => Some(Self::Jpeg),
2766 "image/tif" => Some(Self::Tiff),
2767 _ => None,
2768 }
2769 }
2770}
2771
2772#[derive(Clone, Debug, PartialEq, Eq)]
2774pub struct Image {
2775 pub format: ImageFormat,
2777 pub bytes: Vec<u8>,
2779 pub id: u64,
2781}
2782
2783pub(crate) fn decode_static_image(
2784 bytes: &[u8],
2785 format: image::ImageFormat,
2786) -> Result<SmallVec<[Frame; 1]>> {
2787 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2788 .into_decoder()
2789 .context("creating image decoder")?;
2790 decode_static_image_from_decoder(decoder)
2791}
2792
2793pub(crate) fn decode_static_image_from_decoder(
2794 mut decoder: impl image::ImageDecoder,
2795) -> Result<SmallVec<[Frame; 1]>> {
2796 let orientation = decoder
2797 .orientation()
2798 .context("reading decoder's orientation")?;
2799 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2800 image.apply_orientation(orientation);
2801
2802 let mut data = image.into_rgba8();
2803 for pixel in data.chunks_exact_mut(4) {
2804 pixel.swap(0, 2);
2805 }
2806
2807 Ok(SmallVec::from_elem(Frame::new(data), 1))
2808}
2809
2810impl Hash for Image {
2811 fn hash<H: Hasher>(&self, state: &mut H) {
2812 state.write_u64(self.id);
2813 }
2814}
2815
2816impl Image {
2817 pub fn empty() -> Self {
2819 Self::from_bytes(ImageFormat::Png, Vec::new())
2820 }
2821
2822 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2824 Self {
2825 id: hash(&bytes),
2826 format,
2827 bytes,
2828 }
2829 }
2830
2831 pub fn id(&self) -> u64 {
2833 self.id
2834 }
2835
2836 pub fn use_render_image(
2838 self: Arc<Self>,
2839 window: &mut Window,
2840 cx: &mut App,
2841 ) -> Option<Arc<RenderImage>> {
2842 ImageSource::Image(self)
2843 .use_data(None, window, cx)
2844 .and_then(|result| result.ok())
2845 }
2846
2847 pub fn get_render_image(
2849 self: Arc<Self>,
2850 window: &mut Window,
2851 cx: &mut App,
2852 ) -> Option<Arc<RenderImage>> {
2853 ImageSource::Image(self)
2854 .get_data(None, window, cx)
2855 .and_then(|result| result.ok())
2856 }
2857
2858 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2860 ImageSource::Image(self).remove_asset(cx);
2861 }
2862
2863 #[cfg(any(test, feature = "test-support"))]
2866 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2867 ImageSource::Image(self.clone()).is_asset_cached(cx)
2868 }
2869
2870 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2872 let frames = match self.format {
2873 ImageFormat::Gif => {
2874 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2875 let mut frames = SmallVec::new();
2876
2877 for frame in decoder.into_frames() {
2878 match frame {
2879 Ok(mut frame) => {
2880 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2882 pixel.swap(0, 2);
2883 }
2884 frames.push(frame);
2885 }
2886 Err(err) => {
2887 log::debug!("Skipping GIF frame due to decode error: {err}");
2888 }
2889 }
2890 }
2891
2892 if frames.is_empty() {
2893 anyhow::bail!("GIF could not be decoded: all frames failed");
2894 }
2895
2896 frames
2897 }
2898 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2899 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2900 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2901 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2902 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2903 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2904 ImageFormat::Svg => {
2905 return svg_renderer
2906 .render_single_frame(&self.bytes, 1.0)
2907 .map_err(Into::into);
2908 }
2909 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2910 };
2911
2912 Ok(Arc::new(RenderImage::new(frames)))
2913 }
2914
2915 pub fn format(&self) -> ImageFormat {
2917 self.format
2918 }
2919
2920 pub fn bytes(&self) -> &[u8] {
2922 self.bytes.as_slice()
2923 }
2924}
2925
2926#[derive(Clone, Debug, Eq, PartialEq)]
2928pub struct ClipboardString {
2929 pub text: String,
2931 pub metadata: Option<String>,
2933}
2934
2935impl ClipboardString {
2936 pub fn new(text: String) -> Self {
2938 Self {
2939 text,
2940 metadata: None,
2941 }
2942 }
2943
2944 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2947 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2948 self
2949 }
2950
2951 pub fn text(&self) -> &String {
2953 &self.text
2954 }
2955
2956 pub fn into_text(self) -> String {
2958 self.text
2959 }
2960
2961 pub fn metadata_json<T>(&self) -> Option<T>
2963 where
2964 T: for<'a> Deserialize<'a>,
2965 {
2966 self.metadata
2967 .as_ref()
2968 .and_then(|m| serde_json::from_str(m).ok())
2969 }
2970
2971 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2972 pub fn text_hash(text: &str) -> u64 {
2974 let mut hasher = SeaHasher::new();
2975 text.hash(&mut hasher);
2976 hasher.finish()
2977 }
2978}
2979
2980impl From<String> for ClipboardString {
2981 fn from(value: String) -> Self {
2982 Self {
2983 text: value,
2984 metadata: None,
2985 }
2986 }
2987}
2988
2989#[cfg(test)]
2990mod image_tests {
2991 use super::*;
2992 use std::sync::Arc;
2993
2994 #[test]
2995 fn test_image_to_image_data_applies_exif_orientation() {
2996 let image = Image::from_bytes(
2997 ImageFormat::Jpeg,
2998 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2999 );
3000
3001 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3002
3003 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
3004
3005 let bytes = render_image.as_bytes(0).unwrap();
3006 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
3007 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
3008 }
3009
3010 #[test]
3011 fn test_svg_image_to_image_data_converts_to_bgra() {
3012 let image = Image::from_bytes(
3013 ImageFormat::Svg,
3014 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3015<rect width="1" height="1" fill="#38BDF8"/>
3016</svg>"##
3017 .to_vec(),
3018 );
3019
3020 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3021 let bytes = render_image.as_bytes(0).unwrap();
3022
3023 for pixel in bytes.chunks_exact(4) {
3024 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3025 }
3026 }
3027}
3028
3029#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3030mod tests {
3031 use super::*;
3032 use std::collections::HashSet;
3033
3034 #[test]
3035 fn test_window_button_layout_parse_standard() {
3036 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3037 assert_eq!(
3038 layout.left,
3039 [
3040 Some(WindowButton::Close),
3041 Some(WindowButton::Minimize),
3042 None
3043 ]
3044 );
3045 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3046 }
3047
3048 #[test]
3049 fn test_window_button_layout_parse_right_only() {
3050 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3051 assert_eq!(layout.left, [None, None, None]);
3052 assert_eq!(
3053 layout.right,
3054 [
3055 Some(WindowButton::Minimize),
3056 Some(WindowButton::Maximize),
3057 Some(WindowButton::Close)
3058 ]
3059 );
3060 }
3061
3062 #[test]
3063 fn test_window_button_layout_parse_left_only() {
3064 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3065 assert_eq!(
3066 layout.left,
3067 [
3068 Some(WindowButton::Close),
3069 Some(WindowButton::Minimize),
3070 Some(WindowButton::Maximize)
3071 ]
3072 );
3073 assert_eq!(layout.right, [None, None, None]);
3074 }
3075
3076 #[test]
3077 fn test_window_button_layout_parse_with_whitespace() {
3078 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3079 assert_eq!(
3080 layout.left,
3081 [
3082 Some(WindowButton::Close),
3083 Some(WindowButton::Minimize),
3084 None
3085 ]
3086 );
3087 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3088 }
3089
3090 #[test]
3091 fn test_window_button_layout_parse_empty() {
3092 let layout = WindowButtonLayout::parse("").unwrap();
3093 assert_eq!(layout.left, [None, None, None]);
3094 assert_eq!(layout.right, [None, None, None]);
3095 }
3096
3097 #[test]
3098 fn test_window_button_layout_parse_intentionally_empty() {
3099 let layout = WindowButtonLayout::parse(":").unwrap();
3100 assert_eq!(layout.left, [None, None, None]);
3101 assert_eq!(layout.right, [None, None, None]);
3102 }
3103
3104 #[test]
3105 fn test_window_button_layout_parse_invalid_buttons() {
3106 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3107 assert_eq!(
3108 layout.left,
3109 [
3110 Some(WindowButton::Close),
3111 Some(WindowButton::Minimize),
3112 None
3113 ]
3114 );
3115 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3116 }
3117
3118 #[test]
3119 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3120 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3121 assert_eq!(
3122 layout.right,
3123 [
3124 Some(WindowButton::Close),
3125 Some(WindowButton::Minimize),
3126 None
3127 ]
3128 );
3129 assert_eq!(layout.format(), ":close,minimize");
3130 }
3131
3132 #[test]
3133 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3134 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3135 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3136 assert_eq!(
3137 layout.right,
3138 [
3139 Some(WindowButton::Maximize),
3140 Some(WindowButton::Minimize),
3141 None
3142 ]
3143 );
3144
3145 let button_ids: Vec<_> = layout
3146 .left
3147 .iter()
3148 .chain(layout.right.iter())
3149 .flatten()
3150 .map(WindowButton::id)
3151 .collect();
3152 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3153 assert_eq!(unique_button_ids.len(), button_ids.len());
3154 assert_eq!(layout.format(), "close:maximize,minimize");
3155 }
3156
3157 #[test]
3158 fn test_window_button_layout_parse_gnome_style() {
3159 let layout = WindowButtonLayout::parse("close").unwrap();
3160 assert_eq!(layout.left, [None, None, None]);
3161 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3162 }
3163
3164 #[test]
3165 fn test_window_button_layout_parse_elementary_style() {
3166 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3167 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3168 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3169 }
3170
3171 #[test]
3172 fn test_window_button_layout_round_trip() {
3173 let cases = [
3174 "close:minimize,maximize",
3175 "minimize,maximize,close:",
3176 ":close",
3177 "close:",
3178 "close:maximize",
3179 ":",
3180 ];
3181
3182 for case in cases {
3183 let layout = WindowButtonLayout::parse(case).unwrap();
3184 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3185 }
3186 }
3187
3188 #[test]
3189 fn test_window_button_layout_linux_default() {
3190 let layout = WindowButtonLayout::linux_default();
3191 assert_eq!(layout.left, [None, None, None]);
3192 assert_eq!(
3193 layout.right,
3194 [
3195 Some(WindowButton::Minimize),
3196 Some(WindowButton::Maximize),
3197 Some(WindowButton::Close)
3198 ]
3199 );
3200
3201 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3202 assert_eq!(round_tripped, layout);
3203 }
3204
3205 #[test]
3206 fn test_window_button_layout_parse_all_invalid() {
3207 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3208 }
3209}