1mod app_menu;
2mod keyboard;
3mod keystroke;
4pub mod single_instance;
6pub mod window_positioner;
8
9#[cfg(any(target_os = "linux", target_os = "freebsd"))]
10mod linux;
11
12#[cfg(target_os = "macos")]
13mod mac;
14
15#[cfg(any(
16 all(
17 any(target_os = "linux", target_os = "freebsd"),
18 any(feature = "x11", feature = "wayland")
19 ),
20 all(target_os = "macos", feature = "macos-blade")
21))]
22mod blade;
23
24#[cfg(any(test, feature = "test-support"))]
25mod test;
26
27#[cfg(target_os = "windows")]
28mod windows;
29
30#[cfg(all(
31 feature = "screen-capture",
32 any(
33 target_os = "windows",
34 all(
35 any(target_os = "linux", target_os = "freebsd"),
36 any(feature = "wayland", feature = "x11"),
37 )
38 )
39))]
40pub(crate) mod scap_screen_capture;
41
42use crate::{
43 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
44 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
45 ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
46 Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
47 ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task, TaskLabel, Window,
48 WindowControlArea, hash, point, px, size,
49};
50use anyhow::Result;
51use async_task::Runnable;
52use futures::channel::oneshot;
53use image::codecs::gif::GifDecoder;
54use image::{AnimationDecoder as _, Frame};
55use parking::Unparker;
56use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
57use schemars::JsonSchema;
58use seahash::SeaHasher;
59use serde::{Deserialize, Serialize};
60use smallvec::SmallVec;
61use std::borrow::Cow;
62use std::hash::{Hash, Hasher};
63use std::io::Cursor;
64use std::ops;
65use std::time::{Duration, Instant};
66use std::{
67 fmt::{self, Debug},
68 ops::Range,
69 path::{Path, PathBuf},
70 rc::Rc,
71 sync::Arc,
72};
73use strum::EnumIter;
74use uuid::Uuid;
75
76pub use app_menu::*;
77pub use keyboard::*;
78pub use keystroke::*;
79
80#[cfg(any(target_os = "linux", target_os = "freebsd"))]
81pub(crate) use linux::*;
82#[cfg(target_os = "macos")]
83pub(crate) use mac::*;
84pub use semantic_version::SemanticVersion;
85#[cfg(any(test, feature = "test-support"))]
86pub(crate) use test::*;
87#[cfg(target_os = "windows")]
88pub(crate) use windows::*;
89
90#[cfg(any(test, feature = "test-support"))]
91pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
92
93pub fn background_executor() -> BackgroundExecutor {
95 current_platform(true).background_executor()
96}
97
98#[cfg(target_os = "macos")]
99pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
100 Rc::new(MacPlatform::new(headless))
101}
102
103#[cfg(any(target_os = "linux", target_os = "freebsd"))]
104pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
105 #[cfg(feature = "x11")]
106 use anyhow::Context as _;
107
108 if headless {
109 return Rc::new(HeadlessClient::new());
110 }
111
112 match guess_compositor() {
113 #[cfg(feature = "wayland")]
114 "Wayland" => Rc::new(WaylandClient::new()),
115
116 #[cfg(feature = "x11")]
117 "X11" => Rc::new(
118 X11Client::new()
119 .context("Failed to initialize X11 client.")
120 .unwrap(),
121 ),
122
123 "Headless" => Rc::new(HeadlessClient::new()),
124 _ => unreachable!(),
125 }
126}
127
128#[cfg(any(target_os = "linux", target_os = "freebsd"))]
131#[inline]
132pub fn guess_compositor() -> &'static str {
133 if std::env::var_os("ZED_HEADLESS").is_some() {
134 return "Headless";
135 }
136
137 #[cfg(feature = "wayland")]
138 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
139 #[cfg(not(feature = "wayland"))]
140 let wayland_display: Option<std::ffi::OsString> = None;
141
142 #[cfg(feature = "x11")]
143 let x11_display = std::env::var_os("DISPLAY");
144 #[cfg(not(feature = "x11"))]
145 let x11_display: Option<std::ffi::OsString> = None;
146
147 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
148 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
149
150 if use_wayland {
151 "Wayland"
152 } else if use_x11 {
153 "X11"
154 } else {
155 "Headless"
156 }
157}
158
159#[cfg(target_os = "windows")]
160pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
161 Rc::new(
162 WindowsPlatform::new()
163 .inspect_err(|err| show_error("Failed to launch", err.to_string()))
164 .unwrap(),
165 )
166}
167
168pub(crate) trait Platform: 'static {
169 fn background_executor(&self) -> BackgroundExecutor;
170 fn foreground_executor(&self) -> ForegroundExecutor;
171 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
172
173 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
174 fn quit(&self);
175 fn restart(&self, binary_path: Option<PathBuf>);
176 fn activate(&self, ignoring_other_apps: bool);
177 fn hide(&self);
178 fn hide_other_apps(&self);
179 fn unhide_other_apps(&self);
180
181 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
182 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
183 fn active_window(&self) -> Option<AnyWindowHandle>;
184 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
185 None
186 }
187
188 #[cfg(feature = "screen-capture")]
189 fn is_screen_capture_supported(&self) -> bool;
190 #[cfg(not(feature = "screen-capture"))]
191 fn is_screen_capture_supported(&self) -> bool {
192 false
193 }
194 #[cfg(feature = "screen-capture")]
195 fn screen_capture_sources(&self)
196 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
197 #[cfg(not(feature = "screen-capture"))]
198 fn screen_capture_sources(
199 &self,
200 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
201 let (sources_tx, sources_rx) = oneshot::channel();
202 sources_tx
203 .send(Err(anyhow::anyhow!(
204 "gpui was compiled without the screen-capture feature"
205 )))
206 .ok();
207 sources_rx
208 }
209
210 fn open_window(
211 &self,
212 handle: AnyWindowHandle,
213 options: WindowParams,
214 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
215
216 fn window_appearance(&self) -> WindowAppearance;
218
219 fn open_url(&self, url: &str);
220 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
221 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
222
223 fn prompt_for_paths(
224 &self,
225 options: PathPromptOptions,
226 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
227 fn prompt_for_new_path(
228 &self,
229 directory: &Path,
230 suggested_name: Option<&str>,
231 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
232 fn can_select_mixed_files_and_dirs(&self) -> bool;
233 fn reveal_path(&self, path: &Path);
234 fn open_with_system(&self, path: &Path);
235
236 fn on_quit(&self, callback: Box<dyn FnMut()>);
237 fn on_reopen(&self, callback: Box<dyn FnMut()>);
238
239 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
240 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
241 None
242 }
243
244 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
245 fn perform_dock_menu_action(&self, _action: usize) {}
246 fn add_recent_document(&self, _path: &Path) {}
247 fn update_jump_list(
248 &self,
249 _menus: Vec<MenuItem>,
250 _entries: Vec<SmallVec<[PathBuf; 2]>>,
251 ) -> Vec<SmallVec<[PathBuf; 2]>> {
252 Vec::new()
253 }
254 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
255 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
256 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
257
258 fn compositor_name(&self) -> &'static str {
259 ""
260 }
261 fn app_path(&self) -> Result<PathBuf>;
262 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
263
264 fn set_cursor_style(&self, style: CursorStyle);
265 fn should_auto_hide_scrollbars(&self) -> bool;
266
267 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
268 fn write_to_primary(&self, item: ClipboardItem);
269 fn write_to_clipboard(&self, item: ClipboardItem);
270 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
271 fn read_from_primary(&self) -> Option<ClipboardItem>;
272 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
273
274 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
275 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
276 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
277
278 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
279 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
280 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
281
282 fn set_tray_icon(&self, _icon: Option<&[u8]>) {}
283 fn set_tray_menu(&self, _menu: Vec<TrayMenuItem>) {}
284 fn set_tray_tooltip(&self, _tooltip: &str) {}
285 fn set_tray_panel_mode(&self, _enabled: bool) {}
286 fn get_tray_icon_bounds(&self) -> Option<Bounds<Pixels>> {
287 None
288 }
289 fn on_tray_icon_event(&self, _callback: Box<dyn FnMut(TrayIconEvent)>) {}
290 fn on_tray_menu_action(&self, _callback: Box<dyn FnMut(SharedString)>) {}
291
292 fn register_global_hotkey(&self, _id: u32, _keystroke: &Keystroke) -> Result<()> {
293 Err(anyhow::anyhow!(
294 "Global hotkeys not supported on this platform"
295 ))
296 }
297 fn unregister_global_hotkey(&self, _id: u32) {}
298 fn on_global_hotkey(&self, _callback: Box<dyn FnMut(u32)>) {}
299
300 fn focused_window_info(&self) -> Option<FocusedWindowInfo> {
301 None
302 }
303
304 fn accessibility_status(&self) -> PermissionStatus {
305 PermissionStatus::Granted
306 }
307 fn request_accessibility_permission(&self) {}
308
309 fn microphone_status(&self) -> PermissionStatus {
310 PermissionStatus::Granted
311 }
312 fn request_microphone_permission(&self, callback: Box<dyn FnOnce(bool)>) {
313 callback(true);
314 }
315
316 fn set_auto_launch(&self, _app_id: &str, _enabled: bool) -> Result<()> {
317 Err(anyhow::anyhow!(
318 "Auto-launch not supported on this platform"
319 ))
320 }
321 fn is_auto_launch_enabled(&self, _app_id: &str) -> bool {
322 false
323 }
324
325 fn show_notification(&self, _title: &str, _body: &str) -> Result<()> {
326 Err(anyhow::anyhow!(
327 "Notifications not supported on this platform"
328 ))
329 }
330
331 fn set_keep_alive_without_windows(&self, _keep_alive: bool) {}
332
333 fn on_system_power_event(&self, _callback: Box<dyn FnMut(SystemPowerEvent)>) {}
334 fn start_power_save_blocker(&self, _kind: PowerSaveBlockerKind) -> Option<u32> { None }
335 fn stop_power_save_blocker(&self, _id: u32) {}
336 fn system_idle_time(&self) -> Option<Duration> { None }
337 fn network_status(&self) -> NetworkStatus { NetworkStatus::Online }
338 fn on_network_status_change(&self, _callback: Box<dyn FnMut(NetworkStatus)>) {}
339 fn on_media_key_event(&self, _callback: Box<dyn FnMut(MediaKeyEvent)>) {}
340 fn request_user_attention(&self, _attention_type: AttentionType) {}
341 fn cancel_user_attention(&self) {}
342 fn set_dock_badge(&self, _label: Option<&str>) {}
343 fn show_context_menu(
344 &self,
345 _position: Point<Pixels>,
346 _items: Vec<TrayMenuItem>,
347 _callback: Box<dyn FnMut(SharedString)>,
348 ) {}
349 fn show_dialog(&self, _options: DialogOptions) -> oneshot::Receiver<usize> {
350 let (tx, rx) = oneshot::channel();
351 tx.send(0).ok();
352 rx
353 }
354 fn os_info(&self) -> OsInfo {
355 OsInfo {
356 name: std::env::consts::OS.into(),
357 arch: std::env::consts::ARCH.into(),
358 version: String::new().into(),
359 locale: String::new().into(),
360 hostname: String::new().into(),
361 }
362 }
363 fn biometric_status(&self) -> BiometricStatus { BiometricStatus::Unavailable }
364 fn authenticate_biometric(
365 &self,
366 _reason: &str,
367 callback: Box<dyn FnOnce(bool) + Send>,
368 ) {
369 callback(false);
370 }
371}
372
373pub trait PlatformDisplay: Send + Sync + Debug {
375 fn id(&self) -> DisplayId;
377
378 fn uuid(&self) -> Result<Uuid>;
381
382 fn bounds(&self) -> Bounds<Pixels>;
384
385 fn default_bounds(&self) -> Bounds<Pixels> {
387 let center = self.bounds().center();
388 let offset = DEFAULT_WINDOW_SIZE / 2.0;
389 let origin = point(center.x - offset.width, center.y - offset.height);
390 Bounds::new(origin, DEFAULT_WINDOW_SIZE)
391 }
392}
393
394#[derive(Clone)]
396pub struct SourceMetadata {
397 pub id: u64,
399 pub label: Option<SharedString>,
401 pub is_main: Option<bool>,
403 pub resolution: Size<DevicePixels>,
405}
406
407pub trait ScreenCaptureSource {
409 fn metadata(&self) -> Result<SourceMetadata>;
411
412 fn stream(
415 &self,
416 foreground_executor: &ForegroundExecutor,
417 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
418 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
419}
420
421pub trait ScreenCaptureStream {
423 fn metadata(&self) -> Result<SourceMetadata>;
425}
426
427pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
429
430#[derive(PartialEq, Eq, Hash, Copy, Clone)]
432pub struct DisplayId(pub(crate) u32);
433
434impl From<DisplayId> for u32 {
435 fn from(id: DisplayId) -> Self {
436 id.0
437 }
438}
439
440impl Debug for DisplayId {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 write!(f, "DisplayId({})", self.0)
443 }
444}
445
446unsafe impl Send for DisplayId {}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum ResizeEdge {
451 Top,
453 TopRight,
455 Right,
457 BottomRight,
459 Bottom,
461 BottomLeft,
463 Left,
465 TopLeft,
467}
468
469#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
471pub enum WindowDecorations {
472 #[default]
473 Server,
475 Client,
477}
478
479#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
481pub enum Decorations {
482 #[default]
484 Server,
485 Client {
487 tiling: Tiling,
489 },
490}
491
492#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
494pub struct WindowControls {
495 pub fullscreen: bool,
497 pub maximize: bool,
499 pub minimize: bool,
501 pub window_menu: bool,
503}
504
505impl Default for WindowControls {
506 fn default() -> Self {
507 Self {
509 fullscreen: true,
510 maximize: true,
511 minimize: true,
512 window_menu: true,
513 }
514 }
515}
516
517#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
519pub struct Tiling {
520 pub top: bool,
522 pub left: bool,
524 pub right: bool,
526 pub bottom: bool,
528}
529
530impl Tiling {
531 pub fn tiled() -> Self {
533 Self {
534 top: true,
535 left: true,
536 right: true,
537 bottom: true,
538 }
539 }
540
541 pub fn is_tiled(&self) -> bool {
543 self.top || self.left || self.right || self.bottom
544 }
545}
546
547#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
548pub(crate) struct RequestFrameOptions {
549 pub(crate) require_presentation: bool,
550 pub(crate) force_render: bool,
552}
553
554pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
555 fn bounds(&self) -> Bounds<Pixels>;
556 fn is_maximized(&self) -> bool;
557 fn window_bounds(&self) -> WindowBounds;
558 fn content_size(&self) -> Size<Pixels>;
559 fn resize(&mut self, size: Size<Pixels>);
560 fn scale_factor(&self) -> f32;
561 fn appearance(&self) -> WindowAppearance;
562 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
563 fn mouse_position(&self) -> Point<Pixels>;
564 fn modifiers(&self) -> Modifiers;
565 fn capslock(&self) -> Capslock;
566 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
567 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
568 fn prompt(
569 &self,
570 level: PromptLevel,
571 msg: &str,
572 detail: Option<&str>,
573 answers: &[PromptButton],
574 ) -> Option<oneshot::Receiver<usize>>;
575 fn activate(&self);
576 fn is_active(&self) -> bool;
577 fn is_hovered(&self) -> bool;
578 fn set_title(&mut self, title: &str);
579 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
580 fn minimize(&self);
581 fn zoom(&self);
582 fn toggle_fullscreen(&self);
583 fn is_fullscreen(&self) -> bool;
584 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
585 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
586 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
587 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
588 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
589 fn on_moved(&self, callback: Box<dyn FnMut()>);
590 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
591 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
592 fn on_close(&self, callback: Box<dyn FnOnce()>);
593 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
594 fn draw(&self, scene: &Scene);
595 fn completed_frame(&self) {}
596 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
597
598 fn get_title(&self) -> String {
600 String::new()
601 }
602 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
603 None
604 }
605 fn tab_bar_visible(&self) -> bool {
606 false
607 }
608 fn set_edited(&mut self, _edited: bool) {}
609 fn show_character_palette(&self) {}
610 fn titlebar_double_click(&self) {}
611 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
612 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
613 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
614 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
615 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
616 fn merge_all_windows(&self) {}
617 fn move_tab_to_new_window(&self) {}
618 fn toggle_window_tab_overview(&self) {}
619 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
620
621 #[cfg(target_os = "windows")]
622 fn get_raw_handle(&self) -> windows::HWND;
623
624 fn inner_window_bounds(&self) -> WindowBounds {
626 self.window_bounds()
627 }
628 fn request_decorations(&self, _decorations: WindowDecorations) {}
629 fn show_window_menu(&self, _position: Point<Pixels>) {}
630 fn start_window_move(&self) {}
631 fn start_window_resize(&self, _edge: ResizeEdge) {}
632 fn window_decorations(&self) -> Decorations {
633 Decorations::Server
634 }
635 fn set_app_id(&mut self, _app_id: &str) {}
636 fn map_window(&mut self) -> anyhow::Result<()> {
637 Ok(())
638 }
639 fn window_controls(&self) -> WindowControls {
640 WindowControls::default()
641 }
642 fn set_client_inset(&self, _inset: Pixels) {}
643 fn gpu_specs(&self) -> Option<GpuSpecs>;
644
645 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
646
647 fn show(&self) {}
648 fn hide(&self) {}
649 fn is_visible(&self) -> bool {
650 true
651 }
652 fn set_mouse_passthrough(&self, _passthrough: bool) {}
653 fn set_progress_bar(&self, _state: ProgressBarState) {}
654
655 #[cfg(any(test, feature = "test-support"))]
656 fn as_test(&mut self) -> Option<&mut TestWindow> {
657 None
658 }
659}
660
661#[doc(hidden)]
664pub trait PlatformDispatcher: Send + Sync {
665 fn is_main_thread(&self) -> bool;
666 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
667 fn dispatch_on_main_thread(&self, runnable: Runnable);
668 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
669 fn park(&self, timeout: Option<Duration>) -> bool;
670 fn unparker(&self) -> Unparker;
671 fn now(&self) -> Instant {
672 Instant::now()
673 }
674
675 #[cfg(any(test, feature = "test-support"))]
676 fn as_test(&self) -> Option<&TestDispatcher> {
677 None
678 }
679}
680
681pub(crate) trait PlatformTextSystem: Send + Sync {
682 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
683 fn all_font_names(&self) -> Vec<String>;
684 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
685 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
686 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
687 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
688 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
689 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
690 fn rasterize_glyph(
691 &self,
692 params: &RenderGlyphParams,
693 raster_bounds: Bounds<DevicePixels>,
694 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
695 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
696}
697
698pub(crate) struct NoopTextSystem;
699
700impl NoopTextSystem {
701 #[allow(dead_code)]
702 pub fn new() -> Self {
703 Self
704 }
705}
706
707impl PlatformTextSystem for NoopTextSystem {
708 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
709 Ok(())
710 }
711
712 fn all_font_names(&self) -> Vec<String> {
713 Vec::new()
714 }
715
716 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
717 Ok(FontId(1))
718 }
719
720 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
721 FontMetrics {
722 units_per_em: 1000,
723 ascent: 1025.0,
724 descent: -275.0,
725 line_gap: 0.0,
726 underline_position: -95.0,
727 underline_thickness: 60.0,
728 cap_height: 698.0,
729 x_height: 516.0,
730 bounding_box: Bounds {
731 origin: Point {
732 x: -260.0,
733 y: -245.0,
734 },
735 size: Size {
736 width: 1501.0,
737 height: 1364.0,
738 },
739 },
740 }
741 }
742
743 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
744 Ok(Bounds {
745 origin: Point { x: 54.0, y: 0.0 },
746 size: size(392.0, 528.0),
747 })
748 }
749
750 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
751 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
752 }
753
754 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
755 Some(GlyphId(ch.len_utf16() as u32))
756 }
757
758 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
759 Ok(Default::default())
760 }
761
762 fn rasterize_glyph(
763 &self,
764 _params: &RenderGlyphParams,
765 raster_bounds: Bounds<DevicePixels>,
766 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
767 Ok((raster_bounds.size, Vec::new()))
768 }
769
770 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
771 let mut position = px(0.);
772 let metrics = self.font_metrics(FontId(0));
773 let em_width = font_size
774 * self
775 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
776 .unwrap()
777 .width
778 / metrics.units_per_em as f32;
779 let mut glyphs = Vec::new();
780 for (ix, c) in text.char_indices() {
781 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
782 glyphs.push(ShapedGlyph {
783 id: glyph,
784 position: point(position, px(0.)),
785 index: ix,
786 is_emoji: glyph.0 == 2,
787 });
788 if glyph.0 == 2 {
789 position += em_width * 2.0;
790 } else {
791 position += em_width;
792 }
793 } else {
794 position += em_width
795 }
796 }
797 let mut runs = Vec::default();
798 if !glyphs.is_empty() {
799 runs.push(ShapedRun {
800 font_id: FontId(0),
801 glyphs,
802 });
803 } else {
804 position = px(0.);
805 }
806
807 LineLayout {
808 font_size,
809 width: position,
810 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
811 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
812 runs,
813 len: text.len(),
814 }
815 }
816}
817
818#[derive(PartialEq, Eq, Hash, Clone)]
819pub(crate) enum AtlasKey {
820 Glyph(RenderGlyphParams),
821 Svg(RenderSvgParams),
822 Image(RenderImageParams),
823}
824
825impl AtlasKey {
826 #[cfg_attr(
827 all(
828 any(target_os = "linux", target_os = "freebsd"),
829 not(any(feature = "x11", feature = "wayland"))
830 ),
831 allow(dead_code)
832 )]
833 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
834 match self {
835 AtlasKey::Glyph(params) => {
836 if params.is_emoji {
837 AtlasTextureKind::Polychrome
838 } else {
839 AtlasTextureKind::Monochrome
840 }
841 }
842 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
843 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
844 }
845 }
846}
847
848impl From<RenderGlyphParams> for AtlasKey {
849 fn from(params: RenderGlyphParams) -> Self {
850 Self::Glyph(params)
851 }
852}
853
854impl From<RenderSvgParams> for AtlasKey {
855 fn from(params: RenderSvgParams) -> Self {
856 Self::Svg(params)
857 }
858}
859
860impl From<RenderImageParams> for AtlasKey {
861 fn from(params: RenderImageParams) -> Self {
862 Self::Image(params)
863 }
864}
865
866pub(crate) trait PlatformAtlas: Send + Sync {
867 fn get_or_insert_with<'a>(
868 &self,
869 key: &AtlasKey,
870 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
871 ) -> Result<Option<AtlasTile>>;
872 fn remove(&self, key: &AtlasKey);
873}
874
875struct AtlasTextureList<T> {
876 textures: Vec<Option<T>>,
877 free_list: Vec<usize>,
878}
879
880impl<T> Default for AtlasTextureList<T> {
881 fn default() -> Self {
882 Self {
883 textures: Vec::default(),
884 free_list: Vec::default(),
885 }
886 }
887}
888
889impl<T> ops::Index<usize> for AtlasTextureList<T> {
890 type Output = Option<T>;
891
892 fn index(&self, index: usize) -> &Self::Output {
893 &self.textures[index]
894 }
895}
896
897impl<T> AtlasTextureList<T> {
898 #[allow(unused)]
899 fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
900 self.free_list.clear();
901 self.textures.drain(..)
902 }
903
904 #[allow(dead_code)]
905 fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
906 self.textures.iter_mut().flatten()
907 }
908}
909
910#[derive(Clone, Debug, PartialEq, Eq)]
911#[repr(C)]
912pub(crate) struct AtlasTile {
913 pub(crate) texture_id: AtlasTextureId,
914 pub(crate) tile_id: TileId,
915 pub(crate) padding: u32,
916 pub(crate) bounds: Bounds<DevicePixels>,
917}
918
919#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
920#[repr(C)]
921pub(crate) struct AtlasTextureId {
922 pub(crate) index: u32,
924 pub(crate) kind: AtlasTextureKind,
925}
926
927#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
928#[repr(C)]
929#[cfg_attr(
930 all(
931 any(target_os = "linux", target_os = "freebsd"),
932 not(any(feature = "x11", feature = "wayland"))
933 ),
934 allow(dead_code)
935)]
936pub(crate) enum AtlasTextureKind {
937 Monochrome = 0,
938 Polychrome = 1,
939}
940
941#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
942#[repr(C)]
943pub(crate) struct TileId(pub(crate) u32);
944
945impl From<etagere::AllocId> for TileId {
946 fn from(id: etagere::AllocId) -> Self {
947 Self(id.serialize())
948 }
949}
950
951impl From<TileId> for etagere::AllocId {
952 fn from(id: TileId) -> Self {
953 Self::deserialize(id.0)
954 }
955}
956
957pub(crate) struct PlatformInputHandler {
958 cx: AsyncWindowContext,
959 handler: Box<dyn InputHandler>,
960}
961
962#[cfg_attr(
963 all(
964 any(target_os = "linux", target_os = "freebsd"),
965 not(any(feature = "x11", feature = "wayland"))
966 ),
967 allow(dead_code)
968)]
969impl PlatformInputHandler {
970 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
971 Self { cx, handler }
972 }
973
974 fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
975 self.cx
976 .update(|window, cx| {
977 self.handler
978 .selected_text_range(ignore_disabled_input, window, cx)
979 })
980 .ok()
981 .flatten()
982 }
983
984 #[cfg_attr(target_os = "windows", allow(dead_code))]
985 fn marked_text_range(&mut self) -> Option<Range<usize>> {
986 self.cx
987 .update(|window, cx| self.handler.marked_text_range(window, cx))
988 .ok()
989 .flatten()
990 }
991
992 #[cfg_attr(
993 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
994 allow(dead_code)
995 )]
996 fn text_for_range(
997 &mut self,
998 range_utf16: Range<usize>,
999 adjusted: &mut Option<Range<usize>>,
1000 ) -> Option<String> {
1001 self.cx
1002 .update(|window, cx| {
1003 self.handler
1004 .text_for_range(range_utf16, adjusted, window, cx)
1005 })
1006 .ok()
1007 .flatten()
1008 }
1009
1010 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1011 self.cx
1012 .update(|window, cx| {
1013 self.handler
1014 .replace_text_in_range(replacement_range, text, window, cx);
1015 })
1016 .ok();
1017 }
1018
1019 pub fn replace_and_mark_text_in_range(
1020 &mut self,
1021 range_utf16: Option<Range<usize>>,
1022 new_text: &str,
1023 new_selected_range: Option<Range<usize>>,
1024 ) {
1025 self.cx
1026 .update(|window, cx| {
1027 self.handler.replace_and_mark_text_in_range(
1028 range_utf16,
1029 new_text,
1030 new_selected_range,
1031 window,
1032 cx,
1033 )
1034 })
1035 .ok();
1036 }
1037
1038 #[cfg_attr(target_os = "windows", allow(dead_code))]
1039 fn unmark_text(&mut self) {
1040 self.cx
1041 .update(|window, cx| self.handler.unmark_text(window, cx))
1042 .ok();
1043 }
1044
1045 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1046 self.cx
1047 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1048 .ok()
1049 .flatten()
1050 }
1051
1052 #[allow(dead_code)]
1053 fn apple_press_and_hold_enabled(&mut self) -> bool {
1054 self.handler.apple_press_and_hold_enabled()
1055 }
1056
1057 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1058 self.handler.replace_text_in_range(None, input, window, cx);
1059 }
1060
1061 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1062 let selection = self.handler.selected_text_range(true, window, cx)?;
1063 self.handler.bounds_for_range(
1064 if selection.reversed {
1065 selection.range.start..selection.range.start
1066 } else {
1067 selection.range.end..selection.range.end
1068 },
1069 window,
1070 cx,
1071 )
1072 }
1073
1074 #[allow(unused)]
1075 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1076 self.cx
1077 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1078 .ok()
1079 .flatten()
1080 }
1081}
1082
1083#[derive(Debug)]
1086pub struct UTF16Selection {
1087 pub range: Range<usize>,
1090 pub reversed: bool,
1093}
1094
1095pub trait InputHandler: 'static {
1100 fn selected_text_range(
1105 &mut self,
1106 ignore_disabled_input: bool,
1107 window: &mut Window,
1108 cx: &mut App,
1109 ) -> Option<UTF16Selection>;
1110
1111 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1116
1117 fn text_for_range(
1122 &mut self,
1123 range_utf16: Range<usize>,
1124 adjusted_range: &mut Option<Range<usize>>,
1125 window: &mut Window,
1126 cx: &mut App,
1127 ) -> Option<String>;
1128
1129 fn replace_text_in_range(
1134 &mut self,
1135 replacement_range: Option<Range<usize>>,
1136 text: &str,
1137 window: &mut Window,
1138 cx: &mut App,
1139 );
1140
1141 fn replace_and_mark_text_in_range(
1148 &mut self,
1149 range_utf16: Option<Range<usize>>,
1150 new_text: &str,
1151 new_selected_range: Option<Range<usize>>,
1152 window: &mut Window,
1153 cx: &mut App,
1154 );
1155
1156 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1159
1160 fn bounds_for_range(
1165 &mut self,
1166 range_utf16: Range<usize>,
1167 window: &mut Window,
1168 cx: &mut App,
1169 ) -> Option<Bounds<Pixels>>;
1170
1171 fn character_index_for_point(
1175 &mut self,
1176 point: Point<Pixels>,
1177 window: &mut Window,
1178 cx: &mut App,
1179 ) -> Option<usize>;
1180
1181 #[allow(dead_code)]
1186 fn apple_press_and_hold_enabled(&mut self) -> bool {
1187 true
1188 }
1189}
1190
1191#[derive(Debug)]
1193pub struct WindowOptions {
1194 pub window_bounds: Option<WindowBounds>,
1198
1199 pub titlebar: Option<TitlebarOptions>,
1201
1202 pub focus: bool,
1204
1205 pub show: bool,
1207
1208 pub kind: WindowKind,
1210
1211 pub is_movable: bool,
1213
1214 pub is_resizable: bool,
1216
1217 pub is_minimizable: bool,
1219
1220 pub display_id: Option<DisplayId>,
1223
1224 pub window_background: WindowBackgroundAppearance,
1226
1227 pub app_id: Option<String>,
1229
1230 pub window_min_size: Option<Size<Pixels>>,
1232
1233 pub window_decorations: Option<WindowDecorations>,
1236
1237 pub tabbing_identifier: Option<String>,
1239
1240 pub mouse_passthrough: bool,
1242}
1243
1244#[derive(Debug)]
1246#[cfg_attr(
1247 all(
1248 any(target_os = "linux", target_os = "freebsd"),
1249 not(any(feature = "x11", feature = "wayland"))
1250 ),
1251 allow(dead_code)
1252)]
1253pub(crate) struct WindowParams {
1254 pub bounds: Bounds<Pixels>,
1255
1256 #[cfg_attr(feature = "wayland", allow(dead_code))]
1258 pub titlebar: Option<TitlebarOptions>,
1259
1260 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1262 pub kind: WindowKind,
1263
1264 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1266 pub is_movable: bool,
1267
1268 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1270 pub is_resizable: bool,
1271
1272 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1274 pub is_minimizable: bool,
1275
1276 #[cfg_attr(
1277 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1278 allow(dead_code)
1279 )]
1280 pub focus: bool,
1281
1282 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1283 pub show: bool,
1284
1285 #[cfg_attr(feature = "wayland", allow(dead_code))]
1286 pub display_id: Option<DisplayId>,
1287
1288 pub window_min_size: Option<Size<Pixels>>,
1289 #[cfg(target_os = "macos")]
1290 pub tabbing_identifier: Option<String>,
1291
1292 #[allow(dead_code)]
1293 pub mouse_passthrough: bool,
1294}
1295
1296#[derive(Debug, Copy, Clone, PartialEq)]
1298pub enum WindowBounds {
1299 Windowed(Bounds<Pixels>),
1301 Maximized(Bounds<Pixels>),
1304 Fullscreen(Bounds<Pixels>),
1307}
1308
1309impl Default for WindowBounds {
1310 fn default() -> Self {
1311 WindowBounds::Windowed(Bounds::default())
1312 }
1313}
1314
1315impl WindowBounds {
1316 pub fn get_bounds(&self) -> Bounds<Pixels> {
1318 match self {
1319 WindowBounds::Windowed(bounds) => *bounds,
1320 WindowBounds::Maximized(bounds) => *bounds,
1321 WindowBounds::Fullscreen(bounds) => *bounds,
1322 }
1323 }
1324
1325 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1327 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1328 }
1329}
1330
1331impl Default for WindowOptions {
1332 fn default() -> Self {
1333 Self {
1334 window_bounds: None,
1335 titlebar: Some(TitlebarOptions {
1336 title: Default::default(),
1337 appears_transparent: Default::default(),
1338 traffic_light_position: Default::default(),
1339 }),
1340 focus: true,
1341 show: true,
1342 kind: WindowKind::Normal,
1343 is_movable: true,
1344 is_resizable: true,
1345 is_minimizable: true,
1346 display_id: None,
1347 window_background: WindowBackgroundAppearance::default(),
1348 app_id: None,
1349 window_min_size: None,
1350 window_decorations: None,
1351 tabbing_identifier: None,
1352 mouse_passthrough: false,
1353 }
1354 }
1355}
1356
1357#[derive(Debug, Default)]
1359pub struct TitlebarOptions {
1360 pub title: Option<SharedString>,
1362
1363 pub appears_transparent: bool,
1366
1367 pub traffic_light_position: Option<Point<Pixels>>,
1369}
1370
1371#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1373pub enum WindowKind {
1374 Normal,
1376
1377 PopUp,
1380
1381 Floating,
1383
1384 Overlay,
1386}
1387
1388#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1393pub enum WindowAppearance {
1394 #[default]
1398 Light,
1399
1400 VibrantLight,
1404
1405 Dark,
1409
1410 VibrantDark,
1414}
1415
1416#[derive(Copy, Clone, Debug, Default, PartialEq)]
1419pub enum WindowBackgroundAppearance {
1420 #[default]
1428 Opaque,
1429 Transparent,
1431 Blurred,
1435}
1436
1437#[derive(Debug, Clone, PartialEq, Eq)]
1439pub enum TrayIconEvent {
1440 LeftClick,
1442 RightClick,
1444 DoubleClick,
1446}
1447
1448#[derive(Debug, Clone)]
1450pub enum TrayMenuItem {
1451 Action {
1453 label: SharedString,
1455 id: SharedString,
1457 },
1458 Separator,
1460 Submenu {
1462 label: SharedString,
1464 items: Vec<TrayMenuItem>,
1466 },
1467 Toggle {
1469 label: SharedString,
1471 checked: bool,
1473 id: SharedString,
1475 },
1476}
1477
1478#[derive(Debug, Clone)]
1480pub struct FocusedWindowInfo {
1481 pub app_name: String,
1483 pub window_title: String,
1485 pub bundle_id: Option<String>,
1487 pub pid: Option<u32>,
1489}
1490
1491#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1493pub enum PermissionStatus {
1494 Granted,
1496 Denied,
1498 NotDetermined,
1500}
1501
1502#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1504pub enum SystemPowerEvent {
1505 Suspend,
1507 Resume,
1509 LockScreen,
1511 UnlockScreen,
1513 Shutdown,
1515}
1516
1517#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1519pub enum PowerSaveBlockerKind {
1520 PreventAppSuspension,
1522 PreventDisplaySleep,
1524}
1525
1526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1528pub enum NetworkStatus {
1529 Online,
1531 Offline,
1533}
1534
1535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1537pub enum MediaKeyEvent {
1538 Play,
1540 Pause,
1542 PlayPause,
1544 Stop,
1546 NextTrack,
1548 PreviousTrack,
1550}
1551
1552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1554pub enum AttentionType {
1555 Informational,
1557 Critical,
1559}
1560
1561#[derive(Debug, Clone, Copy, PartialEq)]
1563pub enum ProgressBarState {
1564 None,
1566 Indeterminate,
1568 Normal(f64),
1570 Error(f64),
1572 Paused(f64),
1574}
1575
1576#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1578pub enum DialogKind {
1579 Info,
1581 Warning,
1583 Error,
1585}
1586
1587#[derive(Debug, Clone)]
1589pub struct DialogOptions {
1590 pub kind: DialogKind,
1592 pub title: SharedString,
1594 pub message: SharedString,
1596 pub detail: Option<SharedString>,
1598 pub buttons: Vec<SharedString>,
1600}
1601
1602#[derive(Debug, Clone)]
1604pub struct OsInfo {
1605 pub name: SharedString,
1607 pub version: SharedString,
1609 pub arch: SharedString,
1611 pub locale: SharedString,
1613 pub hostname: SharedString,
1615}
1616
1617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1619pub enum BiometricKind {
1620 TouchId,
1622 WindowsHello,
1624 Fingerprint,
1626}
1627
1628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1630pub enum BiometricStatus {
1631 Available(BiometricKind),
1633 Unavailable,
1635}
1636
1637#[derive(Debug, Clone, PartialEq)]
1639pub struct WindowState {
1640 pub bounds: WindowBounds,
1642 pub display_id: Option<DisplayId>,
1644 pub fullscreen: bool,
1646}
1647
1648#[derive(Debug, Clone, PartialEq)]
1650pub enum WindowPosition {
1651 Center,
1653 CenterOnDisplay(DisplayId),
1655 TrayCenter(Bounds<Pixels>),
1657 TopRight {
1659 margin: Pixels,
1661 },
1662 BottomRight {
1664 margin: Pixels,
1666 },
1667 TopLeft {
1669 margin: Pixels,
1671 },
1672 BottomLeft {
1674 margin: Pixels,
1676 },
1677}
1678
1679#[derive(Debug, Clone)]
1681pub struct CrashReport {
1682 pub message: String,
1684 pub backtrace: String,
1686 pub os_info: OsInfo,
1688 pub app_version: Option<String>,
1690}
1691
1692#[derive(Clone, Debug)]
1694pub struct PathPromptOptions {
1695 pub files: bool,
1697 pub directories: bool,
1699 pub multiple: bool,
1701 pub prompt: Option<SharedString>,
1703}
1704
1705#[derive(Copy, Clone, Debug, PartialEq)]
1707pub enum PromptLevel {
1708 Info,
1710
1711 Warning,
1713
1714 Critical,
1716}
1717
1718#[derive(Clone, Debug, PartialEq)]
1720pub enum PromptButton {
1721 Ok(SharedString),
1723 Cancel(SharedString),
1725 Other(SharedString),
1727}
1728
1729impl PromptButton {
1730 pub fn new(label: impl Into<SharedString>) -> Self {
1732 PromptButton::Other(label.into())
1733 }
1734
1735 pub fn ok(label: impl Into<SharedString>) -> Self {
1737 PromptButton::Ok(label.into())
1738 }
1739
1740 pub fn cancel(label: impl Into<SharedString>) -> Self {
1742 PromptButton::Cancel(label.into())
1743 }
1744
1745 #[allow(dead_code)]
1746 pub(crate) fn is_cancel(&self) -> bool {
1747 matches!(self, PromptButton::Cancel(_))
1748 }
1749
1750 pub fn label(&self) -> &SharedString {
1752 match self {
1753 PromptButton::Ok(label) => label,
1754 PromptButton::Cancel(label) => label,
1755 PromptButton::Other(label) => label,
1756 }
1757 }
1758}
1759
1760impl From<&str> for PromptButton {
1761 fn from(value: &str) -> Self {
1762 match value.to_lowercase().as_str() {
1763 "ok" => PromptButton::Ok("Ok".into()),
1764 "cancel" => PromptButton::Cancel("Cancel".into()),
1765 _ => PromptButton::Other(SharedString::from(value.to_owned())),
1766 }
1767 }
1768}
1769
1770#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1772pub enum CursorStyle {
1773 #[default]
1775 Arrow,
1776
1777 IBeam,
1780
1781 Crosshair,
1784
1785 ClosedHand,
1788
1789 OpenHand,
1792
1793 PointingHand,
1796
1797 ResizeLeft,
1800
1801 ResizeRight,
1804
1805 ResizeLeftRight,
1808
1809 ResizeUp,
1812
1813 ResizeDown,
1816
1817 ResizeUpDown,
1820
1821 ResizeUpLeftDownRight,
1824
1825 ResizeUpRightDownLeft,
1828
1829 ResizeColumn,
1832
1833 ResizeRow,
1836
1837 IBeamCursorForVerticalLayout,
1840
1841 OperationNotAllowed,
1844
1845 DragLink,
1848
1849 DragCopy,
1852
1853 ContextualMenu,
1856
1857 None,
1859}
1860
1861#[derive(Clone, Debug, Eq, PartialEq)]
1863pub struct ClipboardItem {
1864 entries: Vec<ClipboardEntry>,
1865}
1866
1867#[derive(Clone, Debug, Eq, PartialEq)]
1869pub enum ClipboardEntry {
1870 String(ClipboardString),
1872 Image(Image),
1874}
1875
1876impl ClipboardItem {
1877 pub fn new_string(text: String) -> Self {
1879 Self {
1880 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1881 }
1882 }
1883
1884 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1886 Self {
1887 entries: vec![ClipboardEntry::String(ClipboardString {
1888 text,
1889 metadata: Some(metadata),
1890 })],
1891 }
1892 }
1893
1894 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1896 Self {
1897 entries: vec![ClipboardEntry::String(
1898 ClipboardString::new(text).with_json_metadata(metadata),
1899 )],
1900 }
1901 }
1902
1903 pub fn new_image(image: &Image) -> Self {
1905 Self {
1906 entries: vec![ClipboardEntry::Image(image.clone())],
1907 }
1908 }
1909
1910 pub fn text(&self) -> Option<String> {
1913 let mut answer = String::new();
1914 let mut any_entries = false;
1915
1916 for entry in self.entries.iter() {
1917 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1918 answer.push_str(text);
1919 any_entries = true;
1920 }
1921 }
1922
1923 if any_entries { Some(answer) } else { None }
1924 }
1925
1926 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1928 pub fn metadata(&self) -> Option<&String> {
1929 match self.entries().first() {
1930 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1931 clipboard_string.metadata.as_ref()
1932 }
1933 _ => None,
1934 }
1935 }
1936
1937 pub fn entries(&self) -> &[ClipboardEntry] {
1939 &self.entries
1940 }
1941
1942 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1944 self.entries.into_iter()
1945 }
1946}
1947
1948impl From<ClipboardString> for ClipboardEntry {
1949 fn from(value: ClipboardString) -> Self {
1950 Self::String(value)
1951 }
1952}
1953
1954impl From<String> for ClipboardEntry {
1955 fn from(value: String) -> Self {
1956 Self::from(ClipboardString::from(value))
1957 }
1958}
1959
1960impl From<Image> for ClipboardEntry {
1961 fn from(value: Image) -> Self {
1962 Self::Image(value)
1963 }
1964}
1965
1966impl From<ClipboardEntry> for ClipboardItem {
1967 fn from(value: ClipboardEntry) -> Self {
1968 Self {
1969 entries: vec![value],
1970 }
1971 }
1972}
1973
1974impl From<String> for ClipboardItem {
1975 fn from(value: String) -> Self {
1976 Self::from(ClipboardEntry::from(value))
1977 }
1978}
1979
1980impl From<Image> for ClipboardItem {
1981 fn from(value: Image) -> Self {
1982 Self::from(ClipboardEntry::from(value))
1983 }
1984}
1985
1986#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1988pub enum ImageFormat {
1989 Png,
1994 Jpeg,
1996 Webp,
1998 Gif,
2000 Svg,
2002 Bmp,
2004 Tiff,
2006}
2007
2008impl ImageFormat {
2009 pub const fn mime_type(self) -> &'static str {
2011 match self {
2012 ImageFormat::Png => "image/png",
2013 ImageFormat::Jpeg => "image/jpeg",
2014 ImageFormat::Webp => "image/webp",
2015 ImageFormat::Gif => "image/gif",
2016 ImageFormat::Svg => "image/svg+xml",
2017 ImageFormat::Bmp => "image/bmp",
2018 ImageFormat::Tiff => "image/tiff",
2019 }
2020 }
2021
2022 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
2024 match mime_type {
2025 "image/png" => Some(Self::Png),
2026 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
2027 "image/webp" => Some(Self::Webp),
2028 "image/gif" => Some(Self::Gif),
2029 "image/svg+xml" => Some(Self::Svg),
2030 "image/bmp" => Some(Self::Bmp),
2031 "image/tiff" | "image/tif" => Some(Self::Tiff),
2032 _ => None,
2033 }
2034 }
2035}
2036
2037#[derive(Clone, Debug, PartialEq, Eq)]
2039pub struct Image {
2040 pub format: ImageFormat,
2042 pub bytes: Vec<u8>,
2044 id: u64,
2046}
2047
2048impl Hash for Image {
2049 fn hash<H: Hasher>(&self, state: &mut H) {
2050 state.write_u64(self.id);
2051 }
2052}
2053
2054impl Image {
2055 pub fn empty() -> Self {
2057 Self::from_bytes(ImageFormat::Png, Vec::new())
2058 }
2059
2060 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
2062 Self {
2063 id: hash(&bytes),
2064 format,
2065 bytes,
2066 }
2067 }
2068
2069 pub fn id(&self) -> u64 {
2071 self.id
2072 }
2073
2074 pub fn use_render_image(
2076 self: Arc<Self>,
2077 window: &mut Window,
2078 cx: &mut App,
2079 ) -> Option<Arc<RenderImage>> {
2080 ImageSource::Image(self)
2081 .use_data(None, window, cx)
2082 .and_then(|result| result.ok())
2083 }
2084
2085 pub fn get_render_image(
2087 self: Arc<Self>,
2088 window: &mut Window,
2089 cx: &mut App,
2090 ) -> Option<Arc<RenderImage>> {
2091 ImageSource::Image(self)
2092 .get_data(None, window, cx)
2093 .and_then(|result| result.ok())
2094 }
2095
2096 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
2098 ImageSource::Image(self).remove_asset(cx);
2099 }
2100
2101 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
2103 fn frames_for_image(
2104 bytes: &[u8],
2105 format: image::ImageFormat,
2106 ) -> Result<SmallVec<[Frame; 1]>> {
2107 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
2108
2109 for pixel in data.chunks_exact_mut(4) {
2111 pixel.swap(0, 2);
2112 }
2113
2114 Ok(SmallVec::from_elem(Frame::new(data), 1))
2115 }
2116
2117 let frames = match self.format {
2118 ImageFormat::Gif => {
2119 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
2120 let mut frames = SmallVec::new();
2121
2122 for frame in decoder.into_frames() {
2123 let mut frame = frame?;
2124 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
2126 pixel.swap(0, 2);
2127 }
2128 frames.push(frame);
2129 }
2130
2131 frames
2132 }
2133 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
2134 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
2135 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
2136 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
2137 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
2138 ImageFormat::Svg => {
2139 let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
2140
2141 let buffer =
2142 image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
2143 .unwrap();
2144
2145 SmallVec::from_elem(Frame::new(buffer), 1)
2146 }
2147 };
2148
2149 Ok(Arc::new(RenderImage::new(frames)))
2150 }
2151
2152 pub fn format(&self) -> ImageFormat {
2154 self.format
2155 }
2156
2157 pub fn bytes(&self) -> &[u8] {
2159 self.bytes.as_slice()
2160 }
2161}
2162
2163#[derive(Clone, Debug, Eq, PartialEq)]
2165pub struct ClipboardString {
2166 pub(crate) text: String,
2167 pub(crate) metadata: Option<String>,
2168}
2169
2170impl ClipboardString {
2171 pub fn new(text: String) -> Self {
2173 Self {
2174 text,
2175 metadata: None,
2176 }
2177 }
2178
2179 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
2182 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
2183 self
2184 }
2185
2186 pub fn text(&self) -> &String {
2188 &self.text
2189 }
2190
2191 pub fn into_text(self) -> String {
2193 self.text
2194 }
2195
2196 pub fn metadata_json<T>(&self) -> Option<T>
2198 where
2199 T: for<'a> Deserialize<'a>,
2200 {
2201 self.metadata
2202 .as_ref()
2203 .and_then(|m| serde_json::from_str(m).ok())
2204 }
2205
2206 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2207 pub(crate) fn text_hash(text: &str) -> u64 {
2208 let mut hasher = SeaHasher::new();
2209 text.hash(&mut hasher);
2210 hasher.finish()
2211 }
2212}
2213
2214impl From<String> for ClipboardString {
2215 fn from(value: String) -> Self {
2216 Self {
2217 text: value,
2218 metadata: None,
2219 }
2220 }
2221}