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 = "bench"))]
13mod bench_dispatcher;
14
15#[cfg(any(test, feature = "test-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, 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"))]
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 fmt::{self, Debug},
68 ops::Range,
69 path::{Path, PathBuf},
70 rc::Rc,
71 sync::Arc,
72};
73use strum::EnumIter;
74use uuid::Uuid;
75
76pub use app_menu::*;
77pub use keyboard::*;
78pub use keystroke::*;
79
80#[cfg(any(test, feature = "test-support"))]
81pub(crate) use test::*;
82
83#[cfg(any(test, feature = "test-support"))]
84pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
85
86#[cfg(any(test, feature = "bench"))]
87pub use bench_dispatcher::BenchDispatcher;
88
89#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
90pub use visual_test::VisualTestPlatform;
91
92#[cfg(any(target_os = "linux", target_os = "freebsd"))]
96#[inline]
97pub fn guess_compositor() -> &'static str {
98 if std::env::var_os("ZED_HEADLESS").is_some() {
99 return "Headless";
100 }
101
102 #[cfg(feature = "wayland")]
103 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
104 #[cfg(not(feature = "wayland"))]
105 let wayland_display: Option<std::ffi::OsString> = None;
106
107 #[cfg(feature = "x11")]
108 let x11_display = std::env::var_os("DISPLAY");
109 #[cfg(not(feature = "x11"))]
110 let x11_display: Option<std::ffi::OsString> = None;
111
112 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
113 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
114
115 if use_wayland {
116 "Wayland"
117 } else if use_x11 {
118 "X11"
119 } else {
120 "Headless"
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum MacActivationPolicy {
127 #[default]
129 Regular,
130 Accessory,
132 Prohibited,
134}
135
136#[expect(missing_docs)]
137pub trait Platform: 'static {
138 fn background_executor(&self) -> BackgroundExecutor;
139 fn foreground_executor(&self) -> ForegroundExecutor;
140 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
141
142 fn set_mac_activation_policy(&self, _policy: MacActivationPolicy) {}
143 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
144 fn quit(&self);
145 fn restart(&self, binary_path: Option<PathBuf>);
146 fn activate(&self, ignoring_other_apps: bool);
147 fn hide(&self);
148 fn hide_other_apps(&self);
149 fn unhide_other_apps(&self);
150
151 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
152 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
153 fn active_window(&self) -> Option<AnyWindowHandle>;
154 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
155 None
156 }
157
158 fn is_screen_capture_supported(&self) -> bool {
159 false
160 }
161
162 fn screen_capture_sources(
163 &self,
164 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
165 let (sources_tx, sources_rx) = oneshot::channel();
166 sources_tx
167 .send(Err(anyhow::anyhow!(
168 "gpui was compiled without the screen-capture feature"
169 )))
170 .ok();
171 sources_rx
172 }
173
174 fn open_window(
175 &self,
176 handle: AnyWindowHandle,
177 options: WindowParams,
178 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
179
180 fn window_appearance(&self) -> WindowAppearance;
182
183 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
192
193 fn button_layout(&self) -> Option<WindowButtonLayout> {
195 None
196 }
197
198 fn open_url(&self, url: &str);
199 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
200 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
201
202 fn prompt_for_paths(
203 &self,
204 options: PathPromptOptions,
205 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
206 fn prompt_for_new_path(
207 &self,
208 directory: &Path,
209 suggested_name: Option<&str>,
210 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
211 fn can_select_mixed_files_and_dirs(&self) -> bool;
212 fn reveal_path(&self, path: &Path);
213 fn open_with_system(&self, path: &Path);
214
215 fn on_quit(&self, callback: Box<dyn FnMut()>);
216 fn on_reopen(&self, callback: Box<dyn FnMut()>);
217 fn on_system_wake(&self, callback: Box<dyn FnMut()>);
218
219 fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
229
230 fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
235
236 fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
240 None
241 }
242
243 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
244 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
245 None
246 }
247
248 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
249 fn perform_dock_menu_action(&self, _action: usize) {}
250 fn add_recent_document(&self, _path: &Path) {}
251 fn update_jump_list(
252 &self,
253 _menus: Vec<MenuItem>,
254 _entries: Vec<SmallVec<[PathBuf; 2]>>,
255 ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
256 Task::ready(Vec::new())
257 }
258 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
259 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
260 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
261
262 fn thermal_state(&self) -> ThermalState;
263 fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
264
265 fn set_app_identity(&self, identifier: &str, name: &str) {
272 _ = (identifier, name);
273 }
274
275 fn show_system_notification(&self, notification: SystemNotification) {
282 _ = notification;
283 }
284
285 fn dismiss_system_notification(&self, tag: &str) {
290 _ = tag;
291 }
292
293 fn on_system_notification_response(
299 &self,
300 callback: Box<dyn FnMut(SystemNotificationResponse)>,
301 ) {
302 _ = callback;
303 }
304
305 fn compositor_name(&self) -> &'static str {
306 ""
307 }
308 fn app_path(&self) -> Result<PathBuf>;
309 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
310
311 fn set_cursor_style(&self, style: CursorStyle);
312
313 fn hide_cursor_until_mouse_moves(&self);
316
317 fn is_cursor_visible(&self) -> bool;
319
320 fn should_auto_hide_scrollbars(&self) -> bool;
321
322 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
323 fn write_to_clipboard(&self, item: ClipboardItem);
324
325 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
326 fn read_from_primary(&self) -> Option<ClipboardItem>;
327 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
328 fn write_to_primary(&self, item: ClipboardItem);
329
330 #[cfg(target_os = "macos")]
331 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
332 #[cfg(target_os = "macos")]
333 fn write_to_find_pasteboard(&self, item: ClipboardItem);
334
335 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
336 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
337 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
338
339 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
340 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
341 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
342
343 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
347 fn set_gpu_requirements(&self, _requirements: Box<dyn std::any::Any>) {}
348
349 fn set_keyring_label(&self, _label: SharedString) {}
352
353 fn supports_haptic_feedback(&self) -> bool {
355 false
356 }
357
358 fn play_haptic_feedback(&self, _style: HapticFeedbackStyle) {}
362}
363
364pub trait PlatformDisplay: Debug {
366 fn id(&self) -> DisplayId;
368
369 fn uuid(&self) -> Result<Uuid>;
372
373 fn bounds(&self) -> Bounds<Pixels>;
375
376 fn visible_bounds(&self) -> Bounds<Pixels> {
380 self.bounds()
381 }
382
383 fn default_bounds(&self) -> Bounds<Pixels> {
385 let bounds = self.bounds();
386 let center = bounds.center();
387 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
388
389 let offset = clipped_window_size / 2.0;
390 let origin = point(center.x - offset.width, center.y - offset.height);
391 Bounds::new(origin, clipped_window_size)
392 }
393}
394
395#[derive(Clone, Debug, PartialEq, Eq)]
398pub struct SystemNotification {
399 pub tag: SharedString,
403 pub title: SharedString,
405 pub body: SharedString,
407 pub actions: Vec<SystemNotificationAction>,
410}
411
412#[derive(Clone, Debug, PartialEq, Eq, Hash)]
414pub struct SystemNotificationAction {
415 pub id: SharedString,
418 pub label: SharedString,
420}
421
422#[derive(Clone, Debug, PartialEq, Eq)]
424pub struct SystemNotificationResponse {
425 pub tag: SharedString,
427 pub action_id: Option<SharedString>,
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
434pub enum ThermalState {
435 Nominal,
437 Fair,
439 Serious,
441 Critical,
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum HapticFeedbackStyle {
451 Generic,
453 Alignment,
455 LevelChange,
458}
459
460#[derive(Clone)]
462pub struct SourceMetadata {
463 pub id: u64,
465 pub label: Option<SharedString>,
467 pub is_main: Option<bool>,
469 pub resolution: Size<DevicePixels>,
471}
472
473pub trait ScreenCaptureSource {
475 fn metadata(&self) -> Result<SourceMetadata>;
477
478 fn stream(
481 &self,
482 foreground_executor: &ForegroundExecutor,
483 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
484 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
485}
486
487pub trait ScreenCaptureStream {
489 fn metadata(&self) -> Result<SourceMetadata>;
491}
492
493pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
495
496#[derive(PartialEq, Eq, Hash, Copy, Clone)]
498pub struct DisplayId(pub(crate) u64);
499
500impl DisplayId {
501 pub fn new(id: u64) -> Self {
503 Self(id)
504 }
505}
506
507impl From<u64> for DisplayId {
508 fn from(id: u64) -> Self {
509 Self(id)
510 }
511}
512
513impl From<DisplayId> for u64 {
514 fn from(id: DisplayId) -> Self {
515 id.0
516 }
517}
518
519impl Debug for DisplayId {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 write!(f, "DisplayId({})", self.0)
522 }
523}
524
525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
527pub enum ResizeEdge {
528 Top,
530 TopRight,
532 Right,
534 BottomRight,
536 Bottom,
538 BottomLeft,
540 Left,
542 TopLeft,
544}
545
546#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
548pub enum WindowDecorations {
549 #[default]
550 Server,
552 Client,
554}
555
556#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
558pub enum Decorations {
559 #[default]
561 Server,
562 Client {
564 tiling: Tiling,
566 },
567}
568
569#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
571pub struct WindowControls {
572 pub fullscreen: bool,
574 pub maximize: bool,
576 pub minimize: bool,
578 pub window_menu: bool,
580}
581
582impl Default for WindowControls {
583 fn default() -> Self {
584 Self {
586 fullscreen: true,
587 maximize: true,
588 minimize: true,
589 window_menu: true,
590 }
591 }
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
596pub enum WindowButton {
597 Minimize,
599 Maximize,
601 Close,
603}
604
605impl WindowButton {
606 pub fn id(&self) -> &'static str {
608 match self {
609 WindowButton::Minimize => "minimize",
610 WindowButton::Maximize => "maximize",
611 WindowButton::Close => "close",
612 }
613 }
614
615 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
616 fn index(&self) -> usize {
617 match self {
618 WindowButton::Minimize => 0,
619 WindowButton::Maximize => 1,
620 WindowButton::Close => 2,
621 }
622 }
623}
624
625pub const MAX_BUTTONS_PER_SIDE: usize = 3;
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
633pub struct WindowButtonLayout {
634 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
636 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
638}
639
640#[cfg(any(target_os = "linux", target_os = "freebsd"))]
641impl WindowButtonLayout {
642 pub fn linux_default() -> Self {
644 Self {
645 left: [None; MAX_BUTTONS_PER_SIDE],
646 right: [
647 Some(WindowButton::Minimize),
648 Some(WindowButton::Maximize),
649 Some(WindowButton::Close),
650 ],
651 }
652 }
653
654 pub fn parse(layout_string: &str) -> Result<Self> {
656 fn parse_side(
657 s: &str,
658 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
659 unrecognized: &mut Vec<String>,
660 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
661 let mut result = [None; MAX_BUTTONS_PER_SIDE];
662 let mut i = 0;
663 for name in s.split(',') {
664 let trimmed = name.trim();
665 if trimmed.is_empty() {
666 continue;
667 }
668 let button = match trimmed {
669 "minimize" => Some(WindowButton::Minimize),
670 "maximize" => Some(WindowButton::Maximize),
671 "close" => Some(WindowButton::Close),
672 other => {
673 unrecognized.push(other.to_string());
674 None
675 }
676 };
677 if let Some(button) = button {
678 if seen_buttons[button.index()] {
679 continue;
680 }
681 if let Some(slot) = result.get_mut(i) {
682 *slot = Some(button);
683 seen_buttons[button.index()] = true;
684 i += 1;
685 }
686 }
687 }
688 result
689 }
690
691 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
692 let mut unrecognized = Vec::new();
693 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
694 let layout = Self {
695 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
696 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
697 };
698
699 if !unrecognized.is_empty()
700 && layout.left.iter().all(Option::is_none)
701 && layout.right.iter().all(Option::is_none)
702 {
703 bail!(
704 "button layout string {:?} contains no valid buttons (unrecognized: {})",
705 layout_string,
706 unrecognized.join(", ")
707 );
708 }
709
710 Ok(layout)
711 }
712
713 #[cfg(test)]
715 pub fn format(&self) -> String {
716 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
717 buttons
718 .iter()
719 .flatten()
720 .map(|button| match button {
721 WindowButton::Minimize => "minimize",
722 WindowButton::Maximize => "maximize",
723 WindowButton::Close => "close",
724 })
725 .collect::<Vec<_>>()
726 .join(",")
727 }
728
729 format!("{}:{}", format_side(&self.left), format_side(&self.right))
730 }
731}
732
733#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
735pub struct Tiling {
736 pub top: bool,
738 pub left: bool,
740 pub right: bool,
742 pub bottom: bool,
744}
745
746impl Tiling {
747 pub fn tiled() -> Self {
749 Self {
750 top: true,
751 left: true,
752 right: true,
753 bottom: true,
754 }
755 }
756
757 pub fn is_tiled(&self) -> bool {
759 self.top || self.left || self.right || self.bottom
760 }
761}
762
763pub struct A11yCallbacks {
765 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
767 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
769 pub deactivation: Box<dyn Fn() + Send + 'static>,
771}
772
773#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
774#[expect(missing_docs)]
775pub struct RequestFrameOptions {
776 pub require_presentation: bool,
778 pub force_render: bool,
780}
781
782#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
795pub enum AppLifecyclePhase {
796 Active,
798 Inactive,
800 Background,
803 Foreground,
805}
806
807#[derive(Debug, Clone, Default, PartialEq)]
814pub struct WindowInsets {
815 pub safe_area: Edges<Pixels>,
820 pub ime: Edges<Pixels>,
824}
825
826impl WindowInsets {
827 pub fn effective(&self) -> Edges<Pixels> {
829 Edges {
830 top: self.safe_area.top.max(self.ime.top),
831 right: self.safe_area.right.max(self.ime.right),
832 bottom: self.safe_area.bottom.max(self.ime.bottom),
833 left: self.safe_area.left.max(self.ime.left),
834 }
835 }
836}
837
838#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
840pub enum TextInputStateChange {
841 FocusGained,
843 FocusLost,
845 SelectionChanged,
847 ContentChanged,
849}
850
851#[expect(missing_docs)]
852pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
853 fn bounds(&self) -> Bounds<Pixels>;
854 fn is_maximized(&self) -> bool;
855 fn window_bounds(&self) -> WindowBounds;
856 fn content_size(&self) -> Size<Pixels>;
857 fn resize(&mut self, size: Size<Pixels>);
858 fn scale_factor(&self) -> f32;
859 fn appearance(&self) -> WindowAppearance;
860 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
861 fn mouse_position(&self) -> Point<Pixels>;
862 fn modifiers(&self) -> Modifiers;
863 fn capslock(&self) -> Capslock;
864 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
865 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
866 fn prompt(
867 &self,
868 level: PromptLevel,
869 msg: &str,
870 detail: Option<&str>,
871 answers: &[PromptButton],
872 ) -> Option<oneshot::Receiver<usize>>;
873 fn activate(&self);
874 fn request_attention(&self) {}
876 fn is_active(&self) -> bool;
877 fn is_hovered(&self) -> bool;
878 fn background_appearance(&self) -> WindowBackgroundAppearance;
879 fn set_title(&mut self, title: &str);
880 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
881 fn minimize(&self);
882 fn zoom(&self);
883 fn toggle_fullscreen(&self);
884 fn is_fullscreen(&self) -> bool;
885 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
886 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
887 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
888 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
889 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
890 fn on_moved(&self, callback: Box<dyn FnMut()>);
891 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
892 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
893 fn on_close(&self, callback: Box<dyn FnOnce()>);
894 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
895 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
896 fn draw(&self, scene: &Scene);
897 fn completed_frame(&self) {}
898 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
899 fn is_subpixel_rendering_supported(&self) -> bool;
900
901 fn get_title(&self) -> String {
903 String::new()
904 }
905 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
906 None
907 }
908 fn tab_bar_visible(&self) -> bool {
909 false
910 }
911 fn set_edited(&mut self, _edited: bool) {}
912 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
913 #[cfg(target_os = "macos")]
914 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
915 fn show_character_palette(&self) {}
916 fn titlebar_double_click(&self) {}
917 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
918 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
919 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
920 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
921 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
922 fn merge_all_windows(&self) {}
923 fn move_tab_to_new_window(&self) {}
924 fn toggle_window_tab_overview(&self) {}
925 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
926
927 #[cfg(target_os = "windows")]
928 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
929
930 fn inner_window_bounds(&self) -> WindowBounds {
932 self.window_bounds()
933 }
934 fn request_decorations(&self, _decorations: WindowDecorations) {}
935 fn show_window_menu(&self, _position: Point<Pixels>) {}
936 fn start_window_move(&self) {}
937 fn can_start_external_drag(&self) -> bool {
938 false
939 }
940 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
941 false
942 }
943 fn start_window_resize(&self, _edge: ResizeEdge) {}
944 fn set_exclusive_zone(&self, _zone: Pixels) {}
945 #[cfg(all(target_os = "linux", feature = "wayland"))]
946 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
947 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
948 fn window_decorations(&self) -> Decorations {
949 Decorations::Server
950 }
951 fn set_app_id(&mut self, _app_id: &str) {}
952 fn map_window(&mut self) -> anyhow::Result<()> {
953 Ok(())
954 }
955 fn window_controls(&self) -> WindowControls {
956 WindowControls::default()
957 }
958 fn set_client_inset(&self, _inset: Pixels) {}
959 fn gpu_specs(&self) -> Option<GpuSpecs>;
960
961 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
964 fn gpu_context(&self) -> Option<Box<dyn std::any::Any>> {
965 None
966 }
967
968 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
975 fn gpu_device_lost(&self) -> Option<bool> {
976 None
977 }
978
979 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
980
981 fn insets(&self) -> WindowInsets {
986 WindowInsets::default()
987 }
988
989 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
995
996 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
999
1000 fn set_back_enabled(&self, _enabled: bool) {}
1003
1004 fn show_soft_keyboard(&self) {}
1006
1007 fn hide_soft_keyboard(&self) {}
1009
1010 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
1012
1013 fn play_system_bell(&self) {}
1014
1015 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1017
1018 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1020
1021 fn a11y_update_window_bounds(&self) {}
1023
1024 #[cfg(any(test, feature = "test-support"))]
1025 fn as_test(&mut self) -> Option<&mut TestWindow> {
1026 None
1027 }
1028
1029 #[cfg(any(test, feature = "test-support"))]
1033 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1034 anyhow::bail!("render_to_image not implemented for this platform")
1035 }
1036}
1037
1038#[cfg(any(test, feature = "test-support"))]
1040pub trait PlatformHeadlessRenderer {
1041 fn render_scene_to_image(
1043 &mut self,
1044 scene: &Scene,
1045 size: Size<DevicePixels>,
1046 ) -> Result<RgbaImage>;
1047
1048 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1054
1055 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1057}
1058
1059#[doc(hidden)]
1062pub type RunnableVariant = Runnable<RunnableMeta>;
1063
1064#[doc(hidden)]
1065pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1066
1067#[doc(hidden)]
1068pub enum TasksIncluded {
1069 OnlyCompleted,
1070 CompletedAndRunning,
1071}
1072
1073#[doc(hidden)]
1076pub trait PlatformDispatcher: Send + Sync {
1077 fn is_main_thread(&self) -> bool;
1078 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1079 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1080 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1081
1082 fn dispatch_on_main_thread_when_idle(
1083 &self,
1084 runnable: RunnableVariant,
1085 timeout: Option<Duration>,
1086 ) {
1087 let _ = timeout;
1088 self.dispatch_on_main_thread(runnable, Priority::Low);
1089 }
1090
1091 fn idle_time_remaining(&self) -> Option<Duration> {
1092 None
1093 }
1094
1095 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1096
1097 fn now(&self) -> Instant {
1098 Instant::now()
1099 }
1100
1101 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1102 gpui_util::defer(Box::new(|| {}))
1103 }
1104
1105 #[cfg(any(test, feature = "test-support"))]
1106 fn as_test(&self) -> Option<&TestDispatcher> {
1107 None
1108 }
1109
1110 #[cfg(any(test, feature = "bench"))]
1113 fn as_bench(&self) -> Option<&BenchDispatcher> {
1114 None
1115 }
1116}
1117
1118#[expect(missing_docs)]
1119pub trait PlatformTextSystem: Send + Sync {
1120 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1121 fn all_font_names(&self) -> Vec<String>;
1123 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1125 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1127 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1129 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1131 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1133 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1135 fn rasterize_glyph(
1137 &self,
1138 params: &RenderGlyphParams,
1139 raster_bounds: Bounds<DevicePixels>,
1140 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1141 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1143 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1145 -> TextRenderingMode;
1146 fn glyph_dilation_for_color(&self, _color: palette::Hsla) -> u8 {
1148 0
1149 }
1150}
1151
1152#[expect(missing_docs)]
1153pub struct NoopTextSystem;
1154
1155#[expect(missing_docs)]
1156impl NoopTextSystem {
1157 #[allow(dead_code)]
1158 pub fn new() -> Self {
1159 Self
1160 }
1161}
1162
1163impl PlatformTextSystem for NoopTextSystem {
1164 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1165 Ok(())
1166 }
1167
1168 fn all_font_names(&self) -> Vec<String> {
1169 Vec::new()
1170 }
1171
1172 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1173 Ok(FontId(1))
1174 }
1175
1176 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1177 FontMetrics {
1178 units_per_em: 1000,
1179 ascent: 1025.0,
1180 descent: -275.0,
1181 line_gap: 0.0,
1182 underline_position: -95.0,
1183 underline_thickness: 60.0,
1184 cap_height: 698.0,
1185 x_height: 516.0,
1186 bounding_box: Bounds {
1187 origin: Point {
1188 x: -260.0,
1189 y: -245.0,
1190 },
1191 size: Size {
1192 width: 1501.0,
1193 height: 1364.0,
1194 },
1195 },
1196 }
1197 }
1198
1199 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1200 Ok(Bounds {
1201 origin: Point { x: 54.0, y: 0.0 },
1202 size: size(392.0, 528.0),
1203 })
1204 }
1205
1206 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1207 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1208 }
1209
1210 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1211 Some(GlyphId(ch.len_utf16() as u32))
1212 }
1213
1214 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1215 Ok(Default::default())
1216 }
1217
1218 fn rasterize_glyph(
1219 &self,
1220 _params: &RenderGlyphParams,
1221 raster_bounds: Bounds<DevicePixels>,
1222 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1223 Ok((raster_bounds.size, Vec::new()))
1224 }
1225
1226 fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
1227 let mut position = px(0.);
1228 let metrics = self.font_metrics(FontId(0));
1229 let em_width = font_size
1230 * self
1231 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1232 .unwrap()
1233 .width
1234 / metrics.units_per_em as f32;
1235 let mut glyphs = Vec::new();
1236 for (ix, c) in text.char_indices() {
1237 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1238 glyphs.push(ShapedGlyph {
1239 id: glyph,
1240 position: point(position, px(0.)),
1241 index: ix,
1242 is_emoji: glyph.0 == 2,
1243 });
1244 if glyph.0 == 2 {
1245 position += em_width * 2.0;
1246 } else {
1247 position += em_width;
1248 }
1249 } else {
1250 position += em_width
1251 }
1252 }
1253 let mut shaped_runs = Vec::default();
1254 if !glyphs.is_empty() {
1255 shaped_runs.push(ShapedRun {
1256 font_id: FontId(0),
1257 glyphs,
1258 });
1259 } else {
1260 position = px(0.);
1261 }
1262
1263 let mut tracking = px(0.);
1264 let mut byte_offset = 0usize;
1265 for run in font_runs {
1266 let end = byte_offset.saturating_add(run.len).min(text.len());
1267 let slice = text.get(byte_offset..end).unwrap_or("");
1268 let n = slice.chars().count();
1269 if n > 1 {
1270 if let Some(spacing) = run.letter_spacing {
1271 tracking += spacing * (n - 1) as f32;
1272 }
1273 }
1274 byte_offset = byte_offset.saturating_add(run.len);
1275 }
1276
1277 LineLayout {
1278 font_size,
1279 width: position + tracking,
1280 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1281 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1282 runs: shaped_runs,
1283 len: text.len(),
1284 }
1285 }
1286
1287 fn recommended_rendering_mode(
1288 &self,
1289 _font_id: FontId,
1290 _font_size: Pixels,
1291 ) -> TextRenderingMode {
1292 TextRenderingMode::Grayscale
1293 }
1294}
1295
1296#[allow(dead_code)]
1301pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1302 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1303 [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], ];
1317
1318 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1319 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1320
1321 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1322 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1323
1324 [
1325 ratios[0] * NORM13,
1326 ratios[1] * NORM24,
1327 ratios[2] * NORM13,
1328 ratios[3] * NORM24,
1329 ]
1330}
1331
1332#[derive(PartialEq, Eq, Hash, Clone)]
1333#[expect(missing_docs)]
1334pub enum AtlasKey {
1335 Glyph(RenderGlyphParams),
1336 Svg(RenderSvgParams),
1337 Image(RenderImageParams),
1338}
1339
1340impl AtlasKey {
1341 #[cfg_attr(
1342 all(
1343 any(target_os = "linux", target_os = "freebsd"),
1344 not(any(feature = "x11", feature = "wayland"))
1345 ),
1346 allow(dead_code)
1347 )]
1348 pub fn texture_kind(&self) -> AtlasTextureKind {
1350 match self {
1351 AtlasKey::Glyph(params) => {
1352 if params.is_emoji {
1353 AtlasTextureKind::Polychrome
1354 } else if params.subpixel_rendering {
1355 AtlasTextureKind::Subpixel
1356 } else {
1357 AtlasTextureKind::Monochrome
1358 }
1359 }
1360 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1361 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1362 }
1363 }
1364}
1365
1366impl From<RenderGlyphParams> for AtlasKey {
1367 fn from(params: RenderGlyphParams) -> Self {
1368 Self::Glyph(params)
1369 }
1370}
1371
1372impl From<RenderSvgParams> for AtlasKey {
1373 fn from(params: RenderSvgParams) -> Self {
1374 Self::Svg(params)
1375 }
1376}
1377
1378impl From<RenderImageParams> for AtlasKey {
1379 fn from(params: RenderImageParams) -> Self {
1380 Self::Image(params)
1381 }
1382}
1383
1384#[expect(missing_docs)]
1385pub trait PlatformAtlas {
1386 fn get_or_insert_with<'a>(
1387 &self,
1388 key: &AtlasKey,
1389 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1390 ) -> Result<Option<AtlasTile>>;
1391 fn remove(&self, key: &AtlasKey);
1392
1393 #[cfg(any(test, feature = "test-support"))]
1394 fn contains(&self, _key: &AtlasKey) -> bool {
1395 false
1396 }
1397}
1398
1399#[doc(hidden)]
1400pub struct AtlasTextureList<T> {
1401 pub textures: Vec<Option<T>>,
1402 pub free_list: Vec<usize>,
1403}
1404
1405impl<T> Default for AtlasTextureList<T> {
1406 fn default() -> Self {
1407 Self {
1408 textures: Vec::default(),
1409 free_list: Vec::default(),
1410 }
1411 }
1412}
1413
1414impl<T> ops::Index<usize> for AtlasTextureList<T> {
1415 type Output = Option<T>;
1416
1417 fn index(&self, index: usize) -> &Self::Output {
1418 &self.textures[index]
1419 }
1420}
1421
1422impl<T> AtlasTextureList<T> {
1423 #[allow(unused)]
1424 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1425 self.free_list.clear();
1426 self.textures.drain(..)
1427 }
1428
1429 #[allow(dead_code)]
1430 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1431 self.textures.iter_mut().flatten()
1432 }
1433}
1434
1435#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1436#[repr(C)]
1437#[expect(missing_docs)]
1438pub struct AtlasTile {
1439 pub texture_id: AtlasTextureId,
1441 pub tile_id: TileId,
1443 pub padding: u32,
1445 pub bounds: Bounds<DevicePixels>,
1447}
1448
1449#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1450#[repr(C)]
1451#[expect(missing_docs)]
1452pub struct AtlasTextureId {
1453 pub index: u32,
1456 pub kind: AtlasTextureKind,
1458}
1459
1460#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1461#[repr(C)]
1462#[cfg_attr(
1463 all(
1464 any(target_os = "linux", target_os = "freebsd"),
1465 not(any(feature = "x11", feature = "wayland"))
1466 ),
1467 allow(dead_code)
1468)]
1469#[expect(missing_docs)]
1470pub enum AtlasTextureKind {
1471 Monochrome = 0,
1472 Polychrome = 1,
1473 Subpixel = 2,
1474}
1475
1476#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1477#[repr(C)]
1478#[expect(missing_docs)]
1479pub struct TileId(pub u32);
1480
1481impl From<etagere::AllocId> for TileId {
1482 fn from(id: etagere::AllocId) -> Self {
1483 Self(id.serialize())
1484 }
1485}
1486
1487impl From<TileId> for etagere::AllocId {
1488 fn from(id: TileId) -> Self {
1489 Self::deserialize(id.0)
1490 }
1491}
1492
1493#[expect(missing_docs)]
1494pub struct PlatformInputHandler {
1495 cx: AsyncWindowContext,
1496 handler: Box<dyn InputHandler>,
1497}
1498
1499#[expect(missing_docs)]
1500#[cfg_attr(
1501 all(
1502 any(target_os = "linux", target_os = "freebsd"),
1503 not(any(feature = "x11", feature = "wayland"))
1504 ),
1505 allow(dead_code)
1506)]
1507impl PlatformInputHandler {
1508 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1509 Self { cx, handler }
1510 }
1511
1512 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1513 self.cx
1514 .update(|window, cx| {
1515 self.handler
1516 .selected_text_range(ignore_disabled_input, window, cx)
1517 })
1518 .ok()
1519 .flatten()
1520 }
1521
1522 #[cfg_attr(target_os = "windows", allow(dead_code))]
1523 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1524 self.cx
1525 .update(|window, cx| self.handler.marked_text_range(window, cx))
1526 .ok()
1527 .flatten()
1528 }
1529
1530 #[cfg_attr(
1531 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1532 allow(dead_code)
1533 )]
1534 pub fn text_for_range(
1535 &mut self,
1536 range_utf16: Range<usize>,
1537 adjusted: &mut Option<Range<usize>>,
1538 ) -> Option<String> {
1539 self.cx
1540 .update(|window, cx| {
1541 self.handler
1542 .text_for_range(range_utf16, adjusted, window, cx)
1543 })
1544 .ok()
1545 .flatten()
1546 }
1547
1548 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1549 self.cx
1550 .update(|window, cx| {
1551 self.handler
1552 .replace_text_in_range(replacement_range, text, window, cx);
1553 })
1554 .ok();
1555 }
1556
1557 pub fn replace_and_mark_text_in_range(
1558 &mut self,
1559 range_utf16: Option<Range<usize>>,
1560 new_text: &str,
1561 new_selected_range: Option<Range<usize>>,
1562 ) {
1563 self.cx
1564 .update(|window, cx| {
1565 self.handler.replace_and_mark_text_in_range(
1566 range_utf16,
1567 new_text,
1568 new_selected_range,
1569 window,
1570 cx,
1571 )
1572 })
1573 .ok();
1574 }
1575
1576 #[cfg_attr(target_os = "windows", allow(dead_code))]
1577 pub fn unmark_text(&mut self) {
1578 self.cx
1579 .update(|window, cx| self.handler.unmark_text(window, cx))
1580 .ok();
1581 }
1582
1583 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1584 self.cx
1585 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1586 .ok()
1587 .flatten()
1588 }
1589
1590 #[allow(dead_code)]
1591 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1592 self.handler.apple_press_and_hold_enabled()
1593 }
1594
1595 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1596 self.handler.replace_text_in_range(None, input, window, cx);
1597 }
1598
1599 pub fn compute_ime_candidate_bounds(
1600 marked_range: Option<Range<usize>>,
1601 selection: &UTF16Selection,
1602 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1603 ) -> Option<Bounds<Pixels>> {
1604 if let Some(marked_range) = marked_range {
1605 let mut line_start = marked_range.start;
1607
1608 let caret = selection.range.end;
1612 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1613 for i in (marked_range.start..caret).rev() {
1614 if let Some(b) = bounds_for_range(i..i) {
1615 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1616 line_start = i + 1;
1617 break;
1618 }
1619 }
1620 }
1621 }
1622 bounds_for_range(line_start..line_start)
1623 } else {
1624 let offset = if selection.reversed {
1626 selection.range.start
1627 } else {
1628 selection.range.end
1629 };
1630 bounds_for_range(offset..offset)
1631 }
1632 }
1633
1634 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1635 let marked_range = self.handler.marked_text_range(window, cx);
1636 let selection = self.handler.selected_text_range(true, window, cx)?;
1637 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1638 self.handler.bounds_for_range(range, window, cx)
1639 })
1640 }
1641
1642 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1643 let marked_range = self.marked_text_range();
1644 let selection = self.selected_text_range(true)?;
1645 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1646 self.bounds_for_range(range)
1647 })
1648 }
1649
1650 #[allow(unused)]
1651 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1652 self.cx
1653 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1654 .ok()
1655 .flatten()
1656 }
1657
1658 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1660 self.cx
1661 .update(|window, cx| {
1662 self.handler
1663 .set_selected_text_range(range_utf16, window, cx)
1664 })
1665 .ok();
1666 }
1667
1668 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1670 self.cx
1671 .update(|window, cx| self.handler.element_bounds(window, cx))
1672 .ok()
1673 .flatten()
1674 }
1675
1676 pub fn text_length_utf16(&mut self) -> Option<usize> {
1678 self.cx
1679 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1680 .ok()
1681 .flatten()
1682 }
1683
1684 #[allow(dead_code)]
1685 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1686 self.handler.accepts_text_input(window, cx)
1687 }
1688
1689 #[allow(dead_code)]
1690 pub fn query_accepts_text_input(&mut self) -> bool {
1691 self.cx
1692 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1693 .unwrap_or(true)
1694 }
1695
1696 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1702 self.cx
1703 .update(|window, cx| {
1704 !window.has_pending_keystrokes()
1706 && self.handler.prefers_ime_for_printable_keys(window, cx)
1707 })
1708 .unwrap_or(false)
1709 }
1710}
1711
1712#[derive(Debug)]
1715pub struct UTF16Selection {
1716 pub range: Range<usize>,
1719 pub reversed: bool,
1722}
1723
1724pub trait InputHandler: 'static {
1729 fn selected_text_range(
1734 &mut self,
1735 ignore_disabled_input: bool,
1736 window: &mut Window,
1737 cx: &mut App,
1738 ) -> Option<UTF16Selection>;
1739
1740 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1745
1746 fn text_for_range(
1751 &mut self,
1752 range_utf16: Range<usize>,
1753 adjusted_range: &mut Option<Range<usize>>,
1754 window: &mut Window,
1755 cx: &mut App,
1756 ) -> Option<String>;
1757
1758 fn replace_text_in_range(
1763 &mut self,
1764 replacement_range: Option<Range<usize>>,
1765 text: &str,
1766 window: &mut Window,
1767 cx: &mut App,
1768 );
1769
1770 fn replace_and_mark_text_in_range(
1777 &mut self,
1778 range_utf16: Option<Range<usize>>,
1779 new_text: &str,
1780 new_selected_range: Option<Range<usize>>,
1781 window: &mut Window,
1782 cx: &mut App,
1783 );
1784
1785 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1788
1789 fn bounds_for_range(
1794 &mut self,
1795 range_utf16: Range<usize>,
1796 window: &mut Window,
1797 cx: &mut App,
1798 ) -> Option<Bounds<Pixels>>;
1799
1800 fn character_index_for_point(
1804 &mut self,
1805 point: Point<Pixels>,
1806 window: &mut Window,
1807 cx: &mut App,
1808 ) -> Option<usize>;
1809
1810 fn set_selected_text_range(
1820 &mut self,
1821 _range_utf16: Range<usize>,
1822 _window: &mut Window,
1823 _cx: &mut App,
1824 ) {
1825 }
1826
1827 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1834 None
1835 }
1836
1837 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1839 None
1840 }
1841
1842 #[allow(dead_code)]
1847 fn apple_press_and_hold_enabled(&mut self) -> bool {
1848 true
1849 }
1850
1851 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1853 true
1854 }
1855
1856 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1865 false
1866 }
1867}
1868
1869#[derive(Debug)]
1871pub struct WindowOptions {
1872 pub window_bounds: Option<WindowBounds>,
1876
1877 pub titlebar: Option<TitlebarOptions>,
1879
1880 pub focus: bool,
1882
1883 pub show: bool,
1885
1886 pub kind: WindowKind,
1888
1889 pub is_movable: bool,
1893
1894 pub app_owns_titlebar_drag: bool,
1906
1907 pub is_resizable: bool,
1909
1910 pub is_minimizable: bool,
1912
1913 pub display_id: Option<DisplayId>,
1916
1917 pub window_background: WindowBackgroundAppearance,
1919
1920 pub app_id: Option<String>,
1922
1923 pub window_min_size: Option<Size<Pixels>>,
1925
1926 pub window_decorations: Option<WindowDecorations>,
1929
1930 pub icon: Option<Arc<image::RgbaImage>>,
1932
1933 pub tabbing_identifier: Option<String>,
1935}
1936
1937#[derive(Debug)]
1939#[cfg_attr(
1940 all(
1941 any(target_os = "linux", target_os = "freebsd"),
1942 not(any(feature = "x11", feature = "wayland"))
1943 ),
1944 allow(dead_code)
1945)]
1946#[allow(missing_docs)]
1947pub struct WindowParams {
1948 pub bounds: Bounds<Pixels>,
1949
1950 #[cfg_attr(feature = "wayland", allow(dead_code))]
1952 pub titlebar: Option<TitlebarOptions>,
1953
1954 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1956 pub kind: WindowKind,
1957
1958 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1960 pub is_movable: bool,
1961
1962 #[cfg_attr(
1964 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1965 allow(dead_code)
1966 )]
1967 pub app_owns_titlebar_drag: bool,
1968
1969 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1971 pub is_resizable: bool,
1972
1973 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1975 pub is_minimizable: bool,
1976
1977 #[cfg_attr(
1978 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1979 allow(dead_code)
1980 )]
1981 pub focus: bool,
1982
1983 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1984 pub show: bool,
1985
1986 #[cfg_attr(feature = "wayland", allow(dead_code))]
1988 pub icon: Option<Arc<image::RgbaImage>>,
1989
1990 #[cfg_attr(feature = "wayland", allow(dead_code))]
1991 pub display_id: Option<DisplayId>,
1992
1993 #[cfg_attr(feature = "wayland", allow(dead_code))]
1994 pub app_id: Option<String>,
1995
1996 pub window_min_size: Option<Size<Pixels>>,
1997
1998 #[cfg(target_os = "macos")]
1999 pub tabbing_identifier: Option<String>,
2000}
2001
2002#[derive(Debug, Copy, Clone, PartialEq)]
2004pub enum WindowBounds {
2005 Windowed(Bounds<Pixels>),
2007 Maximized(Bounds<Pixels>),
2010 Fullscreen(Bounds<Pixels>),
2013}
2014
2015impl Default for WindowBounds {
2016 fn default() -> Self {
2017 WindowBounds::Windowed(Bounds::default())
2018 }
2019}
2020
2021impl WindowBounds {
2022 pub fn get_bounds(&self) -> Bounds<Pixels> {
2024 match self {
2025 WindowBounds::Windowed(bounds) => *bounds,
2026 WindowBounds::Maximized(bounds) => *bounds,
2027 WindowBounds::Fullscreen(bounds) => *bounds,
2028 }
2029 }
2030
2031 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2033 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2034 }
2035}
2036
2037impl Default for WindowOptions {
2038 fn default() -> Self {
2039 Self {
2040 window_bounds: None,
2041 titlebar: Some(TitlebarOptions {
2042 title: Default::default(),
2043 appears_transparent: Default::default(),
2044 traffic_light_position: Default::default(),
2045 }),
2046 focus: true,
2047 show: true,
2048 kind: WindowKind::Normal,
2049 is_movable: true,
2050 app_owns_titlebar_drag: false,
2051 is_resizable: true,
2052 is_minimizable: true,
2053 display_id: None,
2054 window_background: WindowBackgroundAppearance::default(),
2055 icon: None,
2056 app_id: None,
2057 window_min_size: None,
2058 window_decorations: None,
2059 tabbing_identifier: None,
2060 }
2061 }
2062}
2063
2064#[derive(Debug, Default)]
2066pub struct TitlebarOptions {
2067 pub title: Option<SharedString>,
2069
2070 pub appears_transparent: bool,
2073
2074 pub traffic_light_position: Option<Point<Pixels>>,
2076}
2077
2078#[derive(Clone, Debug, PartialEq, Eq)]
2080pub enum WindowKind {
2081 Normal,
2083
2084 PopUp,
2087
2088 AnchoredPopup(popup::PopupOptions),
2095
2096 Floating,
2098
2099 #[cfg(all(target_os = "linux", feature = "wayland"))]
2102 LayerShell(layer_shell::LayerShellOptions),
2103
2104 Dialog,
2107}
2108
2109#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2114pub enum WindowAppearance {
2115 #[default]
2119 Light,
2120
2121 VibrantLight,
2125
2126 Dark,
2130
2131 VibrantDark,
2135}
2136
2137#[derive(Copy, Clone, Debug, Default, PartialEq)]
2140pub enum WindowBackgroundAppearance {
2141 #[default]
2149 Opaque,
2150 Transparent,
2152 Blurred,
2156 MicaBackdrop,
2158 MicaAltBackdrop,
2160}
2161
2162#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2164pub enum TextRenderingMode {
2165 #[default]
2167 PlatformDefault,
2168 Subpixel,
2170 Grayscale,
2172}
2173
2174#[derive(Clone, Debug)]
2176pub struct PathPromptOptions {
2177 pub files: bool,
2179 pub directories: bool,
2181 pub multiple: bool,
2183 pub prompt: Option<SharedString>,
2185}
2186
2187#[derive(Copy, Clone, Debug, PartialEq)]
2189pub enum PromptLevel {
2190 Info,
2192
2193 Warning,
2195
2196 Critical,
2198}
2199
2200#[derive(Clone, Debug, PartialEq)]
2202pub enum PromptButton {
2203 Ok(SharedString),
2205 Cancel(SharedString),
2207 Other(SharedString),
2209}
2210
2211impl PromptButton {
2212 pub fn new(label: impl Into<SharedString>) -> Self {
2214 PromptButton::Other(label.into())
2215 }
2216
2217 pub fn ok(label: impl Into<SharedString>) -> Self {
2219 PromptButton::Ok(label.into())
2220 }
2221
2222 pub fn cancel(label: impl Into<SharedString>) -> Self {
2224 PromptButton::Cancel(label.into())
2225 }
2226
2227 #[allow(dead_code)]
2229 pub fn is_cancel(&self) -> bool {
2230 matches!(self, PromptButton::Cancel(_))
2231 }
2232
2233 pub fn label(&self) -> &SharedString {
2235 match self {
2236 PromptButton::Ok(label) => label,
2237 PromptButton::Cancel(label) => label,
2238 PromptButton::Other(label) => label,
2239 }
2240 }
2241}
2242
2243impl From<&str> for PromptButton {
2244 fn from(value: &str) -> Self {
2245 match value.to_lowercase().as_str() {
2246 "ok" => PromptButton::Ok("OK".into()),
2247 "cancel" => PromptButton::Cancel("Cancel".into()),
2248 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2249 }
2250 }
2251}
2252
2253#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2255pub enum CursorStyle {
2256 #[default]
2258 Arrow,
2259
2260 IBeam,
2263
2264 Crosshair,
2267
2268 ClosedHand,
2271
2272 OpenHand,
2275
2276 PointingHand,
2279
2280 ResizeLeft,
2283
2284 ResizeRight,
2287
2288 ResizeLeftRight,
2291
2292 ResizeUp,
2295
2296 ResizeDown,
2299
2300 ResizeUpDown,
2303
2304 ResizeUpLeftDownRight,
2307
2308 ResizeUpRightDownLeft,
2311
2312 ResizeColumn,
2315
2316 ResizeRow,
2319
2320 IBeamCursorForVerticalLayout,
2323
2324 OperationNotAllowed,
2327
2328 DragLink,
2331
2332 DragCopy,
2335
2336 ContextualMenu,
2339}
2340
2341#[derive(Clone, Debug, Eq, PartialEq)]
2343pub struct ClipboardItem {
2344 pub entries: Vec<ClipboardEntry>,
2346}
2347
2348#[derive(Clone, Debug, Eq, PartialEq)]
2350pub enum ClipboardEntry {
2351 String(ClipboardString),
2353 Image(Image),
2355 ExternalPaths(crate::ExternalPaths),
2357}
2358
2359impl ClipboardItem {
2360 pub fn new_string(text: String) -> Self {
2362 Self {
2363 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2364 }
2365 }
2366
2367 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2369 Self {
2370 entries: vec![ClipboardEntry::String(ClipboardString {
2371 text,
2372 metadata: Some(metadata),
2373 })],
2374 }
2375 }
2376
2377 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2379 Self {
2380 entries: vec![ClipboardEntry::String(
2381 ClipboardString::new(text).with_json_metadata(metadata),
2382 )],
2383 }
2384 }
2385
2386 pub fn new_image(image: &Image) -> Self {
2388 Self {
2389 entries: vec![ClipboardEntry::Image(image.clone())],
2390 }
2391 }
2392
2393 pub fn text(&self) -> Option<String> {
2396 let mut answer = String::new();
2397
2398 for entry in self.entries.iter() {
2399 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2400 answer.push_str(text);
2401 }
2402 }
2403
2404 if answer.is_empty() {
2405 for entry in self.entries.iter() {
2406 if let ClipboardEntry::ExternalPaths(paths) = entry {
2407 for path in &paths.0 {
2408 use std::fmt::Write as _;
2409 _ = write!(answer, "{}", path.display());
2410 }
2411 }
2412 }
2413 }
2414
2415 if !answer.is_empty() {
2416 Some(answer)
2417 } else {
2418 None
2419 }
2420 }
2421
2422 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2424 pub fn metadata(&self) -> Option<&String> {
2425 match self.entries().first() {
2426 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2427 clipboard_string.metadata.as_ref()
2428 }
2429 _ => None,
2430 }
2431 }
2432
2433 pub fn entries(&self) -> &[ClipboardEntry] {
2435 &self.entries
2436 }
2437
2438 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2440 self.entries.into_iter()
2441 }
2442}
2443
2444impl From<ClipboardString> for ClipboardEntry {
2445 fn from(value: ClipboardString) -> Self {
2446 Self::String(value)
2447 }
2448}
2449
2450impl From<String> for ClipboardEntry {
2451 fn from(value: String) -> Self {
2452 Self::from(ClipboardString::from(value))
2453 }
2454}
2455
2456impl From<Image> for ClipboardEntry {
2457 fn from(value: Image) -> Self {
2458 Self::Image(value)
2459 }
2460}
2461
2462impl From<ClipboardEntry> for ClipboardItem {
2463 fn from(value: ClipboardEntry) -> Self {
2464 Self {
2465 entries: vec![value],
2466 }
2467 }
2468}
2469
2470impl From<String> for ClipboardItem {
2471 fn from(value: String) -> Self {
2472 Self::from(ClipboardEntry::from(value))
2473 }
2474}
2475
2476impl From<Image> for ClipboardItem {
2477 fn from(value: Image) -> Self {
2478 Self::from(ClipboardEntry::from(value))
2479 }
2480}
2481
2482#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2484pub enum ImageFormat {
2485 Png,
2490 Jpeg,
2492 Webp,
2494 Gif,
2496 Svg,
2498 Bmp,
2500 Tiff,
2502 Ico,
2504 Pnm,
2506}
2507
2508impl ImageFormat {
2509 pub const fn mime_type(self) -> &'static str {
2511 match self {
2512 ImageFormat::Png => "image/png",
2513 ImageFormat::Jpeg => "image/jpeg",
2514 ImageFormat::Webp => "image/webp",
2515 ImageFormat::Gif => "image/gif",
2516 ImageFormat::Svg => "image/svg+xml",
2517 ImageFormat::Bmp => "image/bmp",
2518 ImageFormat::Tiff => "image/tiff",
2519 ImageFormat::Ico => "image/ico",
2520 ImageFormat::Pnm => "image/x-portable-anymap",
2521 }
2522 }
2523
2524 pub const fn extension(self) -> &'static str {
2526 match self {
2527 ImageFormat::Png => "png",
2528 ImageFormat::Jpeg => "jpg",
2529 ImageFormat::Webp => "webp",
2530 ImageFormat::Gif => "gif",
2531 ImageFormat::Svg => "svg",
2532 ImageFormat::Bmp => "bmp",
2533 ImageFormat::Tiff => "tiff",
2534 ImageFormat::Ico => "ico",
2535 ImageFormat::Pnm => "pnm",
2536 }
2537 }
2538
2539 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2541 use strum::IntoEnumIterator;
2542 Self::iter()
2543 .find(|format| format.mime_type() == mime_type)
2544 .or_else(|| Self::from_mime_type_alias(mime_type))
2545 }
2546
2547 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2551 match mime_type {
2552 "image/jpg" => Some(Self::Jpeg),
2553 "image/tif" => Some(Self::Tiff),
2554 _ => None,
2555 }
2556 }
2557}
2558
2559#[derive(Clone, Debug, PartialEq, Eq)]
2561pub struct Image {
2562 pub format: ImageFormat,
2564 pub bytes: Vec<u8>,
2566 pub id: u64,
2568}
2569
2570pub(crate) fn decode_static_image(
2571 bytes: &[u8],
2572 format: image::ImageFormat,
2573) -> Result<SmallVec<[Frame; 1]>> {
2574 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2575 .into_decoder()
2576 .context("creating image decoder")?;
2577 decode_static_image_from_decoder(decoder)
2578}
2579
2580pub(crate) fn decode_static_image_from_decoder(
2581 mut decoder: impl image::ImageDecoder,
2582) -> Result<SmallVec<[Frame; 1]>> {
2583 let orientation = decoder
2584 .orientation()
2585 .context("reading decoder's orientation")?;
2586 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2587 image.apply_orientation(orientation);
2588
2589 let mut data = image.into_rgba8();
2590 for pixel in data.chunks_exact_mut(4) {
2591 pixel.swap(0, 2);
2592 }
2593
2594 Ok(SmallVec::from_elem(Frame::new(data), 1))
2595}
2596
2597impl Hash for Image {
2598 fn hash<H: Hasher>(&self, state: &mut H) {
2599 state.write_u64(self.id);
2600 }
2601}
2602
2603impl Image {
2604 pub fn empty() -> Self {
2606 Self::from_bytes(ImageFormat::Png, Vec::new())
2607 }
2608
2609 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2611 Self {
2612 id: hash(&bytes),
2613 format,
2614 bytes,
2615 }
2616 }
2617
2618 pub fn id(&self) -> u64 {
2620 self.id
2621 }
2622
2623 pub fn use_render_image(
2625 self: Arc<Self>,
2626 window: &mut Window,
2627 cx: &mut App,
2628 ) -> Option<Arc<RenderImage>> {
2629 ImageSource::Image(self)
2630 .use_data(None, window, cx)
2631 .and_then(|result| result.ok())
2632 }
2633
2634 pub fn get_render_image(
2636 self: Arc<Self>,
2637 window: &mut Window,
2638 cx: &mut App,
2639 ) -> Option<Arc<RenderImage>> {
2640 ImageSource::Image(self)
2641 .get_data(None, window, cx)
2642 .and_then(|result| result.ok())
2643 }
2644
2645 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2647 ImageSource::Image(self).remove_asset(cx);
2648 }
2649
2650 #[cfg(any(test, feature = "test-support"))]
2653 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2654 ImageSource::Image(self.clone()).is_asset_cached(cx)
2655 }
2656
2657 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2659 let frames = match self.format {
2660 ImageFormat::Gif => {
2661 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2662 let mut frames = SmallVec::new();
2663
2664 for frame in decoder.into_frames() {
2665 match frame {
2666 Ok(mut frame) => {
2667 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2669 pixel.swap(0, 2);
2670 }
2671 frames.push(frame);
2672 }
2673 Err(err) => {
2674 log::debug!("Skipping GIF frame due to decode error: {err}");
2675 }
2676 }
2677 }
2678
2679 if frames.is_empty() {
2680 anyhow::bail!("GIF could not be decoded: all frames failed");
2681 }
2682
2683 frames
2684 }
2685 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2686 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2687 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2688 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2689 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2690 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2691 ImageFormat::Svg => {
2692 return svg_renderer
2693 .render_single_frame(&self.bytes, 1.0)
2694 .map_err(Into::into);
2695 }
2696 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2697 };
2698
2699 Ok(Arc::new(RenderImage::new(frames)))
2700 }
2701
2702 pub fn format(&self) -> ImageFormat {
2704 self.format
2705 }
2706
2707 pub fn bytes(&self) -> &[u8] {
2709 self.bytes.as_slice()
2710 }
2711}
2712
2713#[derive(Clone, Debug, Eq, PartialEq)]
2715pub struct ClipboardString {
2716 pub text: String,
2718 pub metadata: Option<String>,
2720}
2721
2722impl ClipboardString {
2723 pub fn new(text: String) -> Self {
2725 Self {
2726 text,
2727 metadata: None,
2728 }
2729 }
2730
2731 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2734 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2735 self
2736 }
2737
2738 pub fn text(&self) -> &String {
2740 &self.text
2741 }
2742
2743 pub fn into_text(self) -> String {
2745 self.text
2746 }
2747
2748 pub fn metadata_json<T>(&self) -> Option<T>
2750 where
2751 T: for<'a> Deserialize<'a>,
2752 {
2753 self.metadata
2754 .as_ref()
2755 .and_then(|m| serde_json::from_str(m).ok())
2756 }
2757
2758 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2759 pub fn text_hash(text: &str) -> u64 {
2761 let mut hasher = SeaHasher::new();
2762 text.hash(&mut hasher);
2763 hasher.finish()
2764 }
2765}
2766
2767impl From<String> for ClipboardString {
2768 fn from(value: String) -> Self {
2769 Self {
2770 text: value,
2771 metadata: None,
2772 }
2773 }
2774}
2775
2776#[cfg(test)]
2777mod image_tests {
2778 use super::*;
2779 use std::sync::Arc;
2780
2781 #[test]
2782 fn test_image_to_image_data_applies_exif_orientation() {
2783 let image = Image::from_bytes(
2784 ImageFormat::Jpeg,
2785 include_bytes!("../examples/legacy/image/exif-orientation-rotate-180.jpg").to_vec(),
2786 );
2787
2788 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2789
2790 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2791
2792 let bytes = render_image.as_bytes(0).unwrap();
2793 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2794 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2795 }
2796
2797 #[test]
2798 fn test_svg_image_to_image_data_converts_to_bgra() {
2799 let image = Image::from_bytes(
2800 ImageFormat::Svg,
2801 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2802<rect width="1" height="1" fill="#38BDF8"/>
2803</svg>"##
2804 .to_vec(),
2805 );
2806
2807 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2808 let bytes = render_image.as_bytes(0).unwrap();
2809
2810 for pixel in bytes.chunks_exact(4) {
2811 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2812 }
2813 }
2814}
2815
2816#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2817mod tests {
2818 use super::*;
2819 use std::collections::HashSet;
2820
2821 #[test]
2822 fn test_window_button_layout_parse_standard() {
2823 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2824 assert_eq!(
2825 layout.left,
2826 [
2827 Some(WindowButton::Close),
2828 Some(WindowButton::Minimize),
2829 None
2830 ]
2831 );
2832 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2833 }
2834
2835 #[test]
2836 fn test_window_button_layout_parse_right_only() {
2837 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2838 assert_eq!(layout.left, [None, None, None]);
2839 assert_eq!(
2840 layout.right,
2841 [
2842 Some(WindowButton::Minimize),
2843 Some(WindowButton::Maximize),
2844 Some(WindowButton::Close)
2845 ]
2846 );
2847 }
2848
2849 #[test]
2850 fn test_window_button_layout_parse_left_only() {
2851 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2852 assert_eq!(
2853 layout.left,
2854 [
2855 Some(WindowButton::Close),
2856 Some(WindowButton::Minimize),
2857 Some(WindowButton::Maximize)
2858 ]
2859 );
2860 assert_eq!(layout.right, [None, None, None]);
2861 }
2862
2863 #[test]
2864 fn test_window_button_layout_parse_with_whitespace() {
2865 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2866 assert_eq!(
2867 layout.left,
2868 [
2869 Some(WindowButton::Close),
2870 Some(WindowButton::Minimize),
2871 None
2872 ]
2873 );
2874 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2875 }
2876
2877 #[test]
2878 fn test_window_button_layout_parse_empty() {
2879 let layout = WindowButtonLayout::parse("").unwrap();
2880 assert_eq!(layout.left, [None, None, None]);
2881 assert_eq!(layout.right, [None, None, None]);
2882 }
2883
2884 #[test]
2885 fn test_window_button_layout_parse_intentionally_empty() {
2886 let layout = WindowButtonLayout::parse(":").unwrap();
2887 assert_eq!(layout.left, [None, None, None]);
2888 assert_eq!(layout.right, [None, None, None]);
2889 }
2890
2891 #[test]
2892 fn test_window_button_layout_parse_invalid_buttons() {
2893 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
2894 assert_eq!(
2895 layout.left,
2896 [
2897 Some(WindowButton::Close),
2898 Some(WindowButton::Minimize),
2899 None
2900 ]
2901 );
2902 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2903 }
2904
2905 #[test]
2906 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
2907 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
2908 assert_eq!(
2909 layout.right,
2910 [
2911 Some(WindowButton::Close),
2912 Some(WindowButton::Minimize),
2913 None
2914 ]
2915 );
2916 assert_eq!(layout.format(), ":close,minimize");
2917 }
2918
2919 #[test]
2920 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
2921 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
2922 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2923 assert_eq!(
2924 layout.right,
2925 [
2926 Some(WindowButton::Maximize),
2927 Some(WindowButton::Minimize),
2928 None
2929 ]
2930 );
2931
2932 let button_ids: Vec<_> = layout
2933 .left
2934 .iter()
2935 .chain(layout.right.iter())
2936 .flatten()
2937 .map(WindowButton::id)
2938 .collect();
2939 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
2940 assert_eq!(unique_button_ids.len(), button_ids.len());
2941 assert_eq!(layout.format(), "close:maximize,minimize");
2942 }
2943
2944 #[test]
2945 fn test_window_button_layout_parse_gnome_style() {
2946 let layout = WindowButtonLayout::parse("close").unwrap();
2947 assert_eq!(layout.left, [None, None, None]);
2948 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
2949 }
2950
2951 #[test]
2952 fn test_window_button_layout_parse_elementary_style() {
2953 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
2954 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2955 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2956 }
2957
2958 #[test]
2959 fn test_window_button_layout_round_trip() {
2960 let cases = [
2961 "close:minimize,maximize",
2962 "minimize,maximize,close:",
2963 ":close",
2964 "close:",
2965 "close:maximize",
2966 ":",
2967 ];
2968
2969 for case in cases {
2970 let layout = WindowButtonLayout::parse(case).unwrap();
2971 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
2972 }
2973 }
2974
2975 #[test]
2976 fn test_window_button_layout_linux_default() {
2977 let layout = WindowButtonLayout::linux_default();
2978 assert_eq!(layout.left, [None, None, None]);
2979 assert_eq!(
2980 layout.right,
2981 [
2982 Some(WindowButton::Minimize),
2983 Some(WindowButton::Maximize),
2984 Some(WindowButton::Close)
2985 ]
2986 );
2987
2988 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
2989 assert_eq!(round_tripped, layout);
2990 }
2991
2992 #[test]
2993 fn test_window_button_layout_parse_all_invalid() {
2994 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
2995 }
2996}