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#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
82pub(crate) use test::*;
83
84#[cfg(any(test, feature = "test-support"))]
85pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
86
87#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
88pub use threaded_dispatcher::ThreadedDispatcher;
89
90#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
91pub use visual_test::VisualTestPlatform;
92
93#[cfg(any(target_os = "linux", target_os = "freebsd"))]
97#[inline]
98pub fn guess_compositor() -> &'static str {
99 if std::env::var_os("ZED_HEADLESS").is_some() {
100 return "Headless";
101 }
102
103 #[cfg(feature = "wayland")]
104 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
105 #[cfg(not(feature = "wayland"))]
106 let wayland_display: Option<std::ffi::OsString> = None;
107
108 #[cfg(feature = "x11")]
109 let x11_display = std::env::var_os("DISPLAY");
110 #[cfg(not(feature = "x11"))]
111 let x11_display: Option<std::ffi::OsString> = None;
112
113 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
114 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
115
116 if use_wayland {
117 "Wayland"
118 } else if use_x11 {
119 "X11"
120 } else {
121 "Headless"
122 }
123}
124
125#[expect(missing_docs)]
126pub trait Platform: 'static {
127 fn background_executor(&self) -> BackgroundExecutor;
128 fn foreground_executor(&self) -> ForegroundExecutor;
129 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
130
131 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
132 fn quit(&self);
133 fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
134 fn activate(&self, ignoring_other_apps: bool);
135 fn hide(&self);
136 fn hide_other_apps(&self);
137 fn unhide_other_apps(&self);
138
139 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
140 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
141 fn active_window(&self) -> Option<AnyWindowHandle>;
142 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
143 None
144 }
145
146 fn is_screen_capture_supported(&self) -> bool {
147 false
148 }
149
150 fn screen_capture_sources(
151 &self,
152 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
153 let (sources_tx, sources_rx) = oneshot::channel();
154 sources_tx
155 .send(Err(anyhow::anyhow!(
156 "gpui was compiled without the screen-capture feature"
157 )))
158 .ok();
159 sources_rx
160 }
161
162 fn open_window(
163 &self,
164 handle: AnyWindowHandle,
165 options: WindowParams,
166 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
167
168 fn window_appearance(&self) -> WindowAppearance;
170
171 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
180
181 fn button_layout(&self) -> Option<WindowButtonLayout> {
183 None
184 }
185
186 fn open_url(&self, url: &str);
187 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
188 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
189
190 fn prompt_for_paths(
191 &self,
192 options: PathPromptOptions,
193 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
194 fn prompt_for_new_path(
195 &self,
196 directory: &Path,
197 suggested_name: Option<&str>,
198 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
199 fn can_select_mixed_files_and_dirs(&self) -> bool;
200 fn reveal_path(&self, path: &Path);
201 fn open_with_system(&self, path: &Path);
202
203 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
204 fn on_reopen(&self, callback: Box<dyn FnMut()>);
205 fn on_system_wake(&self, callback: Box<dyn FnMut()>);
206
207 fn on_app_lifecycle(&self, _callback: Box<dyn FnMut(AppLifecyclePhase)>) {}
217
218 fn on_memory_warning(&self, _callback: Box<dyn FnMut()>) {}
223
224 fn gestures(&self) -> Option<Rc<dyn PlatformGestures>> {
228 None
229 }
230
231 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
232 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
233 None
234 }
235
236 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
237 fn perform_dock_menu_action(&self, _action: usize) {}
238 fn add_recent_document(&self, _path: &Path) {}
239 fn update_jump_list(
240 &self,
241 _menus: Vec<MenuItem>,
242 _entries: Vec<SmallVec<[PathBuf; 2]>>,
243 ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
244 Task::ready(Vec::new())
245 }
246 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
247 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
248 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
249
250 fn thermal_state(&self) -> ThermalState;
251 fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
252
253 fn set_app_identity(&self, identifier: &str, name: &str) {
260 _ = (identifier, name);
261 }
262
263 fn show_system_notification(&self, notification: SystemNotification) {
270 _ = notification;
271 }
272
273 fn dismiss_system_notification(&self, tag: &str) {
278 _ = tag;
279 }
280
281 fn on_system_notification_response(
287 &self,
288 callback: Box<dyn FnMut(SystemNotificationResponse)>,
289 ) {
290 _ = callback;
291 }
292
293 fn compositor_name(&self) -> &'static str {
294 ""
295 }
296 fn app_path(&self) -> Result<PathBuf>;
297 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
298
299 fn set_cursor_style(&self, style: CursorStyle);
300
301 fn hide_cursor_until_mouse_moves(&self);
304
305 fn is_cursor_visible(&self) -> bool;
307
308 fn should_auto_hide_scrollbars(&self) -> bool;
309
310 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
311 fn write_to_clipboard(&self, item: ClipboardItem);
312
313 fn read_from_clipboard_async(&self) -> Task<Result<Option<ClipboardItem>, ClipboardReadError>> {
321 Task::ready(Ok(self.read_from_clipboard()))
322 }
323
324 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
325 fn read_from_primary(&self) -> Option<ClipboardItem>;
326 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
327 fn write_to_primary(&self, item: ClipboardItem);
328
329 #[cfg(target_os = "macos")]
330 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
331 #[cfg(target_os = "macos")]
332 fn write_to_find_pasteboard(&self, item: ClipboardItem);
333
334 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
335 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
336 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
337
338 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
339 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
340 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
341}
342
343pub trait PlatformDisplay: Debug {
345 fn id(&self) -> DisplayId;
347
348 fn uuid(&self) -> Result<Uuid>;
351
352 fn bounds(&self) -> Bounds<Pixels>;
354
355 fn visible_bounds(&self) -> Bounds<Pixels> {
359 self.bounds()
360 }
361
362 fn default_bounds(&self) -> Bounds<Pixels> {
364 let bounds = self.bounds();
365 let center = bounds.center();
366 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
367
368 let offset = clipped_window_size / 2.0;
369 let origin = point(center.x - offset.width, center.y - offset.height);
370 Bounds::new(origin, clipped_window_size)
371 }
372}
373
374#[derive(Clone, Debug, PartialEq, Eq)]
377pub struct SystemNotification {
378 pub tag: SharedString,
382 pub title: SharedString,
384 pub body: SharedString,
386 pub actions: Vec<SystemNotificationAction>,
389}
390
391#[derive(Clone, Debug, PartialEq, Eq, Hash)]
393pub struct SystemNotificationAction {
394 pub id: SharedString,
397 pub label: SharedString,
399}
400
401#[derive(Clone, Debug, PartialEq, Eq)]
403pub struct SystemNotificationResponse {
404 pub tag: SharedString,
406 pub action_id: Option<SharedString>,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum ThermalState {
414 Nominal,
416 Fair,
418 Serious,
420 Critical,
422}
423
424#[derive(Clone)]
426pub struct SourceMetadata {
427 pub id: u64,
429 pub label: Option<SharedString>,
431 pub is_main: Option<bool>,
433 pub resolution: Size<DevicePixels>,
435}
436
437pub trait ScreenCaptureSource {
439 fn metadata(&self) -> Result<SourceMetadata>;
441
442 fn stream(
445 &self,
446 foreground_executor: &ForegroundExecutor,
447 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
448 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
449}
450
451pub trait ScreenCaptureStream {
453 fn metadata(&self) -> Result<SourceMetadata>;
455}
456
457pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
459
460#[derive(PartialEq, Eq, Hash, Copy, Clone)]
462pub struct DisplayId(pub(crate) u64);
463
464impl DisplayId {
465 pub fn new(id: u64) -> Self {
467 Self(id)
468 }
469}
470
471impl From<u64> for DisplayId {
472 fn from(id: u64) -> Self {
473 Self(id)
474 }
475}
476
477impl From<DisplayId> for u64 {
478 fn from(id: DisplayId) -> Self {
479 id.0
480 }
481}
482
483impl Debug for DisplayId {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 write!(f, "DisplayId({})", self.0)
486 }
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
491pub enum ResizeEdge {
492 Top,
494 TopRight,
496 Right,
498 BottomRight,
500 Bottom,
502 BottomLeft,
504 Left,
506 TopLeft,
508}
509
510#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
512pub enum WindowDecorations {
513 #[default]
514 Server,
516 Client,
518}
519
520#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
522pub enum Decorations {
523 #[default]
525 Server,
526 Client {
528 tiling: Tiling,
530 },
531}
532
533#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
535pub struct WindowControls {
536 pub fullscreen: bool,
538 pub maximize: bool,
540 pub minimize: bool,
542 pub window_menu: bool,
544}
545
546impl Default for WindowControls {
547 fn default() -> Self {
548 Self {
550 fullscreen: true,
551 maximize: true,
552 minimize: true,
553 window_menu: true,
554 }
555 }
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
560pub enum WindowButton {
561 Minimize,
563 Maximize,
565 Close,
567}
568
569impl WindowButton {
570 pub fn id(&self) -> &'static str {
572 match self {
573 WindowButton::Minimize => "minimize",
574 WindowButton::Maximize => "maximize",
575 WindowButton::Close => "close",
576 }
577 }
578
579 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
580 fn index(&self) -> usize {
581 match self {
582 WindowButton::Minimize => 0,
583 WindowButton::Maximize => 1,
584 WindowButton::Close => 2,
585 }
586 }
587}
588
589pub const MAX_BUTTONS_PER_SIDE: usize = 3;
591
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub struct WindowButtonLayout {
598 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
600 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
602}
603
604#[cfg(any(target_os = "linux", target_os = "freebsd"))]
605impl WindowButtonLayout {
606 pub fn linux_default() -> Self {
608 Self {
609 left: [None; MAX_BUTTONS_PER_SIDE],
610 right: [
611 Some(WindowButton::Minimize),
612 Some(WindowButton::Maximize),
613 Some(WindowButton::Close),
614 ],
615 }
616 }
617
618 pub fn parse(layout_string: &str) -> Result<Self> {
620 fn parse_side(
621 s: &str,
622 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
623 unrecognized: &mut Vec<String>,
624 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
625 let mut result = [None; MAX_BUTTONS_PER_SIDE];
626 let mut i = 0;
627 for name in s.split(',') {
628 let trimmed = name.trim();
629 if trimmed.is_empty() {
630 continue;
631 }
632 let button = match trimmed {
633 "minimize" => Some(WindowButton::Minimize),
634 "maximize" => Some(WindowButton::Maximize),
635 "close" => Some(WindowButton::Close),
636 other => {
637 unrecognized.push(other.to_string());
638 None
639 }
640 };
641 if let Some(button) = button {
642 if seen_buttons[button.index()] {
643 continue;
644 }
645 if let Some(slot) = result.get_mut(i) {
646 *slot = Some(button);
647 seen_buttons[button.index()] = true;
648 i += 1;
649 }
650 }
651 }
652 result
653 }
654
655 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
656 let mut unrecognized = Vec::new();
657 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
658 let layout = Self {
659 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
660 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
661 };
662
663 if !unrecognized.is_empty()
664 && layout.left.iter().all(Option::is_none)
665 && layout.right.iter().all(Option::is_none)
666 {
667 bail!(
668 "button layout string {:?} contains no valid buttons (unrecognized: {})",
669 layout_string,
670 unrecognized.join(", ")
671 );
672 }
673
674 Ok(layout)
675 }
676
677 #[cfg(test)]
679 pub fn format(&self) -> String {
680 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
681 buttons
682 .iter()
683 .flatten()
684 .map(|button| match button {
685 WindowButton::Minimize => "minimize",
686 WindowButton::Maximize => "maximize",
687 WindowButton::Close => "close",
688 })
689 .collect::<Vec<_>>()
690 .join(",")
691 }
692
693 format!("{}:{}", format_side(&self.left), format_side(&self.right))
694 }
695}
696
697#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
699pub struct Tiling {
700 pub top: bool,
702 pub left: bool,
704 pub right: bool,
706 pub bottom: bool,
708}
709
710impl Tiling {
711 pub fn tiled() -> Self {
713 Self {
714 top: true,
715 left: true,
716 right: true,
717 bottom: true,
718 }
719 }
720
721 pub fn is_tiled(&self) -> bool {
723 self.top || self.left || self.right || self.bottom
724 }
725}
726
727pub struct A11yCallbacks {
729 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
731 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
733 pub deactivation: Box<dyn Fn() + Send + 'static>,
735}
736
737#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
738#[expect(missing_docs)]
739pub struct RequestFrameOptions {
740 pub require_presentation: bool,
742 pub force_render: bool,
744}
745
746#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
759pub enum AppLifecyclePhase {
760 Active,
762 Inactive,
764 Background,
767 Foreground,
769}
770
771#[derive(Debug, Clone, Default, PartialEq)]
778pub struct WindowInsets {
779 pub safe_area: Edges<Pixels>,
784 pub ime: Edges<Pixels>,
788}
789
790impl WindowInsets {
791 pub fn effective(&self) -> Edges<Pixels> {
793 Edges {
794 top: self.safe_area.top.max(self.ime.top),
795 right: self.safe_area.right.max(self.ime.right),
796 bottom: self.safe_area.bottom.max(self.ime.bottom),
797 left: self.safe_area.left.max(self.ime.left),
798 }
799 }
800}
801
802#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
804pub enum TextInputStateChange {
805 FocusGained,
807 FocusLost,
809 SelectionChanged,
811 ContentChanged,
813}
814
815#[expect(missing_docs)]
816pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
817 fn bounds(&self) -> Bounds<Pixels>;
818 fn is_maximized(&self) -> bool;
819 fn window_bounds(&self) -> WindowBounds;
820 fn content_size(&self) -> Size<Pixels>;
821 fn resize(&mut self, size: Size<Pixels>);
822 fn scale_factor(&self) -> f32;
823 fn appearance(&self) -> WindowAppearance;
824 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
825 fn mouse_position(&self) -> Point<Pixels>;
826 fn modifiers(&self) -> Modifiers;
827 fn capslock(&self) -> Capslock;
828 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
829 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
830 fn prompt(
831 &self,
832 level: PromptLevel,
833 msg: &str,
834 detail: Option<&str>,
835 answers: &[PromptButton],
836 ) -> Option<oneshot::Receiver<usize>>;
837 fn activate(&self);
838 fn request_attention(&self) {}
840 fn is_active(&self) -> bool;
841 fn is_hovered(&self) -> bool;
842 fn background_appearance(&self) -> WindowBackgroundAppearance;
843 fn set_title(&mut self, title: &str);
844 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
845 fn minimize(&self);
846 fn zoom(&self);
847 fn toggle_fullscreen(&self);
848 fn is_fullscreen(&self) -> bool;
849 fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
850 None
851 }
852 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
853 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
854 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
855 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
856 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
857 fn on_moved(&self, callback: Box<dyn FnMut()>);
858 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
859 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
860 fn on_close(&self, callback: Box<dyn FnOnce()>);
861 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
862 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
863 fn draw(&self, scene: &Scene);
864 fn schedule_frame(&self) {}
865 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
866 fn is_subpixel_rendering_supported(&self) -> bool;
867
868 fn get_title(&self) -> String {
870 String::new()
871 }
872 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
873 None
874 }
875 fn tab_bar_visible(&self) -> bool {
876 false
877 }
878 fn set_edited(&mut self, _edited: bool) {}
879 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
880 fn toggle_simple_fullscreen(&self) {}
881 fn is_simple_fullscreen(&self) -> bool {
882 false
883 }
884 #[cfg(target_os = "macos")]
885 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
886 fn show_character_palette(&self) {}
887 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
888 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
889 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
890 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
891 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
892 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
893 fn merge_all_windows(&self) {}
894 fn move_tab_to_new_window(&self) {}
895 fn toggle_window_tab_overview(&self) {}
896 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
897
898 #[cfg(target_os = "windows")]
899 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
900
901 fn inner_window_bounds(&self) -> WindowBounds {
903 self.window_bounds()
904 }
905 fn request_decorations(&self, _decorations: WindowDecorations) {}
906 fn show_window_menu(&self, _position: Point<Pixels>) {}
907 fn start_window_move(&self) {}
908 fn can_start_external_drag(&self) -> bool {
909 false
910 }
911 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
912 false
913 }
914 fn start_window_resize(&self, _edge: ResizeEdge) {}
915 fn set_exclusive_zone(&self, _zone: Pixels) {}
916 #[cfg(all(target_os = "linux", feature = "wayland"))]
917 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
918 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
919 fn window_decorations(&self) -> Decorations {
920 Decorations::Server
921 }
922 fn set_app_id(&mut self, _app_id: &str) {}
923 fn map_window(&mut self) -> anyhow::Result<()> {
924 Ok(())
925 }
926 fn window_controls(&self) -> WindowControls {
927 WindowControls::default()
928 }
929 fn set_client_inset(&self, _inset: Pixels) {}
930 fn gpu_specs(&self) -> Option<GpuSpecs>;
931
932 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
933
934 fn insets(&self) -> WindowInsets {
939 WindowInsets::default()
940 }
941
942 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
948
949 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
952
953 fn set_back_enabled(&self, _enabled: bool) {}
956
957 fn show_soft_keyboard(&self) {}
959
960 fn hide_soft_keyboard(&self) {}
962
963 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
965
966 fn play_system_bell(&self) {}
967
968 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
970
971 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
973
974 fn a11y_update_window_bounds(&self) {}
976
977 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
978 fn as_test(&mut self) -> Option<&mut TestWindow> {
979 None
980 }
981
982 #[cfg(any(test, feature = "test-support"))]
986 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
987 anyhow::bail!("render_to_image not implemented for this platform")
988 }
989}
990
991#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
993pub trait PlatformHeadlessRenderer {
994 fn render_scene_to_image(
996 &mut self,
997 scene: &Scene,
998 size: Size<DevicePixels>,
999 ) -> Result<RgbaImage>;
1000
1001 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1007
1008 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1010}
1011
1012#[doc(hidden)]
1015pub type RunnableVariant = Runnable<RunnableMeta>;
1016
1017#[doc(hidden)]
1018pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1019
1020#[doc(hidden)]
1021pub enum TasksIncluded {
1022 OnlyCompleted,
1023 CompletedAndRunning,
1024}
1025
1026#[doc(hidden)]
1029pub trait PlatformDispatcher: Send + Sync {
1030 fn is_main_thread(&self) -> bool;
1031 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1032 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1033 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1034
1035 fn dispatch_on_main_thread_when_idle(
1036 &self,
1037 runnable: RunnableVariant,
1038 timeout: Option<Duration>,
1039 ) {
1040 let _ = timeout;
1041 self.dispatch_on_main_thread(runnable, Priority::Low);
1042 }
1043
1044 fn idle_time_remaining(&self) -> Option<Duration> {
1045 None
1046 }
1047
1048 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1049
1050 fn now(&self) -> Instant {
1051 Instant::now()
1052 }
1053
1054 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1055 gpui_util::defer(Box::new(|| {}))
1056 }
1057
1058 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1059 fn as_test(&self) -> Option<&TestDispatcher> {
1060 None
1061 }
1062
1063 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1066 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1067 None
1068 }
1069}
1070
1071#[expect(missing_docs)]
1072pub trait PlatformTextSystem: Send + Sync {
1073 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1074 fn all_font_names(&self) -> Vec<String>;
1076 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1078 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1080 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1082 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1084 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1086 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1088 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1090 fn rasterize_glyph(
1092 &self,
1093 params: &RenderGlyphParams,
1094 raster_bounds: Bounds<DevicePixels>,
1095 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1096 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1098 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1100 -> TextRenderingMode;
1101 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1103 0
1104 }
1105}
1106
1107#[expect(missing_docs)]
1108pub struct NoopTextSystem;
1109
1110#[expect(missing_docs)]
1111impl NoopTextSystem {
1112 #[allow(dead_code)]
1113 pub fn new() -> Self {
1114 Self
1115 }
1116}
1117
1118impl PlatformTextSystem for NoopTextSystem {
1119 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1120 Ok(())
1121 }
1122
1123 fn all_font_names(&self) -> Vec<String> {
1124 Vec::new()
1125 }
1126
1127 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1128 Ok(FontId(1))
1129 }
1130
1131 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1132 FontMetrics {
1133 units_per_em: 1000,
1134 ascent: 1025.0,
1135 descent: -275.0,
1136 line_gap: 0.0,
1137 underline_position: -95.0,
1138 underline_thickness: 60.0,
1139 cap_height: 698.0,
1140 x_height: 516.0,
1141 bounding_box: Bounds {
1142 origin: Point {
1143 x: -260.0,
1144 y: -245.0,
1145 },
1146 size: Size {
1147 width: 1501.0,
1148 height: 1364.0,
1149 },
1150 },
1151 }
1152 }
1153
1154 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1155 Ok(Bounds {
1156 origin: Point { x: 54.0, y: 0.0 },
1157 size: size(392.0, 528.0),
1158 })
1159 }
1160
1161 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1162 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1163 }
1164
1165 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1166 Some(GlyphId(ch.len_utf16() as u32))
1167 }
1168
1169 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1170 Ok(Default::default())
1171 }
1172
1173 fn rasterize_glyph(
1174 &self,
1175 _params: &RenderGlyphParams,
1176 raster_bounds: Bounds<DevicePixels>,
1177 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1178 Ok((raster_bounds.size, Vec::new()))
1179 }
1180
1181 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1182 let mut position = px(0.);
1183 let metrics = self.font_metrics(FontId(0));
1184 let em_width = font_size
1185 * self
1186 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1187 .unwrap()
1188 .width
1189 / metrics.units_per_em as f32;
1190 let mut glyphs = Vec::new();
1191 for (ix, c) in text.char_indices() {
1192 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1193 glyphs.push(ShapedGlyph {
1194 id: glyph,
1195 position: point(position, px(0.)),
1196 index: ix,
1197 is_emoji: glyph.0 == 2,
1198 });
1199 if glyph.0 == 2 {
1200 position += em_width * 2.0;
1201 } else {
1202 position += em_width;
1203 }
1204 } else {
1205 position += em_width
1206 }
1207 }
1208 let mut runs = Vec::default();
1209 if !glyphs.is_empty() {
1210 runs.push(ShapedRun {
1211 font_id: FontId(0),
1212 glyphs,
1213 });
1214 } else {
1215 position = px(0.);
1216 }
1217
1218 LineLayout {
1219 font_size,
1220 width: position,
1221 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1222 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1223 runs,
1224 len: text.len(),
1225 }
1226 }
1227
1228 fn recommended_rendering_mode(
1229 &self,
1230 _font_id: FontId,
1231 _font_size: Pixels,
1232 ) -> TextRenderingMode {
1233 TextRenderingMode::Grayscale
1234 }
1235}
1236
1237#[allow(dead_code)]
1242pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1243 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1244 [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], ];
1258
1259 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1260 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1261
1262 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1263 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1264
1265 [
1266 ratios[0] * NORM13,
1267 ratios[1] * NORM24,
1268 ratios[2] * NORM13,
1269 ratios[3] * NORM24,
1270 ]
1271}
1272
1273#[derive(PartialEq, Eq, Hash, Clone)]
1274#[expect(missing_docs)]
1275pub enum AtlasKey {
1276 Glyph(RenderGlyphParams),
1277 Svg(RenderSvgParams),
1278 Image(RenderImageParams),
1279}
1280
1281impl AtlasKey {
1282 #[cfg_attr(
1283 all(
1284 any(target_os = "linux", target_os = "freebsd"),
1285 not(any(feature = "x11", feature = "wayland"))
1286 ),
1287 allow(dead_code)
1288 )]
1289 pub fn texture_kind(&self) -> AtlasTextureKind {
1291 match self {
1292 AtlasKey::Glyph(params) => {
1293 if params.is_emoji {
1294 AtlasTextureKind::Polychrome
1295 } else if params.subpixel_rendering {
1296 AtlasTextureKind::Subpixel
1297 } else {
1298 AtlasTextureKind::Monochrome
1299 }
1300 }
1301 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1302 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1303 }
1304 }
1305}
1306
1307impl From<RenderGlyphParams> for AtlasKey {
1308 fn from(params: RenderGlyphParams) -> Self {
1309 Self::Glyph(params)
1310 }
1311}
1312
1313impl From<RenderSvgParams> for AtlasKey {
1314 fn from(params: RenderSvgParams) -> Self {
1315 Self::Svg(params)
1316 }
1317}
1318
1319impl From<RenderImageParams> for AtlasKey {
1320 fn from(params: RenderImageParams) -> Self {
1321 Self::Image(params)
1322 }
1323}
1324
1325#[expect(missing_docs)]
1326pub trait PlatformAtlas {
1327 fn get_or_insert_with<'a>(
1328 &self,
1329 key: &AtlasKey,
1330 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1331 ) -> Result<Option<AtlasTile>>;
1332 fn remove(&self, key: &AtlasKey);
1333
1334 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1335 fn contains(&self, _key: &AtlasKey) -> bool {
1336 false
1337 }
1338}
1339
1340#[doc(hidden)]
1341pub struct AtlasTextureList<T> {
1342 pub textures: Vec<Option<T>>,
1343 pub free_list: Vec<usize>,
1344}
1345
1346impl<T> Default for AtlasTextureList<T> {
1347 fn default() -> Self {
1348 Self {
1349 textures: Vec::default(),
1350 free_list: Vec::default(),
1351 }
1352 }
1353}
1354
1355impl<T> ops::Index<usize> for AtlasTextureList<T> {
1356 type Output = Option<T>;
1357
1358 fn index(&self, index: usize) -> &Self::Output {
1359 &self.textures[index]
1360 }
1361}
1362
1363impl<T> AtlasTextureList<T> {
1364 #[allow(unused)]
1365 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1366 self.free_list.clear();
1367 self.textures.drain(..)
1368 }
1369
1370 #[allow(dead_code)]
1371 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1372 self.textures.iter_mut().flatten()
1373 }
1374}
1375
1376#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1377#[repr(C)]
1378#[expect(missing_docs)]
1379pub struct AtlasTile {
1380 pub texture_id: AtlasTextureId,
1382 pub tile_id: TileId,
1384 pub padding: u32,
1386 pub bounds: Bounds<DevicePixels>,
1388}
1389
1390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1391#[repr(C)]
1392#[expect(missing_docs)]
1393pub struct AtlasTextureId {
1394 pub index: u32,
1397 pub kind: AtlasTextureKind,
1399}
1400
1401#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1402#[repr(C)]
1403#[cfg_attr(
1404 all(
1405 any(target_os = "linux", target_os = "freebsd"),
1406 not(any(feature = "x11", feature = "wayland"))
1407 ),
1408 allow(dead_code)
1409)]
1410#[expect(missing_docs)]
1411pub enum AtlasTextureKind {
1412 Monochrome = 0,
1413 Polychrome = 1,
1414 Subpixel = 2,
1415}
1416
1417#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1418#[repr(C)]
1419#[expect(missing_docs)]
1420pub struct TileId(pub u32);
1421
1422impl From<etagere::AllocId> for TileId {
1423 fn from(id: etagere::AllocId) -> Self {
1424 Self(id.serialize())
1425 }
1426}
1427
1428impl From<TileId> for etagere::AllocId {
1429 fn from(id: TileId) -> Self {
1430 Self::deserialize(id.0)
1431 }
1432}
1433
1434#[expect(missing_docs)]
1435pub struct PlatformInputHandler {
1436 cx: AsyncWindowContext,
1437 handler: Box<dyn InputHandler>,
1438}
1439
1440#[expect(missing_docs)]
1441#[cfg_attr(
1442 all(
1443 any(target_os = "linux", target_os = "freebsd"),
1444 not(any(feature = "x11", feature = "wayland"))
1445 ),
1446 allow(dead_code)
1447)]
1448impl PlatformInputHandler {
1449 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1450 Self { cx, handler }
1451 }
1452
1453 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1454 self.cx
1455 .update(|window, cx| {
1456 self.handler
1457 .selected_text_range(ignore_disabled_input, window, cx)
1458 })
1459 .ok()
1460 .flatten()
1461 }
1462
1463 #[cfg_attr(target_os = "windows", allow(dead_code))]
1464 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1465 self.cx
1466 .update(|window, cx| self.handler.marked_text_range(window, cx))
1467 .ok()
1468 .flatten()
1469 }
1470
1471 #[cfg_attr(
1472 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1473 allow(dead_code)
1474 )]
1475 pub fn text_for_range(
1476 &mut self,
1477 range_utf16: Range<usize>,
1478 adjusted: &mut Option<Range<usize>>,
1479 ) -> Option<String> {
1480 self.cx
1481 .update(|window, cx| {
1482 self.handler
1483 .text_for_range(range_utf16, adjusted, window, cx)
1484 })
1485 .ok()
1486 .flatten()
1487 }
1488
1489 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1490 self.cx
1491 .update(|window, cx| {
1492 self.handler
1493 .replace_text_in_range(replacement_range, text, window, cx);
1494 })
1495 .ok();
1496 }
1497
1498 pub fn replace_and_mark_text_in_range(
1499 &mut self,
1500 range_utf16: Option<Range<usize>>,
1501 new_text: &str,
1502 new_selected_range: Option<Range<usize>>,
1503 ) {
1504 self.cx
1505 .update(|window, cx| {
1506 self.handler.replace_and_mark_text_in_range(
1507 range_utf16,
1508 new_text,
1509 new_selected_range,
1510 window,
1511 cx,
1512 )
1513 })
1514 .ok();
1515 }
1516
1517 #[cfg_attr(target_os = "windows", allow(dead_code))]
1518 pub fn unmark_text(&mut self) {
1519 self.cx
1520 .update(|window, cx| self.handler.unmark_text(window, cx))
1521 .ok();
1522 }
1523
1524 pub fn paste(&mut self, item: ClipboardItem) {
1525 self.cx
1526 .update(|window, cx| self.handler.paste(item, window, cx))
1527 .ok();
1528 }
1529
1530 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1531 self.cx
1532 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1533 .ok()
1534 .flatten()
1535 }
1536
1537 #[allow(dead_code)]
1538 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1539 self.handler.apple_press_and_hold_enabled()
1540 }
1541
1542 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1543 self.handler.replace_text_in_range(None, input, window, cx);
1544 }
1545
1546 pub fn compute_ime_candidate_bounds(
1547 marked_range: Option<Range<usize>>,
1548 selection: &UTF16Selection,
1549 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1550 ) -> Option<Bounds<Pixels>> {
1551 if let Some(marked_range) = marked_range {
1552 let mut line_start = marked_range.start;
1554
1555 let caret = selection.range.end;
1559 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1560 for i in (marked_range.start..caret).rev() {
1561 if let Some(b) = bounds_for_range(i..i) {
1562 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1563 line_start = i + 1;
1564 break;
1565 }
1566 }
1567 }
1568 }
1569 bounds_for_range(line_start..line_start)
1570 } else {
1571 let offset = if selection.reversed {
1573 selection.range.start
1574 } else {
1575 selection.range.end
1576 };
1577 bounds_for_range(offset..offset)
1578 }
1579 }
1580
1581 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1582 let marked_range = self.handler.marked_text_range(window, cx);
1583 let selection = self.handler.selected_text_range(true, window, cx)?;
1584 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1585 self.handler.bounds_for_range(range, window, cx)
1586 })
1587 }
1588
1589 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1590 let marked_range = self.marked_text_range();
1591 let selection = self.selected_text_range(true)?;
1592 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1593 self.bounds_for_range(range)
1594 })
1595 }
1596
1597 #[allow(unused)]
1598 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1599 self.cx
1600 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1601 .ok()
1602 .flatten()
1603 }
1604
1605 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1607 self.cx
1608 .update(|window, cx| {
1609 self.handler
1610 .set_selected_text_range(range_utf16, window, cx)
1611 })
1612 .ok();
1613 }
1614
1615 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1617 self.cx
1618 .update(|window, cx| self.handler.element_bounds(window, cx))
1619 .ok()
1620 .flatten()
1621 }
1622
1623 pub fn text_length_utf16(&mut self) -> Option<usize> {
1625 self.cx
1626 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1627 .ok()
1628 .flatten()
1629 }
1630
1631 #[allow(dead_code)]
1632 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1633 self.handler.accepts_text_input(window, cx)
1634 }
1635
1636 #[allow(dead_code)]
1637 pub fn query_accepts_text_input(&mut self) -> bool {
1638 self.cx
1639 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1640 .unwrap_or(true)
1641 }
1642
1643 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1649 self.cx
1650 .update(|window, cx| {
1651 !window.has_pending_keystrokes()
1653 && self.handler.prefers_ime_for_printable_keys(window, cx)
1654 })
1655 .unwrap_or(false)
1656 }
1657}
1658
1659#[derive(Debug)]
1662pub struct UTF16Selection {
1663 pub range: Range<usize>,
1666 pub reversed: bool,
1669}
1670
1671pub trait InputHandler: 'static {
1676 fn selected_text_range(
1681 &mut self,
1682 ignore_disabled_input: bool,
1683 window: &mut Window,
1684 cx: &mut App,
1685 ) -> Option<UTF16Selection>;
1686
1687 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1692
1693 fn text_for_range(
1698 &mut self,
1699 range_utf16: Range<usize>,
1700 adjusted_range: &mut Option<Range<usize>>,
1701 window: &mut Window,
1702 cx: &mut App,
1703 ) -> Option<String>;
1704
1705 fn replace_text_in_range(
1710 &mut self,
1711 replacement_range: Option<Range<usize>>,
1712 text: &str,
1713 window: &mut Window,
1714 cx: &mut App,
1715 );
1716
1717 fn replace_and_mark_text_in_range(
1724 &mut self,
1725 range_utf16: Option<Range<usize>>,
1726 new_text: &str,
1727 new_selected_range: Option<Range<usize>>,
1728 window: &mut Window,
1729 cx: &mut App,
1730 );
1731
1732 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1735
1736 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1743 if let Some(text) = item.text() {
1744 self.replace_text_in_range(None, &text, window, cx);
1745 }
1746 }
1747
1748 fn bounds_for_range(
1753 &mut self,
1754 range_utf16: Range<usize>,
1755 window: &mut Window,
1756 cx: &mut App,
1757 ) -> Option<Bounds<Pixels>>;
1758
1759 fn character_index_for_point(
1763 &mut self,
1764 point: Point<Pixels>,
1765 window: &mut Window,
1766 cx: &mut App,
1767 ) -> Option<usize>;
1768
1769 fn set_selected_text_range(
1779 &mut self,
1780 _range_utf16: Range<usize>,
1781 _window: &mut Window,
1782 _cx: &mut App,
1783 ) {
1784 }
1785
1786 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1793 None
1794 }
1795
1796 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1798 None
1799 }
1800
1801 #[allow(dead_code)]
1806 fn apple_press_and_hold_enabled(&mut self) -> bool {
1807 true
1808 }
1809
1810 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1812 true
1813 }
1814
1815 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1824 false
1825 }
1826}
1827
1828#[derive(Debug)]
1830pub struct WindowOptions {
1831 pub window_bounds: Option<WindowBounds>,
1835
1836 pub titlebar: Option<TitlebarOptions>,
1838
1839 pub focus: bool,
1841
1842 pub show: bool,
1844
1845 pub kind: WindowKind,
1847
1848 pub is_movable: bool,
1852
1853 pub app_owns_titlebar_drag: bool,
1865
1866 pub inactive_frame_interval: Option<Duration>,
1870
1871 pub is_resizable: bool,
1873
1874 pub is_minimizable: bool,
1876
1877 pub display_id: Option<DisplayId>,
1880
1881 pub window_background: WindowBackgroundAppearance,
1883
1884 pub app_id: Option<String>,
1886
1887 pub window_min_size: Option<Size<Pixels>>,
1889
1890 pub window_decorations: Option<WindowDecorations>,
1893
1894 pub icon: Option<Arc<image::RgbaImage>>,
1896
1897 pub tabbing_identifier: Option<String>,
1899}
1900
1901#[derive(Debug)]
1903#[cfg_attr(
1904 all(
1905 any(target_os = "linux", target_os = "freebsd"),
1906 not(any(feature = "x11", feature = "wayland"))
1907 ),
1908 allow(dead_code)
1909)]
1910#[allow(missing_docs)]
1911pub struct WindowParams {
1912 pub bounds: Bounds<Pixels>,
1913
1914 #[cfg_attr(feature = "wayland", allow(dead_code))]
1916 pub titlebar: Option<TitlebarOptions>,
1917
1918 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1920 pub kind: WindowKind,
1921
1922 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1924 pub is_movable: bool,
1925
1926 #[cfg_attr(
1928 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1929 allow(dead_code)
1930 )]
1931 pub app_owns_titlebar_drag: bool,
1932
1933 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1935 pub is_resizable: bool,
1936
1937 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1939 pub is_minimizable: bool,
1940
1941 #[cfg_attr(
1942 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1943 allow(dead_code)
1944 )]
1945 pub focus: bool,
1946
1947 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1948 pub show: bool,
1949
1950 #[cfg_attr(feature = "wayland", allow(dead_code))]
1952 pub icon: Option<Arc<image::RgbaImage>>,
1953
1954 #[cfg_attr(feature = "wayland", allow(dead_code))]
1955 pub display_id: Option<DisplayId>,
1956
1957 #[cfg_attr(feature = "wayland", allow(dead_code))]
1958 pub app_id: Option<String>,
1959
1960 pub window_min_size: Option<Size<Pixels>>,
1961
1962 #[cfg(target_os = "macos")]
1963 pub tabbing_identifier: Option<String>,
1964}
1965
1966#[derive(Debug, Copy, Clone, PartialEq)]
1968pub enum WindowBounds {
1969 Windowed(Bounds<Pixels>),
1971 Maximized(Bounds<Pixels>),
1974 Fullscreen(Bounds<Pixels>),
1977}
1978
1979impl Default for WindowBounds {
1980 fn default() -> Self {
1981 WindowBounds::Windowed(Bounds::default())
1982 }
1983}
1984
1985impl WindowBounds {
1986 pub fn get_bounds(&self) -> Bounds<Pixels> {
1988 match self {
1989 WindowBounds::Windowed(bounds) => *bounds,
1990 WindowBounds::Maximized(bounds) => *bounds,
1991 WindowBounds::Fullscreen(bounds) => *bounds,
1992 }
1993 }
1994
1995 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1997 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1998 }
1999}
2000
2001impl Default for WindowOptions {
2002 fn default() -> Self {
2003 Self {
2004 window_bounds: None,
2005 titlebar: Some(TitlebarOptions {
2006 title: Default::default(),
2007 appears_transparent: Default::default(),
2008 traffic_light_position: Default::default(),
2009 }),
2010 focus: true,
2011 show: true,
2012 kind: WindowKind::Normal,
2013 is_movable: true,
2014 app_owns_titlebar_drag: false,
2015 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2016 is_resizable: true,
2017 is_minimizable: true,
2018 display_id: None,
2019 window_background: WindowBackgroundAppearance::default(),
2020 icon: None,
2021 app_id: None,
2022 window_min_size: None,
2023 window_decorations: None,
2024 tabbing_identifier: None,
2025 }
2026 }
2027}
2028
2029#[derive(Debug, Default)]
2031pub struct TitlebarOptions {
2032 pub title: Option<SharedString>,
2034
2035 pub appears_transparent: bool,
2038
2039 pub traffic_light_position: Option<Point<Pixels>>,
2041}
2042
2043#[derive(Clone, Debug, PartialEq, Eq)]
2045pub enum WindowKind {
2046 Normal,
2048
2049 PopUp,
2052
2053 AnchoredPopup(popup::PopupOptions),
2060
2061 Floating,
2063
2064 #[cfg(all(target_os = "linux", feature = "wayland"))]
2067 LayerShell(layer_shell::LayerShellOptions),
2068
2069 Dialog,
2072}
2073
2074#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2079pub enum WindowAppearance {
2080 #[default]
2084 Light,
2085
2086 VibrantLight,
2090
2091 Dark,
2095
2096 VibrantDark,
2100}
2101
2102#[derive(Copy, Clone, Debug, Default, PartialEq)]
2105pub enum WindowBackgroundAppearance {
2106 #[default]
2114 Opaque,
2115 Transparent,
2117 Blurred,
2121 MicaBackdrop,
2123 MicaAltBackdrop,
2125}
2126
2127#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2129pub enum TextRenderingMode {
2130 #[default]
2132 PlatformDefault,
2133 Subpixel,
2135 Grayscale,
2137}
2138
2139#[derive(Clone, Debug)]
2141pub struct PathPromptOptions {
2142 pub files: bool,
2144 pub directories: bool,
2146 pub multiple: bool,
2148 pub prompt: Option<SharedString>,
2150}
2151
2152#[derive(Copy, Clone, Debug, PartialEq)]
2154pub enum PromptLevel {
2155 Info,
2157
2158 Warning,
2160
2161 Critical,
2163}
2164
2165#[derive(Clone, Debug, PartialEq)]
2167pub enum PromptButton {
2168 Ok(SharedString),
2170 Cancel(SharedString),
2172 Other(SharedString),
2174}
2175
2176impl PromptButton {
2177 pub fn new(label: impl Into<SharedString>) -> Self {
2179 PromptButton::Other(label.into())
2180 }
2181
2182 pub fn ok(label: impl Into<SharedString>) -> Self {
2184 PromptButton::Ok(label.into())
2185 }
2186
2187 pub fn cancel(label: impl Into<SharedString>) -> Self {
2189 PromptButton::Cancel(label.into())
2190 }
2191
2192 #[allow(dead_code)]
2194 pub fn is_cancel(&self) -> bool {
2195 matches!(self, PromptButton::Cancel(_))
2196 }
2197
2198 pub fn label(&self) -> &SharedString {
2200 match self {
2201 PromptButton::Ok(label) => label,
2202 PromptButton::Cancel(label) => label,
2203 PromptButton::Other(label) => label,
2204 }
2205 }
2206}
2207
2208impl From<&str> for PromptButton {
2209 fn from(value: &str) -> Self {
2210 match value.to_lowercase().as_str() {
2211 "ok" => PromptButton::Ok("OK".into()),
2212 "cancel" => PromptButton::Cancel("Cancel".into()),
2213 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2214 }
2215 }
2216}
2217
2218#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2220pub enum CursorStyle {
2221 #[default]
2223 Arrow,
2224
2225 IBeam,
2228
2229 Crosshair,
2232
2233 ClosedHand,
2236
2237 OpenHand,
2240
2241 PointingHand,
2244
2245 ResizeLeft,
2248
2249 ResizeRight,
2252
2253 ResizeLeftRight,
2256
2257 ResizeUp,
2260
2261 ResizeDown,
2264
2265 ResizeUpDown,
2268
2269 ResizeUpLeftDownRight,
2272
2273 ResizeUpRightDownLeft,
2276
2277 ResizeColumn,
2280
2281 ResizeRow,
2284
2285 IBeamCursorForVerticalLayout,
2288
2289 OperationNotAllowed,
2292
2293 DragLink,
2296
2297 DragCopy,
2300
2301 ContextualMenu,
2304}
2305
2306#[derive(Clone, Debug, Eq, PartialEq)]
2308pub struct ClipboardItem {
2309 pub entries: Vec<ClipboardEntry>,
2311}
2312
2313#[derive(Clone, Debug, PartialEq, Eq)]
2318pub enum ClipboardReadError {
2319 Unavailable,
2323 Denied(String),
2326 UnsupportedContent,
2329}
2330
2331impl std::fmt::Display for ClipboardReadError {
2332 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2333 match self {
2334 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2335 Self::Denied(message) => {
2336 write!(formatter, "clipboard access was denied: {message}")
2337 }
2338 Self::UnsupportedContent => {
2339 formatter.write_str("the clipboard contents are unsupported")
2340 }
2341 }
2342 }
2343}
2344
2345impl std::error::Error for ClipboardReadError {}
2346
2347#[derive(Clone, Debug, Eq, PartialEq)]
2349pub enum ClipboardEntry {
2350 String(ClipboardString),
2352 Image(Image),
2354 ExternalPaths(crate::ExternalPaths),
2356}
2357
2358impl ClipboardItem {
2359 pub fn new_string(text: String) -> Self {
2361 Self {
2362 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2363 }
2364 }
2365
2366 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2368 Self {
2369 entries: vec![ClipboardEntry::String(ClipboardString {
2370 text,
2371 metadata: Some(metadata),
2372 })],
2373 }
2374 }
2375
2376 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2378 Self {
2379 entries: vec![ClipboardEntry::String(
2380 ClipboardString::new(text).with_json_metadata(metadata),
2381 )],
2382 }
2383 }
2384
2385 pub fn new_image(image: &Image) -> Self {
2387 Self {
2388 entries: vec![ClipboardEntry::Image(image.clone())],
2389 }
2390 }
2391
2392 pub fn text(&self) -> Option<String> {
2395 let mut answer = String::new();
2396
2397 for entry in self.entries.iter() {
2398 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2399 answer.push_str(text);
2400 }
2401 }
2402
2403 if answer.is_empty() {
2404 for entry in self.entries.iter() {
2405 if let ClipboardEntry::ExternalPaths(paths) = entry {
2406 for path in &paths.0 {
2407 use std::fmt::Write as _;
2408 _ = write!(answer, "{}", path.display());
2409 }
2410 }
2411 }
2412 }
2413
2414 if !answer.is_empty() {
2415 Some(answer)
2416 } else {
2417 None
2418 }
2419 }
2420
2421 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2423 pub fn metadata(&self) -> Option<&String> {
2424 match self.entries().first() {
2425 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2426 clipboard_string.metadata.as_ref()
2427 }
2428 _ => None,
2429 }
2430 }
2431
2432 pub fn entries(&self) -> &[ClipboardEntry] {
2434 &self.entries
2435 }
2436
2437 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2439 self.entries.into_iter()
2440 }
2441}
2442
2443impl From<ClipboardString> for ClipboardEntry {
2444 fn from(value: ClipboardString) -> Self {
2445 Self::String(value)
2446 }
2447}
2448
2449impl From<String> for ClipboardEntry {
2450 fn from(value: String) -> Self {
2451 Self::from(ClipboardString::from(value))
2452 }
2453}
2454
2455impl From<Image> for ClipboardEntry {
2456 fn from(value: Image) -> Self {
2457 Self::Image(value)
2458 }
2459}
2460
2461impl From<ClipboardEntry> for ClipboardItem {
2462 fn from(value: ClipboardEntry) -> Self {
2463 Self {
2464 entries: vec![value],
2465 }
2466 }
2467}
2468
2469impl From<String> for ClipboardItem {
2470 fn from(value: String) -> Self {
2471 Self::from(ClipboardEntry::from(value))
2472 }
2473}
2474
2475impl From<Image> for ClipboardItem {
2476 fn from(value: Image) -> Self {
2477 Self::from(ClipboardEntry::from(value))
2478 }
2479}
2480
2481#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2483pub enum ImageFormat {
2484 Png,
2489 Jpeg,
2491 Webp,
2493 Gif,
2495 Svg,
2497 Bmp,
2499 Tiff,
2501 Ico,
2503 Pnm,
2505}
2506
2507impl ImageFormat {
2508 pub const fn mime_type(self) -> &'static str {
2510 match self {
2511 ImageFormat::Png => "image/png",
2512 ImageFormat::Jpeg => "image/jpeg",
2513 ImageFormat::Webp => "image/webp",
2514 ImageFormat::Gif => "image/gif",
2515 ImageFormat::Svg => "image/svg+xml",
2516 ImageFormat::Bmp => "image/bmp",
2517 ImageFormat::Tiff => "image/tiff",
2518 ImageFormat::Ico => "image/ico",
2519 ImageFormat::Pnm => "image/x-portable-anymap",
2520 }
2521 }
2522
2523 pub const fn extension(self) -> &'static str {
2525 match self {
2526 ImageFormat::Png => "png",
2527 ImageFormat::Jpeg => "jpg",
2528 ImageFormat::Webp => "webp",
2529 ImageFormat::Gif => "gif",
2530 ImageFormat::Svg => "svg",
2531 ImageFormat::Bmp => "bmp",
2532 ImageFormat::Tiff => "tiff",
2533 ImageFormat::Ico => "ico",
2534 ImageFormat::Pnm => "pnm",
2535 }
2536 }
2537
2538 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2540 use strum::IntoEnumIterator;
2541 Self::iter()
2542 .find(|format| format.mime_type() == mime_type)
2543 .or_else(|| Self::from_mime_type_alias(mime_type))
2544 }
2545
2546 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2550 match mime_type {
2551 "image/jpg" => Some(Self::Jpeg),
2552 "image/tif" => Some(Self::Tiff),
2553 _ => None,
2554 }
2555 }
2556}
2557
2558#[derive(Clone, Debug, PartialEq, Eq)]
2560pub struct Image {
2561 pub format: ImageFormat,
2563 pub bytes: Vec<u8>,
2565 pub id: u64,
2567}
2568
2569pub(crate) fn decode_static_image(
2570 bytes: &[u8],
2571 format: image::ImageFormat,
2572) -> Result<SmallVec<[Frame; 1]>> {
2573 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2574 .into_decoder()
2575 .context("creating image decoder")?;
2576 decode_static_image_from_decoder(decoder)
2577}
2578
2579pub(crate) fn decode_static_image_from_decoder(
2580 mut decoder: impl image::ImageDecoder,
2581) -> Result<SmallVec<[Frame; 1]>> {
2582 let orientation = decoder
2583 .orientation()
2584 .context("reading decoder's orientation")?;
2585 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2586 image.apply_orientation(orientation);
2587
2588 let mut data = image.into_rgba8();
2589 for pixel in data.chunks_exact_mut(4) {
2590 pixel.swap(0, 2);
2591 }
2592
2593 Ok(SmallVec::from_elem(Frame::new(data), 1))
2594}
2595
2596impl Hash for Image {
2597 fn hash<H: Hasher>(&self, state: &mut H) {
2598 state.write_u64(self.id);
2599 }
2600}
2601
2602impl Image {
2603 pub fn empty() -> Self {
2605 Self::from_bytes(ImageFormat::Png, Vec::new())
2606 }
2607
2608 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2610 Self {
2611 id: hash(&bytes),
2612 format,
2613 bytes,
2614 }
2615 }
2616
2617 pub fn id(&self) -> u64 {
2619 self.id
2620 }
2621
2622 pub fn use_render_image(
2624 self: Arc<Self>,
2625 window: &mut Window,
2626 cx: &mut App,
2627 ) -> Option<Arc<RenderImage>> {
2628 ImageSource::Image(self)
2629 .use_data(None, window, cx)
2630 .and_then(|result| result.ok())
2631 }
2632
2633 pub fn get_render_image(
2635 self: Arc<Self>,
2636 window: &mut Window,
2637 cx: &mut App,
2638 ) -> Option<Arc<RenderImage>> {
2639 ImageSource::Image(self)
2640 .get_data(None, window, cx)
2641 .and_then(|result| result.ok())
2642 }
2643
2644 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2646 ImageSource::Image(self).remove_asset(cx);
2647 }
2648
2649 #[cfg(any(test, feature = "test-support"))]
2652 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2653 ImageSource::Image(self.clone()).is_asset_cached(cx)
2654 }
2655
2656 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2658 let frames = match self.format {
2659 ImageFormat::Gif => {
2660 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2661 let mut frames = SmallVec::new();
2662
2663 for frame in decoder.into_frames() {
2664 match frame {
2665 Ok(mut frame) => {
2666 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2668 pixel.swap(0, 2);
2669 }
2670 frames.push(frame);
2671 }
2672 Err(err) => {
2673 log::debug!("Skipping GIF frame due to decode error: {err}");
2674 }
2675 }
2676 }
2677
2678 if frames.is_empty() {
2679 anyhow::bail!("GIF could not be decoded: all frames failed");
2680 }
2681
2682 frames
2683 }
2684 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2685 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2686 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2687 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2688 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2689 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2690 ImageFormat::Svg => {
2691 return svg_renderer
2692 .render_single_frame(&self.bytes, 1.0)
2693 .map_err(Into::into);
2694 }
2695 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2696 };
2697
2698 Ok(Arc::new(RenderImage::new(frames)))
2699 }
2700
2701 pub fn format(&self) -> ImageFormat {
2703 self.format
2704 }
2705
2706 pub fn bytes(&self) -> &[u8] {
2708 self.bytes.as_slice()
2709 }
2710}
2711
2712#[derive(Clone, Debug, Eq, PartialEq)]
2714pub struct ClipboardString {
2715 pub text: String,
2717 pub metadata: Option<String>,
2719}
2720
2721impl ClipboardString {
2722 pub fn new(text: String) -> Self {
2724 Self {
2725 text,
2726 metadata: None,
2727 }
2728 }
2729
2730 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2733 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2734 self
2735 }
2736
2737 pub fn text(&self) -> &String {
2739 &self.text
2740 }
2741
2742 pub fn into_text(self) -> String {
2744 self.text
2745 }
2746
2747 pub fn metadata_json<T>(&self) -> Option<T>
2749 where
2750 T: for<'a> Deserialize<'a>,
2751 {
2752 self.metadata
2753 .as_ref()
2754 .and_then(|m| serde_json::from_str(m).ok())
2755 }
2756
2757 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2758 pub fn text_hash(text: &str) -> u64 {
2760 let mut hasher = SeaHasher::new();
2761 text.hash(&mut hasher);
2762 hasher.finish()
2763 }
2764}
2765
2766impl From<String> for ClipboardString {
2767 fn from(value: String) -> Self {
2768 Self {
2769 text: value,
2770 metadata: None,
2771 }
2772 }
2773}
2774
2775#[cfg(test)]
2776mod image_tests {
2777 use super::*;
2778 use std::sync::Arc;
2779
2780 #[test]
2781 fn test_image_to_image_data_applies_exif_orientation() {
2782 let image = Image::from_bytes(
2783 ImageFormat::Jpeg,
2784 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2785 );
2786
2787 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2788
2789 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2790
2791 let bytes = render_image.as_bytes(0).unwrap();
2792 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2793 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2794 }
2795
2796 #[test]
2797 fn test_svg_image_to_image_data_converts_to_bgra() {
2798 let image = Image::from_bytes(
2799 ImageFormat::Svg,
2800 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2801<rect width="1" height="1" fill="#38BDF8"/>
2802</svg>"##
2803 .to_vec(),
2804 );
2805
2806 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2807 let bytes = render_image.as_bytes(0).unwrap();
2808
2809 for pixel in bytes.chunks_exact(4) {
2810 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2811 }
2812 }
2813}
2814
2815#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2816mod tests {
2817 use super::*;
2818 use std::collections::HashSet;
2819
2820 #[test]
2821 fn test_window_button_layout_parse_standard() {
2822 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2823 assert_eq!(
2824 layout.left,
2825 [
2826 Some(WindowButton::Close),
2827 Some(WindowButton::Minimize),
2828 None
2829 ]
2830 );
2831 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2832 }
2833
2834 #[test]
2835 fn test_window_button_layout_parse_right_only() {
2836 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2837 assert_eq!(layout.left, [None, None, None]);
2838 assert_eq!(
2839 layout.right,
2840 [
2841 Some(WindowButton::Minimize),
2842 Some(WindowButton::Maximize),
2843 Some(WindowButton::Close)
2844 ]
2845 );
2846 }
2847
2848 #[test]
2849 fn test_window_button_layout_parse_left_only() {
2850 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2851 assert_eq!(
2852 layout.left,
2853 [
2854 Some(WindowButton::Close),
2855 Some(WindowButton::Minimize),
2856 Some(WindowButton::Maximize)
2857 ]
2858 );
2859 assert_eq!(layout.right, [None, None, None]);
2860 }
2861
2862 #[test]
2863 fn test_window_button_layout_parse_with_whitespace() {
2864 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2865 assert_eq!(
2866 layout.left,
2867 [
2868 Some(WindowButton::Close),
2869 Some(WindowButton::Minimize),
2870 None
2871 ]
2872 );
2873 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2874 }
2875
2876 #[test]
2877 fn test_window_button_layout_parse_empty() {
2878 let layout = WindowButtonLayout::parse("").unwrap();
2879 assert_eq!(layout.left, [None, None, None]);
2880 assert_eq!(layout.right, [None, None, None]);
2881 }
2882
2883 #[test]
2884 fn test_window_button_layout_parse_intentionally_empty() {
2885 let layout = WindowButtonLayout::parse(":").unwrap();
2886 assert_eq!(layout.left, [None, None, None]);
2887 assert_eq!(layout.right, [None, None, None]);
2888 }
2889
2890 #[test]
2891 fn test_window_button_layout_parse_invalid_buttons() {
2892 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
2893 assert_eq!(
2894 layout.left,
2895 [
2896 Some(WindowButton::Close),
2897 Some(WindowButton::Minimize),
2898 None
2899 ]
2900 );
2901 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2902 }
2903
2904 #[test]
2905 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
2906 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
2907 assert_eq!(
2908 layout.right,
2909 [
2910 Some(WindowButton::Close),
2911 Some(WindowButton::Minimize),
2912 None
2913 ]
2914 );
2915 assert_eq!(layout.format(), ":close,minimize");
2916 }
2917
2918 #[test]
2919 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
2920 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
2921 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2922 assert_eq!(
2923 layout.right,
2924 [
2925 Some(WindowButton::Maximize),
2926 Some(WindowButton::Minimize),
2927 None
2928 ]
2929 );
2930
2931 let button_ids: Vec<_> = layout
2932 .left
2933 .iter()
2934 .chain(layout.right.iter())
2935 .flatten()
2936 .map(WindowButton::id)
2937 .collect();
2938 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
2939 assert_eq!(unique_button_ids.len(), button_ids.len());
2940 assert_eq!(layout.format(), "close:maximize,minimize");
2941 }
2942
2943 #[test]
2944 fn test_window_button_layout_parse_gnome_style() {
2945 let layout = WindowButtonLayout::parse("close").unwrap();
2946 assert_eq!(layout.left, [None, None, None]);
2947 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
2948 }
2949
2950 #[test]
2951 fn test_window_button_layout_parse_elementary_style() {
2952 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
2953 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
2954 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2955 }
2956
2957 #[test]
2958 fn test_window_button_layout_round_trip() {
2959 let cases = [
2960 "close:minimize,maximize",
2961 "minimize,maximize,close:",
2962 ":close",
2963 "close:",
2964 "close:maximize",
2965 ":",
2966 ];
2967
2968 for case in cases {
2969 let layout = WindowButtonLayout::parse(case).unwrap();
2970 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
2971 }
2972 }
2973
2974 #[test]
2975 fn test_window_button_layout_linux_default() {
2976 let layout = WindowButtonLayout::linux_default();
2977 assert_eq!(layout.left, [None, None, None]);
2978 assert_eq!(
2979 layout.right,
2980 [
2981 Some(WindowButton::Minimize),
2982 Some(WindowButton::Maximize),
2983 Some(WindowButton::Close)
2984 ]
2985 );
2986
2987 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
2988 assert_eq!(round_tripped, layout);
2989 }
2990
2991 #[test]
2992 fn test_window_button_layout_parse_all_invalid() {
2993 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
2994 }
2995}