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 schedule_frame(&self) {}
870 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
871 fn is_subpixel_rendering_supported(&self) -> bool;
872
873 fn get_title(&self) -> String {
875 String::new()
876 }
877 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
878 None
879 }
880 fn tab_bar_visible(&self) -> bool {
881 false
882 }
883 fn set_edited(&mut self, _edited: bool) {}
884 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
885 fn toggle_simple_fullscreen(&self) {}
886 fn is_simple_fullscreen(&self) -> bool {
887 false
888 }
889 #[cfg(target_os = "macos")]
890 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
891 fn show_character_palette(&self) {}
892 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
893 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
894 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
895 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
896 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
897 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
898 fn merge_all_windows(&self) {}
899 fn move_tab_to_new_window(&self) {}
900 fn toggle_window_tab_overview(&self) {}
901 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
902
903 #[cfg(target_os = "windows")]
904 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
905
906 fn inner_window_bounds(&self) -> WindowBounds {
908 self.window_bounds()
909 }
910 fn request_decorations(&self, _decorations: WindowDecorations) {}
911 fn show_window_menu(&self, _position: Point<Pixels>) {}
912 fn start_window_move(&self) {}
913 fn can_start_external_drag(&self) -> bool {
914 false
915 }
916 fn start_external_drag(&self, _payload: &ExternalDragPayload) -> bool {
917 false
918 }
919 fn start_window_resize(&self, _edge: ResizeEdge) {}
920 fn set_exclusive_zone(&self, _zone: Pixels) {}
921 #[cfg(all(target_os = "linux", feature = "wayland"))]
922 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
923 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
924 fn window_decorations(&self) -> Decorations {
925 Decorations::Server
926 }
927 fn set_app_id(&mut self, _app_id: &str) {}
928 fn map_window(&mut self) -> anyhow::Result<()> {
929 Ok(())
930 }
931 fn window_controls(&self) -> WindowControls {
932 WindowControls::default()
933 }
934 fn set_client_inset(&self, _inset: Pixels) {}
935 fn gpu_specs(&self) -> Option<GpuSpecs>;
936
937 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
938
939 fn insets(&self) -> WindowInsets {
944 WindowInsets::default()
945 }
946
947 fn on_insets_changed(&self, _callback: Box<dyn FnMut(WindowInsets)>) {}
953
954 fn set_back_handler(&self, _callback: Box<dyn FnMut()>) {}
957
958 fn set_back_enabled(&self, _enabled: bool) {}
961
962 fn show_soft_keyboard(&self) {}
964
965 fn hide_soft_keyboard(&self) {}
967
968 fn text_input_state_changed(&self, _change: TextInputStateChange) {}
970
971 fn play_system_bell(&self) {}
972
973 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
975
976 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
978
979 fn a11y_update_window_bounds(&self) {}
981
982 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
983 fn as_test(&mut self) -> Option<&mut TestWindow> {
984 None
985 }
986
987 #[cfg(any(test, feature = "test-support"))]
991 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
992 anyhow::bail!("render_to_image not implemented for this platform")
993 }
994}
995
996#[cfg(any(test, feature = "test-support", feature = "bench-support"))]
998pub trait PlatformHeadlessRenderer {
999 fn render_scene_to_image(
1001 &mut self,
1002 scene: &Scene,
1003 size: Size<DevicePixels>,
1004 ) -> Result<RgbaImage>;
1005
1006 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1012
1013 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1015}
1016
1017#[doc(hidden)]
1020pub type RunnableVariant = Runnable<RunnableMeta>;
1021
1022#[doc(hidden)]
1023pub type TimerResolutionGuard = gpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1024
1025#[doc(hidden)]
1026pub enum TasksIncluded {
1027 OnlyCompleted,
1028 CompletedAndRunning,
1029}
1030
1031#[doc(hidden)]
1034pub trait PlatformDispatcher: Send + Sync {
1035 fn is_main_thread(&self) -> bool;
1036 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1037 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1038 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1039
1040 fn dispatch_on_main_thread_when_idle(
1041 &self,
1042 runnable: RunnableVariant,
1043 timeout: Option<Duration>,
1044 ) {
1045 let _ = timeout;
1046 self.dispatch_on_main_thread(runnable, Priority::Low);
1047 }
1048
1049 fn idle_time_remaining(&self) -> Option<Duration> {
1050 None
1051 }
1052
1053 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1054
1055 fn now(&self) -> Instant {
1056 Instant::now()
1057 }
1058
1059 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1060 gpui_util::defer(Box::new(|| {}))
1061 }
1062
1063 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1064 fn as_test(&self) -> Option<&TestDispatcher> {
1065 None
1066 }
1067
1068 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1071 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1072 None
1073 }
1074}
1075
1076#[expect(missing_docs)]
1077pub trait PlatformTextSystem: Send + Sync {
1078 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1079 fn all_font_names(&self) -> Vec<String>;
1081 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1083 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
1085 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1087 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1089 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1091 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1093 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1095 fn rasterize_glyph(
1097 &self,
1098 params: &RenderGlyphParams,
1099 raster_bounds: Bounds<DevicePixels>,
1100 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1101 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1103 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1105 -> TextRenderingMode;
1106 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1108 0
1109 }
1110}
1111
1112#[expect(missing_docs)]
1113pub struct NoopTextSystem;
1114
1115#[expect(missing_docs)]
1116impl NoopTextSystem {
1117 #[allow(dead_code)]
1118 pub fn new() -> Self {
1119 Self
1120 }
1121}
1122
1123impl PlatformTextSystem for NoopTextSystem {
1124 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1125 Ok(())
1126 }
1127
1128 fn all_font_names(&self) -> Vec<String> {
1129 Vec::new()
1130 }
1131
1132 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1133 Ok(FontId(1))
1134 }
1135
1136 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1137 FontMetrics {
1138 units_per_em: 1000,
1139 ascent: 1025.0,
1140 descent: -275.0,
1141 line_gap: 0.0,
1142 underline_position: -95.0,
1143 underline_thickness: 60.0,
1144 cap_height: 698.0,
1145 x_height: 516.0,
1146 bounding_box: Bounds {
1147 origin: Point {
1148 x: -260.0,
1149 y: -245.0,
1150 },
1151 size: Size {
1152 width: 1501.0,
1153 height: 1364.0,
1154 },
1155 },
1156 }
1157 }
1158
1159 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1160 Ok(Bounds {
1161 origin: Point { x: 54.0, y: 0.0 },
1162 size: size(392.0, 528.0),
1163 })
1164 }
1165
1166 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1167 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1168 }
1169
1170 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1171 Some(GlyphId(ch.len_utf16() as u32))
1172 }
1173
1174 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1175 Ok(Default::default())
1176 }
1177
1178 fn rasterize_glyph(
1179 &self,
1180 _params: &RenderGlyphParams,
1181 raster_bounds: Bounds<DevicePixels>,
1182 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1183 Ok((raster_bounds.size, Vec::new()))
1184 }
1185
1186 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1187 let mut position = px(0.);
1188 let metrics = self.font_metrics(FontId(0));
1189 let em_width = font_size
1190 * self
1191 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1192 .unwrap()
1193 .width
1194 / metrics.units_per_em as f32;
1195 let mut glyphs = Vec::new();
1196 for (ix, c) in text.char_indices() {
1197 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1198 glyphs.push(ShapedGlyph {
1199 id: glyph,
1200 position: point(position, px(0.)),
1201 index: ix,
1202 is_emoji: glyph.0 == 2,
1203 });
1204 if glyph.0 == 2 {
1205 position += em_width * 2.0;
1206 } else {
1207 position += em_width;
1208 }
1209 } else {
1210 position += em_width
1211 }
1212 }
1213 let mut runs = Vec::default();
1214 if !glyphs.is_empty() {
1215 runs.push(ShapedRun {
1216 font_id: FontId(0),
1217 glyphs,
1218 });
1219 } else {
1220 position = px(0.);
1221 }
1222
1223 LineLayout {
1224 font_size,
1225 width: position,
1226 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1227 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1228 runs,
1229 len: text.len(),
1230 }
1231 }
1232
1233 fn recommended_rendering_mode(
1234 &self,
1235 _font_id: FontId,
1236 _font_size: Pixels,
1237 ) -> TextRenderingMode {
1238 TextRenderingMode::Grayscale
1239 }
1240}
1241
1242#[allow(dead_code)]
1247pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1248 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1249 [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], ];
1263
1264 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1265 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1266
1267 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1268 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1269
1270 [
1271 ratios[0] * NORM13,
1272 ratios[1] * NORM24,
1273 ratios[2] * NORM13,
1274 ratios[3] * NORM24,
1275 ]
1276}
1277
1278#[derive(PartialEq, Eq, Hash, Clone)]
1279#[expect(missing_docs)]
1280pub enum AtlasKey {
1281 Glyph(RenderGlyphParams),
1282 Svg(RenderSvgParams),
1283 Image(RenderImageParams),
1284}
1285
1286impl AtlasKey {
1287 #[cfg_attr(
1288 all(
1289 any(target_os = "linux", target_os = "freebsd"),
1290 not(any(feature = "x11", feature = "wayland"))
1291 ),
1292 allow(dead_code)
1293 )]
1294 pub fn texture_kind(&self) -> AtlasTextureKind {
1296 match self {
1297 AtlasKey::Glyph(params) => {
1298 if params.is_emoji {
1299 AtlasTextureKind::Polychrome
1300 } else if params.subpixel_rendering {
1301 AtlasTextureKind::Subpixel
1302 } else {
1303 AtlasTextureKind::Monochrome
1304 }
1305 }
1306 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1307 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1308 }
1309 }
1310}
1311
1312impl From<RenderGlyphParams> for AtlasKey {
1313 fn from(params: RenderGlyphParams) -> Self {
1314 Self::Glyph(params)
1315 }
1316}
1317
1318impl From<RenderSvgParams> for AtlasKey {
1319 fn from(params: RenderSvgParams) -> Self {
1320 Self::Svg(params)
1321 }
1322}
1323
1324impl From<RenderImageParams> for AtlasKey {
1325 fn from(params: RenderImageParams) -> Self {
1326 Self::Image(params)
1327 }
1328}
1329
1330#[expect(missing_docs)]
1331pub trait PlatformAtlas {
1332 fn get_or_insert_with<'a>(
1333 &self,
1334 key: &AtlasKey,
1335 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1336 ) -> Result<Option<AtlasTile>>;
1337 fn remove(&self, key: &AtlasKey);
1338
1339 #[cfg(any(test, feature = "test-support", feature = "bench-support"))]
1340 fn contains(&self, _key: &AtlasKey) -> bool {
1341 false
1342 }
1343}
1344
1345#[doc(hidden)]
1346pub struct AtlasTextureList<T> {
1347 pub textures: Vec<Option<T>>,
1348 pub free_list: Vec<usize>,
1349}
1350
1351impl<T> Default for AtlasTextureList<T> {
1352 fn default() -> Self {
1353 Self {
1354 textures: Vec::default(),
1355 free_list: Vec::default(),
1356 }
1357 }
1358}
1359
1360impl<T> ops::Index<usize> for AtlasTextureList<T> {
1361 type Output = Option<T>;
1362
1363 fn index(&self, index: usize) -> &Self::Output {
1364 &self.textures[index]
1365 }
1366}
1367
1368impl<T> AtlasTextureList<T> {
1369 #[allow(unused)]
1370 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1371 self.free_list.clear();
1372 self.textures.drain(..)
1373 }
1374
1375 #[allow(dead_code)]
1376 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1377 self.textures.iter_mut().flatten()
1378 }
1379}
1380
1381#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1382#[repr(C)]
1383#[expect(missing_docs)]
1384pub struct AtlasTile {
1385 pub texture_id: AtlasTextureId,
1387 pub tile_id: TileId,
1389 pub padding: u32,
1391 pub bounds: Bounds<DevicePixels>,
1393}
1394
1395#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1396#[repr(C)]
1397#[expect(missing_docs)]
1398pub struct AtlasTextureId {
1399 pub index: u32,
1402 pub kind: AtlasTextureKind,
1404}
1405
1406#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1407#[repr(C)]
1408#[cfg_attr(
1409 all(
1410 any(target_os = "linux", target_os = "freebsd"),
1411 not(any(feature = "x11", feature = "wayland"))
1412 ),
1413 allow(dead_code)
1414)]
1415#[expect(missing_docs)]
1416pub enum AtlasTextureKind {
1417 Monochrome = 0,
1418 Polychrome = 1,
1419 Subpixel = 2,
1420}
1421
1422#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1423#[repr(C)]
1424#[expect(missing_docs)]
1425pub struct TileId(pub u32);
1426
1427impl From<etagere::AllocId> for TileId {
1428 fn from(id: etagere::AllocId) -> Self {
1429 Self(id.serialize())
1430 }
1431}
1432
1433impl From<TileId> for etagere::AllocId {
1434 fn from(id: TileId) -> Self {
1435 Self::deserialize(id.0)
1436 }
1437}
1438
1439#[expect(missing_docs)]
1440pub struct PlatformInputHandler {
1441 cx: AsyncWindowContext,
1442 handler: Box<dyn InputHandler>,
1443}
1444
1445#[expect(missing_docs)]
1446#[cfg_attr(
1447 all(
1448 any(target_os = "linux", target_os = "freebsd"),
1449 not(any(feature = "x11", feature = "wayland"))
1450 ),
1451 allow(dead_code)
1452)]
1453impl PlatformInputHandler {
1454 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1455 Self { cx, handler }
1456 }
1457
1458 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1459 self.cx
1460 .update(|window, cx| {
1461 self.handler
1462 .selected_text_range(ignore_disabled_input, window, cx)
1463 })
1464 .ok()
1465 .flatten()
1466 }
1467
1468 #[cfg_attr(target_os = "windows", allow(dead_code))]
1469 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1470 self.cx
1471 .update(|window, cx| self.handler.marked_text_range(window, cx))
1472 .ok()
1473 .flatten()
1474 }
1475
1476 #[cfg_attr(
1477 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1478 allow(dead_code)
1479 )]
1480 pub fn text_for_range(
1481 &mut self,
1482 range_utf16: Range<usize>,
1483 adjusted: &mut Option<Range<usize>>,
1484 ) -> Option<String> {
1485 self.cx
1486 .update(|window, cx| {
1487 self.handler
1488 .text_for_range(range_utf16, adjusted, window, cx)
1489 })
1490 .ok()
1491 .flatten()
1492 }
1493
1494 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1495 self.cx
1496 .update(|window, cx| {
1497 self.handler
1498 .replace_text_in_range(replacement_range, text, window, cx);
1499 })
1500 .ok();
1501 }
1502
1503 pub fn replace_and_mark_text_in_range(
1504 &mut self,
1505 range_utf16: Option<Range<usize>>,
1506 new_text: &str,
1507 new_selected_range: Option<Range<usize>>,
1508 ) {
1509 self.cx
1510 .update(|window, cx| {
1511 self.handler.replace_and_mark_text_in_range(
1512 range_utf16,
1513 new_text,
1514 new_selected_range,
1515 window,
1516 cx,
1517 )
1518 })
1519 .ok();
1520 }
1521
1522 #[cfg_attr(target_os = "windows", allow(dead_code))]
1523 pub fn unmark_text(&mut self) {
1524 self.cx
1525 .update(|window, cx| self.handler.unmark_text(window, cx))
1526 .ok();
1527 }
1528
1529 pub fn paste(&mut self, item: ClipboardItem) {
1530 self.cx
1531 .update(|window, cx| self.handler.paste(item, window, cx))
1532 .ok();
1533 }
1534
1535 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1536 self.cx
1537 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1538 .ok()
1539 .flatten()
1540 }
1541
1542 #[allow(dead_code)]
1543 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1544 self.handler.apple_press_and_hold_enabled()
1545 }
1546
1547 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1548 self.handler.replace_text_in_range(None, input, window, cx);
1549 }
1550
1551 pub fn compute_ime_candidate_bounds(
1552 marked_range: Option<Range<usize>>,
1553 selection: &UTF16Selection,
1554 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1555 ) -> Option<Bounds<Pixels>> {
1556 if let Some(marked_range) = marked_range {
1557 let mut line_start = marked_range.start;
1559
1560 let caret = selection.range.end;
1564 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1565 for i in (marked_range.start..caret).rev() {
1566 if let Some(b) = bounds_for_range(i..i) {
1567 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1568 line_start = i + 1;
1569 break;
1570 }
1571 }
1572 }
1573 }
1574 bounds_for_range(line_start..line_start)
1575 } else {
1576 let offset = if selection.reversed {
1578 selection.range.start
1579 } else {
1580 selection.range.end
1581 };
1582 bounds_for_range(offset..offset)
1583 }
1584 }
1585
1586 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1587 let marked_range = self.handler.marked_text_range(window, cx);
1588 let selection = self.handler.selected_text_range(true, window, cx)?;
1589 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1590 self.handler.bounds_for_range(range, window, cx)
1591 })
1592 }
1593
1594 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1595 let marked_range = self.marked_text_range();
1596 let selection = self.selected_text_range(true)?;
1597 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1598 self.bounds_for_range(range)
1599 })
1600 }
1601
1602 #[allow(unused)]
1603 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1604 self.cx
1605 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1606 .ok()
1607 .flatten()
1608 }
1609
1610 pub fn set_selected_text_range(&mut self, range_utf16: Range<usize>) {
1612 self.cx
1613 .update(|window, cx| {
1614 self.handler
1615 .set_selected_text_range(range_utf16, window, cx)
1616 })
1617 .ok();
1618 }
1619
1620 pub fn element_bounds(&mut self) -> Option<Bounds<Pixels>> {
1622 self.cx
1623 .update(|window, cx| self.handler.element_bounds(window, cx))
1624 .ok()
1625 .flatten()
1626 }
1627
1628 pub fn text_length_utf16(&mut self) -> Option<usize> {
1630 self.cx
1631 .update(|window, cx| self.handler.text_length_utf16(window, cx))
1632 .ok()
1633 .flatten()
1634 }
1635
1636 #[allow(dead_code)]
1637 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1638 self.handler.accepts_text_input(window, cx)
1639 }
1640
1641 #[allow(dead_code)]
1642 pub fn query_accepts_text_input(&mut self) -> bool {
1643 self.cx
1644 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1645 .unwrap_or(true)
1646 }
1647
1648 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
1654 self.cx
1655 .update(|window, cx| {
1656 !window.has_pending_keystrokes()
1658 && self.handler.prefers_ime_for_printable_keys(window, cx)
1659 })
1660 .unwrap_or(false)
1661 }
1662
1663 pub fn text_input_configuration(
1665 &mut self,
1666 window: &mut Window,
1667 cx: &mut App,
1668 ) -> TextInputConfiguration {
1669 self.handler.text_input_configuration(window, cx)
1670 }
1671
1672 pub fn text_input_editable_range(&mut self) -> Option<Range<usize>> {
1674 self.cx
1675 .update(|window, cx| self.handler.text_input_editable_range(window, cx))
1676 .ok()
1677 .flatten()
1678 }
1679}
1680
1681#[derive(Debug)]
1684pub struct UTF16Selection {
1685 pub range: Range<usize>,
1688 pub reversed: bool,
1691}
1692
1693pub trait InputHandler: 'static {
1698 fn selected_text_range(
1703 &mut self,
1704 ignore_disabled_input: bool,
1705 window: &mut Window,
1706 cx: &mut App,
1707 ) -> Option<UTF16Selection>;
1708
1709 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1714
1715 fn text_for_range(
1720 &mut self,
1721 range_utf16: Range<usize>,
1722 adjusted_range: &mut Option<Range<usize>>,
1723 window: &mut Window,
1724 cx: &mut App,
1725 ) -> Option<String>;
1726
1727 fn replace_text_in_range(
1732 &mut self,
1733 replacement_range: Option<Range<usize>>,
1734 text: &str,
1735 window: &mut Window,
1736 cx: &mut App,
1737 );
1738
1739 fn replace_and_mark_text_in_range(
1746 &mut self,
1747 range_utf16: Option<Range<usize>>,
1748 new_text: &str,
1749 new_selected_range: Option<Range<usize>>,
1750 window: &mut Window,
1751 cx: &mut App,
1752 );
1753
1754 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1757
1758 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
1765 if let Some(text) = item.text() {
1766 self.replace_text_in_range(None, &text, window, cx);
1767 }
1768 }
1769
1770 fn bounds_for_range(
1775 &mut self,
1776 range_utf16: Range<usize>,
1777 window: &mut Window,
1778 cx: &mut App,
1779 ) -> Option<Bounds<Pixels>>;
1780
1781 fn character_index_for_point(
1785 &mut self,
1786 point: Point<Pixels>,
1787 window: &mut Window,
1788 cx: &mut App,
1789 ) -> Option<usize>;
1790
1791 fn set_selected_text_range(
1801 &mut self,
1802 _range_utf16: Range<usize>,
1803 _window: &mut Window,
1804 _cx: &mut App,
1805 ) {
1806 }
1807
1808 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
1815 None
1816 }
1817
1818 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
1820 None
1821 }
1822
1823 #[allow(dead_code)]
1828 fn apple_press_and_hold_enabled(&mut self) -> bool {
1829 true
1830 }
1831
1832 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1834 true
1835 }
1836
1837 fn text_input_editable_range(
1848 &mut self,
1849 _window: &mut Window,
1850 _cx: &mut App,
1851 ) -> Option<Range<usize>> {
1852 None
1853 }
1854
1855 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1864 false
1865 }
1866
1867 fn text_input_configuration(
1873 &mut self,
1874 _window: &mut Window,
1875 _cx: &mut App,
1876 ) -> TextInputConfiguration {
1877 TextInputConfiguration::default()
1878 }
1879}
1880
1881#[derive(Clone, Debug, Default, PartialEq, Eq)]
1891pub struct TextInputConfiguration {
1892 pub autocorrect: bool,
1894 pub autocapitalize: Autocapitalize,
1896 pub suggestions: bool,
1898 pub input_action: TextInputAction,
1900}
1901
1902#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1904pub enum Autocapitalize {
1905 #[default]
1907 None,
1908 Words,
1910 Sentences,
1912 Characters,
1914}
1915
1916#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1926pub enum TextInputAction {
1927 #[default]
1929 Unspecified,
1930 Enter,
1932 Done,
1934 Go,
1936 Next,
1938 Previous,
1940 Search,
1942 Send,
1944}
1945
1946#[derive(Debug)]
1948pub struct WindowOptions {
1949 pub window_bounds: Option<WindowBounds>,
1953
1954 pub titlebar: Option<TitlebarOptions>,
1956
1957 pub focus: bool,
1959
1960 pub show: bool,
1962
1963 pub kind: WindowKind,
1965
1966 pub is_movable: bool,
1970
1971 pub app_owns_titlebar_drag: bool,
1983
1984 pub inactive_frame_interval: Option<Duration>,
1988
1989 pub is_resizable: bool,
1991
1992 pub is_minimizable: bool,
1994
1995 pub display_id: Option<DisplayId>,
1998
1999 pub window_background: WindowBackgroundAppearance,
2001
2002 pub app_id: Option<String>,
2004
2005 pub window_min_size: Option<Size<Pixels>>,
2007
2008 pub window_decorations: Option<WindowDecorations>,
2011
2012 pub icon: Option<Arc<image::RgbaImage>>,
2014
2015 pub tabbing_identifier: Option<String>,
2017}
2018
2019#[derive(Debug)]
2021#[cfg_attr(
2022 all(
2023 any(target_os = "linux", target_os = "freebsd"),
2024 not(any(feature = "x11", feature = "wayland"))
2025 ),
2026 allow(dead_code)
2027)]
2028#[allow(missing_docs)]
2029pub struct WindowParams {
2030 pub bounds: Bounds<Pixels>,
2031
2032 #[cfg_attr(feature = "wayland", allow(dead_code))]
2034 pub titlebar: Option<TitlebarOptions>,
2035
2036 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2038 pub kind: WindowKind,
2039
2040 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2042 pub is_movable: bool,
2043
2044 #[cfg_attr(
2046 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2047 allow(dead_code)
2048 )]
2049 pub app_owns_titlebar_drag: bool,
2050
2051 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2053 pub is_resizable: bool,
2054
2055 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2057 pub is_minimizable: bool,
2058
2059 #[cfg_attr(
2060 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
2061 allow(dead_code)
2062 )]
2063 pub focus: bool,
2064
2065 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2066 pub show: bool,
2067
2068 #[cfg_attr(feature = "wayland", allow(dead_code))]
2070 pub icon: Option<Arc<image::RgbaImage>>,
2071
2072 #[cfg_attr(feature = "wayland", allow(dead_code))]
2073 pub display_id: Option<DisplayId>,
2074
2075 #[cfg_attr(feature = "wayland", allow(dead_code))]
2076 pub app_id: Option<String>,
2077
2078 pub window_min_size: Option<Size<Pixels>>,
2079
2080 #[cfg(target_os = "macos")]
2081 pub tabbing_identifier: Option<String>,
2082}
2083
2084#[derive(Debug, Copy, Clone, PartialEq)]
2086pub enum WindowBounds {
2087 Windowed(Bounds<Pixels>),
2089 Maximized(Bounds<Pixels>),
2092 Fullscreen(Bounds<Pixels>),
2095}
2096
2097impl Default for WindowBounds {
2098 fn default() -> Self {
2099 WindowBounds::Windowed(Bounds::default())
2100 }
2101}
2102
2103impl WindowBounds {
2104 pub fn get_bounds(&self) -> Bounds<Pixels> {
2106 match self {
2107 WindowBounds::Windowed(bounds) => *bounds,
2108 WindowBounds::Maximized(bounds) => *bounds,
2109 WindowBounds::Fullscreen(bounds) => *bounds,
2110 }
2111 }
2112
2113 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2115 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2116 }
2117}
2118
2119impl Default for WindowOptions {
2120 fn default() -> Self {
2121 Self {
2122 window_bounds: None,
2123 titlebar: Some(TitlebarOptions {
2124 title: Default::default(),
2125 appears_transparent: Default::default(),
2126 traffic_light_position: Default::default(),
2127 }),
2128 focus: true,
2129 show: true,
2130 kind: WindowKind::Normal,
2131 is_movable: true,
2132 app_owns_titlebar_drag: false,
2133 inactive_frame_interval: Some(Duration::from_micros(33_333)),
2134 is_resizable: true,
2135 is_minimizable: true,
2136 display_id: None,
2137 window_background: WindowBackgroundAppearance::default(),
2138 icon: None,
2139 app_id: None,
2140 window_min_size: None,
2141 window_decorations: None,
2142 tabbing_identifier: None,
2143 }
2144 }
2145}
2146
2147#[derive(Debug, Default)]
2149pub struct TitlebarOptions {
2150 pub title: Option<SharedString>,
2152
2153 pub appears_transparent: bool,
2156
2157 pub traffic_light_position: Option<Point<Pixels>>,
2159}
2160
2161#[derive(Clone, Debug, PartialEq, Eq)]
2163pub enum WindowKind {
2164 Normal,
2166
2167 PopUp,
2170
2171 AnchoredPopup(popup::PopupOptions),
2178
2179 Floating,
2181
2182 #[cfg(all(target_os = "linux", feature = "wayland"))]
2185 LayerShell(layer_shell::LayerShellOptions),
2186
2187 Dialog,
2190}
2191
2192#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2197pub enum WindowAppearance {
2198 #[default]
2202 Light,
2203
2204 VibrantLight,
2208
2209 Dark,
2213
2214 VibrantDark,
2218}
2219
2220#[derive(Copy, Clone, Debug, Default, PartialEq)]
2223pub enum WindowBackgroundAppearance {
2224 #[default]
2232 Opaque,
2233 Transparent,
2235 Blurred,
2239 MicaBackdrop,
2241 MicaAltBackdrop,
2243}
2244
2245#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2247pub enum TextRenderingMode {
2248 #[default]
2250 PlatformDefault,
2251 Subpixel,
2253 Grayscale,
2255}
2256
2257#[derive(Clone, Debug)]
2259pub struct PathPromptOptions {
2260 pub files: bool,
2262 pub directories: bool,
2264 pub multiple: bool,
2266 pub prompt: Option<SharedString>,
2268}
2269
2270#[derive(Copy, Clone, Debug, PartialEq)]
2272pub enum PromptLevel {
2273 Info,
2275
2276 Warning,
2278
2279 Critical,
2281}
2282
2283#[derive(Clone, Debug, PartialEq)]
2285pub enum PromptButton {
2286 Ok(SharedString),
2288 Cancel(SharedString),
2290 Other(SharedString),
2292}
2293
2294impl PromptButton {
2295 pub fn new(label: impl Into<SharedString>) -> Self {
2297 PromptButton::Other(label.into())
2298 }
2299
2300 pub fn ok(label: impl Into<SharedString>) -> Self {
2302 PromptButton::Ok(label.into())
2303 }
2304
2305 pub fn cancel(label: impl Into<SharedString>) -> Self {
2307 PromptButton::Cancel(label.into())
2308 }
2309
2310 #[allow(dead_code)]
2312 pub fn is_cancel(&self) -> bool {
2313 matches!(self, PromptButton::Cancel(_))
2314 }
2315
2316 pub fn label(&self) -> &SharedString {
2318 match self {
2319 PromptButton::Ok(label) => label,
2320 PromptButton::Cancel(label) => label,
2321 PromptButton::Other(label) => label,
2322 }
2323 }
2324}
2325
2326impl From<&str> for PromptButton {
2327 fn from(value: &str) -> Self {
2328 match value.to_lowercase().as_str() {
2329 "ok" => PromptButton::Ok("OK".into()),
2330 "cancel" => PromptButton::Cancel("Cancel".into()),
2331 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2332 }
2333 }
2334}
2335
2336#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2338pub enum CursorStyle {
2339 #[default]
2341 Arrow,
2342
2343 IBeam,
2346
2347 Crosshair,
2350
2351 ClosedHand,
2354
2355 OpenHand,
2358
2359 PointingHand,
2362
2363 ResizeLeft,
2366
2367 ResizeRight,
2370
2371 ResizeLeftRight,
2374
2375 ResizeUp,
2378
2379 ResizeDown,
2382
2383 ResizeUpDown,
2386
2387 ResizeUpLeftDownRight,
2390
2391 ResizeUpRightDownLeft,
2394
2395 ResizeColumn,
2398
2399 ResizeRow,
2402
2403 IBeamCursorForVerticalLayout,
2406
2407 OperationNotAllowed,
2410
2411 DragLink,
2414
2415 DragCopy,
2418
2419 ContextualMenu,
2422}
2423
2424#[derive(Clone, Debug, Eq, PartialEq)]
2426pub struct ClipboardItem {
2427 pub entries: Vec<ClipboardEntry>,
2429}
2430
2431#[derive(Clone, Debug, PartialEq, Eq)]
2436pub enum ClipboardReadError {
2437 Unavailable,
2441 Denied(String),
2444 UnsupportedContent,
2447}
2448
2449impl std::fmt::Display for ClipboardReadError {
2450 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2451 match self {
2452 Self::Unavailable => formatter.write_str("the clipboard is unavailable"),
2453 Self::Denied(message) => {
2454 write!(formatter, "clipboard access was denied: {message}")
2455 }
2456 Self::UnsupportedContent => {
2457 formatter.write_str("the clipboard contents are unsupported")
2458 }
2459 }
2460 }
2461}
2462
2463impl std::error::Error for ClipboardReadError {}
2464
2465#[derive(Clone, Debug, Eq, PartialEq)]
2467pub enum ClipboardEntry {
2468 String(ClipboardString),
2470 Image(Image),
2472 ExternalPaths(crate::ExternalPaths),
2474}
2475
2476impl ClipboardItem {
2477 pub fn new_string(text: String) -> Self {
2479 Self {
2480 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2481 }
2482 }
2483
2484 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2486 Self {
2487 entries: vec![ClipboardEntry::String(ClipboardString {
2488 text,
2489 metadata: Some(metadata),
2490 })],
2491 }
2492 }
2493
2494 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2496 Self {
2497 entries: vec![ClipboardEntry::String(
2498 ClipboardString::new(text).with_json_metadata(metadata),
2499 )],
2500 }
2501 }
2502
2503 pub fn new_image(image: &Image) -> Self {
2505 Self {
2506 entries: vec![ClipboardEntry::Image(image.clone())],
2507 }
2508 }
2509
2510 pub fn text(&self) -> Option<String> {
2513 let mut answer = String::new();
2514
2515 for entry in self.entries.iter() {
2516 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2517 answer.push_str(text);
2518 }
2519 }
2520
2521 if answer.is_empty() {
2522 for entry in self.entries.iter() {
2523 if let ClipboardEntry::ExternalPaths(paths) = entry {
2524 for path in &paths.0 {
2525 use std::fmt::Write as _;
2526 _ = write!(answer, "{}", path.display());
2527 }
2528 }
2529 }
2530 }
2531
2532 if !answer.is_empty() {
2533 Some(answer)
2534 } else {
2535 None
2536 }
2537 }
2538
2539 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2541 pub fn metadata(&self) -> Option<&String> {
2542 match self.entries().first() {
2543 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2544 clipboard_string.metadata.as_ref()
2545 }
2546 _ => None,
2547 }
2548 }
2549
2550 pub fn entries(&self) -> &[ClipboardEntry] {
2552 &self.entries
2553 }
2554
2555 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2557 self.entries.into_iter()
2558 }
2559}
2560
2561impl From<ClipboardString> for ClipboardEntry {
2562 fn from(value: ClipboardString) -> Self {
2563 Self::String(value)
2564 }
2565}
2566
2567impl From<String> for ClipboardEntry {
2568 fn from(value: String) -> Self {
2569 Self::from(ClipboardString::from(value))
2570 }
2571}
2572
2573impl From<Image> for ClipboardEntry {
2574 fn from(value: Image) -> Self {
2575 Self::Image(value)
2576 }
2577}
2578
2579impl From<ClipboardEntry> for ClipboardItem {
2580 fn from(value: ClipboardEntry) -> Self {
2581 Self {
2582 entries: vec![value],
2583 }
2584 }
2585}
2586
2587impl From<String> for ClipboardItem {
2588 fn from(value: String) -> Self {
2589 Self::from(ClipboardEntry::from(value))
2590 }
2591}
2592
2593impl From<Image> for ClipboardItem {
2594 fn from(value: Image) -> Self {
2595 Self::from(ClipboardEntry::from(value))
2596 }
2597}
2598
2599#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2601pub enum ImageFormat {
2602 Png,
2607 Jpeg,
2609 Webp,
2611 Gif,
2613 Svg,
2615 Bmp,
2617 Tiff,
2619 Ico,
2621 Pnm,
2623}
2624
2625impl ImageFormat {
2626 pub const fn mime_type(self) -> &'static str {
2628 match self {
2629 ImageFormat::Png => "image/png",
2630 ImageFormat::Jpeg => "image/jpeg",
2631 ImageFormat::Webp => "image/webp",
2632 ImageFormat::Gif => "image/gif",
2633 ImageFormat::Svg => "image/svg+xml",
2634 ImageFormat::Bmp => "image/bmp",
2635 ImageFormat::Tiff => "image/tiff",
2636 ImageFormat::Ico => "image/ico",
2637 ImageFormat::Pnm => "image/x-portable-anymap",
2638 }
2639 }
2640
2641 pub const fn extension(self) -> &'static str {
2643 match self {
2644 ImageFormat::Png => "png",
2645 ImageFormat::Jpeg => "jpg",
2646 ImageFormat::Webp => "webp",
2647 ImageFormat::Gif => "gif",
2648 ImageFormat::Svg => "svg",
2649 ImageFormat::Bmp => "bmp",
2650 ImageFormat::Tiff => "tiff",
2651 ImageFormat::Ico => "ico",
2652 ImageFormat::Pnm => "pnm",
2653 }
2654 }
2655
2656 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2658 use strum::IntoEnumIterator;
2659 Self::iter()
2660 .find(|format| format.mime_type() == mime_type)
2661 .or_else(|| Self::from_mime_type_alias(mime_type))
2662 }
2663
2664 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2668 match mime_type {
2669 "image/jpg" => Some(Self::Jpeg),
2670 "image/tif" => Some(Self::Tiff),
2671 _ => None,
2672 }
2673 }
2674}
2675
2676#[derive(Clone, Debug, PartialEq, Eq)]
2678pub struct Image {
2679 pub format: ImageFormat,
2681 pub bytes: Vec<u8>,
2683 pub id: u64,
2685}
2686
2687pub(crate) fn decode_static_image(
2688 bytes: &[u8],
2689 format: image::ImageFormat,
2690) -> Result<SmallVec<[Frame; 1]>> {
2691 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2692 .into_decoder()
2693 .context("creating image decoder")?;
2694 decode_static_image_from_decoder(decoder)
2695}
2696
2697pub(crate) fn decode_static_image_from_decoder(
2698 mut decoder: impl image::ImageDecoder,
2699) -> Result<SmallVec<[Frame; 1]>> {
2700 let orientation = decoder
2701 .orientation()
2702 .context("reading decoder's orientation")?;
2703 let mut image = DynamicImage::from_decoder(decoder).context("decoding image")?;
2704 image.apply_orientation(orientation);
2705
2706 let mut data = image.into_rgba8();
2707 for pixel in data.chunks_exact_mut(4) {
2708 pixel.swap(0, 2);
2709 }
2710
2711 Ok(SmallVec::from_elem(Frame::new(data), 1))
2712}
2713
2714impl Hash for Image {
2715 fn hash<H: Hasher>(&self, state: &mut H) {
2716 state.write_u64(self.id);
2717 }
2718}
2719
2720impl Image {
2721 pub fn empty() -> Self {
2723 Self::from_bytes(ImageFormat::Png, Vec::new())
2724 }
2725
2726 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2728 Self {
2729 id: hash(&bytes),
2730 format,
2731 bytes,
2732 }
2733 }
2734
2735 pub fn id(&self) -> u64 {
2737 self.id
2738 }
2739
2740 pub fn use_render_image(
2742 self: Arc<Self>,
2743 window: &mut Window,
2744 cx: &mut App,
2745 ) -> Option<Arc<RenderImage>> {
2746 ImageSource::Image(self)
2747 .use_data(None, window, cx)
2748 .and_then(|result| result.ok())
2749 }
2750
2751 pub fn get_render_image(
2753 self: Arc<Self>,
2754 window: &mut Window,
2755 cx: &mut App,
2756 ) -> Option<Arc<RenderImage>> {
2757 ImageSource::Image(self)
2758 .get_data(None, window, cx)
2759 .and_then(|result| result.ok())
2760 }
2761
2762 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2764 ImageSource::Image(self).remove_asset(cx);
2765 }
2766
2767 #[cfg(any(test, feature = "test-support"))]
2770 pub fn is_asset_cached(self: &Arc<Self>, cx: &App) -> bool {
2771 ImageSource::Image(self.clone()).is_asset_cached(cx)
2772 }
2773
2774 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2776 let frames = match self.format {
2777 ImageFormat::Gif => {
2778 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2779 let mut frames = SmallVec::new();
2780
2781 for frame in decoder.into_frames() {
2782 match frame {
2783 Ok(mut frame) => {
2784 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2786 pixel.swap(0, 2);
2787 }
2788 frames.push(frame);
2789 }
2790 Err(err) => {
2791 log::debug!("Skipping GIF frame due to decode error: {err}");
2792 }
2793 }
2794 }
2795
2796 if frames.is_empty() {
2797 anyhow::bail!("GIF could not be decoded: all frames failed");
2798 }
2799
2800 frames
2801 }
2802 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2803 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2804 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2805 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2806 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2807 ImageFormat::Ico => decode_static_image(&self.bytes, image::ImageFormat::Ico)?,
2808 ImageFormat::Svg => {
2809 return svg_renderer
2810 .render_single_frame(&self.bytes, 1.0)
2811 .map_err(Into::into);
2812 }
2813 ImageFormat::Pnm => decode_static_image(&self.bytes, image::ImageFormat::Pnm)?,
2814 };
2815
2816 Ok(Arc::new(RenderImage::new(frames)))
2817 }
2818
2819 pub fn format(&self) -> ImageFormat {
2821 self.format
2822 }
2823
2824 pub fn bytes(&self) -> &[u8] {
2826 self.bytes.as_slice()
2827 }
2828}
2829
2830#[derive(Clone, Debug, Eq, PartialEq)]
2832pub struct ClipboardString {
2833 pub text: String,
2835 pub metadata: Option<String>,
2837}
2838
2839impl ClipboardString {
2840 pub fn new(text: String) -> Self {
2842 Self {
2843 text,
2844 metadata: None,
2845 }
2846 }
2847
2848 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2851 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2852 self
2853 }
2854
2855 pub fn text(&self) -> &String {
2857 &self.text
2858 }
2859
2860 pub fn into_text(self) -> String {
2862 self.text
2863 }
2864
2865 pub fn metadata_json<T>(&self) -> Option<T>
2867 where
2868 T: for<'a> Deserialize<'a>,
2869 {
2870 self.metadata
2871 .as_ref()
2872 .and_then(|m| serde_json::from_str(m).ok())
2873 }
2874
2875 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2876 pub fn text_hash(text: &str) -> u64 {
2878 let mut hasher = SeaHasher::new();
2879 text.hash(&mut hasher);
2880 hasher.finish()
2881 }
2882}
2883
2884impl From<String> for ClipboardString {
2885 fn from(value: String) -> Self {
2886 Self {
2887 text: value,
2888 metadata: None,
2889 }
2890 }
2891}
2892
2893#[cfg(test)]
2894mod image_tests {
2895 use super::*;
2896 use std::sync::Arc;
2897
2898 #[test]
2899 fn test_image_to_image_data_applies_exif_orientation() {
2900 let image = Image::from_bytes(
2901 ImageFormat::Jpeg,
2902 include_bytes!("../examples/image/exif-orientation-rotate-180.jpg").to_vec(),
2903 );
2904
2905 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2906
2907 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2908
2909 let bytes = render_image.as_bytes(0).unwrap();
2910 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2911 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2912 }
2913
2914 #[test]
2915 fn test_svg_image_to_image_data_converts_to_bgra() {
2916 let image = Image::from_bytes(
2917 ImageFormat::Svg,
2918 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
2919<rect width="1" height="1" fill="#38BDF8"/>
2920</svg>"##
2921 .to_vec(),
2922 );
2923
2924 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2925 let bytes = render_image.as_bytes(0).unwrap();
2926
2927 for pixel in bytes.chunks_exact(4) {
2928 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
2929 }
2930 }
2931}
2932
2933#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
2934mod tests {
2935 use super::*;
2936 use std::collections::HashSet;
2937
2938 #[test]
2939 fn test_window_button_layout_parse_standard() {
2940 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
2941 assert_eq!(
2942 layout.left,
2943 [
2944 Some(WindowButton::Close),
2945 Some(WindowButton::Minimize),
2946 None
2947 ]
2948 );
2949 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2950 }
2951
2952 #[test]
2953 fn test_window_button_layout_parse_right_only() {
2954 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
2955 assert_eq!(layout.left, [None, None, None]);
2956 assert_eq!(
2957 layout.right,
2958 [
2959 Some(WindowButton::Minimize),
2960 Some(WindowButton::Maximize),
2961 Some(WindowButton::Close)
2962 ]
2963 );
2964 }
2965
2966 #[test]
2967 fn test_window_button_layout_parse_left_only() {
2968 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
2969 assert_eq!(
2970 layout.left,
2971 [
2972 Some(WindowButton::Close),
2973 Some(WindowButton::Minimize),
2974 Some(WindowButton::Maximize)
2975 ]
2976 );
2977 assert_eq!(layout.right, [None, None, None]);
2978 }
2979
2980 #[test]
2981 fn test_window_button_layout_parse_with_whitespace() {
2982 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
2983 assert_eq!(
2984 layout.left,
2985 [
2986 Some(WindowButton::Close),
2987 Some(WindowButton::Minimize),
2988 None
2989 ]
2990 );
2991 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
2992 }
2993
2994 #[test]
2995 fn test_window_button_layout_parse_empty() {
2996 let layout = WindowButtonLayout::parse("").unwrap();
2997 assert_eq!(layout.left, [None, None, None]);
2998 assert_eq!(layout.right, [None, None, None]);
2999 }
3000
3001 #[test]
3002 fn test_window_button_layout_parse_intentionally_empty() {
3003 let layout = WindowButtonLayout::parse(":").unwrap();
3004 assert_eq!(layout.left, [None, None, None]);
3005 assert_eq!(layout.right, [None, None, None]);
3006 }
3007
3008 #[test]
3009 fn test_window_button_layout_parse_invalid_buttons() {
3010 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3011 assert_eq!(
3012 layout.left,
3013 [
3014 Some(WindowButton::Close),
3015 Some(WindowButton::Minimize),
3016 None
3017 ]
3018 );
3019 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3020 }
3021
3022 #[test]
3023 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3024 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3025 assert_eq!(
3026 layout.right,
3027 [
3028 Some(WindowButton::Close),
3029 Some(WindowButton::Minimize),
3030 None
3031 ]
3032 );
3033 assert_eq!(layout.format(), ":close,minimize");
3034 }
3035
3036 #[test]
3037 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3038 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3039 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3040 assert_eq!(
3041 layout.right,
3042 [
3043 Some(WindowButton::Maximize),
3044 Some(WindowButton::Minimize),
3045 None
3046 ]
3047 );
3048
3049 let button_ids: Vec<_> = layout
3050 .left
3051 .iter()
3052 .chain(layout.right.iter())
3053 .flatten()
3054 .map(WindowButton::id)
3055 .collect();
3056 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3057 assert_eq!(unique_button_ids.len(), button_ids.len());
3058 assert_eq!(layout.format(), "close:maximize,minimize");
3059 }
3060
3061 #[test]
3062 fn test_window_button_layout_parse_gnome_style() {
3063 let layout = WindowButtonLayout::parse("close").unwrap();
3064 assert_eq!(layout.left, [None, None, None]);
3065 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3066 }
3067
3068 #[test]
3069 fn test_window_button_layout_parse_elementary_style() {
3070 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3071 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3072 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3073 }
3074
3075 #[test]
3076 fn test_window_button_layout_round_trip() {
3077 let cases = [
3078 "close:minimize,maximize",
3079 "minimize,maximize,close:",
3080 ":close",
3081 "close:",
3082 "close:maximize",
3083 ":",
3084 ];
3085
3086 for case in cases {
3087 let layout = WindowButtonLayout::parse(case).unwrap();
3088 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3089 }
3090 }
3091
3092 #[test]
3093 fn test_window_button_layout_linux_default() {
3094 let layout = WindowButtonLayout::linux_default();
3095 assert_eq!(layout.left, [None, None, None]);
3096 assert_eq!(
3097 layout.right,
3098 [
3099 Some(WindowButton::Minimize),
3100 Some(WindowButton::Maximize),
3101 Some(WindowButton::Close)
3102 ]
3103 );
3104
3105 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3106 assert_eq!(round_tripped, layout);
3107 }
3108
3109 #[test]
3110 fn test_window_button_layout_parse_all_invalid() {
3111 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3112 }
3113}