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"))]
1761 fn contains(&self, _key: &AtlasKey) -> bool {
1762 false
1763 }
1764}
1765
1766#[doc(hidden)]
1767pub struct AtlasTextureList<T> {
1768 pub textures: Vec<Option<T>>,
1769 pub free_list: Vec<usize>,
1770}
1771
1772impl<T> Default for AtlasTextureList<T> {
1773 fn default() -> Self {
1774 Self {
1775 textures: Vec::default(),
1776 free_list: Vec::default(),
1777 }
1778 }
1779}
1780
1781impl<T> ops::Index<usize> for AtlasTextureList<T> {
1782 type Output = Option<T>;
1783
1784 fn index(&self, index: usize) -> &Self::Output {
1785 &self.textures[index]
1786 }
1787}
1788
1789impl<T> AtlasTextureList<T> {
1790 #[allow(unused)]
1791 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1792 self.free_list.clear();
1793 self.textures.drain(..)
1794 }
1795
1796 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1797 self.textures.iter_mut().flatten()
1798 }
1799}
1800
1801#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1803#[repr(C)]
1804pub struct AtlasTile {
1805 pub texture_id: AtlasTextureId,
1807 pub tile_id: TileId,
1809 pub padding: u32,
1811 pub bounds: Bounds<DevicePixels>,
1813}
1814
1815#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1817#[repr(C)]
1818pub struct AtlasTextureId {
1819 pub index: u32,
1822 pub kind: AtlasTextureKind,
1824}
1825
1826#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1827#[repr(C)]
1828pub enum AtlasTextureKind {
1830 Monochrome,
1832 Polychrome,
1834 Subpixel,
1836}
1837
1838#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1840#[repr(C)]
1841pub struct TileId(pub u32);
1842
1843impl From<etagere::AllocId> for TileId {
1844 fn from(id: etagere::AllocId) -> Self {
1845 Self(id.serialize())
1846 }
1847}
1848
1849impl From<TileId> for etagere::AllocId {
1850 fn from(id: TileId) -> Self {
1851 Self::deserialize(id.0)
1852 }
1853}
1854
1855pub struct PlatformInputHandler {
1857 cx: AsyncWindowContext,
1858 handler: Box<dyn InputHandler>,
1859}
1860
1861impl PlatformInputHandler {
1862 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1864 Self { cx, handler }
1865 }
1866
1867 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1869 self.cx
1870 .update(|window, cx| {
1871 self.handler
1872 .selected_text_range(ignore_disabled_input, window, cx)
1873 })
1874 .ok()
1875 .flatten()
1876 }
1877
1878 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1880 self.cx
1881 .update(|window, cx| self.handler.marked_text_range(window, cx))
1882 .ok()
1883 .flatten()
1884 }
1885
1886 pub fn text_for_range(
1888 &mut self,
1889 range_utf16: Range<usize>,
1890 adjusted: &mut Option<Range<usize>>,
1891 ) -> Option<String> {
1892 self.cx
1893 .update(|window, cx| {
1894 self.handler
1895 .text_for_range(range_utf16, adjusted, window, cx)
1896 })
1897 .ok()
1898 .flatten()
1899 }
1900
1901 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1903 self.cx
1904 .update(|window, cx| {
1905 self.handler
1906 .replace_text_in_range(replacement_range, text, window, cx);
1907 })
1908 .ok();
1909 }
1910
1911 pub fn replace_and_mark_text_in_range(
1913 &mut self,
1914 range_utf16: Option<Range<usize>>,
1915 new_text: &str,
1916 new_selected_range: Option<Range<usize>>,
1917 ) {
1918 self.cx
1919 .update(|window, cx| {
1920 self.handler.replace_and_mark_text_in_range(
1921 range_utf16,
1922 new_text,
1923 new_selected_range,
1924 window,
1925 cx,
1926 )
1927 })
1928 .ok();
1929 }
1930
1931 pub fn unmark_text(&mut self) {
1933 self.cx
1934 .update(|window, cx| self.handler.unmark_text(window, cx))
1935 .ok();
1936 }
1937
1938 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1940 self.cx
1941 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1942 .ok()
1943 .flatten()
1944 }
1945
1946 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1948 self.handler.apple_press_and_hold_enabled()
1949 }
1950
1951 pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1953 self.handler.replace_text_in_range(None, input, window, cx);
1954 }
1955
1956 pub fn compute_ime_candidate_bounds(
1958 marked_range: Option<Range<usize>>,
1959 selection: &UTF16Selection,
1960 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1961 ) -> Option<Bounds<Pixels>> {
1962 if let Some(marked_range) = marked_range {
1963 let mut line_start = marked_range.start;
1965
1966 let caret = selection.range.end;
1970 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1971 for i in (marked_range.start..caret).rev() {
1972 if let Some(b) = bounds_for_range(i..i) {
1973 if (b.origin.y - caret_bounds.origin.y).abs() > px(0.1) {
1974 line_start = i + 1;
1975 break;
1976 }
1977 }
1978 }
1979 }
1980 bounds_for_range(line_start..line_start)
1981 } else {
1982 let offset = if selection.reversed {
1984 selection.range.start
1985 } else {
1986 selection.range.end
1987 };
1988 bounds_for_range(offset..offset)
1989 }
1990 }
1991
1992 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1994 let marked_range = self.handler.marked_text_range(window, cx);
1995 let selection = self.handler.selected_text_range(true, window, cx)?;
1996 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1997 self.handler.bounds_for_range(range, window, cx)
1998 })
1999 }
2000
2001 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
2003 let marked_range = self.marked_text_range();
2004 let selection = self.selected_text_range(true)?;
2005 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
2006 self.bounds_for_range(range)
2007 })
2008 }
2009
2010 #[allow(unused)]
2012 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
2013 self.cx
2014 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
2015 .ok()
2016 .flatten()
2017 }
2018
2019 pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
2021 self.handler.accepts_text_input(window, cx)
2022 }
2023
2024 pub fn query_accepts_text_input(&mut self) -> bool {
2026 self.cx
2027 .update(|window, cx| self.handler.accepts_text_input(window, cx))
2028 .unwrap_or(true)
2029 }
2030
2031 pub fn query_prefers_ime_for_printable_keys(&mut self) -> bool {
2036 self.cx
2037 .update(|window, cx| {
2038 !window.has_pending_keystrokes()
2040 && self.handler.prefers_ime_for_printable_keys(window, cx)
2041 })
2042 .unwrap_or(false)
2043 }
2044}
2045
2046#[derive(Debug)]
2049pub struct UTF16Selection {
2050 pub range: Range<usize>,
2052 pub reversed: bool,
2054}
2055
2056pub trait InputHandler: 'static {
2061 fn selected_text_range(
2066 &mut self,
2067 ignore_disabled_input: bool,
2068 window: &mut Window,
2069 cx: &mut App,
2070 ) -> Option<UTF16Selection>;
2071
2072 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
2077
2078 fn text_for_range(
2083 &mut self,
2084 range_utf16: Range<usize>,
2085 adjusted_range: &mut Option<Range<usize>>,
2086 window: &mut Window,
2087 cx: &mut App,
2088 ) -> Option<String>;
2089
2090 fn replace_text_in_range(
2095 &mut self,
2096 replacement_range: Option<Range<usize>>,
2097 text: &str,
2098 window: &mut Window,
2099 cx: &mut App,
2100 );
2101
2102 fn replace_and_mark_text_in_range(
2109 &mut self,
2110 range_utf16: Option<Range<usize>>,
2111 new_text: &str,
2112 new_selected_range: Option<Range<usize>>,
2113 window: &mut Window,
2114 cx: &mut App,
2115 );
2116
2117 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
2120
2121 fn bounds_for_range(
2126 &mut self,
2127 range_utf16: Range<usize>,
2128 window: &mut Window,
2129 cx: &mut App,
2130 ) -> Option<Bounds<Pixels>>;
2131
2132 fn character_index_for_point(
2136 &mut self,
2137 point: Point<Pixels>,
2138 window: &mut Window,
2139 cx: &mut App,
2140 ) -> Option<usize>;
2141
2142 fn apple_press_and_hold_enabled(&mut self) -> bool {
2146 true
2147 }
2148
2149 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2151 true
2152 }
2153
2154 fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
2162 false
2163 }
2164
2165 fn set_selected_text_range(
2167 &mut self,
2168 _range_utf16: Range<usize>,
2169 _window: &mut Window,
2170 _cx: &mut App,
2171 ) {
2172 }
2173
2174 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
2176 None
2177 }
2178
2179 fn text_length_utf16(&mut self, _window: &mut Window, _cx: &mut App) -> Option<usize> {
2181 None
2182 }
2183}
2184
2185#[derive(Debug)]
2187pub struct WindowOptions {
2188 pub window_bounds: Option<WindowBounds>,
2192
2193 pub titlebar: Option<TitlebarOptions>,
2195
2196 pub focus: bool,
2198
2199 pub show: bool,
2201
2202 pub kind: WindowKind,
2204
2205 pub is_movable: bool,
2207
2208 pub is_resizable: bool,
2210
2211 pub is_minimizable: bool,
2213
2214 pub display_id: Option<DisplayId>,
2217
2218 pub window_background: WindowBackgroundAppearance,
2220
2221 pub app_id: Option<String>,
2223
2224 pub window_min_size: Option<Size<Pixels>>,
2226
2227 pub window_decorations: Option<WindowDecorations>,
2230
2231 pub icon: Option<Arc<image::RgbaImage>>,
2233
2234 pub tabbing_identifier: Option<String>,
2236
2237 pub app_owns_titlebar_drag: bool,
2240
2241 pub mouse_passthrough: bool,
2244}
2245
2246#[derive(Debug)]
2248pub struct WindowParams {
2249 pub bounds: Bounds<Pixels>,
2251
2252 pub titlebar: Option<TitlebarOptions>,
2254
2255 pub kind: WindowKind,
2257
2258 pub is_movable: bool,
2260
2261 pub is_resizable: bool,
2263
2264 pub is_minimizable: bool,
2266
2267 pub focus: bool,
2269
2270 pub show: bool,
2272
2273 pub icon: Option<Arc<image::RgbaImage>>,
2275
2276 pub display_id: Option<DisplayId>,
2278
2279 pub app_id: Option<String>,
2281
2282 pub window_min_size: Option<Size<Pixels>>,
2284 #[cfg(target_os = "macos")]
2286 pub tabbing_identifier: Option<String>,
2287
2288 pub app_owns_titlebar_drag: bool,
2291
2292 pub mouse_passthrough: bool,
2295}
2296
2297#[derive(Debug, Copy, Clone, PartialEq)]
2299pub enum WindowBounds {
2300 Windowed(Bounds<Pixels>),
2302 Maximized(Bounds<Pixels>),
2305 Fullscreen(Bounds<Pixels>),
2308}
2309
2310impl Default for WindowBounds {
2311 fn default() -> Self {
2312 WindowBounds::Windowed(Bounds::default())
2313 }
2314}
2315
2316impl WindowBounds {
2317 pub fn get_bounds(&self) -> Bounds<Pixels> {
2319 match self {
2320 WindowBounds::Windowed(bounds) => *bounds,
2321 WindowBounds::Maximized(bounds) => *bounds,
2322 WindowBounds::Fullscreen(bounds) => *bounds,
2323 }
2324 }
2325
2326 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
2328 WindowBounds::Windowed(Bounds::centered(None, size, cx))
2329 }
2330}
2331
2332impl Default for WindowOptions {
2333 fn default() -> Self {
2334 Self {
2335 window_bounds: None,
2336 titlebar: Some(TitlebarOptions {
2337 title: Default::default(),
2338 appears_transparent: Default::default(),
2339 traffic_light_position: Default::default(),
2340 }),
2341 focus: true,
2342 show: true,
2343 kind: WindowKind::Normal,
2344 is_movable: true,
2345 is_resizable: true,
2346 is_minimizable: true,
2347 display_id: None,
2348 window_background: WindowBackgroundAppearance::default(),
2349 icon: None,
2350 app_id: None,
2351 window_min_size: None,
2352 window_decorations: None,
2353 tabbing_identifier: None,
2354 app_owns_titlebar_drag: false,
2355 mouse_passthrough: false,
2356 }
2357 }
2358}
2359
2360#[derive(Debug, Default)]
2362pub struct TitlebarOptions {
2363 pub title: Option<SharedString>,
2365
2366 pub appears_transparent: bool,
2369
2370 pub traffic_light_position: Option<Point<Pixels>>,
2372}
2373
2374#[derive(Clone, Debug, PartialEq, Eq)]
2376pub enum WindowKind {
2377 Normal,
2379
2380 PopUp,
2383
2384 AnchoredPopup(popup::PopupOptions),
2391
2392 Floating,
2394
2395 #[cfg(all(target_os = "linux", feature = "wayland"))]
2398 LayerShell(layer_shell::LayerShellOptions),
2399
2400 Dialog,
2403
2404 Overlay,
2406}
2407
2408#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2413pub enum WindowAppearance {
2414 #[default]
2418 Light,
2419
2420 VibrantLight,
2424
2425 Dark,
2429
2430 VibrantDark,
2434}
2435
2436#[derive(Copy, Clone, Debug, Default, PartialEq)]
2438pub enum WindowBackgroundAppearance {
2439 #[default]
2445 Opaque,
2446 Transparent,
2448 Blurred,
2452 MicaBackdrop,
2454 MicaAltBackdrop,
2456}
2457
2458#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
2460pub enum TextRenderingMode {
2461 #[default]
2463 PlatformDefault,
2464 Subpixel,
2466 Grayscale,
2468}
2469
2470#[derive(Clone, Debug)]
2472pub struct PathPromptOptions {
2473 pub files: bool,
2475 pub directories: bool,
2477 pub multiple: bool,
2479 pub prompt: Option<SharedString>,
2481}
2482
2483#[derive(Copy, Clone, Debug, PartialEq)]
2485pub enum PromptLevel {
2486 Info,
2488
2489 Warning,
2491
2492 Critical,
2494}
2495
2496#[derive(Clone, Debug, PartialEq)]
2498pub enum PromptButton {
2499 Ok(SharedString),
2501 Cancel(SharedString),
2503 Other(SharedString),
2505}
2506
2507impl PromptButton {
2508 pub fn new(label: impl Into<SharedString>) -> Self {
2510 PromptButton::Other(label.into())
2511 }
2512
2513 pub fn ok(label: impl Into<SharedString>) -> Self {
2515 PromptButton::Ok(label.into())
2516 }
2517
2518 pub fn cancel(label: impl Into<SharedString>) -> Self {
2520 PromptButton::Cancel(label.into())
2521 }
2522
2523 pub fn is_cancel(&self) -> bool {
2525 matches!(self, PromptButton::Cancel(_))
2526 }
2527
2528 pub fn label(&self) -> &SharedString {
2530 match self {
2531 PromptButton::Ok(label) => label,
2532 PromptButton::Cancel(label) => label,
2533 PromptButton::Other(label) => label,
2534 }
2535 }
2536}
2537
2538impl From<&str> for PromptButton {
2539 fn from(value: &str) -> Self {
2540 match value.to_lowercase().as_str() {
2541 "ok" => PromptButton::Ok("OK".into()),
2542 "cancel" => PromptButton::Cancel("Cancel".into()),
2543 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2544 }
2545 }
2546}
2547
2548#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2550pub enum CursorStyle {
2551 #[default]
2553 Arrow,
2554
2555 IBeam,
2558
2559 Crosshair,
2562
2563 ClosedHand,
2566
2567 OpenHand,
2570
2571 PointingHand,
2574
2575 ResizeLeft,
2578
2579 ResizeRight,
2582
2583 ResizeLeftRight,
2586
2587 ResizeUp,
2590
2591 ResizeDown,
2594
2595 ResizeUpDown,
2598
2599 ResizeUpLeftDownRight,
2602
2603 ResizeUpRightDownLeft,
2606
2607 ResizeColumn,
2610
2611 ResizeRow,
2614
2615 IBeamCursorForVerticalLayout,
2618
2619 OperationNotAllowed,
2622
2623 DragLink,
2626
2627 DragCopy,
2630
2631 ContextualMenu,
2634}
2635
2636#[derive(Clone, Debug, Eq, PartialEq)]
2638pub struct ClipboardItem {
2639 pub entries: Vec<ClipboardEntry>,
2641}
2642
2643#[derive(Clone, Debug, Eq, PartialEq)]
2645pub enum ClipboardEntry {
2646 String(ClipboardString),
2648 Image(Image),
2650 ExternalPaths(crate::ExternalPaths),
2652}
2653
2654impl ClipboardItem {
2655 pub fn new_string(text: String) -> Self {
2657 Self {
2658 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2659 }
2660 }
2661
2662 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2664 Self {
2665 entries: vec![ClipboardEntry::String(ClipboardString {
2666 text,
2667 metadata: Some(metadata),
2668 })],
2669 }
2670 }
2671
2672 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2674 Self {
2675 entries: vec![ClipboardEntry::String(
2676 ClipboardString::new(text).with_json_metadata(metadata),
2677 )],
2678 }
2679 }
2680
2681 pub fn new_image(image: &Image) -> Self {
2683 Self {
2684 entries: vec![ClipboardEntry::Image(image.clone())],
2685 }
2686 }
2687
2688 pub fn text(&self) -> Option<String> {
2691 let mut answer = String::new();
2692
2693 for entry in self.entries.iter() {
2694 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2695 answer.push_str(text);
2696 }
2697 }
2698
2699 if answer.is_empty() {
2700 for entry in self.entries.iter() {
2701 if let ClipboardEntry::ExternalPaths(paths) = entry {
2702 for path in &paths.0 {
2703 use std::fmt::Write as _;
2704 _ = write!(answer, "{}", path.display());
2705 }
2706 }
2707 }
2708 }
2709
2710 if !answer.is_empty() {
2711 Some(answer)
2712 } else {
2713 None
2714 }
2715 }
2716
2717 pub fn metadata(&self) -> Option<&String> {
2719 match self.entries().first() {
2720 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2721 clipboard_string.metadata.as_ref()
2722 }
2723 _ => None,
2724 }
2725 }
2726
2727 pub fn entries(&self) -> &[ClipboardEntry] {
2729 &self.entries
2730 }
2731
2732 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2734 self.entries.into_iter()
2735 }
2736}
2737
2738impl From<ClipboardString> for ClipboardEntry {
2739 fn from(value: ClipboardString) -> Self {
2740 Self::String(value)
2741 }
2742}
2743
2744impl From<String> for ClipboardEntry {
2745 fn from(value: String) -> Self {
2746 Self::from(ClipboardString::from(value))
2747 }
2748}
2749
2750impl From<Image> for ClipboardEntry {
2751 fn from(value: Image) -> Self {
2752 Self::Image(value)
2753 }
2754}
2755
2756impl From<ClipboardEntry> for ClipboardItem {
2757 fn from(value: ClipboardEntry) -> Self {
2758 Self {
2759 entries: vec![value],
2760 }
2761 }
2762}
2763
2764impl From<String> for ClipboardItem {
2765 fn from(value: String) -> Self {
2766 Self::from(ClipboardEntry::from(value))
2767 }
2768}
2769
2770impl From<Image> for ClipboardItem {
2771 fn from(value: Image) -> Self {
2772 Self::from(ClipboardEntry::from(value))
2773 }
2774}
2775
2776#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2778pub enum ImageFormat {
2779 Png,
2783 Jpeg,
2785 Webp,
2787 Gif,
2789 Svg,
2791 Bmp,
2793 Tiff,
2795 Ico,
2797 Pnm,
2799}
2800
2801impl ImageFormat {
2802 pub const fn mime_type(self) -> &'static str {
2804 match self {
2805 ImageFormat::Png => "image/png",
2806 ImageFormat::Jpeg => "image/jpeg",
2807 ImageFormat::Webp => "image/webp",
2808 ImageFormat::Gif => "image/gif",
2809 ImageFormat::Svg => "image/svg+xml",
2810 ImageFormat::Bmp => "image/bmp",
2811 ImageFormat::Tiff => "image/tiff",
2812 ImageFormat::Ico => "image/ico",
2813 ImageFormat::Pnm => "image/x-portable-anymap",
2814 }
2815 }
2816
2817 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2819 use strum::IntoEnumIterator;
2820 Self::iter()
2821 .find(|format| format.mime_type() == mime_type)
2822 .or_else(|| Self::from_mime_type_alias(mime_type))
2823 }
2824
2825 fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
2829 match mime_type {
2830 "image/jpg" => Some(Self::Jpeg),
2831 "image/tif" => Some(Self::Tiff),
2832 _ => None,
2833 }
2834 }
2835}
2836
2837#[derive(Clone, Debug, PartialEq, Eq)]
2839pub struct Image {
2840 pub format: ImageFormat,
2842 pub bytes: Vec<u8>,
2844 pub id: u64,
2846}
2847
2848impl Hash for Image {
2849 fn hash<H: Hasher>(&self, state: &mut H) {
2850 state.write_u64(self.id);
2851 }
2852}
2853
2854impl Image {
2855 pub fn empty() -> Self {
2857 Self::from_bytes(ImageFormat::Png, Vec::new())
2858 }
2859
2860 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2862 Self {
2863 id: hash(&bytes),
2864 format,
2865 bytes,
2866 }
2867 }
2868
2869 pub fn id(&self) -> u64 {
2871 self.id
2872 }
2873
2874 pub fn use_render_image(
2876 self: Arc<Self>,
2877 window: &mut Window,
2878 cx: &mut App,
2879 ) -> Option<Arc<RenderImage>> {
2880 ImageSource::Image(self)
2881 .use_data(None, window, cx)
2882 .and_then(|result| result.ok())
2883 }
2884
2885 pub fn get_render_image(
2887 self: Arc<Self>,
2888 window: &mut Window,
2889 cx: &mut App,
2890 ) -> Option<Arc<RenderImage>> {
2891 ImageSource::Image(self)
2892 .get_data(None, window, cx)
2893 .and_then(|result| result.ok())
2894 }
2895
2896 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2898 ImageSource::Image(self).remove_asset(cx);
2899 }
2900
2901 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2903 fn frames_for_image(
2904 bytes: &[u8],
2905 format: image::ImageFormat,
2906 ) -> Result<SmallVec<[Frame; 1]>> {
2907 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
2908
2909 for pixel in data.chunks_exact_mut(4) {
2911 pixel.swap(0, 2);
2912 }
2913
2914 Ok(SmallVec::from_elem(Frame::new(data), 1))
2915 }
2916
2917 let frames = match self.format {
2918 ImageFormat::Gif => {
2919 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2920 let mut frames = SmallVec::new();
2921
2922 for frame in decoder.into_frames() {
2923 match frame {
2924 Ok(mut frame) => {
2925 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2927 pixel.swap(0, 2);
2928 }
2929 frames.push(frame);
2930 }
2931 Err(err) => {
2932 log::debug!("Skipping GIF frame due to decode error: {err}");
2933 }
2934 }
2935 }
2936
2937 if frames.is_empty() {
2938 anyhow::bail!("GIF could not be decoded: all frames failed");
2939 }
2940
2941 frames
2942 }
2943 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
2944 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
2945 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
2946 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
2947 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
2948 ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
2949 ImageFormat::Svg => {
2950 return svg_renderer
2951 .render_single_frame(&self.bytes, 1.0)
2952 .map_err(Into::into);
2953 }
2954 ImageFormat::Pnm => frames_for_image(&self.bytes, image::ImageFormat::Pnm)?,
2955 };
2956
2957 Ok(Arc::new(RenderImage::new(frames)))
2958 }
2959
2960 pub fn format(&self) -> ImageFormat {
2962 self.format
2963 }
2964
2965 pub fn bytes(&self) -> &[u8] {
2967 self.bytes.as_slice()
2968 }
2969}
2970
2971#[derive(Clone, Debug, Eq, PartialEq)]
2973pub struct ClipboardString {
2974 pub text: String,
2976 pub metadata: Option<String>,
2978}
2979
2980impl ClipboardString {
2981 pub fn new(text: String) -> Self {
2983 Self {
2984 text,
2985 metadata: None,
2986 }
2987 }
2988
2989 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2991 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2992 self
2993 }
2994
2995 pub fn text(&self) -> &String {
2997 &self.text
2998 }
2999
3000 pub fn into_text(self) -> String {
3002 self.text
3003 }
3004
3005 pub fn metadata_json<T>(&self) -> Option<T>
3007 where
3008 T: for<'a> Deserialize<'a>,
3009 {
3010 self.metadata
3011 .as_ref()
3012 .and_then(|m| serde_json::from_str(m).ok())
3013 }
3014
3015 pub fn text_hash(text: &str) -> u64 {
3017 let mut hasher = SeaHasher::new();
3018 text.hash(&mut hasher);
3019 hasher.finish()
3020 }
3021}
3022
3023impl From<String> for ClipboardString {
3024 fn from(value: String) -> Self {
3025 Self {
3026 text: value,
3027 metadata: None,
3028 }
3029 }
3030}
3031
3032#[cfg(test)]
3033mod image_tests {
3034 use super::*;
3035 use std::sync::Arc;
3036
3037 #[test]
3038 fn test_svg_image_to_image_data_converts_to_bgra() {
3039 let image = Image::from_bytes(
3040 ImageFormat::Svg,
3041 br##"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
3042<rect width="1" height="1" fill="#38BDF8"/>
3043</svg>"##
3044 .to_vec(),
3045 );
3046
3047 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
3048 let bytes = render_image.as_bytes(0).unwrap();
3049
3050 for pixel in bytes.chunks_exact(4) {
3051 assert_eq!(pixel, &[0xF8, 0xBD, 0x38, 0xFF]);
3052 }
3053 }
3054}
3055
3056#[cfg(all(test, any(target_os = "linux", target_os = "freebsd")))]
3057mod tests {
3058 use super::*;
3059 use rgpui::collections::HashSet;
3060
3061 #[test]
3062 fn test_window_button_layout_parse_standard() {
3063 let layout = WindowButtonLayout::parse("close,minimize:maximize").unwrap();
3064 assert_eq!(
3065 layout.left,
3066 [
3067 Some(WindowButton::Close),
3068 Some(WindowButton::Minimize),
3069 None
3070 ]
3071 );
3072 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3073 }
3074
3075 #[test]
3076 fn test_window_button_layout_parse_right_only() {
3077 let layout = WindowButtonLayout::parse("minimize,maximize,close").unwrap();
3078 assert_eq!(layout.left, [None, None, None]);
3079 assert_eq!(
3080 layout.right,
3081 [
3082 Some(WindowButton::Minimize),
3083 Some(WindowButton::Maximize),
3084 Some(WindowButton::Close)
3085 ]
3086 );
3087 }
3088
3089 #[test]
3090 fn test_window_button_layout_parse_left_only() {
3091 let layout = WindowButtonLayout::parse("close,minimize,maximize:").unwrap();
3092 assert_eq!(
3093 layout.left,
3094 [
3095 Some(WindowButton::Close),
3096 Some(WindowButton::Minimize),
3097 Some(WindowButton::Maximize)
3098 ]
3099 );
3100 assert_eq!(layout.right, [None, None, None]);
3101 }
3102
3103 #[test]
3104 fn test_window_button_layout_parse_with_whitespace() {
3105 let layout = WindowButtonLayout::parse(" close , minimize : maximize ").unwrap();
3106 assert_eq!(
3107 layout.left,
3108 [
3109 Some(WindowButton::Close),
3110 Some(WindowButton::Minimize),
3111 None
3112 ]
3113 );
3114 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3115 }
3116
3117 #[test]
3118 fn test_window_button_layout_parse_empty() {
3119 let layout = WindowButtonLayout::parse("").unwrap();
3120 assert_eq!(layout.left, [None, None, None]);
3121 assert_eq!(layout.right, [None, None, None]);
3122 }
3123
3124 #[test]
3125 fn test_window_button_layout_parse_intentionally_empty() {
3126 let layout = WindowButtonLayout::parse(":").unwrap();
3127 assert_eq!(layout.left, [None, None, None]);
3128 assert_eq!(layout.right, [None, None, None]);
3129 }
3130
3131 #[test]
3132 fn test_window_button_layout_parse_invalid_buttons() {
3133 let layout = WindowButtonLayout::parse("close,invalid,minimize:maximize,foo").unwrap();
3134 assert_eq!(
3135 layout.left,
3136 [
3137 Some(WindowButton::Close),
3138 Some(WindowButton::Minimize),
3139 None
3140 ]
3141 );
3142 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3143 }
3144
3145 #[test]
3146 fn test_window_button_layout_parse_deduplicates_same_side_buttons() {
3147 let layout = WindowButtonLayout::parse("close,close,minimize").unwrap();
3148 assert_eq!(
3149 layout.right,
3150 [
3151 Some(WindowButton::Close),
3152 Some(WindowButton::Minimize),
3153 None
3154 ]
3155 );
3156 assert_eq!(layout.format(), ":close,minimize");
3157 }
3158
3159 #[test]
3160 fn test_window_button_layout_parse_deduplicates_buttons_across_sides() {
3161 let layout = WindowButtonLayout::parse("close:maximize,close,minimize").unwrap();
3162 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3163 assert_eq!(
3164 layout.right,
3165 [
3166 Some(WindowButton::Maximize),
3167 Some(WindowButton::Minimize),
3168 None
3169 ]
3170 );
3171
3172 let button_ids: Vec<_> = layout
3173 .left
3174 .iter()
3175 .chain(layout.right.iter())
3176 .flatten()
3177 .map(WindowButton::id)
3178 .collect();
3179 let unique_button_ids = button_ids.iter().copied().collect::<HashSet<_>>();
3180 assert_eq!(unique_button_ids.len(), button_ids.len());
3181 assert_eq!(layout.format(), "close:maximize,minimize");
3182 }
3183
3184 #[test]
3185 fn test_window_button_layout_parse_gnome_style() {
3186 let layout = WindowButtonLayout::parse("close").unwrap();
3187 assert_eq!(layout.left, [None, None, None]);
3188 assert_eq!(layout.right, [Some(WindowButton::Close), None, None]);
3189 }
3190
3191 #[test]
3192 fn test_window_button_layout_parse_elementary_style() {
3193 let layout = WindowButtonLayout::parse("close:maximize").unwrap();
3194 assert_eq!(layout.left, [Some(WindowButton::Close), None, None]);
3195 assert_eq!(layout.right, [Some(WindowButton::Maximize), None, None]);
3196 }
3197
3198 #[test]
3199 fn test_window_button_layout_round_trip() {
3200 let cases = [
3201 "close:minimize,maximize",
3202 "minimize,maximize,close:",
3203 ":close",
3204 "close:",
3205 "close:maximize",
3206 ":",
3207 ];
3208
3209 for case in cases {
3210 let layout = WindowButtonLayout::parse(case).unwrap();
3211 assert_eq!(layout.format(), case, "Round-trip failed for: {}", case);
3212 }
3213 }
3214
3215 #[test]
3216 fn test_window_button_layout_linux_default() {
3217 let layout = WindowButtonLayout::linux_default();
3218 assert_eq!(layout.left, [None, None, None]);
3219 assert_eq!(
3220 layout.right,
3221 [
3222 Some(WindowButton::Minimize),
3223 Some(WindowButton::Maximize),
3224 Some(WindowButton::Close)
3225 ]
3226 );
3227
3228 let round_tripped = WindowButtonLayout::parse(&layout.format()).unwrap();
3229 assert_eq!(round_tripped, layout);
3230 }
3231
3232 #[test]
3233 fn test_window_button_layout_parse_all_invalid() {
3234 assert!(WindowButtonLayout::parse("asdfghjkl").is_err());
3235 }
3236}