1mod app_menu;
4mod keyboard;
5mod keystroke;
6
7pub mod popup;
9
10#[cfg(all(
11 any(test, feature = "test-support"),
12 any(target_os = "windows", target_os = "linux", target_family = "wasm")
13))]
14mod threaded_dispatcher;
15
16#[cfg(all(target_os = "linux", feature = "wayland"))]
18pub mod layer_shell;
19
20#[cfg(any(test, feature = "test-support"))]
21mod test;
22
23#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
24mod visual_test;
25
26#[cfg(all(
27 feature = "screen-capture",
28 any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
29))]
30pub mod scap_screen_capture;
31
32#[cfg(all(
33 any(target_os = "windows", target_os = "linux"),
34 feature = "screen-capture"
35))]
36pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
37#[cfg(not(feature = "screen-capture"))]
38pub(crate) type PlatformScreenCaptureFrame = ();
39#[cfg(all(target_os = "macos", feature = "screen-capture"))]
40pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
41
42use crate::rgpui_util;
43use crate::scheduler::Instant;
44pub use crate::scheduler::RunnableMeta;
45use crate::{
46 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
47 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
48 ForegroundExecutor, GlyphId, GpuSpecs, Hsla, ImageSource, Keymap, LineLayout, Pixels,
49 PlatformInput, Point, Priority, RenderGlyphParams, RenderImage, RenderImageParams,
50 RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer,
51 SystemWindowTab, Task, Window, WindowControlArea, hash, point, px, size,
52};
53use crate::{Tray, TrayIconEvent, TrayMenuItem};
54use anyhow::Result;
55#[cfg(any(target_os = "linux", target_os = "freebsd"))]
56use anyhow::bail;
57use async_task::Runnable;
58use futures::channel::oneshot;
59#[cfg(any(test, feature = "test-support"))]
60use image::RgbaImage;
61use image::codecs::gif::GifDecoder;
62use image::{AnimationDecoder as _, Frame};
63use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
64use schemars::JsonSchema;
65use seahash::SeaHasher;
66use serde::{Deserialize, Serialize};
67use smallvec::SmallVec;
68use std::borrow::Cow;
69use std::hash::{Hash, Hasher};
70use std::io::Cursor;
71use std::ops;
72use std::time::Duration;
73use std::{
74 fmt::{self, Debug},
75 ops::Range,
76 path::{Path, PathBuf},
77 rc::Rc,
78 sync::Arc,
79};
80use strum::EnumIter;
81use uuid::Uuid;
82
83pub use app_menu::*;
84pub use keyboard::*;
85pub use keystroke::*;
86
87#[cfg(any(test, feature = "test-support"))]
88pub(crate) use test::*;
89
90#[cfg(any(test, feature = "test-support"))]
91pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
92
93#[cfg(all(
94 any(test, feature = "test-support"),
95 any(target_os = "windows", target_os = "linux", target_family = "wasm")
96))]
97pub use threaded_dispatcher::ThreadedDispatcher;
98
99#[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
100pub use visual_test::VisualTestPlatform;
101
102#[cfg(any(target_os = "linux", target_os = "freebsd"))]
106#[inline]
107pub fn guess_compositor() -> &'static str {
108 if std::env::var_os("ZED_HEADLESS").is_some() {
109 return "Headless";
110 }
111
112 #[cfg(feature = "wayland")]
113 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
114 #[cfg(not(feature = "wayland"))]
115 let wayland_display: Option<std::ffi::OsString> = None;
116
117 #[cfg(feature = "x11")]
118 let x11_display = std::env::var_os("DISPLAY");
119 #[cfg(not(feature = "x11"))]
120 let x11_display: Option<std::ffi::OsString> = None;
121
122 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
123 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
124
125 if use_wayland {
126 "Wayland"
127 } else if use_x11 {
128 "X11"
129 } else {
130 "Headless"
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum SystemPowerEvent {
141 Sleep,
143 WakeUp,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum PowerSaveBlockerKind {
150 PreventSleep,
152 PreventDisplaySleep,
154}
155
156#[derive(Debug, Clone)]
158pub struct OsInfo {
159 pub name: String,
161 pub version: String,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum PermissionStatus {
168 NotDetermined,
170 Granted,
172 Denied,
174 Unavailable,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum PermissionType {
183 Accessibility,
187
188 ScreenCapture,
192
193 InputMonitoring,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum NetworkStatus {
202 Disconnected,
204 ConnectedBelowRequired,
206 Connected,
208}
209
210#[derive(Debug, Clone)]
212pub struct MediaKeyEvent {
213 pub key_code: u16,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum BiometricStatus {
220 Unavailable,
222 Unlocked,
224 Locked,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum AttentionType {
231 Informational,
233 Critical,
235}
236
237#[derive(Debug, Clone)]
239pub struct DialogOptions {
240 pub dialog_type: DialogType,
242 pub title: String,
244 pub message: String,
246 pub confirm_label: Option<String>,
248 pub cancel_label: Option<String>,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub enum DialogType {
255 Info,
257 Warning,
259 Error,
261}
262
263#[derive(Debug, Clone)]
265pub struct FocusedWindowInfo {
266 pub app_name: String,
268 pub window_title: String,
270 pub bundle_id: Option<String>,
272 pub pid: Option<u32>,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq)]
278pub enum WindowPosition {
279 Center,
281 CenterOnDisplay(DisplayId),
283 TrayCenter(Bounds<Pixels>),
285 TopRight {
287 margin: Pixels,
289 },
290 BottomRight {
292 margin: Pixels,
294 },
295 TopLeft {
297 margin: Pixels,
299 },
300 BottomLeft {
302 margin: Pixels,
304 },
305}
306
307pub trait Platform: 'static {
312 fn background_executor(&self) -> BackgroundExecutor;
314 fn foreground_executor(&self) -> ForegroundExecutor;
316 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
318
319 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
321 fn quit(&self);
323 fn restart(&self, binary_path: Option<PathBuf>);
325 fn activate(&self, ignoring_other_apps: bool);
327 fn hide(&self);
329 fn hide_other_apps(&self);
331 fn unhide_other_apps(&self);
333
334 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
336 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
338 fn active_window(&self) -> Option<AnyWindowHandle>;
340 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
342 None
343 }
344
345 fn is_screen_capture_supported(&self) -> bool {
347 false
348 }
349
350 fn screen_capture_sources(
352 &self,
353 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
354 let (sources_tx, sources_rx) = oneshot::channel();
355 sources_tx
356 .send(Err(anyhow::anyhow!(
357 "rgpui was compiled without the screen-capture feature"
358 )))
359 .ok();
360 sources_rx
361 }
362
363 fn open_window(
365 &self,
366 handle: AnyWindowHandle,
367 options: WindowParams,
368 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
369
370 fn window_appearance(&self) -> WindowAppearance;
372
373 fn button_layout(&self) -> Option<WindowButtonLayout> {
375 None
376 }
377
378 fn open_url(&self, url: &str);
380 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
382 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
384
385 fn prompt_for_paths(
387 &self,
388 options: PathPromptOptions,
389 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
390 fn prompt_for_new_path(
392 &self,
393 directory: &Path,
394 suggested_name: Option<&str>,
395 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
396 fn can_select_mixed_files_and_dirs(&self) -> bool;
398 fn reveal_path(&self, path: &Path);
400 fn open_with_system(&self, path: &Path);
402
403 fn on_quit(&self, callback: Box<dyn FnMut()>);
405 fn on_reopen(&self, callback: Box<dyn FnMut()>);
407
408 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
410 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
412 None
413 }
414
415 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
417 fn perform_dock_menu_action(&self, _action: usize) {}
419 fn add_recent_document(&self, _path: &Path) {}
421 fn update_jump_list(
423 &self,
424 _menus: Vec<MenuItem>,
425 _entries: Vec<SmallVec<[PathBuf; 2]>>,
426 ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {
427 Task::ready(Vec::new())
428 }
429 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
431 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
433 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
435
436 fn thermal_state(&self) -> ThermalState;
438 fn on_thermal_state_change(&self, callback: Box<dyn FnMut()>);
440
441 fn compositor_name(&self) -> &'static str {
443 ""
444 }
445 fn app_path(&self) -> Result<PathBuf>;
447 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
449
450 fn set_cursor_style(&self, style: CursorStyle);
452
453 fn hide_cursor_until_mouse_moves(&self);
455
456 fn is_cursor_visible(&self) -> bool;
458
459 fn should_auto_hide_scrollbars(&self) -> bool;
461
462 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
464 fn write_to_clipboard(&self, item: ClipboardItem);
466
467 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
469 fn read_from_primary(&self) -> Option<ClipboardItem>;
470 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
472 fn write_to_primary(&self, item: ClipboardItem);
473
474 #[cfg(target_os = "macos")]
476 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
477 #[cfg(target_os = "macos")]
479 fn write_to_find_pasteboard(&self, item: ClipboardItem);
480
481 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
483 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
485 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
487
488 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
490 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
492 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
494
495 fn set_tray(&self, _tray: Tray, _menus: Option<Vec<MenuItem>>, _keymap: &Keymap) {}
497 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
499 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
501 fn set_tray_tooltip(&self, _tooltip: &str) {}
503 fn set_tray_panel_mode(&self, _enabled: bool) {}
505 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
507 None
508 }
509 fn on_tray_icon_event(&self, _callback: Box<dyn FnMut(TrayIconEvent)>) {}
511 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
513
514 fn set_keep_alive_without_windows(&self, _keep_alive: bool) {}
516
517 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
519 Ok(())
520 }
521 fn unregister_global_hotkey(&self, _id: u32) {}
523 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
525
526 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
528 Ok(())
529 }
530
531 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
533 Ok(())
534 }
535 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
537 false
538 }
539
540 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
542 None
543 }
544
545 fn accessibility_status(&self) -> PermissionStatus {
547 PermissionStatus::Unavailable
548 }
549 fn request_accessibility_permission(&self) {}
551
552 fn microphone_status(&self) -> PermissionStatus {
554 PermissionStatus::Unavailable
555 }
556 fn request_microphone_permission(&self, _callback: Box<dyn FnOnce(bool)>) {}
558
559 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
561
562 fn on_system_wake(&self, _callback: Box<dyn FnMut()>) {}
564
565 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
567 None
568 }
569 fn stop_power_save_blocker(&self, _id: u32) {}
571
572 fn system_idle_time(&self) -> Option<Duration> {
574 None
575 }
576
577 fn network_status(&self) -> NetworkStatus {
579 NetworkStatus::Connected
580 }
581 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
583
584 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
586
587 fn request_user_attention(&self, _attention_type: AttentionType) {}
589 fn cancel_user_attention(&self) {}
591
592 fn set_dock_badge(&self, _label: Option<&str>) {}
594
595 fn show_context_menu(
597 &self,
598 _position: Point<Pixels>,
599 _items: Vec<TrayMenuItem>,
600 _callback: Box<dyn FnMut(SharedString)>,
601 ) {
602 }
603
604 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
606 let (tx, rx) = oneshot::channel();
607 let _ = tx.send(0);
608 rx
609 }
610
611 fn os_info(&self) -> OsInfo {
613 OsInfo {
614 name: String::new(),
615 version: String::new(),
616 }
617 }
618
619 fn biometric_status(&self) -> BiometricStatus {
621 BiometricStatus::Unavailable
622 }
623 fn authenticate_biometric(&self, _reason: &str, _callback: Box<dyn FnOnce(bool)>) {}
625}
626
627pub trait PlatformDisplay: Debug {
629 fn id(&self) -> DisplayId;
631
632 fn uuid(&self) -> Result<Uuid>;
634
635 fn bounds(&self) -> Bounds<Pixels>;
637
638 fn visible_bounds(&self) -> Bounds<Pixels> {
642 self.bounds()
643 }
644
645 fn default_bounds(&self) -> Bounds<Pixels> {
647 let bounds = self.bounds();
648 let center = bounds.center();
649 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
650
651 let offset = clipped_window_size / 2.0;
652 let origin = point(center.x - offset.width, center.y - offset.height);
653 Bounds::new(origin, clipped_window_size)
654 }
655}
656
657#[derive(Debug, Clone, Copy, PartialEq, Eq)]
659pub enum ThermalState {
660 Nominal,
662 Fair,
664 Serious,
666 Critical,
668}
669
670#[derive(Clone)]
672pub struct SourceMetadata {
673 pub id: u64,
675 pub label: Option<SharedString>,
677 pub is_main: Option<bool>,
679 pub resolution: Size<DevicePixels>,
681}
682
683pub trait ScreenCaptureSource {
685 fn metadata(&self) -> Result<SourceMetadata>;
687
688 fn stream(
690 &self,
691 foreground_executor: &ForegroundExecutor,
692 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
693 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
694}
695
696pub trait ScreenCaptureStream {
698 fn metadata(&self) -> Result<SourceMetadata>;
700}
701
702pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
704
705#[cfg(all(
706 any(target_os = "windows", target_os = "linux"),
707 feature = "screen-capture"
708))]
709impl ScreenCaptureFrame {
710 pub fn width(&self) -> u32 {
712 match &self.0 {
713 scap::frame::Frame::YUVFrame(f) => f.width as u32,
714 scap::frame::Frame::RGB(f) => f.width as u32,
715 scap::frame::Frame::RGBx(f) => f.width as u32,
716 scap::frame::Frame::XBGR(f) => f.width as u32,
717 scap::frame::Frame::BGRx(f) => f.width as u32,
718 scap::frame::Frame::BGR0(f) => f.width as u32,
719 scap::frame::Frame::BGRA(f) => f.width as u32,
720 }
721 }
722
723 pub fn height(&self) -> u32 {
725 match &self.0 {
726 scap::frame::Frame::YUVFrame(f) => f.height as u32,
727 scap::frame::Frame::RGB(f) => f.height as u32,
728 scap::frame::Frame::RGBx(f) => f.height as u32,
729 scap::frame::Frame::XBGR(f) => f.height as u32,
730 scap::frame::Frame::BGRx(f) => f.height as u32,
731 scap::frame::Frame::BGR0(f) => f.height as u32,
732 scap::frame::Frame::BGRA(f) => f.height as u32,
733 }
734 }
735
736 pub fn to_rgba(&self) -> Option<image::RgbaImage> {
741 match &self.0 {
742 scap::frame::Frame::BGRA(f) => {
743 let w = f.width as u32;
744 let h = f.height as u32;
745 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
746 for chunk in f.data.chunks_exact(4) {
747 rgba.push(chunk[2]); rgba.push(chunk[1]); rgba.push(chunk[0]); rgba.push(chunk[3]); }
752 image::RgbaImage::from_raw(w, h, rgba)
753 }
754 scap::frame::Frame::BGR0(f) => {
755 let w = f.width as u32;
756 let h = f.height as u32;
757 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
758 for chunk in f.data.chunks_exact(4) {
759 rgba.push(chunk[2]); rgba.push(chunk[1]); rgba.push(chunk[0]); rgba.push(255); }
764 image::RgbaImage::from_raw(w, h, rgba)
765 }
766 scap::frame::Frame::BGRx(f) => {
767 let w = f.width as u32;
768 let h = f.height as u32;
769 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
770 for chunk in f.data.chunks_exact(4) {
771 rgba.push(chunk[2]); rgba.push(chunk[1]); rgba.push(chunk[0]); rgba.push(255); }
776 image::RgbaImage::from_raw(w, h, rgba)
777 }
778 scap::frame::Frame::XBGR(f) => {
779 let w = f.width as u32;
780 let h = f.height as u32;
781 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
782 for chunk in f.data.chunks_exact(4) {
783 rgba.push(chunk[3]); rgba.push(chunk[2]); rgba.push(chunk[1]); rgba.push(255); }
788 image::RgbaImage::from_raw(w, h, rgba)
789 }
790 scap::frame::Frame::RGB(f) => {
791 let w = f.width as u32;
792 let h = f.height as u32;
793 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
794 for chunk in f.data.chunks_exact(3) {
795 rgba.push(chunk[0]); rgba.push(chunk[1]); rgba.push(chunk[2]); rgba.push(255); }
800 image::RgbaImage::from_raw(w, h, rgba)
801 }
802 scap::frame::Frame::RGBx(f) => {
803 let w = f.width as u32;
804 let h = f.height as u32;
805 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
806 for chunk in f.data.chunks_exact(4) {
807 rgba.push(chunk[0]); rgba.push(chunk[1]); rgba.push(chunk[2]); rgba.push(255); }
812 image::RgbaImage::from_raw(w, h, rgba)
813 }
814 scap::frame::Frame::YUVFrame(f) => {
815 let w = f.width as u32;
816 let h = f.height as u32;
817 let mut rgba = Vec::with_capacity((w * h * 4) as usize);
818
819 let y_plane = &f.luminance_bytes;
821 let uv_plane = &f.chrominance_bytes;
822 let y_stride = f.luminance_stride as usize;
823 let uv_stride = f.chrominance_stride as usize;
824
825 for row in 0..h as usize {
826 for col in 0..w as usize {
827 let y_idx = row * y_stride + col;
829 let y = if y_idx < y_plane.len() {
830 y_plane[y_idx] as i32
831 } else {
832 0
833 };
834
835 let uv_row = row / 2;
837 let uv_col = (col / 2) * 2;
838 let uv_idx = uv_row * uv_stride + uv_col;
839 let u = if uv_idx < uv_plane.len() {
840 uv_plane[uv_idx] as i32
841 } else {
842 128
843 };
844 let v = if uv_idx + 1 < uv_plane.len() {
845 uv_plane[uv_idx + 1] as i32
846 } else {
847 128
848 };
849
850 let c = 298 * (y - 16);
852 let r = ((c + 409 * (v - 128) + 128) >> 8).clamp(0, 255) as u8;
853 let g = ((c - 100 * (u - 128) - 208 * (v - 128) + 128) >> 8).clamp(0, 255)
854 as u8;
855 let b = ((c + 516 * (u - 128) + 128) >> 8).clamp(0, 255) as u8;
856
857 rgba.push(r);
858 rgba.push(g);
859 rgba.push(b);
860 rgba.push(255); }
862 }
863 image::RgbaImage::from_raw(w, h, rgba)
864 }
865 }
866 }
867}
868
869#[cfg(all(target_os = "macos", feature = "screen-capture"))]
870impl ScreenCaptureFrame {
871 pub fn width(&self) -> u32 {
873 0
874 }
875
876 pub fn height(&self) -> u32 {
878 0
879 }
880
881 pub fn to_rgba(&self) -> Option<image::RgbaImage> {
883 None
884 }
885}
886
887#[derive(PartialEq, Eq, Hash, Copy, Clone)]
889pub struct DisplayId(pub(crate) u64);
890
891impl DisplayId {
892 pub fn new(id: u64) -> Self {
894 Self(id)
895 }
896}
897
898impl From<u64> for DisplayId {
899 fn from(id: u64) -> Self {
900 Self(id)
901 }
902}
903
904impl From<DisplayId> for u64 {
905 fn from(id: DisplayId) -> Self {
906 id.0
907 }
908}
909
910impl Debug for DisplayId {
911 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
912 write!(f, "DisplayId({})", self.0)
913 }
914}
915
916#[derive(Debug, Clone, Copy, PartialEq, Eq)]
918pub enum ResizeEdge {
919 Top,
921 TopRight,
923 Right,
925 BottomRight,
927 Bottom,
929 BottomLeft,
931 Left,
933 TopLeft,
935}
936
937#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
939pub enum WindowDecorations {
940 #[default]
941 Server,
943 Client,
945}
946
947#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
949pub enum Decorations {
950 #[default]
952 Server,
953 Client {
955 tiling: Tiling,
957 },
958}
959
960#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
962pub struct WindowControls {
963 pub fullscreen: bool,
965 pub maximize: bool,
967 pub minimize: bool,
969 pub window_menu: bool,
971}
972
973impl Default for WindowControls {
974 fn default() -> Self {
975 Self {
977 fullscreen: true,
978 maximize: true,
979 minimize: true,
980 window_menu: true,
981 }
982 }
983}
984
985#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
987pub enum WindowButton {
988 Minimize,
990 Maximize,
992 Close,
994}
995
996impl WindowButton {
997 pub fn id(&self) -> &'static str {
999 match self {
1000 WindowButton::Minimize => "minimize",
1001 WindowButton::Maximize => "maximize",
1002 WindowButton::Close => "close",
1003 }
1004 }
1005
1006 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
1007 fn index(&self) -> usize {
1008 match self {
1009 WindowButton::Minimize => 0,
1010 WindowButton::Maximize => 1,
1011 WindowButton::Close => 2,
1012 }
1013 }
1014}
1015
1016pub const MAX_BUTTONS_PER_SIDE: usize = 3;
1018
1019#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1024pub struct WindowButtonLayout {
1025 pub left: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1027 pub right: [Option<WindowButton>; MAX_BUTTONS_PER_SIDE],
1029}
1030
1031#[cfg(any(target_os = "linux", target_os = "freebsd"))]
1032impl WindowButtonLayout {
1033 pub fn linux_default() -> Self {
1035 Self {
1036 left: [None; MAX_BUTTONS_PER_SIDE],
1037 right: [
1038 Some(WindowButton::Minimize),
1039 Some(WindowButton::Maximize),
1040 Some(WindowButton::Close),
1041 ],
1042 }
1043 }
1044
1045 pub fn parse(layout_string: &str) -> Result<Self> {
1047 fn parse_side(
1048 s: &str,
1049 seen_buttons: &mut [bool; MAX_BUTTONS_PER_SIDE],
1050 unrecognized: &mut Vec<String>,
1051 ) -> [Option<WindowButton>; MAX_BUTTONS_PER_SIDE] {
1052 let mut result = [None; MAX_BUTTONS_PER_SIDE];
1053 let mut i = 0;
1054 for name in s.split(',') {
1055 let trimmed = name.trim();
1056 if trimmed.is_empty() {
1057 continue;
1058 }
1059 let button = match trimmed {
1060 "minimize" => Some(WindowButton::Minimize),
1061 "maximize" => Some(WindowButton::Maximize),
1062 "close" => Some(WindowButton::Close),
1063 other => {
1064 unrecognized.push(other.to_string());
1065 None
1066 }
1067 };
1068 if let Some(button) = button {
1069 if seen_buttons[button.index()] {
1070 continue;
1071 }
1072 if let Some(slot) = result.get_mut(i) {
1073 *slot = Some(button);
1074 seen_buttons[button.index()] = true;
1075 i += 1;
1076 }
1077 }
1078 }
1079 result
1080 }
1081
1082 let (left_str, right_str) = layout_string.split_once(':').unwrap_or(("", layout_string));
1083 let mut unrecognized = Vec::new();
1084 let mut seen_buttons = [false; MAX_BUTTONS_PER_SIDE];
1085 let layout = Self {
1086 left: parse_side(left_str, &mut seen_buttons, &mut unrecognized),
1087 right: parse_side(right_str, &mut seen_buttons, &mut unrecognized),
1088 };
1089
1090 if !unrecognized.is_empty()
1091 && layout.left.iter().all(Option::is_none)
1092 && layout.right.iter().all(Option::is_none)
1093 {
1094 bail!(
1095 "button layout string {:?} contains no valid buttons (unrecognized: {})",
1096 layout_string,
1097 unrecognized.join(", ")
1098 );
1099 }
1100
1101 Ok(layout)
1102 }
1103
1104 #[cfg(test)]
1106 pub fn format(&self) -> String {
1107 fn format_side(buttons: &[Option<WindowButton>; MAX_BUTTONS_PER_SIDE]) -> String {
1108 buttons
1109 .iter()
1110 .flatten()
1111 .map(|button| match button {
1112 WindowButton::Minimize => "minimize",
1113 WindowButton::Maximize => "maximize",
1114 WindowButton::Close => "close",
1115 })
1116 .collect::<Vec<_>>()
1117 .join(",")
1118 }
1119
1120 format!("{}:{}", format_side(&self.left), format_side(&self.right))
1121 }
1122}
1123
1124#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
1126pub struct Tiling {
1127 pub top: bool,
1129 pub left: bool,
1131 pub right: bool,
1133 pub bottom: bool,
1135}
1136
1137impl Tiling {
1138 pub fn tiled() -> Self {
1140 Self {
1141 top: true,
1142 left: true,
1143 right: true,
1144 bottom: true,
1145 }
1146 }
1147
1148 pub fn is_tiled(&self) -> bool {
1150 self.top || self.left || self.right || self.bottom
1151 }
1152}
1153
1154pub struct A11yCallbacks {
1156 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
1158 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
1160 pub deactivation: Box<dyn Fn() + Send + 'static>,
1162}
1163
1164#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
1166pub struct RequestFrameOptions {
1167 pub require_presentation: bool,
1169 pub force_render: bool,
1171}
1172
1173pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
1177 fn bounds(&self) -> Bounds<Pixels>;
1179 fn is_maximized(&self) -> bool;
1181 fn window_bounds(&self) -> WindowBounds;
1183 fn content_size(&self) -> Size<Pixels>;
1185 fn resize(&mut self, size: Size<Pixels>);
1187 fn scale_factor(&self) -> f32;
1189 fn appearance(&self) -> WindowAppearance;
1191 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
1193 fn mouse_position(&self) -> Point<Pixels>;
1195 fn modifiers(&self) -> Modifiers;
1197 fn capslock(&self) -> Capslock;
1199 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
1201 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
1203 fn prompt(
1205 &self,
1206 level: PromptLevel,
1207 msg: &str,
1208 detail: Option<&str>,
1209 answers: &[PromptButton],
1210 ) -> Option<oneshot::Receiver<usize>>;
1211 fn activate(&self);
1213 fn is_active(&self) -> bool;
1215 fn is_hovered(&self) -> bool;
1217 fn background_appearance(&self) -> WindowBackgroundAppearance;
1219 fn set_title(&mut self, title: &str);
1221 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
1223 fn minimize(&self);
1225 fn zoom(&self);
1227 fn toggle_fullscreen(&self);
1229 fn is_fullscreen(&self) -> bool;
1231 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
1233 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
1235 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
1237 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
1239 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
1241 fn on_moved(&self, callback: Box<dyn FnMut()>);
1243 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
1245 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
1247 fn on_close(&self, callback: Box<dyn FnOnce()>);
1249 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
1251 fn on_button_layout_changed(&self, _callback: Box<dyn FnMut()>) {}
1253 fn draw(&self, scene: &Scene);
1255 fn completed_frame(&self) {}
1257 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1259 fn is_subpixel_rendering_supported(&self) -> bool;
1261
1262 #[cfg(feature = "dom-backend")]
1267 fn supports_dom(&self) -> bool {
1268 false
1269 }
1270
1271 #[cfg(feature = "dom-backend")]
1276 fn dom_tree_update(&self, _tree: &crate::dom::DomTree) {}
1277
1278 #[cfg(feature = "dom-backend")]
1283 fn on_dom_event(
1284 &self,
1285 _callback: Box<dyn FnMut(Vec<crate::DomNodeKey>, PlatformInput) -> DispatchEventResult>,
1286 ) {
1287 }
1288
1289 #[cfg(feature = "dom-backend")]
1293 fn on_dom_scroll(&self, _callback: Box<dyn FnMut(Vec<crate::DomNodeKey>, f64, f64)>) {}
1294
1295 fn get_title(&self) -> String {
1298 String::new()
1299 }
1300 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
1302 None
1303 }
1304 fn tab_bar_visible(&self) -> bool {
1306 false
1307 }
1308 fn set_edited(&mut self, _edited: bool) {}
1310 fn set_document_path(&self, _path: Option<&std::path::Path>) {}
1312 #[cfg(target_os = "macos")]
1314 fn set_traffic_light_position(&self, _position: Point<Pixels>) {}
1315 fn show_character_palette(&self) {}
1317 fn titlebar_double_click(&self, _is_resizable: bool, _is_minimizable: bool) {}
1319 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
1321 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
1323 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
1325 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
1327 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
1329 fn merge_all_windows(&self) {}
1331 fn move_tab_to_new_window(&self) {}
1333 fn toggle_window_tab_overview(&self) {}
1335 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
1337
1338 #[cfg(target_os = "windows")]
1340 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
1341
1342 fn inner_window_bounds(&self) -> WindowBounds {
1344 self.window_bounds()
1345 }
1346 fn request_decorations(&self, _decorations: WindowDecorations) {}
1348 fn show_window_menu(&self, _position: Point<Pixels>) {}
1350 fn start_window_move(&self) {}
1352 fn start_window_resize(&self, _edge: ResizeEdge) {}
1354 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
1356 fn window_decorations(&self) -> Decorations {
1358 Decorations::Server
1359 }
1360 fn set_app_id(&mut self, _app_id: &str) {}
1362 fn map_window(&mut self) -> anyhow::Result<()> {
1364 Ok(())
1365 }
1366 fn window_controls(&self) -> WindowControls {
1368 WindowControls::default()
1369 }
1370 fn set_client_inset(&self, _inset: Pixels) {}
1372 fn gpu_specs(&self) -> Option<GpuSpecs>;
1374
1375 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
1377
1378 fn play_system_bell(&self) {}
1380
1381 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
1383
1384 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
1386
1387 fn a11y_update_window_bounds(&self) {}
1389
1390 #[cfg(any(test, feature = "test-support"))]
1392 fn as_test(&mut self) -> Option<&mut TestWindow> {
1393 None
1394 }
1395
1396 #[cfg(any(test, feature = "test-support"))]
1399 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
1400 anyhow::bail!("render_to_image not implemented for this platform")
1401 }
1402
1403 fn set_exclusive_zone(&self, _zone: Pixels) {}
1405 #[cfg(all(target_os = "linux", feature = "wayland"))]
1407 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
1408
1409 fn request_attention(&self) {}
1411
1412 fn set_position(&mut self, _position: Point<Pixels>) {}
1414
1415 fn hide(&self) {}
1417
1418 fn set_mouse_passthrough(&self, _passthrough: bool) {}
1420
1421 fn window_extended_style(&self) -> u32 {
1423 0
1424 }
1425 fn set_window_extended_style(&self, _style: u32) {}
1427
1428 fn set_titlebar_visible(&self, _visible: bool) {}
1430
1431 fn set_text_content_type(&self, _content_type: Option<&'static str>) {}
1434}
1435
1436#[cfg(any(test, feature = "test-support"))]
1438pub trait PlatformHeadlessRenderer {
1439 fn render_scene_to_image(
1441 &mut self,
1442 scene: &Scene,
1443 size: Size<DevicePixels>,
1444 ) -> Result<RgbaImage>;
1445
1446 fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()>;
1450
1451 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
1453}
1454
1455#[doc(hidden)]
1458pub type RunnableVariant = Runnable<RunnableMeta>;
1459
1460#[doc(hidden)]
1461pub type TimerResolutionGuard = rgpui_util::Deferred<Box<dyn FnOnce() + Send>>;
1462
1463#[doc(hidden)]
1464pub enum TasksIncluded {
1465 OnlyCompleted,
1466 CompletedAndRunning,
1467}
1468
1469#[doc(hidden)]
1471pub trait PlatformDispatcher: Send + Sync {
1472 fn is_main_thread(&self) -> bool;
1473 fn dispatch(&self, runnable: RunnableVariant, priority: Priority);
1474 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
1475 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
1476
1477 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>);
1478
1479 fn now(&self) -> Instant {
1480 Instant::now()
1481 }
1482
1483 fn increase_timer_resolution(&self) -> TimerResolutionGuard {
1484 rgpui_util::defer(Box::new(|| {}))
1485 }
1486
1487 #[cfg(any(test, feature = "test-support"))]
1488 fn as_test(&self) -> Option<&TestDispatcher> {
1489 None
1490 }
1491
1492 #[cfg(all(
1494 any(test, feature = "test-support"),
1495 any(target_os = "windows", target_os = "linux", target_family = "wasm")
1496 ))]
1497 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
1498 None
1499 }
1500}
1501
1502pub trait PlatformTextSystem: Send + Sync {
1504 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
1506 fn all_font_names(&self) -> Vec<String>;
1508 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
1510 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
1512 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
1514 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
1516 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
1518 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
1520 fn rasterize_glyph(
1522 &self,
1523 params: &RenderGlyphParams,
1524 raster_bounds: Bounds<DevicePixels>,
1525 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
1526 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
1528 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
1530 -> TextRenderingMode;
1531 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
1533 0
1534 }
1535}
1536
1537pub struct NoopTextSystem;
1539
1540impl NoopTextSystem {
1541 pub fn new() -> Self {
1543 Self
1544 }
1545}
1546
1547impl PlatformTextSystem for NoopTextSystem {
1548 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
1549 Ok(())
1550 }
1551
1552 fn all_font_names(&self) -> Vec<String> {
1553 Vec::new()
1554 }
1555
1556 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
1557 Ok(FontId(1))
1558 }
1559
1560 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
1561 FontMetrics {
1562 units_per_em: 1000,
1563 ascent: 1025.0,
1564 descent: -275.0,
1565 line_gap: 0.0,
1566 underline_position: -95.0,
1567 underline_thickness: 60.0,
1568 cap_height: 698.0,
1569 x_height: 516.0,
1570 bounding_box: Bounds {
1571 origin: Point {
1572 x: -260.0,
1573 y: -245.0,
1574 },
1575 size: Size {
1576 width: 1501.0,
1577 height: 1364.0,
1578 },
1579 },
1580 }
1581 }
1582
1583 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
1584 Ok(Bounds {
1585 origin: Point { x: 54.0, y: 0.0 },
1586 size: size(392.0, 528.0),
1587 })
1588 }
1589
1590 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
1591 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
1592 }
1593
1594 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
1595 Some(GlyphId(ch.len_utf16() as u32))
1596 }
1597
1598 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
1599 Ok(Default::default())
1600 }
1601
1602 fn rasterize_glyph(
1603 &self,
1604 _params: &RenderGlyphParams,
1605 raster_bounds: Bounds<DevicePixels>,
1606 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
1607 Ok((raster_bounds.size, Vec::new()))
1608 }
1609
1610 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
1611 let mut position = px(0.);
1612 let metrics = self.font_metrics(FontId(0));
1613 let em_width = font_size
1614 * self
1615 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
1616 .unwrap()
1617 .width
1618 / metrics.units_per_em as f32;
1619 let mut glyphs = Vec::new();
1620 for (ix, c) in text.char_indices() {
1621 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
1622 glyphs.push(ShapedGlyph {
1623 id: glyph,
1624 position: point(position, px(0.)),
1625 index: ix,
1626 is_emoji: glyph.0 == 2,
1627 });
1628 if glyph.0 == 2 {
1629 position += em_width * 2.0;
1630 } else {
1631 position += em_width;
1632 }
1633 } else {
1634 position += em_width
1635 }
1636 }
1637 let mut runs = Vec::default();
1638 if !glyphs.is_empty() {
1639 runs.push(ShapedRun {
1640 font_id: FontId(0),
1641 glyphs,
1642 });
1643 } else {
1644 position = px(0.);
1645 }
1646
1647 LineLayout {
1648 font_size,
1649 width: position,
1650 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1651 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1652 runs,
1653 len: text.len(),
1654 }
1655 }
1656
1657 fn recommended_rendering_mode(
1658 &self,
1659 _font_id: FontId,
1660 _font_size: Pixels,
1661 ) -> TextRenderingMode {
1662 TextRenderingMode::Grayscale
1663 }
1664}
1665
1666pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
1671 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
1672 [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], ];
1686
1687 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
1688 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
1689
1690 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
1691 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
1692
1693 [
1694 ratios[0] * NORM13,
1695 ratios[1] * NORM24,
1696 ratios[2] * NORM13,
1697 ratios[3] * NORM24,
1698 ]
1699}
1700
1701#[derive(PartialEq, Eq, Hash, Clone)]
1703pub enum AtlasKey {
1704 Glyph(RenderGlyphParams),
1706 Svg(RenderSvgParams),
1708 Image(RenderImageParams),
1710}
1711
1712impl AtlasKey {
1713 pub fn texture_kind(&self) -> AtlasTextureKind {
1715 match self {
1716 AtlasKey::Glyph(params) => {
1717 if params.is_emoji {
1718 AtlasTextureKind::Polychrome
1719 } else if params.subpixel_rendering {
1720 AtlasTextureKind::Subpixel
1721 } else {
1722 AtlasTextureKind::Monochrome
1723 }
1724 }
1725 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1726 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1727 }
1728 }
1729}
1730
1731impl From<RenderGlyphParams> for AtlasKey {
1732 fn from(params: RenderGlyphParams) -> Self {
1733 Self::Glyph(params)
1734 }
1735}
1736
1737impl From<RenderSvgParams> for AtlasKey {
1738 fn from(params: RenderSvgParams) -> Self {
1739 Self::Svg(params)
1740 }
1741}
1742
1743impl From<RenderImageParams> for AtlasKey {
1744 fn from(params: RenderImageParams) -> Self {
1745 Self::Image(params)
1746 }
1747}
1748
1749pub trait PlatformAtlas {
1751 fn get_or_insert_with<'a>(
1753 &self,
1754 key: &AtlasKey,
1755 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1756 ) -> Result<Option<AtlasTile>>;
1757 fn remove(&self, key: &AtlasKey);
1759 #[cfg(any(test, feature = "test-support"))]
1760 fn contains(&self, _key: &AtlasKey) -> bool {
1761 false
1762 }
1763}
1764
1765#[doc(hidden)]
1766pub struct AtlasTextureList<T> {
1767 pub textures: Vec<Option<T>>,
1768 pub free_list: Vec<usize>,
1769}
1770
1771impl<T> Default for AtlasTextureList<T> {
1772 fn default() -> Self {
1773 Self {
1774 textures: Vec::default(),
1775 free_list: Vec::default(),
1776 }
1777 }
1778}
1779
1780impl<T> ops::Index<usize> for AtlasTextureList<T> {
1781 type Output = Option<T>;
1782
1783 fn index(&self, index: usize) -> &Self::Output {
1784 &self.textures[index]
1785 }
1786}
1787
1788impl<T> AtlasTextureList<T> {
1789 #[allow(unused)]
1790 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1791 self.free_list.clear();
1792 self.textures.drain(..)
1793 }
1794
1795 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1796 self.textures.iter_mut().flatten()
1797 }
1798}
1799
1800#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1802#[repr(C)]
1803pub struct AtlasTile {
1804 pub texture_id: AtlasTextureId,
1806 pub tile_id: TileId,
1808 pub padding: u32,
1810 pub bounds: Bounds<DevicePixels>,
1812}
1813
1814#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1816#[repr(C)]
1817pub struct AtlasTextureId {
1818 pub index: u32,
1821 pub kind: AtlasTextureKind,
1823}
1824
1825#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1826#[repr(C)]
1827pub enum AtlasTextureKind {
1829 Monochrome,
1831 Polychrome,
1833 Subpixel,
1835}
1836
1837#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1839#[repr(C)]
1840pub struct TileId(pub u32);
1841
1842impl From<etagere::AllocId> for TileId {
1843 fn from(id: etagere::AllocId) -> Self {
1844 Self(id.serialize())
1845 }
1846}
1847
1848impl From<TileId> for etagere::AllocId {
1849 fn from(id: TileId) -> Self {
1850 Self::deserialize(id.0)
1851 }
1852}
1853
1854pub struct PlatformInputHandler {
1856 cx: AsyncWindowContext,
1857 handler: Box<dyn InputHandler>,
1858}
1859
1860impl PlatformInputHandler {
1861 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1863 Self { cx, handler }
1864 }
1865
1866 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1868 self.cx
1869 .update(|window, cx| {
1870 self.handler
1871 .selected_text_range(ignore_disabled_input, window, cx)
1872 })
1873 .ok()
1874 .flatten()
1875 }
1876
1877 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1879 self.cx
1880 .update(|window, cx| self.handler.marked_text_range(window, cx))
1881 .ok()
1882 .flatten()
1883 }
1884
1885 pub fn text_for_range(
1887 &mut self,
1888 range_utf16: Range<usize>,
1889 adjusted: &mut Option<Range<usize>>,
1890 ) -> Option<String> {
1891 self.cx
1892 .update(|window, cx| {
1893 self.handler
1894 .text_for_range(range_utf16, adjusted, window, cx)
1895 })
1896 .ok()
1897 .flatten()
1898 }
1899
1900 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1902 self.cx
1903 .update(|window, cx| {
1904 self.handler
1905 .replace_text_in_range(replacement_range, text, window, cx);
1906 })
1907 .ok();
1908 }
1909
1910 pub fn replace_and_mark_text_in_range(
1912 &mut self,
1913 range_utf16: Option<Range<usize>>,
1914 new_text: &str,
1915 new_selected_range: Option<Range<usize>>,
1916 ) {
1917 self.cx
1918 .update(|window, cx| {
1919 self.handler.replace_and_mark_text_in_range(
1920 range_utf16,
1921 new_text,
1922 new_selected_range,
1923 window,
1924 cx,
1925 )
1926 })
1927 .ok();
1928 }
1929
1930 pub fn unmark_text(&mut self) {
1932 self.cx
1933 .update(|window, cx| self.handler.unmark_text(window, cx))
1934 .ok();
1935 }
1936
1937 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1939 self.cx
1940 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1941 .ok()
1942 .flatten()
1943 }
1944
1945 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1947 self.handler.apple_press_and_hold_enabled()
1948 }
1949
1950 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1952 self.handler.replace_text_in_range(None, input, window, cx);
1953 }
1954
1955 pub fn compute_ime_candidate_bounds(
1957 marked_range: Option<Range<usize>>,
1958 selection: &UTF16Selection,
1959 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1960 ) -> Option<Bounds<Pixels>> {
1961 if let Some(marked_range) = marked_range {
1962 let mut line_start = marked_range.start;
1964
1965 let caret = selection.range.end;
1969 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1970 for i in (marked_range.start..caret).rev() {
1971 if let Some(b) = bounds_for_range(i..i) {
1972 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1973 line_start = i + 1;
1974 break;
1975 }
1976 }
1977 }
1978 }
1979 bounds_for_range(line_start..line_start)
1980 } else {
1981 let offset = if selection.reversed {
1983 selection.range.start
1984 } else {
1985 selection.range.end
1986 };
1987 bounds_for_range(offset..offset)
1988 }
1989 }
1990
1991 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1993 let marked_range = self.handler.marked_text_range(window, cx);
1994 let selection = self.handler.selected_text_range(true, window, cx)?;
1995 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1996 self.handler.bounds_for_range(range, window, cx)
1997 })
1998 }
1999
2000 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
2002 let marked_range = self.marked_text_range();
2003 let selection = self.selected_text_range(true)?;
2004 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2005 self.bounds_for_range(range)
2006 })
2007 }
2008
2009 #[allow(unused)]
2011 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
2012 self.cx
2013 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
2014 .ok()
2015 .flatten()
2016 }
2017
2018 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
2020 self.handler.accepts_text_input(window, cx)
2021 }
2022
2023 pub fn query_accepts_text_input(&mut self) -> bool {
2025 self.cx
2026 .update(|window, cx| self.handler.accepts_text_input(window, cx))
2027 .unwrap_or(true)
2028 }
2029
2030 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
2035 self.cx
2036 .update(|window, cx| {
2037 !window.has_pending_keystrokes()
2039 && self.handler.prefers_ime_for_printable_keys(window, cx)
2040 })
2041 .unwrap_or(false)
2042 }
2043}
2044
2045#[derive(Debug)]
2048pub struct UTF16Selection {
2049 pub range: Range<usize>,
2051 pub reversed: bool,
2053}
2054
2055pub trait InputHandler: 'static {
2060 fn selected_text_range(
2065 &mut self,
2066 ignore_disabled_input: bool,
2067 window: &mut Window,
2068 cx: &mut App,
2069 ) -> Option<UTF16Selection>;
2070
2071 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
2076
2077 fn text_for_range(
2082 &mut self,
2083 range_utf16: Range<usize>,
2084 adjusted_range: &mut Option<Range<usize>>,
2085 window: &mut Window,
2086 cx: &mut App,
2087 ) -> Option<String>;
2088
2089 fn replace_text_in_range(
2094 &mut self,
2095 replacement_range: Option<Range<usize>>,
2096 text: &str,
2097 window: &mut Window,
2098 cx: &mut App,
2099 );
2100
2101 fn replace_and_mark_text_in_range(
2108 &mut self,
2109 range_utf16: Option<Range<usize>>,
2110 new_text: &str,
2111 new_selected_range: Option<Range<usize>>,
2112 window: &mut Window,
2113 cx: &mut App,
2114 );
2115
2116 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
2119
2120 fn bounds_for_range(
2125 &mut self,
2126 range_utf16: Range<usize>,
2127 window: &mut Window,
2128 cx: &mut App,
2129 ) -> Option<Bounds<Pixels>>;
2130
2131 fn character_index_for_point(
2135 &mut self,
2136 point: Point<Pixels>,
2137 window: &mut Window,
2138 cx: &mut App,
2139 ) -> Option<usize>;
2140
2141 fn apple_press_and_hold_enabled(&mut self) -> bool {
2145 true
2146 }
2147
2148 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2150 true
2151 }
2152
2153 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2161 false
2162 }
2163
2164 fn set_selected_text_range(
2166 &mut self,
2167 _range_utf16: Range<usize>,
2168 _window: &mut Window,
2169 _cx: &mut App,
2170 ) {
2171 }
2172
2173 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2175 None
2176 }
2177
2178 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2180 None
2181 }
2182}
2183
2184#[derive(Debug)]
2186pub struct WindowOptions {
2187 pub window_bounds: Option<WindowBounds>,
2191
2192 pub titlebar: Option<TitlebarOptions>,
2194
2195 pub focus: bool,
2197
2198 pub show: bool,
2200
2201 pub kind: WindowKind,
2203
2204 pub is_movable: bool,
2206
2207 pub is_resizable: bool,
2209
2210 pub is_minimizable: bool,
2212
2213 pub display_id: Option<DisplayId>,
2216
2217 pub window_background: WindowBackgroundAppearance,
2219
2220 pub app_id: Option<String>,
2222
2223 pub window_min_size: Option<Size<Pixels>>,
2225
2226 pub window_decorations: Option<WindowDecorations>,
2229
2230 pub icon: Option<Arc<image::RgbaImage>>,
2232
2233 pub tabbing_identifier: Option<String>,
2235
2236 pub app_owns_titlebar_drag: bool,
2239
2240 pub mouse_passthrough: bool,
2243}
2244
2245#[derive(Debug)]
2247pub struct WindowParams {
2248 pub bounds: Bounds<Pixels>,
2250
2251 pub titlebar: Option<TitlebarOptions>,
2253
2254 pub kind: WindowKind,
2256
2257 pub is_movable: bool,
2259
2260 pub is_resizable: bool,
2262
2263 pub is_minimizable: bool,
2265
2266 pub focus: bool,
2268
2269 pub show: bool,
2271
2272 pub icon: Option<Arc<image::RgbaImage>>,
2274
2275 pub display_id: Option<DisplayId>,
2277
2278 pub app_id: Option<String>,
2280
2281 pub window_min_size: Option<Size<Pixels>>,
2283 #[cfg(target_os = "macos")]
2285 pub tabbing_identifier: Option<String>,
2286
2287 pub app_owns_titlebar_drag: bool,
2290
2291 pub mouse_passthrough: bool,
2294}
2295
2296#[derive(Debug, Copy, Clone, PartialEq)]
2298pub enum WindowBounds {
2299 Windowed(Bounds<Pixels>),
2301 Maximized(Bounds<Pixels>),
2304 Fullscreen(Bounds<Pixels>),
2307}
2308
2309impl Default for WindowBounds {
2310 fn default() -> Self {
2311 WindowBounds::Windowed(Bounds::default())
2312 }
2313}
2314
2315impl WindowBounds {
2316 pub fn get_bounds(&self) -> Bounds<Pixels> {
2318 match self {
2319 WindowBounds::Windowed(bounds) => *bounds,
2320 WindowBounds::Maximized(bounds) => *bounds,
2321 WindowBounds::Fullscreen(bounds) => *bounds,
2322 }
2323 }
2324
2325 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2327 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2328 }
2329}
2330
2331impl Default for WindowOptions {
2332 fn default() -> Self {
2333 Self {
2334 window_bounds: None,
2335 titlebar: Some(TitlebarOptions {
2336 title: Default::default(),
2337 appears_transparent: Default::default(),
2338 traffic_light_position: Default::default(),
2339 }),
2340 focus: true,
2341 show: true,
2342 kind: WindowKind::Normal,
2343 is_movable: true,
2344 is_resizable: true,
2345 is_minimizable: true,
2346 display_id: None,
2347 window_background: WindowBackgroundAppearance::default(),
2348 icon: None,
2349 app_id: None,
2350 window_min_size: None,
2351 window_decorations: None,
2352 tabbing_identifier: None,
2353 app_owns_titlebar_drag: false,
2354 mouse_passthrough: false,
2355 }
2356 }
2357}
2358
2359#[derive(Debug, Default)]
2361pub struct TitlebarOptions {
2362 pub title: Option<SharedString>,
2364
2365 pub appears_transparent: bool,
2368
2369 pub traffic_light_position: Option<Point<Pixels>>,
2371}
2372
2373#[derive(Clone, Debug, PartialEq, Eq)]
2375pub enum WindowKind {
2376 Normal,
2378
2379 PopUp,
2382
2383 AnchoredPopup(popup::PopupOptions),
2390
2391 Floating,
2393
2394 #[cfg(all(target_os = "linux", feature = "wayland"))]
2397 LayerShell(layer_shell::LayerShellOptions),
2398
2399 Dialog,
2402
2403 Overlay,
2405}
2406
2407#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2412pub enum WindowAppearance {
2413 #[default]
2417 Light,
2418
2419 VibrantLight,
2423
2424 Dark,
2428
2429 VibrantDark,
2433}
2434
2435#[derive(Copy, Clone, Debug, Default, PartialEq)]
2437pub enum WindowBackgroundAppearance {
2438 #[default]
2444 Opaque,
2445 Transparent,
2447 Blurred,
2451 MicaBackdrop,
2453 MicaAltBackdrop,
2455}
2456
2457#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2459pub enum TextRenderingMode {
2460 #[default]
2462 PlatformDefault,
2463 Subpixel,
2465 Grayscale,
2467}
2468
2469#[derive(Clone, Debug)]
2471pub struct PathPromptOptions {
2472 pub files: bool,
2474 pub directories: bool,
2476 pub multiple: bool,
2478 pub prompt: Option<SharedString>,
2480}
2481
2482#[derive(Copy, Clone, Debug, PartialEq)]
2484pub enum PromptLevel {
2485 Info,
2487
2488 Warning,
2490
2491 Critical,
2493}
2494
2495#[derive(Clone, Debug, PartialEq)]
2497pub enum PromptButton {
2498 Ok(SharedString),
2500 Cancel(SharedString),
2502 Other(SharedString),
2504}
2505
2506impl PromptButton {
2507 pub fn new(label: impl Into<SharedString>) -> Self {
2509 PromptButton::Other(label.into())
2510 }
2511
2512 pub fn ok(label: impl Into<SharedString>) -> Self {
2514 PromptButton::Ok(label.into())
2515 }
2516
2517 pub fn cancel(label: impl Into<SharedString>) -> Self {
2519 PromptButton::Cancel(label.into())
2520 }
2521
2522 pub fn is_cancel(&self) -> bool {
2524 matches!(self, PromptButton::Cancel(_))
2525 }
2526
2527 pub fn label(&self) -> &SharedString {
2529 match self {
2530 PromptButton::Ok(label) => label,
2531 PromptButton::Cancel(label) => label,
2532 PromptButton::Other(label) => label,
2533 }
2534 }
2535}
2536
2537impl From<&str> for PromptButton {
2538 fn from(value: &str) -> Self {
2539 match value.to_lowercase().as_str() {
2540 "ok" => PromptButton::Ok("OK".into()),
2541 "cancel" => PromptButton::Cancel("Cancel".into()),
2542 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2543 }
2544 }
2545}
2546
2547#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2549pub enum CursorStyle {
2550 #[default]
2552 Arrow,
2553
2554 IBeam,
2557
2558 Crosshair,
2561
2562 ClosedHand,
2565
2566 OpenHand,
2569
2570 PointingHand,
2573
2574 ResizeLeft,
2577
2578 ResizeRight,
2581
2582 ResizeLeftRight,
2585
2586 ResizeUp,
2589
2590 ResizeDown,
2593
2594 ResizeUpDown,
2597
2598 ResizeUpLeftDownRight,
2601
2602 ResizeUpRightDownLeft,
2605
2606 ResizeColumn,
2609
2610 ResizeRow,
2613
2614 IBeamCursorForVerticalLayout,
2617
2618 OperationNotAllowed,
2621
2622 DragLink,
2625
2626 DragCopy,
2629
2630 ContextualMenu,
2633}
2634
2635#[derive(Clone, Debug, Eq, PartialEq)]
2637pub struct ClipboardItem {
2638 pub entries: Vec<ClipboardEntry>,
2640}
2641
2642#[derive(Clone, Debug, Eq, PartialEq)]
2644pub enum ClipboardEntry {
2645 String(ClipboardString),
2647 Image(Image),
2649 ExternalPaths(crate::ExternalPaths),
2651}
2652
2653impl ClipboardItem {
2654 pub fn new_string(text: String) -> Self {
2656 Self {
2657 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2658 }
2659 }
2660
2661 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2663 Self {
2664 entries: vec![ClipboardEntry::String(ClipboardString {
2665 text,
2666 metadata: Some(metadata),
2667 })],
2668 }
2669 }
2670
2671 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2673 Self {
2674 entries: vec![ClipboardEntry::String(
2675 ClipboardString::new(text).with_json_metadata(metadata),
2676 )],
2677 }
2678 }
2679
2680 pub fn new_image(image: &Image) -> Self {
2682 Self {
2683 entries: vec![ClipboardEntry::Image(image.clone())],
2684 }
2685 }
2686
2687 pub fn text(&self) -> Option<String> {
2690 let mut answer = String::new();
2691
2692 for entry in self.entries.iter() {
2693 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2694 answer.push_str(text);
2695 }
2696 }
2697
2698 if answer.is_empty() {
2699 for entry in self.entries.iter() {
2700 if let ClipboardEntry::ExternalPaths(paths) = entry {
2701 for path in &paths.0 {
2702 use std::fmt::Write as _;
2703 _ = write!(answer, "{}", path.display());
2704 }
2705 }
2706 }
2707 }
2708
2709 if !answer.is_empty() {
2710 Some(answer)
2711 } else {
2712 None
2713 }
2714 }
2715
2716 pub fn metadata(&self) -> Option<&String> {
2718 match self.entries().first() {
2719 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2720 clipboard_string.metadata.as_ref()
2721 }
2722 _ => None,
2723 }
2724 }
2725
2726 pub fn entries(&self) -> &[ClipboardEntry] {
2728 &self.entries
2729 }
2730
2731 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2733 self.entries.into_iter()
2734 }
2735}
2736
2737impl From<ClipboardString> for ClipboardEntry {
2738 fn from(value: ClipboardString) -> Self {
2739 Self::String(value)
2740 }
2741}
2742
2743impl From<String> for ClipboardEntry {
2744 fn from(value: String) -> Self {
2745 Self::from(ClipboardString::from(value))
2746 }
2747}
2748
2749impl From<Image> for ClipboardEntry {
2750 fn from(value: Image) -> Self {
2751 Self::Image(value)
2752 }
2753}
2754
2755impl From<ClipboardEntry> for ClipboardItem {
2756 fn from(value: ClipboardEntry) -> Self {
2757 Self {
2758 entries: vec![value],
2759 }
2760 }
2761}
2762
2763impl From<String> for ClipboardItem {
2764 fn from(value: String) -> Self {
2765 Self::from(ClipboardEntry::from(value))
2766 }
2767}
2768
2769impl From<Image> for ClipboardItem {
2770 fn from(value: Image) -> Self {
2771 Self::from(ClipboardEntry::from(value))
2772 }
2773}
2774
2775#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2777pub enum ImageFormat {
2778 Png,
2782 Jpeg,
2784 Webp,
2786 Gif,
2788 Svg,
2790 Bmp,
2792 Tiff,
2794 Ico,
2796 Pnm,
2798}
2799
2800impl ImageFormat {
2801 pub const fn mime_type(self) -> &'static str {
2803 match self {
2804 ImageFormat::Png => "image/png",
2805 ImageFormat::Jpeg => "image/jpeg",
2806 ImageFormat::Webp => "image/webp",
2807 ImageFormat::Gif => "image/gif",
2808 ImageFormat::Svg => "image/svg+xml",
2809 ImageFormat::Bmp => "image/bmp",
2810 ImageFormat::Tiff => "image/tiff",
2811 ImageFormat::Ico => "image/ico",
2812 ImageFormat::Pnm => "image/x-portable-anymap",
2813 }
2814 }
2815
2816 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2818 use strum::IntoEnumIterator;
2819 Self::iter()
2820 .find(|format| format.mime_type() == mime_type)
2821 .or_else(|| Self::from_mime_type_alias(mime_type))
2822 }
2823
2824 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2828 match mime_type {
2829 "image/jpg" => Some(Self::Jpeg),
2830 "image/tif" => Some(Self::Tiff),
2831 _ => None,
2832 }
2833 }
2834}
2835
2836#[derive(Clone, Debug, PartialEq, Eq)]
2838pub struct Image {
2839 pub format: ImageFormat,
2841 pub bytes: Vec<u8>,
2843 pub id: u64,
2845}
2846
2847impl Hash for Image {
2848 fn hash<H: Hasher>(&self, state: &mut H) {
2849 state.write_u64(self.id);
2850 }
2851}
2852
2853impl Image {
2854 pub fn empty() -> Self {
2856 Self::from_bytes(ImageFormat::Png, Vec::new())
2857 }
2858
2859 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2861 Self {
2862 id: hash(&bytes),
2863 format,
2864 bytes,
2865 }
2866 }
2867
2868 pub fn id(&self) -> u64 {
2870 self.id
2871 }
2872
2873 pub fn use_render_image(
2875 self: Arc<Self>,
2876 window: &mut Window,
2877 cx: &mut App,
2878 ) -> Option<Arc<RenderImage>> {
2879 ImageSource::Image(self)
2880 .use_data(None, window, cx)
2881 .and_then(|result| result.ok())
2882 }
2883
2884 pub fn get_render_image(
2886 self: Arc<Self>,
2887 window: &mut Window,
2888 cx: &mut App,
2889 ) -> Option<Arc<RenderImage>> {
2890 ImageSource::Image(self)
2891 .get_data(None, window, cx)
2892 .and_then(|result| result.ok())
2893 }
2894
2895 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2897 ImageSource::Image(self).remove_asset(cx);
2898 }
2899
2900 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2902 fn frames_for_image(
2903 bytes: &[u8],
2904 format: image::ImageFormat,
2905 ) -> Result<SmallVec<[Frame; 1]>> {
2906 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
2907
2908 for pixel in data.chunks_exact_mut(4) {
2910 pixel.swap(0, 2);
2911 }
2912
2913 Ok(SmallVec::from_elem(Frame::new(data), 1))
2914 }
2915
2916 let frames = match self.format {
2917 ImageFormat::Gif => {
2918 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2919 let mut frames = SmallVec::new();
2920
2921 for frame in decoder.into_frames() {
2922 match frame {
2923 Ok(mut frame) => {
2924 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2926 pixel.swap(0, 2);
2927 }
2928 frames.push(frame);
2929 }
2930 Err(err) => {
2931 log::debug!("Skipping GIF frame due to decode error: {err}");
2932 }
2933 }
2934 }
2935
2936 if frames.is_empty() {
2937 anyhow::bail!("GIF could not be decoded: all frames failed");
2938 }
2939
2940 frames
2941 }
2942 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
2943 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
2944 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
2945 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
2946 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
2947 ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
2948 ImageFormat::Svg => {
2949 return svg_renderer
2950 .render_single_frame(&self.bytes, 1.0)
2951 .map_err(Into::into);
2952 }
2953 ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?,
2954 };
2955
2956 Ok(Arc::new(RenderImage::new(frames)))
2957 }
2958
2959 pub fn format(&self) -> ImageFormat {
2961 self.format
2962 }
2963
2964 pub fn bytes(&self) -> &[u8] {
2966 self.bytes.as_slice()
2967 }
2968}
2969
2970#[derive(Clone, Debug, Eq, PartialEq)]
2972pub struct ClipboardString {
2973 pub text: String,
2975 pub metadata: Option<String>,
2977}
2978
2979impl ClipboardString {
2980 pub fn new(text: String) -> Self {
2982 Self {
2983 text,
2984 metadata: None,
2985 }
2986 }
2987
2988 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2990 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2991 self
2992 }
2993
2994 pub fn text(&self) -> &String {
2996 &self.text
2997 }
2998
2999 pub fn into_text(self) -> String {
3001 self.text
3002 }
3003
3004 pub fn metadata_json<T>(&self) -> Option<T>
3006 where
3007 T: for<'a> Deserialize<'a>,
3008 {
3009 self.metadata
3010 .as_ref()
3011 .and_then(|m| serde_json::from_str(m).ok())
3012 }
3013
3014 pub fn text_hash(text: &str) -> u64 {
3016 let mut hasher = SeaHasher::new();
3017 text.hash(&mut hasher);
3018 hasher.finish()
3019 }
3020}
3021
3022impl From<String> for ClipboardString {
3023 fn from(value: String) -> Self {
3024 Self {
3025 text: value,
3026 metadata: None,
3027 }
3028 }
3029}
3030
3031#[cfg(test)]
3032mod image_tests {
3033 use super::*;
3034 use std::sync::Arc;
3035
3036 #[test]
3037 fn test_svg_image_to_image_data_converts_to_bgra() {
3038 let image = Image::from_bytes(
3039 ImageFormat::Svg,
3040 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3041<rect width="1" height="1" fill="#38BDF8"/>
3042</svg>"##
3043 .to_vec(),
3044 );
3045
3046 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3047 let bytes = render_image.as_bytes(0).unwrap();
3048
3049 for pixel in bytes.chunks_exact(4) {
3050 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3051 }
3052 }
3053}
3054
3055#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3056mod tests {
3057 use super::*;
3058 use rgpui::collections::HashSet;
3059
3060 #[test]
3061 fn test_window_button_layout_parse_standard() {
3062 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3063 assert_eq!(
3064 layout.left,
3065 [
3066 Some(WindowButton::Close),
3067 Some(WindowButton::Minimize),
3068 None
3069 ]
3070 );
3071 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3072 }
3073
3074 #[test]
3075 fn test_window_button_layout_parse_right_only() {
3076 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3077 assert_eq!(layout.left, [None, None, None]);
3078 assert_eq!(
3079 layout.right,
3080 [
3081 Some(WindowButton::Minimize),
3082 Some(WindowButton::Maximize),
3083 Some(WindowButton::Close)
3084 ]
3085 );
3086 }
3087
3088 #[test]
3089 fn test_window_button_layout_parse_left_only() {
3090 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3091 assert_eq!(
3092 layout.left,
3093 [
3094 Some(WindowButton::Close),
3095 Some(WindowButton::Minimize),
3096 Some(WindowButton::Maximize)
3097 ]
3098 );
3099 assert_eq!(layout.right, [None, None, None]);
3100 }
3101
3102 #[test]
3103 fn test_window_button_layout_parse_with_whitespace() {
3104 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3105 assert_eq!(
3106 layout.left,
3107 [
3108 Some(WindowButton::Close),
3109 Some(WindowButton::Minimize),
3110 None
3111 ]
3112 );
3113 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3114 }
3115
3116 #[test]
3117 fn test_window_button_layout_parse_empty() {
3118 let layout = WindowButtonLayout::parse("").unwrap();
3119 assert_eq!(layout.left, [None, None, None]);
3120 assert_eq!(layout.right, [None, None, None]);
3121 }
3122
3123 #[test]
3124 fn test_window_button_layout_parse_intentionally_empty() {
3125 let layout = WindowButtonLayout::parse(":").unwrap();
3126 assert_eq!(layout.left, [None, None, None]);
3127 assert_eq!(layout.right, [None, None, None]);
3128 }
3129
3130 #[test]
3131 fn test_window_button_layout_parse_invalid_buttons() {
3132 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3133 assert_eq!(
3134 layout.left,
3135 [
3136 Some(WindowButton::Close),
3137 Some(WindowButton::Minimize),
3138 None
3139 ]
3140 );
3141 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3142 }
3143
3144 #[test]
3145 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3146 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3147 assert_eq!(
3148 layout.right,
3149 [
3150 Some(WindowButton::Close),
3151 Some(WindowButton::Minimize),
3152 None
3153 ]
3154 );
3155 assert_eq!(layout.format(), ":close,minimize");
3156 }
3157
3158 #[test]
3159 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3160 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3161 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3162 assert_eq!(
3163 layout.right,
3164 [
3165 Some(WindowButton::Maximize),
3166 Some(WindowButton::Minimize),
3167 None
3168 ]
3169 );
3170
3171 let button_ids: Vec<_> = layout
3172 .left
3173 .iter()
3174 .chain(layout.right.iter())
3175 .flatten()
3176 .map(WindowButton::id)
3177 .collect();
3178 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3179 assert_eq!(unique_button_ids.len(), button_ids.len());
3180 assert_eq!(layout.format(), "close:maximize,minimize");
3181 }
3182
3183 #[test]
3184 fn test_window_button_layout_parse_gnome_style() {
3185 let layout = WindowButtonLayout::parse("close").unwrap();
3186 assert_eq!(layout.left, [None, None, None]);
3187 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3188 }
3189
3190 #[test]
3191 fn test_window_button_layout_parse_elementary_style() {
3192 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3193 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3194 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3195 }
3196
3197 #[test]
3198 fn test_window_button_layout_round_trip() {
3199 let cases = [
3200 "close:minimize,maximize",
3201 "minimize,maximize,close:",
3202 ":close",
3203 "close:",
3204 "close:maximize",
3205 ":",
3206 ];
3207
3208 for case in cases {
3209 let layout = WindowButtonLayout::parse(case).unwrap();
3210 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3211 }
3212 }
3213
3214 #[test]
3215 fn test_window_button_layout_linux_default() {
3216 let layout = WindowButtonLayout::linux_default();
3217 assert_eq!(layout.left, [None, None, None]);
3218 assert_eq!(
3219 layout.right,
3220 [
3221 Some(WindowButton::Minimize),
3222 Some(WindowButton::Maximize),
3223 Some(WindowButton::Close)
3224 ]
3225 );
3226
3227 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3228 assert_eq!(round_tripped, layout);
3229 }
3230
3231 #[test]
3232 fn test_window_button_layout_parse_all_invalid() {
3233 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3234 }
3235}