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 set_text_input_configuration(&mut self, _configuration: TextInputConfiguration) {}
835 fn prompt(
836 &self,
837 level: PromptLevel,
838 msg: &str,
839 detail: Option<&str>,
840 answers: &[PromptButton],
841 ) -> Option<oneshot::Receiver<usize>>;
842 fn activate(&self);
843 fn request_attention(&self) {}
845 fn is_active(&self) -> bool;
846 fn is_hovered(&self) -> bool;
847 fn background_appearance(&self) -> WindowBackgroundAppearance;
848 fn set_title(&mut self, title: &str);
849 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
850 fn minimize(&self);
851 fn zoom(&self);
852 fn toggle_fullscreen(&self);
853 fn is_fullscreen(&self) -> bool;
854 fn frame_waker(&self) -> Option<Rc<dyn Fn()>> {
855 None
856 }
857 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
858 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
859 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
860 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
861 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
862 fn on_moved(&self, callback: Box<dyn FnMut()>);
863 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
864 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
865 fn on_close(&self, callback: Box<dyn FnOnce()>);
866 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
867 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
868 fn draw(&self, scene: &Scene);
869 fn gpu_time(&self) -> Option<Duration> {
873 None
874 }
875 fn schedule_frame(&self) {}
876 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
877 fn is_subpixel_rendering_supported(&self) -> bool;
878
879 fn get_title(&self) -> String {
881 String::new()
882 }
883 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
884 None
885 }
886 fn tab_bar_visible(&self) -> bool {
887 false
888 }
889 fn set_edited(&mut self, _edited: bool) {}
890 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
891 fn toggle_simple_fullscreen(&self) {}
892 fn is_simple_fullscreen(&self) -> bool {
893 false
894 }
895 #[cfg(target_os = "macos")]
896 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
897 fn show_character_palette(&self) {}
898 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
899 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
900 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
901 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
902 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
903 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
904 fn merge_all_windows(&self) {}
905 fn move_tab_to_new_window(&self) {}
906 fn toggle_window_tab_overview(&self) {}
907 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
908
909 #[cfg(target_os = "windows")]
910 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
911
912 fn inner_window_bounds(&self) -> WindowBounds {
914 self.window_bounds()
915 }
916 fn request_decorations(&self, _decorations: WindowDecorations) {}
917 fn show_window_menu(&self, _position: Point<Pixels>) {}
918 fn start_window_move(&self) {}
919 fn can_start_external_drag(&self) -> bool {
920 false
921 }
922 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
923 false
924 }
925 fn start_window_resize(&self, _edge: ResizeEdge) {}
926 fn set_exclusive_zone(&self, _zone: Pixels) {}
927 #[cfg(all(target_os = "linux", feature = "wayland"))]
928 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
929 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
930 fn window_decorations(&self) -> Decorations {
931 Decorations::Server
932 }
933 fn set_app_id(&mut self, _app_id: &str) {}
934 fn map_window(&mut self) -> anyhow::Result<()> {
935 Ok(())
936 }
937 fn window_controls(&self) -> WindowControls {
938 WindowControls::default()
939 }
940 fn set_client_inset(&self, _inset: Pixels) {}
941 fn gpu_specs(&self) -> Option<GpuSpecs>;
942
943 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
944
945 fn insets(&self) -> WindowInsets {
950 WindowInsets::default()
951 }
952
953 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
959
960 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
963
964 fn set_back_enabled(&self, _enabled: bool) {}
967
968 fn show_soft_keyboard(&self) {}
970
971 fn hide_soft_keyboard(&self) {}
973
974 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
976
977 fn play_system_bell(&self) {}
978
979 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
981
982 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
984
985 fn a11y_update_window_bounds(&self) {}
987
988 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
989 fn as_test(&mut self) -> Option<&mut TestWindow> {
990 None
991 }
992
993 #[cfg(any(test, feature = "test-support"))]
997 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
998 anyhow::bail!("render_to_image not implemented for this platform")
999 }
1000}
1001
1002#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1004pub trait PlatformHeadlessRenderer {
1005 fn render_scene_to_image(
1007 &mut self,
1008 scene: &Scene,
1009 size: Size<DevicePixels>,
1010 ) -> Result<RgbaImage>;
1011
1012 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1018
1019 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1021}
1022
1023#[doc(hidden)]
1026pub type RunnableVariant = Runnable<RunnableMeta>;
1027
1028#[doc(hidden)]
1029pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1030
1031#[doc(hidden)]
1032pub enum TasksIncluded {
1033 OnlyCompleted,
1034 CompletedAndRunning,
1035}
1036
1037#[doc(hidden)]
1040pub trait PlatformDispatcher: Send + Sync {
1041 fn is_main_thread(&self) -> bool;
1042 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1043 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1044 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1045
1046 fn dispatch_on_main_thread_when_idle(
1047 &self,
1048 runnable: RunnableVariant,
1049 timeout: Option<Duration>,
1050 ) {
1051 let _ = timeout;
1052 self.dispatch_on_main_thread(runnable, Priority::Low);
1053 }
1054
1055 fn idle_time_remaining(&self) -> Option<Duration> {
1056 None
1057 }
1058
1059 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1060
1061 fn now(&self) -> Instant {
1062 Instant::now()
1063 }
1064
1065 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1066 gpui_util::defer(Box::new(|| {}))
1067 }
1068
1069 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1070 fn as_test(&self) -> Option<&TestDispatcher> {
1071 None
1072 }
1073
1074 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1077 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1078 None
1079 }
1080}
1081
1082#[expect(missing_docs)]
1083pub trait PlatformTextSystem: Send + Sync {
1084 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1085 fn all_font_names(&self) -> Vec<String>;
1087 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1089 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1091 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1093 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1095 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1097 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1099 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1101 fn rasterize_glyph(
1103 &self,
1104 params: &RenderGlyphParams,
1105 raster_bounds: Bounds<DevicePixels>,
1106 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1107 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1109 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1111 -> TextRenderingMode;
1112 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1114 0
1115 }
1116}
1117
1118#[expect(missing_docs)]
1119pub struct NoopTextSystem;
1120
1121#[expect(missing_docs)]
1122impl NoopTextSystem {
1123 #[allow(dead_code)]
1124 pub fn new() -> Self {
1125 Self
1126 }
1127}
1128
1129impl PlatformTextSystem for NoopTextSystem {
1130 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1131 Ok(())
1132 }
1133
1134 fn all_font_names(&self) -> Vec<String> {
1135 Vec::new()
1136 }
1137
1138 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1139 Ok(FontId(1))
1140 }
1141
1142 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1143 FontMetrics {
1144 units_per_em: 1000,
1145 ascent: 1025.0,
1146 descent: -275.0,
1147 line_gap: 0.0,
1148 underline_position: -95.0,
1149 underline_thickness: 60.0,
1150 cap_height: 698.0,
1151 x_height: 516.0,
1152 bounding_box: Bounds {
1153 origin: Point {
1154 x: -260.0,
1155 y: -245.0,
1156 },
1157 size: Size {
1158 width: 1501.0,
1159 height: 1364.0,
1160 },
1161 },
1162 }
1163 }
1164
1165 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1166 Ok(Bounds {
1167 origin: Point { x: 54.0, y: 0.0 },
1168 size: size(392.0, 528.0),
1169 })
1170 }
1171
1172 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1173 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1174 }
1175
1176 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1177 Some(GlyphId(ch.len_utf16() as u32))
1178 }
1179
1180 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1181 Ok(Default::default())
1182 }
1183
1184 fn rasterize_glyph(
1185 &self,
1186 _params: &RenderGlyphParams,
1187 raster_bounds: Bounds<DevicePixels>,
1188 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1189 Ok((raster_bounds.size, Vec::new()))
1190 }
1191
1192 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1193 let mut position = px(0.);
1194 let metrics = self.font_metrics(FontId(0));
1195 let em_width = font_size
1196 * self
1197 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1198 .unwrap()
1199 .width
1200 / metrics.units_per_em as f32;
1201 let mut glyphs = Vec::new();
1202 for (ix, c) in text.char_indices() {
1203 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1204 glyphs.push(ShapedGlyph {
1205 id: glyph,
1206 position: point(position, px(0.)),
1207 index: ix,
1208 is_emoji: glyph.0 == 2,
1209 });
1210 if glyph.0 == 2 {
1211 position += em_width * 2.0;
1212 } else {
1213 position += em_width;
1214 }
1215 } else {
1216 position += em_width
1217 }
1218 }
1219 let mut runs = Vec::default();
1220 if !glyphs.is_empty() {
1221 runs.push(ShapedRun {
1222 font_id: FontId(0),
1223 glyphs,
1224 });
1225 } else {
1226 position = px(0.);
1227 }
1228
1229 LineLayout {
1230 font_size,
1231 width: position,
1232 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1233 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1234 runs,
1235 len: text.len(),
1236 }
1237 }
1238
1239 fn recommended_rendering_mode(
1240 &self,
1241 _font_id: FontId,
1242 _font_size: Pixels,
1243 ) -> TextRenderingMode {
1244 TextRenderingMode::Grayscale
1245 }
1246}
1247
1248#[allow(dead_code)]
1253pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1254 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1255 [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], ];
1269
1270 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1271 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1272
1273 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1274 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1275
1276 [
1277 ratios[0] * NORM13,
1278 ratios[1] * NORM24,
1279 ratios[2] * NORM13,
1280 ratios[3] * NORM24,
1281 ]
1282}
1283
1284#[derive(PartialEq, Eq, Hash, Clone)]
1285#[expect(missing_docs)]
1286pub enum AtlasKey {
1287 Glyph(RenderGlyphParams),
1288 Svg(RenderSvgParams),
1289 Image(RenderImageParams),
1290}
1291
1292impl AtlasKey {
1293 #[cfg_attr(
1294 all(
1295 any(target_os = "linux", target_os = "freebsd"),
1296 not(any(feature = "x11", feature = "wayland"))
1297 ),
1298 allow(dead_code)
1299 )]
1300 pub fn texture_kind(&self) -> AtlasTextureKind {
1302 match self {
1303 AtlasKey::Glyph(params) => {
1304 if params.is_emoji {
1305 AtlasTextureKind::Polychrome
1306 } else if params.subpixel_rendering {
1307 AtlasTextureKind::Subpixel
1308 } else {
1309 AtlasTextureKind::Monochrome
1310 }
1311 }
1312 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1313 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1314 }
1315 }
1316}
1317
1318impl From<RenderGlyphParams> for AtlasKey {
1319 fn from(params: RenderGlyphParams) -> Self {
1320 Self::Glyph(params)
1321 }
1322}
1323
1324impl From<RenderSvgParams> for AtlasKey {
1325 fn from(params: RenderSvgParams) -> Self {
1326 Self::Svg(params)
1327 }
1328}
1329
1330impl From<RenderImageParams> for AtlasKey {
1331 fn from(params: RenderImageParams) -> Self {
1332 Self::Image(params)
1333 }
1334}
1335
1336#[expect(missing_docs)]
1337pub trait PlatformAtlas {
1338 fn get_or_insert_with<'a>(
1339 &self,
1340 key: &AtlasKey,
1341 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1342 ) -> Result<Option<AtlasTile>>;
1343 fn remove(&self, key: &AtlasKey);
1344
1345 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1346 fn contains(&self, _key: &AtlasKey) -> bool {
1347 false
1348 }
1349}
1350
1351#[doc(hidden)]
1352pub struct AtlasTextureList<T> {
1353 pub textures: Vec<Option<T>>,
1354 pub free_list: Vec<usize>,
1355}
1356
1357impl<T> Default for AtlasTextureList<T> {
1358 fn default() -> Self {
1359 Self {
1360 textures: Vec::default(),
1361 free_list: Vec::default(),
1362 }
1363 }
1364}
1365
1366impl<T> ops::Index<usize> for AtlasTextureList<T> {
1367 type Output = Option<T>;
1368
1369 fn index(&self, index: usize) -> &Self::Output {
1370 &self.textures[index]
1371 }
1372}
1373
1374impl<T> AtlasTextureList<T> {
1375 #[allow(unused)]
1376 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1377 self.free_list.clear();
1378 self.textures.drain(..)
1379 }
1380
1381 #[allow(dead_code)]
1382 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1383 self.textures.iter_mut().flatten()
1384 }
1385}
1386
1387#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1388#[repr(C)]
1389#[expect(missing_docs)]
1390pub struct AtlasTile {
1391 pub texture_id: AtlasTextureId,
1393 pub tile_id: TileId,
1395 pub padding: u32,
1397 pub bounds: Bounds<DevicePixels>,
1399}
1400
1401#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1402#[repr(C)]
1403#[expect(missing_docs)]
1404pub struct AtlasTextureId {
1405 pub index: u32,
1408 pub kind: AtlasTextureKind,
1410}
1411
1412#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1413#[repr(C)]
1414#[cfg_attr(
1415 all(
1416 any(target_os = "linux", target_os = "freebsd"),
1417 not(any(feature = "x11", feature = "wayland"))
1418 ),
1419 allow(dead_code)
1420)]
1421#[expect(missing_docs)]
1422pub enum AtlasTextureKind {
1423 Monochrome = 0,
1424 Polychrome = 1,
1425 Subpixel = 2,
1426}
1427
1428#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1429#[repr(C)]
1430#[expect(missing_docs)]
1431pub struct TileId(pub u32);
1432
1433impl From<etagere::AllocId> for TileId {
1434 fn from(id: etagere::AllocId) -> Self {
1435 Self(id.serialize())
1436 }
1437}
1438
1439impl From<TileId> for etagere::AllocId {
1440 fn from(id: TileId) -> Self {
1441 Self::deserialize(id.0)
1442 }
1443}
1444
1445#[expect(missing_docs)]
1446pub struct PlatformInputHandler {
1447 cx: AsyncWindowContext,
1448 handler: Box<dyn InputHandler>,
1449}
1450
1451#[expect(missing_docs)]
1452#[cfg_attr(
1453 all(
1454 any(target_os = "linux", target_os = "freebsd"),
1455 not(any(feature = "x11", feature = "wayland"))
1456 ),
1457 allow(dead_code)
1458)]
1459impl PlatformInputHandler {
1460 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1461 Self { cx, handler }
1462 }
1463
1464 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1465 self.cx
1466 .update(|window, cx| {
1467 self.handler
1468 .selected_text_range(ignore_disabled_input, window, cx)
1469 })
1470 .ok()
1471 .flatten()
1472 }
1473
1474 #[cfg_attr(target_os = "windows", allow(dead_code))]
1475 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1476 self.cx
1477 .update(|window, cx| self.handler.marked_text_range(window, cx))
1478 .ok()
1479 .flatten()
1480 }
1481
1482 #[cfg_attr(
1483 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1484 allow(dead_code)
1485 )]
1486 pub fn text_for_range(
1487 &mut self,
1488 range_utf16: Range<usize>,
1489 adjusted: &mut Option<Range<usize>>,
1490 ) -> Option<String> {
1491 self.cx
1492 .update(|window, cx| {
1493 self.handler
1494 .text_for_range(range_utf16, adjusted, window, cx)
1495 })
1496 .ok()
1497 .flatten()
1498 }
1499
1500 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1501 self.cx
1502 .update(|window, cx| {
1503 self.handler
1504 .replace_text_in_range(replacement_range, text, window, cx);
1505 })
1506 .ok();
1507 }
1508
1509 pub fn replace_and_mark_text_in_range(
1510 &mut self,
1511 range_utf16: Option<Range<usize>>,
1512 new_text: &str,
1513 new_selected_range: Option<Range<usize>>,
1514 ) {
1515 self.cx
1516 .update(|window, cx| {
1517 self.handler.replace_and_mark_text_in_range(
1518 range_utf16,
1519 new_text,
1520 new_selected_range,
1521 window,
1522 cx,
1523 )
1524 })
1525 .ok();
1526 }
1527
1528 #[cfg_attr(target_os = "windows", allow(dead_code))]
1529 pub fn unmark_text(&mut self) {
1530 self.cx
1531 .update(|window, cx| self.handler.unmark_text(window, cx))
1532 .ok();
1533 }
1534
1535 pub fn paste(&mut self, item: ClipboardItem) {
1536 self.cx
1537 .update(|window, cx| self.handler.paste(item, window, cx))
1538 .ok();
1539 }
1540
1541 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1542 self.cx
1543 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1544 .ok()
1545 .flatten()
1546 }
1547
1548 #[allow(dead_code)]
1549 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1550 self.handler.apple_press_and_hold_enabled()
1551 }
1552
1553 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1554 self.handler.replace_text_in_range(None, input, window, cx);
1555 }
1556
1557 pub fn compute_ime_candidate_bounds(
1558 marked_range: Option<Range<usize>>,
1559 selection: &UTF16Selection,
1560 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1561 ) -> Option<Bounds<Pixels>> {
1562 if let Some(marked_range) = marked_range {
1563 let mut line_start = marked_range.start;
1565
1566 let caret = selection.range.end;
1570 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1571 for i in (marked_range.start..caret).rev() {
1572 if let Some(b) = bounds_for_range(i..i) {
1573 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1574 line_start = i + 1;
1575 break;
1576 }
1577 }
1578 }
1579 }
1580 bounds_for_range(line_start..line_start)
1581 } else {
1582 let offset = if selection.reversed {
1584 selection.range.start
1585 } else {
1586 selection.range.end
1587 };
1588 bounds_for_range(offset..offset)
1589 }
1590 }
1591
1592 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1593 let marked_range = self.handler.marked_text_range(window, cx);
1594 let selection = self.handler.selected_text_range(true, window, cx)?;
1595 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1596 self.handler.bounds_for_range(range, window, cx)
1597 })
1598 }
1599
1600 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1601 let marked_range = self.marked_text_range();
1602 let selection = self.selected_text_range(true)?;
1603 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1604 self.bounds_for_range(range)
1605 })
1606 }
1607
1608 #[allow(unused)]
1609 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1610 self.cx
1611 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1612 .ok()
1613 .flatten()
1614 }
1615
1616 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1618 self.cx
1619 .update(|window, cx| {
1620 self.handler
1621 .set_selected_text_range(range_utf16, window, cx)
1622 })
1623 .ok();
1624 }
1625
1626 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1628 self.cx
1629 .update(|window, cx| self.handler.element_bounds(window, cx))
1630 .ok()
1631 .flatten()
1632 }
1633
1634 pub fn text_length_utf16(&mut self) -> Option<usize> {
1636 self.cx
1637 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1638 .ok()
1639 .flatten()
1640 }
1641
1642 #[allow(dead_code)]
1643 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1644 self.handler.accepts_text_input(window, cx)
1645 }
1646
1647 #[allow(dead_code)]
1648 pub fn query_accepts_text_input(&mut self) -> bool {
1649 self.cx
1650 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1651 .unwrap_or(true)
1652 }
1653
1654 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1660 self.cx
1661 .update(|window, cx| {
1662 !window.has_pending_keystrokes()
1664 && self.handler.prefers_ime_for_printable_keys(window, cx)
1665 })
1666 .unwrap_or(false)
1667 }
1668
1669 pub fn text_input_configuration(
1671 &mut self,
1672 window: &mut Window,
1673 cx: &mut App,
1674 ) -> TextInputConfiguration {
1675 self.handler.text_input_configuration(window, cx)
1676 }
1677
1678 pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1680 self.cx
1681 .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1682 .ok()
1683 .flatten()
1684 }
1685}
1686
1687#[derive(Debug)]
1690pub struct UTF16Selection {
1691 pub range: Range<usize>,
1694 pub reversed: bool,
1697}
1698
1699pub trait InputHandler: 'static {
1704 fn selected_text_range(
1709 &mut self,
1710 ignore_disabled_input: bool,
1711 window: &mut Window,
1712 cx: &mut App,
1713 ) -> Option<UTF16Selection>;
1714
1715 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1720
1721 fn text_for_range(
1726 &mut self,
1727 range_utf16: Range<usize>,
1728 adjusted_range: &mut Option<Range<usize>>,
1729 window: &mut Window,
1730 cx: &mut App,
1731 ) -> Option<String>;
1732
1733 fn replace_text_in_range(
1738 &mut self,
1739 replacement_range: Option<Range<usize>>,
1740 text: &str,
1741 window: &mut Window,
1742 cx: &mut App,
1743 );
1744
1745 fn replace_and_mark_text_in_range(
1752 &mut self,
1753 range_utf16: Option<Range<usize>>,
1754 new_text: &str,
1755 new_selected_range: Option<Range<usize>>,
1756 window: &mut Window,
1757 cx: &mut App,
1758 );
1759
1760 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1763
1764 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1771 if let Some(text) = item.text() {
1772 self.replace_text_in_range(None, &text, window, cx);
1773 }
1774 }
1775
1776 fn bounds_for_range(
1781 &mut self,
1782 range_utf16: Range<usize>,
1783 window: &mut Window,
1784 cx: &mut App,
1785 ) -> Option<Bounds<Pixels>>;
1786
1787 fn character_index_for_point(
1791 &mut self,
1792 point: Point<Pixels>,
1793 window: &mut Window,
1794 cx: &mut App,
1795 ) -> Option<usize>;
1796
1797 fn set_selected_text_range(
1807 &mut self,
1808 _range_utf16: Range<usize>,
1809 _window: &mut Window,
1810 _cx: &mut App,
1811 ) {
1812 }
1813
1814 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1821 None
1822 }
1823
1824 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1826 None
1827 }
1828
1829 #[allow(dead_code)]
1834 fn apple_press_and_hold_enabled(&mut self) -> bool {
1835 true
1836 }
1837
1838 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1840 true
1841 }
1842
1843 fn text_input_editable_range(
1854 &mut self,
1855 _window: &mut Window,
1856 _cx: &mut App,
1857 ) -> Option<Range<usize>> {
1858 None
1859 }
1860
1861 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1870 false
1871 }
1872
1873 fn text_input_configuration(
1879 &mut self,
1880 _window: &mut Window,
1881 _cx: &mut App,
1882 ) -> TextInputConfiguration {
1883 TextInputConfiguration::default()
1884 }
1885}
1886
1887#[derive(Clone, Debug, Default, PartialEq, Eq)]
1897pub struct TextInputConfiguration {
1898 pub autocorrect: bool,
1900 pub autocapitalize: Autocapitalize,
1902 pub suggestions: bool,
1904 pub input_action: TextInputAction,
1906}
1907
1908#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1910pub enum Autocapitalize {
1911 #[default]
1913 None,
1914 Words,
1916 Sentences,
1918 Characters,
1920}
1921
1922#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1932pub enum TextInputAction {
1933 #[default]
1935 Unspecified,
1936 Enter,
1938 Done,
1940 Go,
1942 Next,
1944 Previous,
1946 Search,
1948 Send,
1950}
1951
1952#[derive(Debug)]
1954pub struct WindowOptions {
1955 pub window_bounds: Option<WindowBounds>,
1959
1960 pub titlebar: Option<TitlebarOptions>,
1962
1963 pub focus: bool,
1965
1966 pub show: bool,
1968
1969 pub kind: WindowKind,
1971
1972 pub is_movable: bool,
1976
1977 pub app_owns_titlebar_drag: bool,
1989
1990 pub inactive_frame_interval: Option<Duration>,
1994
1995 pub is_resizable: bool,
1997
1998 pub is_minimizable: bool,
2000
2001 pub display_id: Option<DisplayId>,
2004
2005 pub window_background: WindowBackgroundAppearance,
2007
2008 pub app_id: Option<String>,
2010
2011 pub window_min_size: Option<Size<Pixels>>,
2013
2014 pub window_decorations: Option<WindowDecorations>,
2017
2018 pub icon: Option<Arc<image::RgbaImage>>,
2020
2021 pub tabbing_identifier: Option<String>,
2023}
2024
2025#[derive(Debug)]
2027#[cfg_attr(
2028 all(
2029 any(target_os = "linux", target_os = "freebsd"),
2030 not(any(feature = "x11", feature = "wayland"))
2031 ),
2032 allow(dead_code)
2033)]
2034#[allow(missing_docs)]
2035pub struct WindowParams {
2036 pub bounds: Bounds<Pixels>,
2037
2038 #[cfg_attr(feature = "wayland", allow(dead_code))]
2040 pub titlebar: Option<TitlebarOptions>,
2041
2042 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2044 pub kind: WindowKind,
2045
2046 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2048 pub is_movable: bool,
2049
2050 #[cfg_attr(
2052 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2053 allow(dead_code)
2054 )]
2055 pub app_owns_titlebar_drag: bool,
2056
2057 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2059 pub is_resizable: bool,
2060
2061 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2063 pub is_minimizable: bool,
2064
2065 #[cfg_attr(
2066 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2067 allow(dead_code)
2068 )]
2069 pub focus: bool,
2070
2071 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2072 pub show: bool,
2073
2074 #[cfg_attr(feature = "wayland", allow(dead_code))]
2076 pub icon: Option<Arc<image::RgbaImage>>,
2077
2078 #[cfg_attr(feature = "wayland", allow(dead_code))]
2079 pub display_id: Option<DisplayId>,
2080
2081 #[cfg_attr(feature = "wayland", allow(dead_code))]
2082 pub app_id: Option<String>,
2083
2084 pub window_min_size: Option<Size<Pixels>>,
2085
2086 #[cfg(target_os = "macos")]
2087 pub tabbing_identifier: Option<String>,
2088}
2089
2090#[derive(Debug, Copy, Clone, PartialEq)]
2092pub enum WindowBounds {
2093 Windowed(Bounds<Pixels>),
2095 Maximized(Bounds<Pixels>),
2098 Fullscreen(Bounds<Pixels>),
2101}
2102
2103impl Default for WindowBounds {
2104 fn default() -> Self {
2105 WindowBounds::Windowed(Bounds::default())
2106 }
2107}
2108
2109impl WindowBounds {
2110 pub fn get_bounds(&self) -> Bounds<Pixels> {
2112 match self {
2113 WindowBounds::Windowed(bounds) => *bounds,
2114 WindowBounds::Maximized(bounds) => *bounds,
2115 WindowBounds::Fullscreen(bounds) => *bounds,
2116 }
2117 }
2118
2119 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2121 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2122 }
2123}
2124
2125impl Default for WindowOptions {
2126 fn default() -> Self {
2127 Self {
2128 window_bounds: None,
2129 titlebar: Some(TitlebarOptions {
2130 title: Default::default(),
2131 appears_transparent: Default::default(),
2132 traffic_light_position: Default::default(),
2133 }),
2134 focus: true,
2135 show: true,
2136 kind: WindowKind::Normal,
2137 is_movable: true,
2138 app_owns_titlebar_drag: false,
2139 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2140 is_resizable: true,
2141 is_minimizable: true,
2142 display_id: None,
2143 window_background: WindowBackgroundAppearance::default(),
2144 icon: None,
2145 app_id: None,
2146 window_min_size: None,
2147 window_decorations: None,
2148 tabbing_identifier: None,
2149 }
2150 }
2151}
2152
2153#[derive(Debug, Default)]
2155pub struct TitlebarOptions {
2156 pub title: Option<SharedString>,
2158
2159 pub appears_transparent: bool,
2162
2163 pub traffic_light_position: Option<Point<Pixels>>,
2165}
2166
2167#[derive(Clone, Debug, PartialEq, Eq)]
2169pub enum WindowKind {
2170 Normal,
2172
2173 PopUp,
2176
2177 AnchoredPopup(popup::PopupOptions),
2184
2185 Floating,
2187
2188 #[cfg(all(target_os = "linux", feature = "wayland"))]
2191 LayerShell(layer_shell::LayerShellOptions),
2192
2193 Dialog,
2196}
2197
2198#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2203pub enum WindowAppearance {
2204 #[default]
2208 Light,
2209
2210 VibrantLight,
2214
2215 Dark,
2219
2220 VibrantDark,
2224}
2225
2226#[derive(Copy, Clone, Debug, Default, PartialEq)]
2229pub enum WindowBackgroundAppearance {
2230 #[default]
2238 Opaque,
2239 Transparent,
2241 Blurred,
2245 MicaBackdrop,
2247 MicaAltBackdrop,
2249}
2250
2251#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2253pub enum TextRenderingMode {
2254 #[default]
2256 PlatformDefault,
2257 Subpixel,
2259 Grayscale,
2261}
2262
2263#[derive(Clone, Debug)]
2265pub struct PathPromptOptions {
2266 pub files: bool,
2268 pub directories: bool,
2270 pub multiple: bool,
2272 pub prompt: Option<SharedString>,
2274}
2275
2276#[derive(Copy, Clone, Debug, PartialEq)]
2278pub enum PromptLevel {
2279 Info,
2281
2282 Warning,
2284
2285 Critical,
2287}
2288
2289#[derive(Clone, Debug, PartialEq)]
2291pub enum PromptButton {
2292 Ok(SharedString),
2294 Cancel(SharedString),
2296 Other(SharedString),
2298}
2299
2300impl PromptButton {
2301 pub fn new(label: impl Into<SharedString>) -> Self {
2303 PromptButton::Other(label.into())
2304 }
2305
2306 pub fn ok(label: impl Into<SharedString>) -> Self {
2308 PromptButton::Ok(label.into())
2309 }
2310
2311 pub fn cancel(label: impl Into<SharedString>) -> Self {
2313 PromptButton::Cancel(label.into())
2314 }
2315
2316 #[allow(dead_code)]
2318 pub fn is_cancel(&self) -> bool {
2319 matches!(self, PromptButton::Cancel(_))
2320 }
2321
2322 pub fn label(&self) -> &SharedString {
2324 match self {
2325 PromptButton::Ok(label) => label,
2326 PromptButton::Cancel(label) => label,
2327 PromptButton::Other(label) => label,
2328 }
2329 }
2330}
2331
2332impl From<&str> for PromptButton {
2333 fn from(value: &str) -> Self {
2334 match value.to_lowercase().as_str() {
2335 "ok" => PromptButton::Ok("OK".into()),
2336 "cancel" => PromptButton::Cancel("Cancel".into()),
2337 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2338 }
2339 }
2340}
2341
2342#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2344pub enum CursorStyle {
2345 #[default]
2347 Arrow,
2348
2349 IBeam,
2352
2353 Crosshair,
2356
2357 ClosedHand,
2360
2361 OpenHand,
2364
2365 PointingHand,
2368
2369 ResizeLeft,
2372
2373 ResizeRight,
2376
2377 ResizeLeftRight,
2380
2381 ResizeUp,
2384
2385 ResizeDown,
2388
2389 ResizeUpDown,
2392
2393 ResizeUpLeftDownRight,
2396
2397 ResizeUpRightDownLeft,
2400
2401 ResizeColumn,
2404
2405 ResizeRow,
2408
2409 IBeamCursorForVerticalLayout,
2412
2413 OperationNotAllowed,
2416
2417 DragLink,
2420
2421 DragCopy,
2424
2425 ContextualMenu,
2428}
2429
2430#[derive(Clone, Debug, Eq, PartialEq)]
2432pub struct ClipboardItem {
2433 pub entries: Vec<ClipboardEntry>,
2435}
2436
2437#[derive(Clone, Debug, PartialEq, Eq)]
2442pub enum ClipboardReadError {
2443 Unavailable,
2447 Denied(String),
2450 UnsupportedContent,
2453}
2454
2455impl std::fmt::Display for ClipboardReadError {
2456 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2457 match self {
2458 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2459 Self::Denied(message) => {
2460 write!(formatter, "clipboard access was denied: {message}")
2461 }
2462 Self::UnsupportedContent => {
2463 formatter.write_str("the clipboard contents are unsupported")
2464 }
2465 }
2466 }
2467}
2468
2469impl std::error::Error for ClipboardReadError {}
2470
2471#[derive(Clone, Debug, Eq, PartialEq)]
2473pub enum ClipboardEntry {
2474 String(ClipboardString),
2476 Image(Image),
2478 ExternalPaths(crate::ExternalPaths),
2480}
2481
2482impl ClipboardItem {
2483 pub fn new_string(text: String) -> Self {
2485 Self {
2486 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2487 }
2488 }
2489
2490 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2492 Self {
2493 entries: vec![ClipboardEntry::String(ClipboardString {
2494 text,
2495 metadata: Some(metadata),
2496 })],
2497 }
2498 }
2499
2500 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2502 Self {
2503 entries: vec![ClipboardEntry::String(
2504 ClipboardString::new(text).with_json_metadata(metadata),
2505 )],
2506 }
2507 }
2508
2509 pub fn new_image(image: &Image) -> Self {
2511 Self {
2512 entries: vec![ClipboardEntry::Image(image.clone())],
2513 }
2514 }
2515
2516 pub fn text(&self) -> Option<String> {
2519 let mut answer = String::new();
2520
2521 for entry in self.entries.iter() {
2522 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2523 answer.push_str(text);
2524 }
2525 }
2526
2527 if answer.is_empty() {
2528 for entry in self.entries.iter() {
2529 if let ClipboardEntry::ExternalPaths(paths) = entry {
2530 for path in &paths.0 {
2531 use std::fmt::Write as _;
2532 _ = write!(answer, "{}", path.display());
2533 }
2534 }
2535 }
2536 }
2537
2538 if !answer.is_empty() {
2539 Some(answer)
2540 } else {
2541 None
2542 }
2543 }
2544
2545 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2547 pub fn metadata(&self) -> Option<&String> {
2548 match self.entries().first() {
2549 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2550 clipboard_string.metadata.as_ref()
2551 }
2552 _ => None,
2553 }
2554 }
2555
2556 pub fn entries(&self) -> &[ClipboardEntry] {
2558 &self.entries
2559 }
2560
2561 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2563 self.entries.into_iter()
2564 }
2565}
2566
2567impl From<ClipboardString> for ClipboardEntry {
2568 fn from(value: ClipboardString) -> Self {
2569 Self::String(value)
2570 }
2571}
2572
2573impl From<String> for ClipboardEntry {
2574 fn from(value: String) -> Self {
2575 Self::from(ClipboardString::from(value))
2576 }
2577}
2578
2579impl From<Image> for ClipboardEntry {
2580 fn from(value: Image) -> Self {
2581 Self::Image(value)
2582 }
2583}
2584
2585impl From<ClipboardEntry> for ClipboardItem {
2586 fn from(value: ClipboardEntry) -> Self {
2587 Self {
2588 entries: vec![value],
2589 }
2590 }
2591}
2592
2593impl From<String> for ClipboardItem {
2594 fn from(value: String) -> Self {
2595 Self::from(ClipboardEntry::from(value))
2596 }
2597}
2598
2599impl From<Image> for ClipboardItem {
2600 fn from(value: Image) -> Self {
2601 Self::from(ClipboardEntry::from(value))
2602 }
2603}
2604
2605#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2607pub enum ImageFormat {
2608 Png,
2613 Jpeg,
2615 Webp,
2617 Gif,
2619 Svg,
2621 Bmp,
2623 Tiff,
2625 Ico,
2627 Pnm,
2629}
2630
2631impl ImageFormat {
2632 pub const fn mime_type(self) -> &'static str {
2634 match self {
2635 ImageFormat::Png => "image/png",
2636 ImageFormat::Jpeg => "image/jpeg",
2637 ImageFormat::Webp => "image/webp",
2638 ImageFormat::Gif => "image/gif",
2639 ImageFormat::Svg => "image/svg+xml",
2640 ImageFormat::Bmp => "image/bmp",
2641 ImageFormat::Tiff => "image/tiff",
2642 ImageFormat::Ico => "image/ico",
2643 ImageFormat::Pnm => "image/x-portable-anymap",
2644 }
2645 }
2646
2647 pub const fn extension(self) -> &'static str {
2649 match self {
2650 ImageFormat::Png => "png",
2651 ImageFormat::Jpeg => "jpg",
2652 ImageFormat::Webp => "webp",
2653 ImageFormat::Gif => "gif",
2654 ImageFormat::Svg => "svg",
2655 ImageFormat::Bmp => "bmp",
2656 ImageFormat::Tiff => "tiff",
2657 ImageFormat::Ico => "ico",
2658 ImageFormat::Pnm => "pnm",
2659 }
2660 }
2661
2662 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2664 use strum::IntoEnumIterator;
2665 Self::iter()
2666 .find(|format| format.mime_type() == mime_type)
2667 .or_else(|| Self::from_mime_type_alias(mime_type))
2668 }
2669
2670 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2674 match mime_type {
2675 "image/jpg" => Some(Self::Jpeg),
2676 "image/tif" => Some(Self::Tiff),
2677 _ => None,
2678 }
2679 }
2680}
2681
2682#[derive(Clone, Debug, PartialEq, Eq)]
2684pub struct Image {
2685 pub format: ImageFormat,
2687 pub bytes: Vec<u8>,
2689 pub id: u64,
2691}
2692
2693pub(crate) fn decode_static_image(
2694 bytes: &[u8],
2695 format: image::ImageFormat,
2696) -> Result<SmallVec<[Frame; 1]>> {
2697 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2698 .into_decoder()
2699 .context("creating image decoder")?;
2700 decode_static_image_from_decoder(decoder)
2701}
2702
2703pub(crate) fn decode_static_image_from_decoder(
2704 mut decoder: impl image::ImageDecoder,
2705) -> Result<SmallVec<[Frame; 1]>> {
2706 let orientation = decoder
2707 .orientation()
2708 .context("reading decoder's orientation")?;
2709 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2710 image.apply_orientation(orientation);
2711
2712 let mut data = image.into_rgba8();
2713 for pixel in data.chunks_exact_mut(4) {
2714 pixel.swap(0, 2);
2715 }
2716
2717 Ok(SmallVec::from_elem(Frame::new(data), 1))
2718}
2719
2720impl Hash for Image {
2721 fn hash<H: Hasher>(&self, state: &mut H) {
2722 state.write_u64(self.id);
2723 }
2724}
2725
2726impl Image {
2727 pub fn empty() -> Self {
2729 Self::from_bytes(ImageFormat::Png, Vec::new())
2730 }
2731
2732 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2734 Self {
2735 id: hash(&bytes),
2736 format,
2737 bytes,
2738 }
2739 }
2740
2741 pub fn id(&self) -> u64 {
2743 self.id
2744 }
2745
2746 pub fn use_render_image(
2748 self: Arc<Self>,
2749 window: &mut Window,
2750 cx: &mut App,
2751 ) -> Option<Arc<RenderImage>> {
2752 ImageSource::Image(self)
2753 .use_data(None, window, cx)
2754 .and_then(|result| result.ok())
2755 }
2756
2757 pub fn get_render_image(
2759 self: Arc<Self>,
2760 window: &mut Window,
2761 cx: &mut App,
2762 ) -> Option<Arc<RenderImage>> {
2763 ImageSource::Image(self)
2764 .get_data(None, window, cx)
2765 .and_then(|result| result.ok())
2766 }
2767
2768 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2770 ImageSource::Image(self).remove_asset(cx);
2771 }
2772
2773 #[cfg(any(test, feature = "test-support"))]
2776 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2777 ImageSource::Image(self.clone()).is_asset_cached(cx)
2778 }
2779
2780 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2782 let frames = match self.format {
2783 ImageFormat::Gif => {
2784 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2785 let mut frames = SmallVec::new();
2786
2787 for frame in decoder.into_frames() {
2788 match frame {
2789 Ok(mut frame) => {
2790 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2792 pixel.swap(0, 2);
2793 }
2794 frames.push(frame);
2795 }
2796 Err(err) => {
2797 log::debug!("Skipping GIF frame due to decode error: {err}");
2798 }
2799 }
2800 }
2801
2802 if frames.is_empty() {
2803 anyhow::bail!("GIF could not be decoded: all frames failed");
2804 }
2805
2806 frames
2807 }
2808 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2809 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2810 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2811 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2812 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2813 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2814 ImageFormat::Svg => {
2815 return svg_renderer
2816 .render_single_frame(&self.bytes, 1.0)
2817 .map_err(Into::into);
2818 }
2819 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2820 };
2821
2822 Ok(Arc::new(RenderImage::new(frames)))
2823 }
2824
2825 pub fn format(&self) -> ImageFormat {
2827 self.format
2828 }
2829
2830 pub fn bytes(&self) -> &[u8] {
2832 self.bytes.as_slice()
2833 }
2834}
2835
2836#[derive(Clone, Debug, Eq, PartialEq)]
2838pub struct ClipboardString {
2839 pub text: String,
2841 pub metadata: Option<String>,
2843}
2844
2845impl ClipboardString {
2846 pub fn new(text: String) -> Self {
2848 Self {
2849 text,
2850 metadata: None,
2851 }
2852 }
2853
2854 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2857 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2858 self
2859 }
2860
2861 pub fn text(&self) -> &String {
2863 &self.text
2864 }
2865
2866 pub fn into_text(self) -> String {
2868 self.text
2869 }
2870
2871 pub fn metadata_json<T>(&self) -> Option<T>
2873 where
2874 T: for<'a> Deserialize<'a>,
2875 {
2876 self.metadata
2877 .as_ref()
2878 .and_then(|m| serde_json::from_str(m).ok())
2879 }
2880
2881 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2882 pub fn text_hash(text: &str) -> u64 {
2884 let mut hasher = SeaHasher::new();
2885 text.hash(&mut hasher);
2886 hasher.finish()
2887 }
2888}
2889
2890impl From<String> for ClipboardString {
2891 fn from(value: String) -> Self {
2892 Self {
2893 text: value,
2894 metadata: None,
2895 }
2896 }
2897}
2898
2899#[cfg(test)]
2900mod image_tests {
2901 use super::*;
2902 use std::sync::Arc;
2903
2904 #[test]
2905 fn test_image_to_image_data_applies_exif_orientation() {
2906 let image = Image::from_bytes(
2907 ImageFormat::Jpeg,
2908 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2909 );
2910
2911 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2912
2913 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2914
2915 let bytes = render_image.as_bytes(0).unwrap();
2916 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2917 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2918 }
2919
2920 #[test]
2921 fn test_svg_image_to_image_data_converts_to_bgra() {
2922 let image = Image::from_bytes(
2923 ImageFormat::Svg,
2924 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2925<rect width="1" height="1" fill="#38BDF8"/>
2926</svg>"##
2927 .to_vec(),
2928 );
2929
2930 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2931 let bytes = render_image.as_bytes(0).unwrap();
2932
2933 for pixel in bytes.chunks_exact(4) {
2934 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2935 }
2936 }
2937}
2938
2939#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2940mod tests {
2941 use super::*;
2942 use std::collections::HashSet;
2943
2944 #[test]
2945 fn test_window_button_layout_parse_standard() {
2946 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2947 assert_eq!(
2948 layout.left,
2949 [
2950 Some(WindowButton::Close),
2951 Some(WindowButton::Minimize),
2952 None
2953 ]
2954 );
2955 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2956 }
2957
2958 #[test]
2959 fn test_window_button_layout_parse_right_only() {
2960 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2961 assert_eq!(layout.left, [None, None, None]);
2962 assert_eq!(
2963 layout.right,
2964 [
2965 Some(WindowButton::Minimize),
2966 Some(WindowButton::Maximize),
2967 Some(WindowButton::Close)
2968 ]
2969 );
2970 }
2971
2972 #[test]
2973 fn test_window_button_layout_parse_left_only() {
2974 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2975 assert_eq!(
2976 layout.left,
2977 [
2978 Some(WindowButton::Close),
2979 Some(WindowButton::Minimize),
2980 Some(WindowButton::Maximize)
2981 ]
2982 );
2983 assert_eq!(layout.right, [None, None, None]);
2984 }
2985
2986 #[test]
2987 fn test_window_button_layout_parse_with_whitespace() {
2988 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2989 assert_eq!(
2990 layout.left,
2991 [
2992 Some(WindowButton::Close),
2993 Some(WindowButton::Minimize),
2994 None
2995 ]
2996 );
2997 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2998 }
2999
3000 #[test]
3001 fn test_window_button_layout_parse_empty() {
3002 let layout = WindowButtonLayout::parse("").unwrap();
3003 assert_eq!(layout.left, [None, None, None]);
3004 assert_eq!(layout.right, [None, None, None]);
3005 }
3006
3007 #[test]
3008 fn test_window_button_layout_parse_intentionally_empty() {
3009 let layout = WindowButtonLayout::parse(":").unwrap();
3010 assert_eq!(layout.left, [None, None, None]);
3011 assert_eq!(layout.right, [None, None, None]);
3012 }
3013
3014 #[test]
3015 fn test_window_button_layout_parse_invalid_buttons() {
3016 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3017 assert_eq!(
3018 layout.left,
3019 [
3020 Some(WindowButton::Close),
3021 Some(WindowButton::Minimize),
3022 None
3023 ]
3024 );
3025 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3026 }
3027
3028 #[test]
3029 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3030 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3031 assert_eq!(
3032 layout.right,
3033 [
3034 Some(WindowButton::Close),
3035 Some(WindowButton::Minimize),
3036 None
3037 ]
3038 );
3039 assert_eq!(layout.format(), ":close,minimize");
3040 }
3041
3042 #[test]
3043 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3044 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3045 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3046 assert_eq!(
3047 layout.right,
3048 [
3049 Some(WindowButton::Maximize),
3050 Some(WindowButton::Minimize),
3051 None
3052 ]
3053 );
3054
3055 let button_ids: Vec<_> = layout
3056 .left
3057 .iter()
3058 .chain(layout.right.iter())
3059 .flatten()
3060 .map(WindowButton::id)
3061 .collect();
3062 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3063 assert_eq!(unique_button_ids.len(), button_ids.len());
3064 assert_eq!(layout.format(), "close:maximize,minimize");
3065 }
3066
3067 #[test]
3068 fn test_window_button_layout_parse_gnome_style() {
3069 let layout = WindowButtonLayout::parse("close").unwrap();
3070 assert_eq!(layout.left, [None, None, None]);
3071 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3072 }
3073
3074 #[test]
3075 fn test_window_button_layout_parse_elementary_style() {
3076 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3077 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3078 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3079 }
3080
3081 #[test]
3082 fn test_window_button_layout_round_trip() {
3083 let cases = [
3084 "close:minimize,maximize",
3085 "minimize,maximize,close:",
3086 ":close",
3087 "close:",
3088 "close:maximize",
3089 ":",
3090 ];
3091
3092 for case in cases {
3093 let layout = WindowButtonLayout::parse(case).unwrap();
3094 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3095 }
3096 }
3097
3098 #[test]
3099 fn test_window_button_layout_linux_default() {
3100 let layout = WindowButtonLayout::linux_default();
3101 assert_eq!(layout.left, [None, None, None]);
3102 assert_eq!(
3103 layout.right,
3104 [
3105 Some(WindowButton::Minimize),
3106 Some(WindowButton::Maximize),
3107 Some(WindowButton::Close)
3108 ]
3109 );
3110
3111 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3112 assert_eq!(round_tripped, layout);
3113 }
3114
3115 #[test]
3116 fn test_window_button_layout_parse_all_invalid() {
3117 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3118 }
3119}