1mod app_menu;
2mod keyboard;
3mod keystroke;
4#[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
6pub mod layer_shell;
7pub mod single_instance;
9pub mod window_positioner;
11
12#[cfg(all(
13 feature = "screen-capture",
14 any(target_os = "linux", target_os = "freebsd")
15))]
16type PlatformScreenCaptureFrame = scap::frame::Frame;
17#[cfg(all(
18 any(target_os = "linux", target_os = "freebsd"),
19 not(feature = "screen-capture")
20))]
21type PlatformScreenCaptureFrame = ();
22
23#[cfg(target_os = "macos")]
24type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
25
26#[cfg(all(target_os = "windows", feature = "screen-capture"))]
27type PlatformScreenCaptureFrame = scap::frame::Frame;
28#[cfg(all(target_os = "windows", not(feature = "screen-capture")))]
29type PlatformScreenCaptureFrame = ();
30
31#[cfg(any(test, feature = "test-support"))]
32#[doc(hidden)]
33#[allow(missing_docs)]
34pub mod render_image;
35
36#[cfg(any(test, feature = "test-support"))]
37mod test;
38#[cfg(any(test, feature = "test-support"))]
39mod threaded_dispatcher;
40
41#[cfg(all(
42 feature = "screen-capture",
43 any(
44 target_os = "windows",
45 all(
46 any(target_os = "linux", target_os = "freebsd"),
47 any(feature = "wayland", feature = "x11"),
48 )
49 )
50))]
51#[doc(hidden)]
53pub mod scap_screen_capture;
54
55use crate::{
56 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
57 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
58 ForegroundExecutor, GlyphId, GpuResourceBudget, GpuSpecs, Hsla, ImageSource, Keymap,
59 LineLayout, Pixels, PlatformInput, Point, QuitMode, RenderGlyphParams, RenderImage,
60 RenderImageParams, RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString, Size,
61 SvgRenderer, SystemWindowTab, Task, TaskLabel, Window, WindowControlArea, hash, point, px,
62 size,
63};
64use anyhow::{Context as _, Result};
65use async_task::Runnable;
66use futures::channel::oneshot;
67#[cfg(feature = "image-format-gif")]
68use image::AnimationDecoder as _;
69#[cfg(feature = "image-format-gif")]
70use image::codecs::gif::GifDecoder;
71use image::{DynamicImage, Frame};
72use parking::Unparker;
73use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
74use schemars::JsonSchema;
75use seahash::SeaHasher;
76use serde::{Deserialize, Serialize};
77use smallvec::SmallVec;
78use std::borrow::Cow;
79use std::hash::{Hash, Hasher};
80use std::io::Cursor;
81use std::ops;
82use std::time::{Duration, Instant};
83use std::{
84 fmt::{self, Debug},
85 ops::Range,
86 path::{Path, PathBuf},
87 rc::Rc,
88 sync::Arc,
89};
90use strum::EnumIter;
91use uuid::Uuid;
92
93pub use app_menu::*;
94pub use keyboard::*;
95pub use keystroke::*;
96
97pub use semantic_version::SemanticVersion;
98#[cfg(any(test, feature = "test-support"))]
99pub(crate) use test::*;
100
101#[cfg(any(test, feature = "test-support"))]
102pub use test::{
103 TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream, VisualRenderArtifact,
104};
105#[cfg(any(test, feature = "test-support"))]
106pub use threaded_dispatcher::ThreadedDispatcher;
107
108#[cfg(all(
109 any(test, feature = "test-support"),
110 any(
111 target_os = "macos",
112 target_os = "linux",
113 target_os = "freebsd",
114 target_os = "windows"
115 )
116))]
117#[doc(hidden)]
118pub use test::VisualTestPlatform;
119
120#[cfg(any(target_os = "linux", target_os = "freebsd"))]
122#[inline]
123pub fn guess_compositor() -> &'static str {
124 if std::env::var_os("ZED_HEADLESS").is_some() {
125 return "Headless";
126 }
127
128 #[cfg(feature = "wayland")]
129 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
130 #[cfg(not(feature = "wayland"))]
131 let wayland_display: Option<std::ffi::OsString> = None;
132
133 #[cfg(feature = "x11")]
134 let x11_display = std::env::var_os("DISPLAY");
135 #[cfg(not(feature = "x11"))]
136 let x11_display: Option<std::ffi::OsString> = None;
137
138 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
139 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
140
141 if use_wayland {
142 "Wayland"
143 } else if use_x11 {
144 "X11"
145 } else {
146 "Headless"
147 }
148}
149
150#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
157#[non_exhaustive]
158pub struct RendererCacheStats {
159 pub idle_gpu_buffers: usize,
161 pub gpu_buffer_size_bytes: usize,
163}
164
165impl RendererCacheStats {
166 #[doc(hidden)]
168 pub fn new(idle_gpu_buffers: usize, gpu_buffer_size_bytes: usize) -> Self {
169 Self {
170 idle_gpu_buffers,
171 gpu_buffer_size_bytes,
172 }
173 }
174}
175
176#[allow(missing_docs)]
178pub trait Platform: 'static {
179 fn background_executor(&self) -> BackgroundExecutor;
180 fn foreground_executor(&self) -> ForegroundExecutor;
181 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
182
183 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
184 fn quit(&self);
185 fn restart(&self, binary_path: Option<PathBuf>);
186 fn activate(&self, ignoring_other_apps: bool);
187 fn hide(&self);
188 fn hide_other_apps(&self);
189 fn unhide_other_apps(&self);
190
191 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
192 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
193 fn active_window(&self) -> Option<AnyWindowHandle>;
194 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
195 None
196 }
197
198 #[cfg(feature = "screen-capture")]
199 fn is_screen_capture_supported(&self) -> bool;
200 #[cfg(not(feature = "screen-capture"))]
201 fn is_screen_capture_supported(&self) -> bool {
202 false
203 }
204 #[cfg(feature = "screen-capture")]
205 fn screen_capture_sources(&self)
206 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
207 #[cfg(not(feature = "screen-capture"))]
208 fn screen_capture_sources(
209 &self,
210 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
211 let (sources_tx, sources_rx) = oneshot::channel();
212 sources_tx
213 .send(Err(anyhow::anyhow!(
214 "gpui was compiled without the screen-capture feature"
215 )))
216 .ok();
217 sources_rx
218 }
219
220 fn open_window(
221 &self,
222 handle: AnyWindowHandle,
223 options: WindowParams,
224 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
225
226 fn trim_renderer_caches(&self) {}
230
231 fn configure_gpu_resources(&self, _gpu: &GpuResourceBudget) {}
234
235 fn renderer_cache_stats(&self) -> RendererCacheStats {
239 RendererCacheStats::default()
240 }
241
242 fn window_appearance(&self) -> WindowAppearance;
244
245 fn set_window_appearance(&self, _appearance: Option<WindowAppearance>) {}
254
255 fn open_url(&self, url: &str);
256 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
257 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
258
259 fn prompt_for_paths(
260 &self,
261 options: PathPromptOptions,
262 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
263 fn prompt_for_new_path(
264 &self,
265 directory: &Path,
266 suggested_name: Option<&str>,
267 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
268 fn can_select_mixed_files_and_dirs(&self) -> bool;
269 fn reveal_path(&self, path: &Path);
270 fn open_with_system(&self, path: &Path);
271
272 fn on_quit(&self, callback: Box<dyn FnMut() -> bool>);
273 fn on_reopen(&self, callback: Box<dyn FnMut()>);
274
275 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
276 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
277 None
278 }
279
280 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
281 fn perform_dock_menu_action(&self, _action: usize) {}
282 fn add_recent_document(&self, _path: &Path) {}
283 fn update_jump_list(
284 &self,
285 _menus: Vec<MenuItem>,
286 _entries: Vec<SmallVec<[PathBuf; 2]>>,
287 ) -> Vec<SmallVec<[PathBuf; 2]>> {
288 Vec::new()
289 }
290 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
291 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
292 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
293
294 fn thermal_state(&self) -> ThermalState {
295 ThermalState::Nominal
296 }
297 fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
298
299 fn compositor_name(&self) -> &'static str {
300 ""
301 }
302 fn app_path(&self) -> Result<PathBuf>;
303 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
304
305 fn set_cursor_style(&self, style: CursorStyle);
306 fn should_auto_hide_scrollbars(&self) -> bool;
307
308 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
309 fn write_to_primary(&self, item: ClipboardItem);
310 fn write_to_clipboard(&self, item: ClipboardItem);
311 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
312 fn read_from_primary(&self) -> Option<ClipboardItem>;
313 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
314
315 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
316 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
317 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
318
319 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
320 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
321 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
322
323 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
324 fn set_tray_icon_rendering_mode(&self, _rendering_mode: TrayIconRenderingMode) {}
325 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
326 fn set_tray_tooltip(&self, _tooltip: &str) {}
327 fn set_tray_panel_mode(&self, _enabled: bool) {}
328 fn get_tray_icon_anchor(&self) -> Option<TrayAnchor> {
329 None
330 }
331 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
332 None
333 }
334 fn on_tray_icon_event(&self, mut callback: Box<dyn FnMut(TrayIconEvent)>) {
335 self.on_tray_icon_click_event(Box::new(move |event| callback(event.kind)));
336 }
337 fn on_tray_icon_click_event(&self, _callback: Box<dyn FnMut(TrayIconClickEvent)>) {}
338 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
339
340 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
341 Err(anyhow::anyhow!(
342 "Global hotkeys not supported on this platform"
343 ))
344 }
345 fn unregister_global_hotkey(&self, _id: u32) {}
346 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
347
348 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
349 None
350 }
351
352 fn accessibility_status(&self) -> PermissionStatus {
353 PermissionStatus::Unavailable
354 }
355 fn request_accessibility_permission(&self) -> PermissionRequestStatus {
356 PermissionRequestStatus::Unavailable
357 }
358
359 fn microphone_status(&self) -> PermissionStatus {
360 PermissionStatus::Unavailable
361 }
362 fn request_microphone_permission(
363 &self,
364 callback: Box<dyn FnOnce(bool)>,
365 ) -> PermissionRequestStatus {
366 callback(false);
367 PermissionRequestStatus::Unavailable
368 }
369
370 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
371 Err(anyhow::anyhow!(
372 "Auto-launch not supported on this platform"
373 ))
374 }
375 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
376 false
377 }
378
379 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
380 Err(anyhow::anyhow!(
381 "Notifications not supported on this platform"
382 ))
383 }
384
385 fn set_quit_mode(&self, _mode: QuitMode) {}
392
393 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
394 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> {
395 None
396 }
397 fn stop_power_save_blocker(&self, _id: u32) {}
398 fn system_idle_time(&self) -> Option<Duration> {
399 None
400 }
401 fn network_status(&self) -> NetworkStatus {
402 NetworkStatus::Online
403 }
404 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
405 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
406 fn request_user_attention(&self, _attention_type: AttentionType) {}
407 fn cancel_user_attention(&self) {}
408 fn set_dock_badge(&self, _label: Option<&str>) {}
409 fn show_context_menu(
410 &self,
411 _position: Point<Pixels>,
412 _items: Vec<TrayMenuItem>,
413 _callback: Box<dyn FnMut(SharedString)>,
414 ) {
415 }
416 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
417 let (tx, rx) = oneshot::channel();
418 tx.send(0).ok();
419 rx
420 }
421 fn os_info(&self) -> OsInfo {
422 OsInfo {
423 name: std::env::consts::OS.into(),
424 arch: std::env::consts::ARCH.into(),
425 version: String::new().into(),
426 locale: String::new().into(),
427 hostname: String::new().into(),
428 }
429 }
430 fn biometric_status(&self) -> BiometricStatus {
431 BiometricStatus::Unavailable
432 }
433 fn authenticate_biometric(&self, _reason: &str, callback: Box<dyn FnOnce(bool) + Send>) {
434 callback(false);
435 }
436}
437
438pub trait PlatformDisplay: Send + Sync + Debug {
440 fn id(&self) -> DisplayId;
442
443 fn uuid(&self) -> Result<Uuid>;
446
447 fn bounds(&self) -> Bounds<Pixels>;
449
450 fn visible_bounds(&self) -> Bounds<Pixels> {
454 self.bounds()
455 }
456
457 fn default_bounds(&self) -> Bounds<Pixels> {
459 let bounds = self.visible_bounds();
460 let center = bounds.center();
461 let window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
462 let offset = window_size / 2.0;
463 let origin = point(center.x - offset.width, center.y - offset.height);
464 Bounds::new(origin, window_size)
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
470pub enum ThermalState {
471 Nominal,
473 Fair,
475 Serious,
477 Critical,
479}
480
481#[derive(Clone)]
483pub struct SourceMetadata {
484 pub id: u64,
486 pub label: Option<SharedString>,
488 pub is_main: Option<bool>,
490 pub resolution: Size<DevicePixels>,
492}
493
494pub trait ScreenCaptureSource {
496 fn metadata(&self) -> Result<SourceMetadata>;
498
499 fn stream(
502 &self,
503 foreground_executor: &ForegroundExecutor,
504 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
505 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
506
507 fn stream_with_termination(
513 &self,
514 _foreground_executor: &ForegroundExecutor,
515 _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
516 _termination_callback: ScreenCaptureTerminationCallback,
517 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
518 let (mut sender, receiver) = oneshot::channel();
519 sender
520 .send(Err(anyhow::anyhow!(
521 "runtime termination notification is not supported by this screen capture source"
522 )))
523 .ok();
524 receiver
525 }
526}
527
528pub type ScreenCaptureTerminationCallback = Box<dyn FnOnce(ScreenCaptureStreamTermination) + Send>;
530
531#[derive(Debug, Clone, PartialEq, Eq)]
533pub enum ScreenCaptureStreamTermination {
534 Ended,
536 Cancelled,
538 Failed(SharedString),
540}
541
542pub trait ScreenCaptureStream {
544 fn metadata(&self) -> Result<SourceMetadata>;
546}
547
548pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
550
551#[derive(PartialEq, Eq, Hash, Copy, Clone)]
553pub struct DisplayId(pub(crate) u64);
554
555impl DisplayId {
556 #[doc(hidden)]
558 pub fn new(id: u64) -> Self {
559 Self(id)
560 }
561
562 #[doc(hidden)]
564 pub fn index(self) -> usize {
565 self.0 as usize
566 }
567}
568
569impl From<u32> for DisplayId {
570 fn from(id: u32) -> Self {
571 Self(u64::from(id))
572 }
573}
574
575impl From<DisplayId> for u32 {
576 fn from(id: DisplayId) -> Self {
577 id.0 as u32
578 }
579}
580
581impl From<DisplayId> for u64 {
582 fn from(id: DisplayId) -> Self {
583 id.0
584 }
585}
586
587impl Debug for DisplayId {
588 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
589 write!(f, "DisplayId({})", self.0)
590 }
591}
592
593unsafe impl Send for DisplayId {}
594
595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub enum ResizeEdge {
598 Top,
600 TopRight,
602 Right,
604 BottomRight,
606 Bottom,
608 BottomLeft,
610 Left,
612 TopLeft,
614}
615
616#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
618pub enum WindowDecorations {
619 #[default]
620 Server,
622 Client,
624}
625
626#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
628pub enum Decorations {
629 #[default]
631 Server,
632 Client {
634 tiling: Tiling,
636 },
637}
638
639#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
641pub struct WindowControls {
642 pub fullscreen: bool,
644 pub maximize: bool,
646 pub minimize: bool,
648 pub window_menu: bool,
650}
651
652impl Default for WindowControls {
653 fn default() -> Self {
654 Self {
656 fullscreen: true,
657 maximize: true,
658 minimize: true,
659 window_menu: true,
660 }
661 }
662}
663
664#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
666pub struct Tiling {
667 pub top: bool,
669 pub left: bool,
671 pub right: bool,
673 pub bottom: bool,
675}
676
677impl Tiling {
678 pub fn tiled() -> Self {
680 Self {
681 top: true,
682 left: true,
683 right: true,
684 bottom: true,
685 }
686 }
687
688 pub fn is_tiled(&self) -> bool {
690 self.top || self.left || self.right || self.bottom
691 }
692}
693
694#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
695#[allow(missing_docs)]
696pub struct RequestFrameOptions {
697 pub require_presentation: bool,
698 pub force_render: bool,
700}
701
702pub struct A11yCallbacks {
704 pub activation: Box<dyn Fn() -> Option<accesskit::TreeUpdate> + Send + 'static>,
706 pub action: Box<dyn Fn(accesskit::ActionRequest) + Send + 'static>,
708 pub deactivation: Box<dyn Fn() + Send + 'static>,
710}
711
712#[allow(missing_docs)]
713pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
714 fn bounds(&self) -> Bounds<Pixels>;
715 fn is_maximized(&self) -> bool;
716 fn window_bounds(&self) -> WindowBounds;
717 fn content_size(&self) -> Size<Pixels>;
718 fn resize(&mut self, size: Size<Pixels>);
719 fn scale_factor(&self) -> f32;
720 fn appearance(&self) -> WindowAppearance;
721 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
722 fn mouse_position(&self) -> Point<Pixels>;
723 fn modifiers(&self) -> Modifiers;
724 fn capslock(&self) -> Capslock;
725 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
726 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
727 fn prompt(
728 &self,
729 level: PromptLevel,
730 msg: &str,
731 detail: Option<&str>,
732 answers: &[PromptButton],
733 ) -> Option<oneshot::Receiver<usize>>;
734 fn activate(&self);
735 fn request_attention(&self) {}
737 fn is_active(&self) -> bool;
738 fn is_hovered(&self) -> bool;
739 fn set_title(&mut self, title: &str);
740 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
741 fn minimize(&self);
742 fn zoom(&self);
743 fn toggle_fullscreen(&self);
744 fn is_fullscreen(&self) -> bool;
745 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
746 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
747 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
748 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
749 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
750 fn on_moved(&self, callback: Box<dyn FnMut()>);
751 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
752 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
753 fn on_close(&self, callback: Box<dyn FnOnce()>);
754 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
755 fn draw(&self, scene: &Scene);
756 #[cfg(any(test, feature = "test-support"))]
757 fn render_to_image(&self, _scene: &Scene) -> anyhow::Result<image::RgbaImage> {
758 anyhow::bail!("render_to_image not implemented for this platform")
759 }
760 fn schedule_frame(&self) {}
761 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
762
763 fn get_title(&self) -> String {
765 String::new()
766 }
767 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
768 None
769 }
770 fn tab_bar_visible(&self) -> bool {
771 false
772 }
773 fn set_edited(&mut self, _edited: bool) {}
774 fn show_character_palette(&self) {}
775 fn titlebar_double_click(&self) {}
776 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
777 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
778 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
779 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
780 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
781 fn merge_all_windows(&self) {}
782 fn move_tab_to_new_window(&self) {}
783 fn toggle_window_tab_overview(&self) {}
784 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
785
786 #[cfg(target_os = "windows")]
787 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
788
789 fn inner_window_bounds(&self) -> WindowBounds {
791 self.window_bounds()
792 }
793 fn request_decorations(&self, _decorations: WindowDecorations) {}
794 fn show_window_menu(&self, _position: Point<Pixels>) {}
795 fn start_window_move(&self) {}
796 fn start_window_resize(&self, _edge: ResizeEdge) {}
797 fn set_exclusive_zone(&self, _zone: Pixels) {}
798 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
799 fn set_exclusive_edge(&self, _edge: layer_shell::Anchor) {}
800 fn set_input_region(&self, _region: Option<&[Bounds<Pixels>]>) {}
801 fn window_decorations(&self) -> Decorations {
802 Decorations::Server
803 }
804 fn set_app_id(&mut self, _app_id: &str) {}
805 fn map_window(&mut self) -> anyhow::Result<()> {
806 Ok(())
807 }
808 fn window_controls(&self) -> WindowControls {
809 WindowControls::default()
810 }
811 fn set_client_inset(&self, _inset: Pixels) {}
812 fn gpu_specs(&self) -> Option<GpuSpecs>;
813
814 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
815
816 fn play_system_bell(&self) {}
817
818 fn a11y_init(&self, _callbacks: A11yCallbacks) {}
820
821 fn a11y_tree_update(&self, _tree_update: accesskit::TreeUpdate) {}
823
824 fn a11y_update_window_bounds(&self) {}
826
827 fn show(&self) {}
828 fn hide(&self) {}
829 fn is_visible(&self) -> bool {
830 true
831 }
832 fn set_mouse_passthrough(&self, _passthrough: bool) {}
833 fn set_progress_bar(&self, _state: ProgressBarState) {}
834 #[allow(dead_code)]
835 fn set_window_icon(&self, _icon: Option<image::RgbaImage>) {}
836
837 fn set_cursor_style(&self, _style: CursorStyle) {}
838
839 #[cfg(any(test, feature = "test-support"))]
840 fn as_test(&mut self) -> Option<&mut TestWindow> {
841 None
842 }
843}
844
845#[doc(hidden)]
848pub trait PlatformDispatcher: Send + Sync {
849 fn is_main_thread(&self) -> bool;
850 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
851 fn dispatch_on_main_thread(&self, runnable: Runnable);
852 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
853 fn park(&self, timeout: Option<Duration>) -> bool;
854 fn unparker(&self) -> Unparker;
855 fn now(&self) -> Instant {
856 Instant::now()
857 }
858
859 #[cfg(any(test, feature = "test-support"))]
860 fn as_test(&self) -> Option<&TestDispatcher> {
861 None
862 }
863
864 #[cfg(any(test, feature = "test-support"))]
865 fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
866 None
867 }
868}
869
870#[allow(missing_docs)]
871pub trait PlatformTextSystem: Send + Sync {
872 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
873 fn all_font_names(&self) -> Vec<String>;
874 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
875 fn prewarm_fonts(&self, _font_ids: &[FontId]) {}
877 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
878 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
879 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
880 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
881 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
882 fn rasterize_glyph(
883 &self,
884 params: &RenderGlyphParams,
885 raster_bounds: Bounds<DevicePixels>,
886 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
887 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
888
889 fn glyph_dilation_for_color(&self, _color: Hsla) -> u8 {
890 0
891 }
892}
893
894#[allow(missing_docs)]
895pub struct NoopTextSystem;
896
897#[allow(missing_docs)]
898impl NoopTextSystem {
899 #[allow(dead_code)]
900 pub fn new() -> Self {
901 Self
902 }
903}
904
905impl PlatformTextSystem for NoopTextSystem {
906 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
907 Ok(())
908 }
909
910 fn all_font_names(&self) -> Vec<String> {
911 Vec::new()
912 }
913
914 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
915 Ok(FontId(1))
916 }
917
918 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
919 FontMetrics {
920 units_per_em: 1000,
921 ascent: 1025.0,
922 descent: -275.0,
923 line_gap: 0.0,
924 underline_position: -95.0,
925 underline_thickness: 60.0,
926 cap_height: 698.0,
927 x_height: 516.0,
928 bounding_box: Bounds {
929 origin: Point {
930 x: -260.0,
931 y: -245.0,
932 },
933 size: Size {
934 width: 1501.0,
935 height: 1364.0,
936 },
937 },
938 }
939 }
940
941 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
942 Ok(Bounds {
943 origin: Point { x: 54.0, y: 0.0 },
944 size: size(392.0, 528.0),
945 })
946 }
947
948 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
949 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
950 }
951
952 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
953 Some(GlyphId(ch.len_utf16() as u32))
954 }
955
956 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
957 Ok(Default::default())
958 }
959
960 fn rasterize_glyph(
961 &self,
962 _params: &RenderGlyphParams,
963 raster_bounds: Bounds<DevicePixels>,
964 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
965 Ok((raster_bounds.size, Vec::new()))
966 }
967
968 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
969 let mut position = px(0.);
970 let metrics = self.font_metrics(FontId(0));
971 let em_width = font_size
972 * self
973 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
974 .unwrap()
975 .width
976 / metrics.units_per_em as f32;
977 let mut glyphs = Vec::new();
978 for (ix, c) in text.char_indices() {
979 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
980 glyphs.push(ShapedGlyph {
981 id: glyph,
982 position: point(position, px(0.)),
983 index: ix,
984 is_emoji: glyph.0 == 2,
985 });
986 if glyph.0 == 2 {
987 position += em_width * 2.0;
988 } else {
989 position += em_width;
990 }
991 } else {
992 position += em_width
993 }
994 }
995 let mut runs = Vec::default();
996 if !glyphs.is_empty() {
997 runs.push(ShapedRun {
998 font_id: FontId(0),
999 glyphs,
1000 });
1001 } else {
1002 position = px(0.);
1003 }
1004
1005 LineLayout {
1006 font_size,
1007 width: position,
1008 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
1009 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
1010 runs,
1011 len: text.len(),
1012 }
1013 }
1014}
1015
1016#[derive(PartialEq, Eq, Hash, Clone)]
1017#[allow(missing_docs)]
1018pub enum AtlasKey {
1019 Glyph(RenderGlyphParams),
1020 Svg(RenderSvgParams),
1021 Image(RenderImageParams),
1022}
1023
1024#[allow(missing_docs)]
1025impl AtlasKey {
1026 #[cfg_attr(
1027 all(
1028 any(target_os = "linux", target_os = "freebsd"),
1029 not(any(feature = "x11", feature = "wayland"))
1030 ),
1031 allow(dead_code)
1032 )]
1033 pub fn texture_kind(&self) -> AtlasTextureKind {
1034 match self {
1035 AtlasKey::Glyph(params) => {
1036 if params.is_emoji {
1037 AtlasTextureKind::Polychrome
1038 } else {
1039 AtlasTextureKind::Monochrome
1040 }
1041 }
1042 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
1043 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
1044 }
1045 }
1046}
1047
1048impl From<RenderGlyphParams> for AtlasKey {
1049 fn from(params: RenderGlyphParams) -> Self {
1050 Self::Glyph(params)
1051 }
1052}
1053
1054impl From<RenderSvgParams> for AtlasKey {
1055 fn from(params: RenderSvgParams) -> Self {
1056 Self::Svg(params)
1057 }
1058}
1059
1060impl From<RenderImageParams> for AtlasKey {
1061 fn from(params: RenderImageParams) -> Self {
1062 Self::Image(params)
1063 }
1064}
1065
1066#[allow(missing_docs)]
1067pub trait PlatformAtlas: Send + Sync {
1068 fn get_or_insert_with<'a>(
1069 &self,
1070 key: &AtlasKey,
1071 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
1072 ) -> Result<Option<AtlasTile>>;
1073 fn remove(&self, key: &AtlasKey);
1074}
1075
1076#[doc(hidden)]
1077pub struct AtlasTextureList<T> {
1078 pub textures: Vec<Option<T>>,
1079 pub free_list: Vec<usize>,
1080}
1081
1082impl<T> Default for AtlasTextureList<T> {
1083 fn default() -> Self {
1084 Self {
1085 textures: Vec::default(),
1086 free_list: Vec::default(),
1087 }
1088 }
1089}
1090
1091impl<T> ops::Index<usize> for AtlasTextureList<T> {
1092 type Output = Option<T>;
1093
1094 fn index(&self, index: usize) -> &Self::Output {
1095 &self.textures[index]
1096 }
1097}
1098
1099#[allow(missing_docs)]
1100impl<T> AtlasTextureList<T> {
1101 #[allow(unused)]
1102 pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
1103 self.free_list.clear();
1104 self.textures.drain(..)
1105 }
1106
1107 #[allow(dead_code)]
1108 pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
1109 self.textures.iter_mut().flatten()
1110 }
1111}
1112
1113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1114#[repr(C)]
1115#[allow(missing_docs)]
1116pub struct AtlasTile {
1117 pub texture_id: AtlasTextureId,
1118 pub tile_id: TileId,
1119 pub padding: u32,
1120 pub bounds: Bounds<DevicePixels>,
1121}
1122
1123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1124#[repr(C)]
1125#[allow(missing_docs)]
1126pub struct AtlasTextureId {
1127 pub index: u32,
1129 pub kind: AtlasTextureKind,
1130}
1131
1132#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1133#[repr(C)]
1134#[cfg_attr(
1135 all(
1136 any(target_os = "linux", target_os = "freebsd"),
1137 not(any(feature = "x11", feature = "wayland"))
1138 ),
1139 allow(dead_code)
1140)]
1141#[allow(missing_docs)]
1142pub enum AtlasTextureKind {
1143 Monochrome = 0,
1144 Polychrome = 1,
1145}
1146
1147#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1148#[repr(C)]
1149#[allow(missing_docs)]
1150pub struct TileId(pub u32);
1151
1152impl From<etagere::AllocId> for TileId {
1153 fn from(id: etagere::AllocId) -> Self {
1154 Self(id.serialize())
1155 }
1156}
1157
1158impl From<TileId> for etagere::AllocId {
1159 fn from(id: TileId) -> Self {
1160 Self::deserialize(id.0)
1161 }
1162}
1163
1164#[allow(missing_docs)]
1165pub struct PlatformInputHandler {
1166 cx: AsyncWindowContext,
1167 handler: Box<dyn InputHandler>,
1168}
1169
1170#[cfg_attr(
1171 all(
1172 any(target_os = "linux", target_os = "freebsd"),
1173 not(any(feature = "x11", feature = "wayland"))
1174 ),
1175 allow(dead_code)
1176)]
1177#[allow(missing_docs)]
1178impl PlatformInputHandler {
1179 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
1180 Self { cx, handler }
1181 }
1182
1183 fn mark_keyboard_input(window: &Window) {
1184 window.input_rate_tracker.borrow_mut().record_input();
1185 window.last_input_was_keyboard.set(true);
1186 }
1187
1188 pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
1189 self.cx
1190 .update(|window, cx| {
1191 self.handler
1192 .selected_text_range(ignore_disabled_input, window, cx)
1193 })
1194 .ok()
1195 .flatten()
1196 }
1197
1198 #[cfg_attr(target_os = "windows", allow(dead_code))]
1199 pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
1200 self.cx
1201 .update(|window, cx| self.handler.marked_text_range(window, cx))
1202 .ok()
1203 .flatten()
1204 }
1205
1206 #[cfg_attr(
1207 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1208 allow(dead_code)
1209 )]
1210 pub fn text_for_range(
1211 &mut self,
1212 range_utf16: Range<usize>,
1213 adjusted: &mut Option<Range<usize>>,
1214 ) -> Option<String> {
1215 self.cx
1216 .update(|window, cx| {
1217 self.handler
1218 .text_for_range(range_utf16, adjusted, window, cx)
1219 })
1220 .ok()
1221 .flatten()
1222 }
1223
1224 pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1225 self.cx
1226 .update(|window, cx| {
1227 Self::mark_keyboard_input(window);
1228 self.handler
1229 .replace_text_in_range(replacement_range, text, window, cx);
1230 })
1231 .ok();
1232 }
1233
1234 pub fn replace_and_mark_text_in_range(
1235 &mut self,
1236 range_utf16: Option<Range<usize>>,
1237 new_text: &str,
1238 new_selected_range: Option<Range<usize>>,
1239 ) {
1240 self.cx
1241 .update(|window, cx| {
1242 Self::mark_keyboard_input(window);
1243 self.handler.replace_and_mark_text_in_range(
1244 range_utf16,
1245 new_text,
1246 new_selected_range,
1247 window,
1248 cx,
1249 )
1250 })
1251 .ok();
1252 }
1253
1254 #[cfg_attr(target_os = "windows", allow(dead_code))]
1255 pub fn unmark_text(&mut self) {
1256 self.cx
1257 .update(|window, cx| {
1258 Self::mark_keyboard_input(window);
1259 self.handler.unmark_text(window, cx)
1260 })
1261 .ok();
1262 }
1263
1264 pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1265 self.cx
1266 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1267 .ok()
1268 .flatten()
1269 }
1270
1271 #[allow(dead_code)]
1272 pub fn apple_press_and_hold_enabled(&mut self) -> bool {
1273 self.handler.apple_press_and_hold_enabled()
1274 }
1275
1276 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1277 window.last_input_was_keyboard.set(true);
1280 self.handler.replace_text_in_range(None, input, window, cx);
1281 }
1282
1283 pub fn compute_ime_candidate_bounds(
1284 marked_range: Option<Range<usize>>,
1285 selection: &UTF16Selection,
1286 mut bounds_for_range: impl FnMut(Range<usize>) -> Option<Bounds<Pixels>>,
1287 ) -> Option<Bounds<Pixels>> {
1288 if let Some(marked_range) = marked_range {
1289 let mut line_start = marked_range.start;
1290 let caret = selection.range.end;
1291 if let Some(caret_bounds) = bounds_for_range(caret..caret) {
1292 for i in (marked_range.start..caret).rev() {
1293 if let Some(bounds) = bounds_for_range(i..i)
1294 && (bounds.origin.y - caret_bounds.origin.y).abs() > px(0.1)
1295 {
1296 line_start = i + 1;
1297 break;
1298 }
1299 }
1300 }
1301 bounds_for_range(line_start..line_start)
1302 } else {
1303 let offset = if selection.reversed {
1304 selection.range.start
1305 } else {
1306 selection.range.end
1307 };
1308 bounds_for_range(offset..offset)
1309 }
1310 }
1311
1312 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1313 let marked_range = self.handler.marked_text_range(window, cx);
1314 let selection = self.handler.selected_text_range(true, window, cx)?;
1315 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1316 self.handler.bounds_for_range(range, window, cx)
1317 })
1318 }
1319
1320 #[allow(dead_code)]
1321 pub fn ime_candidate_bounds(&mut self) -> Option<Bounds<Pixels>> {
1322 let marked_range = self.marked_text_range();
1323 let selection = self.selected_text_range(true)?;
1324 Self::compute_ime_candidate_bounds(marked_range, &selection, |range| {
1325 self.bounds_for_range(range)
1326 })
1327 }
1328
1329 #[allow(unused)]
1330 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1331 self.cx
1332 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1333 .ok()
1334 .flatten()
1335 }
1336
1337 #[allow(dead_code)]
1338 pub fn query_accepts_text_input(&mut self) -> bool {
1339 self.cx
1340 .update(|window, cx| self.handler.accepts_text_input(window, cx))
1341 .unwrap_or(true)
1342 }
1343}
1344
1345#[derive(Debug)]
1348pub struct UTF16Selection {
1349 pub range: Range<usize>,
1352 pub reversed: bool,
1355}
1356
1357pub trait InputHandler: 'static {
1362 fn selected_text_range(
1367 &mut self,
1368 ignore_disabled_input: bool,
1369 window: &mut Window,
1370 cx: &mut App,
1371 ) -> Option<UTF16Selection>;
1372
1373 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1378
1379 fn text_for_range(
1384 &mut self,
1385 range_utf16: Range<usize>,
1386 adjusted_range: &mut Option<Range<usize>>,
1387 window: &mut Window,
1388 cx: &mut App,
1389 ) -> Option<String>;
1390
1391 fn replace_text_in_range(
1396 &mut self,
1397 replacement_range: Option<Range<usize>>,
1398 text: &str,
1399 window: &mut Window,
1400 cx: &mut App,
1401 );
1402
1403 fn replace_and_mark_text_in_range(
1410 &mut self,
1411 range_utf16: Option<Range<usize>>,
1412 new_text: &str,
1413 new_selected_range: Option<Range<usize>>,
1414 window: &mut Window,
1415 cx: &mut App,
1416 );
1417
1418 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1421
1422 fn bounds_for_range(
1427 &mut self,
1428 range_utf16: Range<usize>,
1429 window: &mut Window,
1430 cx: &mut App,
1431 ) -> Option<Bounds<Pixels>>;
1432
1433 fn character_index_for_point(
1437 &mut self,
1438 point: Point<Pixels>,
1439 window: &mut Window,
1440 cx: &mut App,
1441 ) -> Option<usize>;
1442
1443 #[allow(dead_code)]
1448 fn apple_press_and_hold_enabled(&mut self) -> bool {
1449 true
1450 }
1451
1452 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1454 true
1455 }
1456}
1457
1458#[derive(Debug)]
1460pub struct WindowOptions {
1461 pub window_bounds: Option<WindowBounds>,
1465
1466 pub titlebar: Option<TitlebarOptions>,
1468
1469 pub focus: bool,
1471
1472 pub show: bool,
1474
1475 pub kind: WindowKind,
1477
1478 pub is_movable: bool,
1480
1481 pub app_owns_titlebar_drag: bool,
1486
1487 pub inactive_frame_interval: Option<Duration>,
1493
1494 pub is_resizable: bool,
1496
1497 pub is_minimizable: bool,
1499
1500 pub display_id: Option<DisplayId>,
1503
1504 pub window_background: WindowBackgroundAppearance,
1506
1507 pub app_id: Option<String>,
1509
1510 pub window_min_size: Option<Size<Pixels>>,
1512
1513 pub window_decorations: Option<WindowDecorations>,
1516
1517 pub tabbing_identifier: Option<String>,
1519
1520 pub mouse_passthrough: bool,
1522
1523 pub icon: Option<image::RgbaImage>,
1525}
1526
1527#[derive(Debug)]
1529#[cfg_attr(
1530 all(
1531 any(target_os = "linux", target_os = "freebsd"),
1532 not(any(feature = "x11", feature = "wayland"))
1533 ),
1534 allow(dead_code)
1535)]
1536#[allow(missing_docs)]
1537pub struct WindowParams {
1538 pub bounds: Bounds<Pixels>,
1539
1540 #[cfg_attr(feature = "wayland", allow(dead_code))]
1542 pub titlebar: Option<TitlebarOptions>,
1543
1544 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1546 pub kind: WindowKind,
1547
1548 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1550 pub is_movable: bool,
1551
1552 #[cfg_attr(
1554 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1555 allow(dead_code)
1556 )]
1557 pub app_owns_titlebar_drag: bool,
1558
1559 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1561 pub is_resizable: bool,
1562
1563 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1565 pub is_minimizable: bool,
1566
1567 #[cfg_attr(
1568 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1569 allow(dead_code)
1570 )]
1571 pub focus: bool,
1572
1573 #[cfg_attr(
1574 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1575 allow(dead_code)
1576 )]
1577 pub app_id: Option<String>,
1578
1579 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1580 pub show: bool,
1581
1582 #[cfg_attr(feature = "wayland", allow(dead_code))]
1583 pub display_id: Option<DisplayId>,
1584
1585 pub window_min_size: Option<Size<Pixels>>,
1586 #[cfg(target_os = "macos")]
1587 pub tabbing_identifier: Option<String>,
1588
1589 #[allow(dead_code)]
1590 pub mouse_passthrough: bool,
1591
1592 #[cfg_attr(not(any(target_os = "linux", target_os = "freebsd")), allow(dead_code))]
1594 pub icon: Option<image::RgbaImage>,
1595}
1596
1597#[derive(Debug, Copy, Clone, PartialEq)]
1599pub enum WindowBounds {
1600 Windowed(Bounds<Pixels>),
1602 Maximized(Bounds<Pixels>),
1605 Fullscreen(Bounds<Pixels>),
1608}
1609
1610impl Default for WindowBounds {
1611 fn default() -> Self {
1612 WindowBounds::Windowed(Bounds::default())
1613 }
1614}
1615
1616impl WindowBounds {
1617 pub fn get_bounds(&self) -> Bounds<Pixels> {
1619 match self {
1620 WindowBounds::Windowed(bounds) => *bounds,
1621 WindowBounds::Maximized(bounds) => *bounds,
1622 WindowBounds::Fullscreen(bounds) => *bounds,
1623 }
1624 }
1625
1626 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1628 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1629 }
1630}
1631
1632impl Default for WindowOptions {
1633 fn default() -> Self {
1634 Self {
1635 window_bounds: None,
1636 titlebar: Some(TitlebarOptions {
1637 title: Default::default(),
1638 appears_transparent: Default::default(),
1639 traffic_light_position: Default::default(),
1640 }),
1641 focus: true,
1642 show: true,
1643 kind: WindowKind::Normal,
1644 is_movable: true,
1645 app_owns_titlebar_drag: false,
1646 inactive_frame_interval: Some(Duration::from_micros(33_333)),
1647 is_resizable: true,
1648 is_minimizable: true,
1649 display_id: None,
1650 window_background: WindowBackgroundAppearance::default(),
1651 app_id: None,
1652 window_min_size: None,
1653 window_decorations: None,
1654 tabbing_identifier: None,
1655 mouse_passthrough: false,
1656 icon: None,
1657 }
1658 }
1659}
1660
1661#[cfg(test)]
1662mod platform_input_handler_tests {
1663 use super::*;
1664 use crate::{
1665 AppContext, Context, ElementInputHandler, Empty, EntityInputHandler, TestAppContext,
1666 };
1667
1668 struct RejectingEntityInputHandler;
1669
1670 impl EntityInputHandler for RejectingEntityInputHandler {
1671 fn text_for_range(
1672 &mut self,
1673 _range: Range<usize>,
1674 _adjusted_range: &mut Option<Range<usize>>,
1675 _window: &mut Window,
1676 _cx: &mut Context<Self>,
1677 ) -> Option<String> {
1678 None
1679 }
1680
1681 fn selected_text_range(
1682 &mut self,
1683 _ignore_disabled_input: bool,
1684 _window: &mut Window,
1685 _cx: &mut Context<Self>,
1686 ) -> Option<UTF16Selection> {
1687 None
1688 }
1689
1690 fn marked_text_range(
1691 &self,
1692 _window: &mut Window,
1693 _cx: &mut Context<Self>,
1694 ) -> Option<Range<usize>> {
1695 None
1696 }
1697
1698 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
1699
1700 fn replace_text_in_range(
1701 &mut self,
1702 _range: Option<Range<usize>>,
1703 _text: &str,
1704 _window: &mut Window,
1705 _cx: &mut Context<Self>,
1706 ) {
1707 }
1708
1709 fn replace_and_mark_text_in_range(
1710 &mut self,
1711 _range: Option<Range<usize>>,
1712 _new_text: &str,
1713 _new_selected_range: Option<Range<usize>>,
1714 _window: &mut Window,
1715 _cx: &mut Context<Self>,
1716 ) {
1717 }
1718
1719 fn bounds_for_range(
1720 &mut self,
1721 _range_utf16: Range<usize>,
1722 _element_bounds: Bounds<Pixels>,
1723 _window: &mut Window,
1724 _cx: &mut Context<Self>,
1725 ) -> Option<Bounds<Pixels>> {
1726 None
1727 }
1728
1729 fn character_index_for_point(
1730 &mut self,
1731 _point: Point<Pixels>,
1732 _window: &mut Window,
1733 _cx: &mut Context<Self>,
1734 ) -> Option<usize> {
1735 None
1736 }
1737
1738 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
1739 false
1740 }
1741 }
1742
1743 #[gpui_macros::test]
1744 fn element_input_handler_forwards_text_input_rejection(cx: &mut TestAppContext) {
1745 let entity = cx.new(|_| RejectingEntityInputHandler);
1746 let window = cx.add_window(|_, _| Empty);
1747
1748 cx.update_window(window.into(), |_, window, cx| {
1749 let mut handler = ElementInputHandler::new(Bounds::default(), entity);
1750 assert!(!handler.accepts_text_input(window, cx));
1751 })
1752 .unwrap();
1753 }
1754
1755 #[gpui_macros::test]
1756 fn platform_input_handler_accepts_text_when_window_update_fails(cx: &mut TestAppContext) {
1757 let entity = cx.new(|_| RejectingEntityInputHandler);
1758 let window = cx.add_window(|_, _| Empty);
1759 let window = AnyWindowHandle::from(window);
1760 let async_cx = cx
1761 .update_window(window, |_, window, cx| window.to_async(cx))
1762 .unwrap();
1763
1764 cx.update_window(window, |_, window, _| window.remove_window())
1765 .unwrap();
1766 assert!(cx.update_window(window, |_, _, _| ()).is_err());
1767
1768 let mut handler = PlatformInputHandler::new(
1769 async_cx,
1770 Box::new(ElementInputHandler::new(Bounds::default(), entity)),
1771 );
1772 assert!(handler.query_accepts_text_input());
1773 }
1774
1775 fn bounds_at(offset: usize, y: f32) -> Bounds<Pixels> {
1776 Bounds::new(
1777 point(px(offset as f32 * 10.0), px(y)),
1778 size(px(1.0), px(20.0)),
1779 )
1780 }
1781
1782 #[test]
1783 fn ime_candidate_bounds_uses_selection_endpoint_without_marked_range() {
1784 let selection = UTF16Selection {
1785 range: 3..5,
1786 reversed: false,
1787 };
1788 let mut requests = Vec::new();
1789 let bounds =
1790 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1791 requests.push(range.clone());
1792 Some(bounds_at(range.start, 0.0))
1793 })
1794 .unwrap();
1795
1796 assert_eq!(bounds.origin.x, px(50.0));
1797 assert_eq!(requests, vec![5..5]);
1798
1799 let selection = UTF16Selection {
1800 range: 3..5,
1801 reversed: true,
1802 };
1803 let bounds =
1804 PlatformInputHandler::compute_ime_candidate_bounds(None, &selection, |range| {
1805 Some(bounds_at(range.start, 0.0))
1806 })
1807 .unwrap();
1808
1809 assert_eq!(bounds.origin.x, px(30.0));
1810 }
1811
1812 #[test]
1813 fn ime_candidate_bounds_anchors_marked_text_to_visual_line_start() {
1814 let selection = UTF16Selection {
1815 range: 15..15,
1816 reversed: false,
1817 };
1818 let mut requests = Vec::new();
1819 let bounds =
1820 PlatformInputHandler::compute_ime_candidate_bounds(Some(10..15), &selection, |range| {
1821 requests.push(range.clone());
1822 let y = if range.start >= 13 { 20.0 } else { 0.0 };
1823 Some(bounds_at(range.start, y))
1824 })
1825 .unwrap();
1826
1827 assert_eq!(bounds.origin.x, px(130.0));
1828 assert_eq!(requests.last(), Some(&(13..13)));
1829 }
1830}
1831
1832#[derive(Debug, Default)]
1834pub struct TitlebarOptions {
1835 pub title: Option<SharedString>,
1837
1838 pub appears_transparent: bool,
1841
1842 pub traffic_light_position: Option<Point<Pixels>>,
1844}
1845
1846#[derive(Clone, Debug, PartialEq)]
1848pub enum WindowKind {
1849 Normal,
1851
1852 PopUp,
1855
1856 Floating,
1858
1859 Overlay,
1861
1862 #[cfg(all(any(target_os = "linux", target_os = "freebsd"), feature = "wayland"))]
1865 LayerShell(layer_shell::LayerShellOptions),
1866}
1867
1868#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1873pub enum WindowAppearance {
1874 #[default]
1878 Light,
1879
1880 VibrantLight,
1884
1885 Dark,
1889
1890 VibrantDark,
1894}
1895
1896#[derive(Copy, Clone, Debug, Default, PartialEq)]
1899pub enum WindowBackgroundAppearance {
1900 #[default]
1908 Opaque,
1909 Transparent,
1911 Blurred,
1915}
1916
1917#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1919#[non_exhaustive]
1920pub enum TrayIconRenderingMode {
1921 #[default]
1925 Adaptive,
1926 Original,
1930}
1931
1932#[derive(Debug, Clone, PartialEq, Eq)]
1934pub enum TrayIconEvent {
1935 LeftClick,
1937 RightClick,
1939 DoubleClick,
1941}
1942
1943#[derive(Debug, Clone, PartialEq)]
1945pub struct TrayIconClickEvent {
1946 pub kind: TrayIconEvent,
1948 pub position: Option<Point<Pixels>>,
1955}
1956
1957impl TrayIconClickEvent {
1958 pub fn new(kind: TrayIconEvent) -> Self {
1960 Self {
1961 kind,
1962 position: None,
1963 }
1964 }
1965
1966 pub fn with_position(kind: TrayIconEvent, position: Point<Pixels>) -> Self {
1968 Self {
1969 kind,
1970 position: Some(position),
1971 }
1972 }
1973}
1974
1975#[allow(missing_docs)]
1976#[derive(Debug, Clone, PartialEq)]
1977pub struct TrayAnchor {
1978 pub display_id: DisplayId,
1979 pub bounds: Bounds<Pixels>,
1980}
1981
1982#[derive(Debug, Clone)]
1984pub enum TrayMenuItem {
1985 Action {
1987 label: SharedString,
1989 id: SharedString,
1991 },
1992 Separator,
1994 Submenu {
1996 label: SharedString,
1998 items: Vec<TrayMenuItem>,
2000 },
2001 Toggle {
2003 label: SharedString,
2005 checked: bool,
2007 id: SharedString,
2009 },
2010}
2011
2012#[derive(Debug, Clone)]
2014pub struct FocusedWindowInfo {
2015 pub app_name: String,
2017 pub window_title: String,
2019 pub bundle_id: Option<String>,
2021 pub pid: Option<u32>,
2023}
2024
2025#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2027pub enum PermissionStatus {
2028 Granted,
2030 Denied,
2032 NotDetermined,
2034 Unavailable,
2036}
2037
2038#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2040pub enum PermissionRequestStatus {
2041 Requested,
2043 Unavailable,
2045}
2046
2047#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2049pub enum SystemPowerEvent {
2050 Suspend,
2052 Resume,
2054 LockScreen,
2056 UnlockScreen,
2058 Shutdown,
2060}
2061
2062#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2064pub enum PowerSaveBlockerKind {
2065 PreventAppSuspension,
2067 PreventDisplaySleep,
2069}
2070
2071#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2073pub enum NetworkStatus {
2074 Online,
2076 Offline,
2078}
2079
2080#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2082pub enum MediaKeyEvent {
2083 Play,
2085 Pause,
2087 PlayPause,
2089 Stop,
2091 NextTrack,
2093 PreviousTrack,
2095}
2096
2097#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2099pub enum AttentionType {
2100 Informational,
2102 Critical,
2104}
2105
2106#[derive(Debug, Clone, Copy, PartialEq)]
2108pub enum ProgressBarState {
2109 None,
2111 Indeterminate,
2113 Normal(f64),
2115 Error(f64),
2117 Paused(f64),
2119}
2120
2121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2123pub enum DialogKind {
2124 Info,
2126 Warning,
2128 Error,
2130}
2131
2132#[derive(Debug, Clone)]
2134pub struct DialogOptions {
2135 pub kind: DialogKind,
2137 pub title: SharedString,
2139 pub message: SharedString,
2141 pub detail: Option<SharedString>,
2143 pub buttons: Vec<SharedString>,
2145}
2146
2147#[derive(Debug, Clone)]
2149pub struct OsInfo {
2150 pub name: SharedString,
2152 pub version: SharedString,
2154 pub arch: SharedString,
2156 pub locale: SharedString,
2158 pub hostname: SharedString,
2160}
2161
2162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2164pub enum BiometricKind {
2165 TouchId,
2167 WindowsHello,
2169 Fingerprint,
2171}
2172
2173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2175pub enum BiometricStatus {
2176 Available(BiometricKind),
2178 Unavailable,
2180}
2181
2182#[derive(Debug, Clone, PartialEq)]
2184pub struct WindowState {
2185 pub bounds: WindowBounds,
2187 pub display_id: Option<DisplayId>,
2189 pub fullscreen: bool,
2191}
2192
2193#[derive(Debug, Clone, PartialEq)]
2195pub enum WindowPosition {
2196 Center,
2198 CenterOnDisplay(DisplayId),
2200 #[deprecated(
2207 since = "0.5.3",
2208 note = "Use `WindowPosition::TrayAnchored(TrayAnchor)` instead; the new variant carries `display_id` and fixes multi-monitor placement. This variant will be removed in a future release."
2209 )]
2210 TrayCenter(Bounds<Pixels>),
2211 TrayAnchored(TrayAnchor),
2218 TopRight {
2220 margin: Pixels,
2222 },
2223 BottomRight {
2225 margin: Pixels,
2227 },
2228 TopLeft {
2230 margin: Pixels,
2232 },
2233 BottomLeft {
2235 margin: Pixels,
2237 },
2238}
2239
2240#[derive(Debug, Clone)]
2242pub struct CrashReport {
2243 pub message: String,
2245 pub backtrace: String,
2247 pub os_info: OsInfo,
2249 pub app_version: Option<String>,
2251}
2252
2253#[derive(Clone, Debug)]
2255pub struct PathPromptOptions {
2256 pub files: bool,
2258 pub directories: bool,
2260 pub multiple: bool,
2262 pub prompt: Option<SharedString>,
2264}
2265
2266#[derive(Copy, Clone, Debug, PartialEq)]
2268pub enum PromptLevel {
2269 Info,
2271
2272 Warning,
2274
2275 Critical,
2277}
2278
2279#[derive(Clone, Debug, PartialEq)]
2281pub enum PromptButton {
2282 Ok(SharedString),
2284 Cancel(SharedString),
2286 Other(SharedString),
2288}
2289
2290impl PromptButton {
2291 pub fn new(label: impl Into<SharedString>) -> Self {
2293 PromptButton::Other(label.into())
2294 }
2295
2296 pub fn ok(label: impl Into<SharedString>) -> Self {
2298 PromptButton::Ok(label.into())
2299 }
2300
2301 pub fn cancel(label: impl Into<SharedString>) -> Self {
2303 PromptButton::Cancel(label.into())
2304 }
2305
2306 #[allow(dead_code)]
2307 #[doc(hidden)]
2308 pub fn is_cancel(&self) -> bool {
2309 matches!(self, PromptButton::Cancel(_))
2310 }
2311
2312 pub fn label(&self) -> &SharedString {
2314 match self {
2315 PromptButton::Ok(label) => label,
2316 PromptButton::Cancel(label) => label,
2317 PromptButton::Other(label) => label,
2318 }
2319 }
2320}
2321
2322impl From<&str> for PromptButton {
2323 fn from(value: &str) -> Self {
2324 match value.to_lowercase().as_str() {
2325 "ok" => PromptButton::Ok("Ok".into()),
2326 "cancel" => PromptButton::Cancel("Cancel".into()),
2327 _ => PromptButton::Other(SharedString::from(value.to_owned())),
2328 }
2329 }
2330}
2331
2332#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
2334pub enum CursorStyle {
2335 #[default]
2337 Arrow,
2338
2339 IBeam,
2342
2343 Crosshair,
2346
2347 ClosedHand,
2350
2351 OpenHand,
2354
2355 PointingHand,
2358
2359 ResizeLeft,
2362
2363 ResizeRight,
2366
2367 ResizeLeftRight,
2370
2371 ResizeUp,
2374
2375 ResizeDown,
2378
2379 ResizeUpDown,
2382
2383 ResizeUpLeftDownRight,
2386
2387 ResizeUpRightDownLeft,
2390
2391 ResizeColumn,
2394
2395 ResizeRow,
2398
2399 IBeamCursorForVerticalLayout,
2402
2403 OperationNotAllowed,
2406
2407 DragLink,
2410
2411 DragCopy,
2414
2415 ContextualMenu,
2418
2419 None,
2421}
2422
2423#[derive(Clone, Debug, Eq, PartialEq)]
2425pub struct ClipboardItem {
2426 entries: Vec<ClipboardEntry>,
2427}
2428
2429#[derive(Clone, Debug, Eq, PartialEq)]
2431pub enum ClipboardEntry {
2432 String(ClipboardString),
2434 Image(Image),
2436}
2437
2438impl ClipboardItem {
2439 pub fn new_string(text: String) -> Self {
2441 Self {
2442 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
2443 }
2444 }
2445
2446 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
2448 Self {
2449 entries: vec![ClipboardEntry::String(ClipboardString {
2450 text,
2451 metadata: Some(metadata),
2452 })],
2453 }
2454 }
2455
2456 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
2458 Self {
2459 entries: vec![ClipboardEntry::String(
2460 ClipboardString::new(text).with_json_metadata(metadata),
2461 )],
2462 }
2463 }
2464
2465 pub fn new_image(image: &Image) -> Self {
2467 Self {
2468 entries: vec![ClipboardEntry::Image(image.clone())],
2469 }
2470 }
2471
2472 pub fn text(&self) -> Option<String> {
2475 let mut answer = String::new();
2476 let mut any_entries = false;
2477
2478 for entry in self.entries.iter() {
2479 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
2480 answer.push_str(text);
2481 any_entries = true;
2482 }
2483 }
2484
2485 if any_entries { Some(answer) } else { None }
2486 }
2487
2488 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
2490 pub fn metadata(&self) -> Option<&String> {
2491 match self.entries().first() {
2492 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
2493 clipboard_string.metadata.as_ref()
2494 }
2495 _ => None,
2496 }
2497 }
2498
2499 pub fn entries(&self) -> &[ClipboardEntry] {
2501 &self.entries
2502 }
2503
2504 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
2506 self.entries.into_iter()
2507 }
2508}
2509
2510impl From<ClipboardString> for ClipboardEntry {
2511 fn from(value: ClipboardString) -> Self {
2512 Self::String(value)
2513 }
2514}
2515
2516impl From<String> for ClipboardEntry {
2517 fn from(value: String) -> Self {
2518 Self::from(ClipboardString::from(value))
2519 }
2520}
2521
2522impl From<Image> for ClipboardEntry {
2523 fn from(value: Image) -> Self {
2524 Self::Image(value)
2525 }
2526}
2527
2528impl From<ClipboardEntry> for ClipboardItem {
2529 fn from(value: ClipboardEntry) -> Self {
2530 Self {
2531 entries: vec![value],
2532 }
2533 }
2534}
2535
2536impl From<String> for ClipboardItem {
2537 fn from(value: String) -> Self {
2538 Self::from(ClipboardEntry::from(value))
2539 }
2540}
2541
2542impl From<Image> for ClipboardItem {
2543 fn from(value: Image) -> Self {
2544 Self::from(ClipboardEntry::from(value))
2545 }
2546}
2547
2548#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
2550pub enum ImageFormat {
2551 Png,
2556 Jpeg,
2558 Webp,
2560 Gif,
2562 Svg,
2564 Bmp,
2566 Tiff,
2568}
2569
2570impl ImageFormat {
2571 pub const fn mime_type(self) -> &'static str {
2573 match self {
2574 ImageFormat::Png => "image/png",
2575 ImageFormat::Jpeg => "image/jpeg",
2576 ImageFormat::Webp => "image/webp",
2577 ImageFormat::Gif => "image/gif",
2578 ImageFormat::Svg => "image/svg+xml",
2579 ImageFormat::Bmp => "image/bmp",
2580 ImageFormat::Tiff => "image/tiff",
2581 }
2582 }
2583
2584 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2586 match mime_type {
2587 "image/png" => Some(Self::Png),
2588 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2589 "image/webp" => Some(Self::Webp),
2590 "image/gif" => Some(Self::Gif),
2591 "image/svg+xml" => Some(Self::Svg),
2592 "image/bmp" => Some(Self::Bmp),
2593 "image/tiff" | "image/tif" => Some(Self::Tiff),
2594 _ => None,
2595 }
2596 }
2597}
2598
2599#[derive(Clone, Debug, PartialEq, Eq)]
2601pub struct Image {
2602 pub format: ImageFormat,
2604 pub bytes: Vec<u8>,
2606 id: u64,
2608}
2609
2610impl Image {
2611 pub fn new(format: ImageFormat, bytes: Vec<u8>) -> Self {
2613 let id = crate::hash(&bytes);
2614 Self { format, bytes, id }
2615 }
2616}
2617
2618impl Hash for Image {
2619 fn hash<H: Hasher>(&self, state: &mut H) {
2620 state.write_u64(self.id);
2621 }
2622}
2623
2624pub(crate) fn decode_static_image(
2625 bytes: &[u8],
2626 format: image::ImageFormat,
2627) -> Result<SmallVec<[Frame; 1]>> {
2628 let decoder = image::ImageReader::with_format(Cursor::new(bytes), format)
2629 .into_decoder()
2630 .context("creating image decoder")?;
2631 decode_static_image_from_decoder(decoder)
2632}
2633
2634pub(crate) fn decode_static_image_from_decoder(
2635 mut decoder: impl image::ImageDecoder,
2636) -> Result<SmallVec<[Frame; 1]>> {
2637 let orientation = decoder.orientation().context("reading image orientation")?;
2638 let mut image = DynamicImage::from_decoder(decoder).context("decoding static image")?;
2639 image.apply_orientation(orientation);
2640
2641 let mut data = image.into_rgba8();
2642 for pixel in data.chunks_exact_mut(4) {
2643 pixel.swap(0, 2);
2644 }
2645
2646 Ok(SmallVec::from_elem(Frame::new(data), 1))
2647}
2648
2649impl Image {
2650 pub fn empty() -> Self {
2652 Self::from_bytes(ImageFormat::Png, Vec::new())
2653 }
2654
2655 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2657 Self {
2658 id: hash(&bytes),
2659 format,
2660 bytes,
2661 }
2662 }
2663
2664 pub fn id(&self) -> u64 {
2666 self.id
2667 }
2668
2669 pub fn use_render_image(
2671 self: Arc<Self>,
2672 window: &mut Window,
2673 cx: &mut App,
2674 ) -> Option<Arc<RenderImage>> {
2675 ImageSource::Image(self)
2676 .use_data(None, window, cx)
2677 .and_then(|result| result.ok())
2678 }
2679
2680 pub fn get_render_image(
2682 self: Arc<Self>,
2683 window: &mut Window,
2684 cx: &mut App,
2685 ) -> Option<Arc<RenderImage>> {
2686 ImageSource::Image(self)
2687 .get_data(None, window, cx)
2688 .and_then(|result| result.ok())
2689 }
2690
2691 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2693 ImageSource::Image(self).remove_asset(cx);
2694 }
2695
2696 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2698 let frames = match self.format {
2699 #[cfg(feature = "image-format-gif")]
2700 ImageFormat::Gif => {
2701 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2702 let mut frames = SmallVec::new();
2703
2704 for frame in decoder.into_frames() {
2705 let mut frame = frame?;
2706 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2708 pixel.swap(0, 2);
2709 }
2710 frames.push(frame);
2711 }
2712
2713 frames
2714 }
2715 #[cfg(not(feature = "image-format-gif"))]
2716 ImageFormat::Gif => decode_static_image(&self.bytes, image::ImageFormat::Gif)?,
2717 ImageFormat::Png => decode_static_image(&self.bytes, image::ImageFormat::Png)?,
2718 ImageFormat::Jpeg => decode_static_image(&self.bytes, image::ImageFormat::Jpeg)?,
2719 ImageFormat::Webp => decode_static_image(&self.bytes, image::ImageFormat::WebP)?,
2720 ImageFormat::Bmp => decode_static_image(&self.bytes, image::ImageFormat::Bmp)?,
2721 ImageFormat::Tiff => decode_static_image(&self.bytes, image::ImageFormat::Tiff)?,
2722 ImageFormat::Svg => {
2723 return svg_renderer
2724 .render_single_frame(&self.bytes, 1.0)
2725 .map_err(Into::into);
2726 }
2727 };
2728
2729 Ok(Arc::new(RenderImage::new(frames)))
2730 }
2731
2732 pub fn format(&self) -> ImageFormat {
2734 self.format
2735 }
2736
2737 pub fn bytes(&self) -> &[u8] {
2739 self.bytes.as_slice()
2740 }
2741}
2742
2743#[cfg(test)]
2744mod image_tests {
2745 use super::*;
2746
2747 #[test]
2748 fn image_to_image_data_applies_exif_orientation() {
2749 let image = Image::from_bytes(
2750 ImageFormat::Jpeg,
2751 include_bytes!("../test_data/exif-orientation-rotate-180.jpg").to_vec(),
2752 );
2753
2754 let render_image = image.to_image_data(SvgRenderer::new(Arc::new(()))).unwrap();
2755
2756 assert_eq!(render_image.size(0), size(16.into(), 32.into()));
2757 let bytes = render_image.as_bytes(0).unwrap();
2758 assert_eq!(&bytes[..4], &[255, 255, 255, 255]);
2759 assert_eq!(&bytes[(16 * 32 - 1) * 4..], &[0, 0, 0, 255]);
2760 }
2761}
2762
2763#[derive(Clone, Debug, Eq, PartialEq)]
2765pub struct ClipboardString {
2766 pub(crate) text: String,
2767 pub(crate) metadata: Option<String>,
2768}
2769
2770impl ClipboardString {
2771 pub fn new(text: String) -> Self {
2773 Self {
2774 text,
2775 metadata: None,
2776 }
2777 }
2778
2779 #[doc(hidden)]
2781 pub fn with_metadata(mut self, metadata: String) -> Self {
2782 self.metadata = Some(metadata);
2783 self
2784 }
2785
2786 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2789 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2790 self
2791 }
2792
2793 pub fn text(&self) -> &String {
2795 &self.text
2796 }
2797
2798 pub fn into_text(self) -> String {
2800 self.text
2801 }
2802
2803 pub fn metadata_json<T>(&self) -> Option<T>
2805 where
2806 T: for<'a> Deserialize<'a>,
2807 {
2808 self.metadata
2809 .as_ref()
2810 .and_then(|m| serde_json::from_str(m).ok())
2811 }
2812
2813 #[doc(hidden)]
2815 pub fn metadata(&self) -> Option<&str> {
2816 self.metadata.as_deref()
2817 }
2818
2819 #[doc(hidden)]
2820 pub fn text_hash(text: &str) -> u64 {
2821 let mut hasher = SeaHasher::new();
2822 text.hash(&mut hasher);
2823 hasher.finish()
2824 }
2825}
2826
2827impl From<String> for ClipboardString {
2828 fn from(value: String) -> Self {
2829 Self {
2830 text: value,
2831 metadata: None,
2832 }
2833 }
2834}
2835
2836#[cfg(test)]
2837mod clipboard_string_tests {
2838 use super::ClipboardString;
2839
2840 #[test]
2841 fn raw_metadata_preserves_the_clipboard_text() {
2842 let clipboard_string = ClipboardString::new("copied text".to_owned())
2843 .with_metadata("application/x-adabraka".to_owned());
2844
2845 assert_eq!(clipboard_string.text(), "copied text");
2846 assert_eq!(clipboard_string.metadata(), Some("application/x-adabraka"));
2847 }
2848}