1mod app_menu;
2mod keyboard;
3mod keystroke;
4
5#[cfg(any(target_os = "linux", target_os = "freebsd"))]
6mod linux;
7
8#[cfg(target_os = "macos")]
9mod mac;
10
11#[cfg(any(
12 all(
13 any(target_os = "linux", target_os = "freebsd"),
14 any(feature = "x11", feature = "wayland")
15 ),
16 all(target_os = "macos", feature = "macos-blade")
17))]
18mod blade;
19
20#[cfg(any(test, feature = "test-support"))]
21mod test;
22
23#[cfg(target_os = "windows")]
24mod windows;
25
26#[cfg(all(
27 feature = "screen-capture",
28 any(
29 target_os = "windows",
30 all(
31 any(target_os = "linux", target_os = "freebsd"),
32 any(feature = "wayland", feature = "x11"),
33 )
34 )
35))]
36pub(crate) mod scap_screen_capture;
37
38use crate::{
39 Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
40 DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
41 ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
42 Point, Priority, RealtimePriority, RenderGlyphParams, RenderImage, RenderImageParams,
43 RenderSvgParams, Scene, ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer,
44 SystemWindowTab, Task, TaskLabel, TaskTiming, ThreadTaskTimings, Window, WindowControlArea,
45 hash, point, px, size,
46};
47use anyhow::Result;
48use async_task::Runnable;
49use futures::channel::oneshot;
50#[cfg(any(test, feature = "test-support"))]
51use image::RgbaImage;
52use image::codecs::gif::GifDecoder;
53use image::{AnimationDecoder as _, Frame};
54use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
55use schemars::JsonSchema;
56use seahash::SeaHasher;
57use serde::{Deserialize, Serialize};
58use smallvec::SmallVec;
59use std::borrow::Cow;
60use std::hash::{Hash, Hasher};
61use std::io::Cursor;
62use std::ops;
63use std::time::{Duration, Instant};
64use std::{
65 fmt::{self, Debug},
66 ops::Range,
67 path::{Path, PathBuf},
68 rc::Rc,
69 sync::Arc,
70};
71use strum::EnumIter;
72use uuid::Uuid;
73
74pub use app_menu::*;
75pub use keyboard::*;
76pub use keystroke::*;
77
78#[cfg(any(target_os = "linux", target_os = "freebsd"))]
79pub(crate) use linux::*;
80#[cfg(target_os = "macos")]
81pub(crate) use mac::*;
82#[cfg(any(test, feature = "test-support"))]
83pub(crate) use test::*;
84#[cfg(target_os = "windows")]
85pub(crate) use windows::*;
86
87#[cfg(all(target_os = "linux", feature = "wayland"))]
88pub use linux::layer_shell;
89
90#[cfg(any(test, feature = "test-support"))]
91pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
92
93pub fn background_executor() -> BackgroundExecutor {
95 current_platform(true, std::sync::Weak::new()).background_executor()
98}
99
100#[cfg(target_os = "macos")]
101pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
102 Rc::new(MacPlatform::new(headless, liveness))
103}
104
105#[cfg(any(target_os = "linux", target_os = "freebsd"))]
106pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
107 #[cfg(feature = "x11")]
108 use anyhow::Context as _;
109
110 if headless {
111 return Rc::new(HeadlessClient::new(liveness));
112 }
113
114 match guess_compositor() {
115 #[cfg(feature = "wayland")]
116 "Wayland" => Rc::new(WaylandClient::new(liveness)),
117
118 #[cfg(feature = "x11")]
119 "X11" => Rc::new(
120 X11Client::new(liveness)
121 .context("Failed to initialize X11 client.")
122 .unwrap(),
123 ),
124
125 "Headless" => Rc::new(HeadlessClient::new(liveness)),
126 _ => unreachable!(),
127 }
128}
129
130#[cfg(target_os = "windows")]
131pub(crate) fn current_platform(_headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
132 Rc::new(
133 WindowsPlatform::new(liveness)
134 .inspect_err(|err| show_error("Failed to launch", err.to_string()))
135 .unwrap(),
136 )
137}
138
139#[cfg(any(target_os = "linux", target_os = "freebsd"))]
142#[inline]
143pub fn guess_compositor() -> &'static str {
144 if std::env::var_os("ZED_HEADLESS").is_some() {
145 return "Headless";
146 }
147
148 #[cfg(feature = "wayland")]
149 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
150 #[cfg(not(feature = "wayland"))]
151 let wayland_display: Option<std::ffi::OsString> = None;
152
153 #[cfg(feature = "x11")]
154 let x11_display = std::env::var_os("DISPLAY");
155 #[cfg(not(feature = "x11"))]
156 let x11_display: Option<std::ffi::OsString> = None;
157
158 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
159 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
160
161 if use_wayland {
162 "Wayland"
163 } else if use_x11 {
164 "X11"
165 } else {
166 "Headless"
167 }
168}
169
170pub(crate) trait Platform: 'static {
171 fn background_executor(&self) -> BackgroundExecutor;
172 fn foreground_executor(&self) -> ForegroundExecutor;
173 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
174
175 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
176 fn quit(&self);
177 fn restart(&self, binary_path: Option<PathBuf>);
178 fn activate(&self, ignoring_other_apps: bool);
179 fn hide(&self);
180 fn hide_other_apps(&self);
181 fn unhide_other_apps(&self);
182
183 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
184 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
185 fn active_window(&self) -> Option<AnyWindowHandle>;
186 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
187 None
188 }
189
190 #[cfg(feature = "screen-capture")]
191 fn is_screen_capture_supported(&self) -> bool;
192 #[cfg(not(feature = "screen-capture"))]
193 fn is_screen_capture_supported(&self) -> bool {
194 false
195 }
196 #[cfg(feature = "screen-capture")]
197 fn screen_capture_sources(&self)
198 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
199 #[cfg(not(feature = "screen-capture"))]
200 fn screen_capture_sources(
201 &self,
202 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
203 let (sources_tx, sources_rx) = oneshot::channel();
204 sources_tx
205 .send(Err(anyhow::anyhow!(
206 "gpui was compiled without the screen-capture feature"
207 )))
208 .ok();
209 sources_rx
210 }
211
212 fn open_window(
213 &self,
214 handle: AnyWindowHandle,
215 options: WindowParams,
216 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
217
218 fn window_appearance(&self) -> WindowAppearance;
220
221 fn open_url(&self, url: &str);
222 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
223 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
224
225 fn prompt_for_paths(
226 &self,
227 options: PathPromptOptions,
228 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
229 fn prompt_for_new_path(
230 &self,
231 directory: &Path,
232 suggested_name: Option<&str>,
233 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
234 fn can_select_mixed_files_and_dirs(&self) -> bool;
235 fn reveal_path(&self, path: &Path);
236 fn open_with_system(&self, path: &Path);
237
238 fn on_quit(&self, callback: Box<dyn FnMut()>);
239 fn on_reopen(&self, callback: Box<dyn FnMut()>);
240
241 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
242 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
243 None
244 }
245
246 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
247 fn perform_dock_menu_action(&self, _action: usize) {}
248 fn add_recent_document(&self, _path: &Path) {}
249 fn update_jump_list(
250 &self,
251 _menus: Vec<MenuItem>,
252 _entries: Vec<SmallVec<[PathBuf; 2]>>,
253 ) -> Vec<SmallVec<[PathBuf; 2]>> {
254 Vec::new()
255 }
256 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
257 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
258 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
259
260 fn compositor_name(&self) -> &'static str {
261 ""
262 }
263 fn app_path(&self) -> Result<PathBuf>;
264 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
265
266 fn set_cursor_style(&self, style: CursorStyle);
267 fn should_auto_hide_scrollbars(&self) -> bool;
268
269 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
270 fn write_to_clipboard(&self, item: ClipboardItem);
271
272 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
273 fn read_from_primary(&self) -> Option<ClipboardItem>;
274 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
275 fn write_to_primary(&self, item: ClipboardItem);
276
277 #[cfg(target_os = "macos")]
278 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
279 #[cfg(target_os = "macos")]
280 fn write_to_find_pasteboard(&self, item: ClipboardItem);
281
282 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
283 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
284 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
285
286 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
287 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
288 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
289}
290
291pub trait PlatformDisplay: Send + Sync + Debug {
293 fn id(&self) -> DisplayId;
295
296 fn uuid(&self) -> Result<Uuid>;
299
300 fn bounds(&self) -> Bounds<Pixels>;
302
303 fn visible_bounds(&self) -> Bounds<Pixels> {
307 self.bounds()
308 }
309
310 fn default_bounds(&self) -> Bounds<Pixels> {
312 let bounds = self.bounds();
313 let center = bounds.center();
314 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
315
316 let offset = clipped_window_size / 2.0;
317 let origin = point(center.x - offset.width, center.y - offset.height);
318 Bounds::new(origin, clipped_window_size)
319 }
320}
321
322#[derive(Clone)]
324pub struct SourceMetadata {
325 pub id: u64,
327 pub label: Option<SharedString>,
329 pub is_main: Option<bool>,
331 pub resolution: Size<DevicePixels>,
333}
334
335pub trait ScreenCaptureSource {
337 fn metadata(&self) -> Result<SourceMetadata>;
339
340 fn stream(
343 &self,
344 foreground_executor: &ForegroundExecutor,
345 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
346 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
347}
348
349pub trait ScreenCaptureStream {
351 fn metadata(&self) -> Result<SourceMetadata>;
353}
354
355pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
357
358#[derive(PartialEq, Eq, Hash, Copy, Clone)]
360pub struct DisplayId(pub(crate) u32);
361
362impl From<DisplayId> for u32 {
363 fn from(id: DisplayId) -> Self {
364 id.0
365 }
366}
367
368impl Debug for DisplayId {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 write!(f, "DisplayId({})", self.0)
371 }
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum ResizeEdge {
377 Top,
379 TopRight,
381 Right,
383 BottomRight,
385 Bottom,
387 BottomLeft,
389 Left,
391 TopLeft,
393}
394
395#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
397pub enum WindowDecorations {
398 #[default]
399 Server,
401 Client,
403}
404
405#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
407pub enum Decorations {
408 #[default]
410 Server,
411 Client {
413 tiling: Tiling,
415 },
416}
417
418#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
420pub struct WindowControls {
421 pub fullscreen: bool,
423 pub maximize: bool,
425 pub minimize: bool,
427 pub window_menu: bool,
429}
430
431impl Default for WindowControls {
432 fn default() -> Self {
433 Self {
435 fullscreen: true,
436 maximize: true,
437 minimize: true,
438 window_menu: true,
439 }
440 }
441}
442
443#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
445pub struct Tiling {
446 pub top: bool,
448 pub left: bool,
450 pub right: bool,
452 pub bottom: bool,
454}
455
456impl Tiling {
457 pub fn tiled() -> Self {
459 Self {
460 top: true,
461 left: true,
462 right: true,
463 bottom: true,
464 }
465 }
466
467 pub fn is_tiled(&self) -> bool {
469 self.top || self.left || self.right || self.bottom
470 }
471}
472
473#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
474pub(crate) struct RequestFrameOptions {
475 pub(crate) require_presentation: bool,
476 pub(crate) force_render: bool,
478}
479
480pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
481 fn bounds(&self) -> Bounds<Pixels>;
482 fn is_maximized(&self) -> bool;
483 fn window_bounds(&self) -> WindowBounds;
484 fn content_size(&self) -> Size<Pixels>;
485 fn resize(&mut self, size: Size<Pixels>);
486 fn scale_factor(&self) -> f32;
487 fn appearance(&self) -> WindowAppearance;
488 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
489 fn mouse_position(&self) -> Point<Pixels>;
490 fn modifiers(&self) -> Modifiers;
491 fn capslock(&self) -> Capslock;
492 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
493 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
494 fn prompt(
495 &self,
496 level: PromptLevel,
497 msg: &str,
498 detail: Option<&str>,
499 answers: &[PromptButton],
500 ) -> Option<oneshot::Receiver<usize>>;
501 fn activate(&self);
502 fn is_active(&self) -> bool;
503 fn is_hovered(&self) -> bool;
504 fn background_appearance(&self) -> WindowBackgroundAppearance;
505 fn set_title(&mut self, title: &str);
506 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
507 fn minimize(&self);
508 fn zoom(&self);
509 fn toggle_fullscreen(&self);
510 fn is_fullscreen(&self) -> bool;
511 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
512 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
513 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
514 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
515 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
516 fn on_moved(&self, callback: Box<dyn FnMut()>);
517 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
518 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
519 fn on_close(&self, callback: Box<dyn FnOnce()>);
520 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
521 fn draw(&self, scene: &Scene);
522 fn completed_frame(&self) {}
523 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
524 fn is_subpixel_rendering_supported(&self) -> bool;
525
526 fn get_title(&self) -> String {
528 String::new()
529 }
530 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
531 None
532 }
533 fn tab_bar_visible(&self) -> bool {
534 false
535 }
536 fn set_edited(&mut self, _edited: bool) {}
537 fn show_character_palette(&self) {}
538 fn titlebar_double_click(&self) {}
539 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
540 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
541 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
542 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
543 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
544 fn merge_all_windows(&self) {}
545 fn move_tab_to_new_window(&self) {}
546 fn toggle_window_tab_overview(&self) {}
547 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
548
549 #[cfg(target_os = "windows")]
550 fn get_raw_handle(&self) -> windows::HWND;
551
552 fn inner_window_bounds(&self) -> WindowBounds {
554 self.window_bounds()
555 }
556 fn request_decorations(&self, _decorations: WindowDecorations) {}
557 fn show_window_menu(&self, _position: Point<Pixels>) {}
558 fn start_window_move(&self) {}
559 fn start_window_resize(&self, _edge: ResizeEdge) {}
560 fn window_decorations(&self) -> Decorations {
561 Decorations::Server
562 }
563 fn set_app_id(&mut self, _app_id: &str) {}
564 fn map_window(&mut self) -> anyhow::Result<()> {
565 Ok(())
566 }
567 fn window_controls(&self) -> WindowControls {
568 WindowControls::default()
569 }
570 fn set_client_inset(&self, _inset: Pixels) {}
571 fn gpu_specs(&self) -> Option<GpuSpecs>;
572
573 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
574
575 #[cfg(any(test, feature = "test-support"))]
576 fn as_test(&mut self) -> Option<&mut TestWindow> {
577 None
578 }
579
580 #[cfg(any(test, feature = "test-support"))]
584 fn render_to_image(&self, _scene: &Scene) -> Result<RgbaImage> {
585 anyhow::bail!("render_to_image not implemented for this platform")
586 }
587}
588
589#[doc(hidden)]
592pub struct RunnableMeta {
593 pub location: &'static core::panic::Location<'static>,
595 pub app: Option<std::sync::Weak<()>>,
597}
598
599impl std::fmt::Debug for RunnableMeta {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 f.debug_struct("RunnableMeta")
602 .field("location", &self.location)
603 .field("app_alive", &self.is_app_alive())
604 .finish()
605 }
606}
607
608impl RunnableMeta {
609 pub fn is_app_alive(&self) -> bool {
611 match &self.app {
612 Some(weak) => weak.strong_count() > 0,
613 None => true,
614 }
615 }
616}
617
618#[doc(hidden)]
619pub enum RunnableVariant {
620 Meta(Runnable<RunnableMeta>),
621 Compat(Runnable),
622}
623
624#[doc(hidden)]
627pub trait PlatformDispatcher: Send + Sync {
628 fn get_all_timings(&self) -> Vec<ThreadTaskTimings>;
629 fn get_current_thread_timings(&self) -> Vec<TaskTiming>;
630 fn is_main_thread(&self) -> bool;
631 fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, priority: Priority);
632 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
633 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
634 fn spawn_realtime(&self, priority: RealtimePriority, f: Box<dyn FnOnce() + Send>);
635
636 fn now(&self) -> Instant {
637 Instant::now()
638 }
639
640 #[cfg(any(test, feature = "test-support"))]
641 fn as_test(&self) -> Option<&TestDispatcher> {
642 None
643 }
644}
645
646pub(crate) trait PlatformTextSystem: Send + Sync {
647 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
648 fn all_font_names(&self) -> Vec<String>;
649 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
650 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
651 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
652 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
653 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
654 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
655 fn rasterize_glyph(
656 &self,
657 params: &RenderGlyphParams,
658 raster_bounds: Bounds<DevicePixels>,
659 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
660 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
661 fn recommended_rendering_mode(&self, _font_id: FontId, _font_size: Pixels)
662 -> TextRenderingMode;
663}
664
665pub(crate) struct NoopTextSystem;
666
667impl NoopTextSystem {
668 #[allow(dead_code)]
669 pub fn new() -> Self {
670 Self
671 }
672}
673
674impl PlatformTextSystem for NoopTextSystem {
675 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
676 Ok(())
677 }
678
679 fn all_font_names(&self) -> Vec<String> {
680 Vec::new()
681 }
682
683 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
684 Ok(FontId(1))
685 }
686
687 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
688 FontMetrics {
689 units_per_em: 1000,
690 ascent: 1025.0,
691 descent: -275.0,
692 line_gap: 0.0,
693 underline_position: -95.0,
694 underline_thickness: 60.0,
695 cap_height: 698.0,
696 x_height: 516.0,
697 bounding_box: Bounds {
698 origin: Point {
699 x: -260.0,
700 y: -245.0,
701 },
702 size: Size {
703 width: 1501.0,
704 height: 1364.0,
705 },
706 },
707 }
708 }
709
710 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
711 Ok(Bounds {
712 origin: Point { x: 54.0, y: 0.0 },
713 size: size(392.0, 528.0),
714 })
715 }
716
717 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
718 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
719 }
720
721 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
722 Some(GlyphId(ch.len_utf16() as u32))
723 }
724
725 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
726 Ok(Default::default())
727 }
728
729 fn rasterize_glyph(
730 &self,
731 _params: &RenderGlyphParams,
732 raster_bounds: Bounds<DevicePixels>,
733 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
734 Ok((raster_bounds.size, Vec::new()))
735 }
736
737 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
738 let mut position = px(0.);
739 let metrics = self.font_metrics(FontId(0));
740 let em_width = font_size
741 * self
742 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
743 .unwrap()
744 .width
745 / metrics.units_per_em as f32;
746 let mut glyphs = Vec::new();
747 for (ix, c) in text.char_indices() {
748 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
749 glyphs.push(ShapedGlyph {
750 id: glyph,
751 position: point(position, px(0.)),
752 index: ix,
753 is_emoji: glyph.0 == 2,
754 });
755 if glyph.0 == 2 {
756 position += em_width * 2.0;
757 } else {
758 position += em_width;
759 }
760 } else {
761 position += em_width
762 }
763 }
764 let mut runs = Vec::default();
765 if !glyphs.is_empty() {
766 runs.push(ShapedRun {
767 font_id: FontId(0),
768 glyphs,
769 });
770 } else {
771 position = px(0.);
772 }
773
774 LineLayout {
775 font_size,
776 width: position,
777 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
778 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
779 runs,
780 len: text.len(),
781 }
782 }
783
784 fn recommended_rendering_mode(
785 &self,
786 _font_id: FontId,
787 _font_size: Pixels,
788 ) -> TextRenderingMode {
789 TextRenderingMode::Grayscale
790 }
791}
792
793#[allow(dead_code)]
797pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
798 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
799 [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], ];
813
814 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
815 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
816
817 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
818 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
819
820 [
821 ratios[0] * NORM13,
822 ratios[1] * NORM24,
823 ratios[2] * NORM13,
824 ratios[3] * NORM24,
825 ]
826}
827
828#[derive(PartialEq, Eq, Hash, Clone)]
829pub(crate) enum AtlasKey {
830 Glyph(RenderGlyphParams),
831 Svg(RenderSvgParams),
832 Image(RenderImageParams),
833}
834
835impl AtlasKey {
836 #[cfg_attr(
837 all(
838 any(target_os = "linux", target_os = "freebsd"),
839 not(any(feature = "x11", feature = "wayland"))
840 ),
841 allow(dead_code)
842 )]
843 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
844 match self {
845 AtlasKey::Glyph(params) => {
846 if params.is_emoji {
847 AtlasTextureKind::Polychrome
848 } else if params.subpixel_rendering {
849 AtlasTextureKind::Subpixel
850 } else {
851 AtlasTextureKind::Monochrome
852 }
853 }
854 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
855 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
856 }
857 }
858}
859
860impl From<RenderGlyphParams> for AtlasKey {
861 fn from(params: RenderGlyphParams) -> Self {
862 Self::Glyph(params)
863 }
864}
865
866impl From<RenderSvgParams> for AtlasKey {
867 fn from(params: RenderSvgParams) -> Self {
868 Self::Svg(params)
869 }
870}
871
872impl From<RenderImageParams> for AtlasKey {
873 fn from(params: RenderImageParams) -> Self {
874 Self::Image(params)
875 }
876}
877
878pub(crate) trait PlatformAtlas: Send + Sync {
879 fn get_or_insert_with<'a>(
880 &self,
881 key: &AtlasKey,
882 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
883 ) -> Result<Option<AtlasTile>>;
884 fn remove(&self, key: &AtlasKey);
885}
886
887struct AtlasTextureList<T> {
888 textures: Vec<Option<T>>,
889 free_list: Vec<usize>,
890}
891
892impl<T> Default for AtlasTextureList<T> {
893 fn default() -> Self {
894 Self {
895 textures: Vec::default(),
896 free_list: Vec::default(),
897 }
898 }
899}
900
901impl<T> ops::Index<usize> for AtlasTextureList<T> {
902 type Output = Option<T>;
903
904 fn index(&self, index: usize) -> &Self::Output {
905 &self.textures[index]
906 }
907}
908
909impl<T> AtlasTextureList<T> {
910 #[allow(unused)]
911 fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
912 self.free_list.clear();
913 self.textures.drain(..)
914 }
915
916 #[allow(dead_code)]
917 fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
918 self.textures.iter_mut().flatten()
919 }
920}
921
922#[derive(Clone, Debug, PartialEq, Eq)]
923#[repr(C)]
924pub(crate) struct AtlasTile {
925 pub(crate) texture_id: AtlasTextureId,
926 pub(crate) tile_id: TileId,
927 pub(crate) padding: u32,
928 pub(crate) bounds: Bounds<DevicePixels>,
929}
930
931#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
932#[repr(C)]
933pub(crate) struct AtlasTextureId {
934 pub(crate) index: u32,
936 pub(crate) kind: AtlasTextureKind,
937}
938
939#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
940#[repr(C)]
941#[cfg_attr(
942 all(
943 any(target_os = "linux", target_os = "freebsd"),
944 not(any(feature = "x11", feature = "wayland"))
945 ),
946 allow(dead_code)
947)]
948pub(crate) enum AtlasTextureKind {
949 Monochrome = 0,
950 Polychrome = 1,
951 Subpixel = 2,
952}
953
954#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
955#[repr(C)]
956pub(crate) struct TileId(pub(crate) u32);
957
958impl From<etagere::AllocId> for TileId {
959 fn from(id: etagere::AllocId) -> Self {
960 Self(id.serialize())
961 }
962}
963
964impl From<TileId> for etagere::AllocId {
965 fn from(id: TileId) -> Self {
966 Self::deserialize(id.0)
967 }
968}
969
970pub(crate) struct PlatformInputHandler {
971 cx: AsyncWindowContext,
972 handler: Box<dyn InputHandler>,
973}
974
975#[cfg_attr(
976 all(
977 any(target_os = "linux", target_os = "freebsd"),
978 not(any(feature = "x11", feature = "wayland"))
979 ),
980 allow(dead_code)
981)]
982impl PlatformInputHandler {
983 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
984 Self { cx, handler }
985 }
986
987 fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
988 self.cx
989 .update(|window, cx| {
990 self.handler
991 .selected_text_range(ignore_disabled_input, window, cx)
992 })
993 .ok()
994 .flatten()
995 }
996
997 #[cfg_attr(target_os = "windows", allow(dead_code))]
998 fn marked_text_range(&mut self) -> Option<Range<usize>> {
999 self.cx
1000 .update(|window, cx| self.handler.marked_text_range(window, cx))
1001 .ok()
1002 .flatten()
1003 }
1004
1005 #[cfg_attr(
1006 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1007 allow(dead_code)
1008 )]
1009 fn text_for_range(
1010 &mut self,
1011 range_utf16: Range<usize>,
1012 adjusted: &mut Option<Range<usize>>,
1013 ) -> Option<String> {
1014 self.cx
1015 .update(|window, cx| {
1016 self.handler
1017 .text_for_range(range_utf16, adjusted, window, cx)
1018 })
1019 .ok()
1020 .flatten()
1021 }
1022
1023 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
1024 self.cx
1025 .update(|window, cx| {
1026 self.handler
1027 .replace_text_in_range(replacement_range, text, window, cx);
1028 })
1029 .ok();
1030 }
1031
1032 pub fn replace_and_mark_text_in_range(
1033 &mut self,
1034 range_utf16: Option<Range<usize>>,
1035 new_text: &str,
1036 new_selected_range: Option<Range<usize>>,
1037 ) {
1038 self.cx
1039 .update(|window, cx| {
1040 self.handler.replace_and_mark_text_in_range(
1041 range_utf16,
1042 new_text,
1043 new_selected_range,
1044 window,
1045 cx,
1046 )
1047 })
1048 .ok();
1049 }
1050
1051 #[cfg_attr(target_os = "windows", allow(dead_code))]
1052 fn unmark_text(&mut self) {
1053 self.cx
1054 .update(|window, cx| self.handler.unmark_text(window, cx))
1055 .ok();
1056 }
1057
1058 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1059 self.cx
1060 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1061 .ok()
1062 .flatten()
1063 }
1064
1065 #[allow(dead_code)]
1066 fn apple_press_and_hold_enabled(&mut self) -> bool {
1067 self.handler.apple_press_and_hold_enabled()
1068 }
1069
1070 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1071 self.handler.replace_text_in_range(None, input, window, cx);
1072 }
1073
1074 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1075 let selection = self.handler.selected_text_range(true, window, cx)?;
1076 self.handler.bounds_for_range(
1077 if selection.reversed {
1078 selection.range.start..selection.range.start
1079 } else {
1080 selection.range.end..selection.range.end
1081 },
1082 window,
1083 cx,
1084 )
1085 }
1086
1087 #[allow(unused)]
1088 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1089 self.cx
1090 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1091 .ok()
1092 .flatten()
1093 }
1094
1095 #[allow(dead_code)]
1096 pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1097 self.handler.accepts_text_input(window, cx)
1098 }
1099}
1100
1101#[derive(Debug)]
1104pub struct UTF16Selection {
1105 pub range: Range<usize>,
1108 pub reversed: bool,
1111}
1112
1113pub trait InputHandler: 'static {
1118 fn selected_text_range(
1123 &mut self,
1124 ignore_disabled_input: bool,
1125 window: &mut Window,
1126 cx: &mut App,
1127 ) -> Option<UTF16Selection>;
1128
1129 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1134
1135 fn text_for_range(
1140 &mut self,
1141 range_utf16: Range<usize>,
1142 adjusted_range: &mut Option<Range<usize>>,
1143 window: &mut Window,
1144 cx: &mut App,
1145 ) -> Option<String>;
1146
1147 fn replace_text_in_range(
1152 &mut self,
1153 replacement_range: Option<Range<usize>>,
1154 text: &str,
1155 window: &mut Window,
1156 cx: &mut App,
1157 );
1158
1159 fn replace_and_mark_text_in_range(
1166 &mut self,
1167 range_utf16: Option<Range<usize>>,
1168 new_text: &str,
1169 new_selected_range: Option<Range<usize>>,
1170 window: &mut Window,
1171 cx: &mut App,
1172 );
1173
1174 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1177
1178 fn bounds_for_range(
1183 &mut self,
1184 range_utf16: Range<usize>,
1185 window: &mut Window,
1186 cx: &mut App,
1187 ) -> Option<Bounds<Pixels>>;
1188
1189 fn character_index_for_point(
1193 &mut self,
1194 point: Point<Pixels>,
1195 window: &mut Window,
1196 cx: &mut App,
1197 ) -> Option<usize>;
1198
1199 #[allow(dead_code)]
1204 fn apple_press_and_hold_enabled(&mut self) -> bool {
1205 true
1206 }
1207
1208 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1210 true
1211 }
1212}
1213
1214#[derive(Debug)]
1216pub struct WindowOptions {
1217 pub window_bounds: Option<WindowBounds>,
1221
1222 pub titlebar: Option<TitlebarOptions>,
1224
1225 pub focus: bool,
1227
1228 pub show: bool,
1230
1231 pub kind: WindowKind,
1233
1234 pub is_movable: bool,
1236
1237 pub is_resizable: bool,
1239
1240 pub is_minimizable: bool,
1242
1243 pub display_id: Option<DisplayId>,
1246
1247 pub window_background: WindowBackgroundAppearance,
1249
1250 pub app_id: Option<String>,
1252
1253 pub window_min_size: Option<Size<Pixels>>,
1255
1256 pub window_decorations: Option<WindowDecorations>,
1259
1260 pub tabbing_identifier: Option<String>,
1262}
1263
1264#[derive(Debug)]
1266#[cfg_attr(
1267 all(
1268 any(target_os = "linux", target_os = "freebsd"),
1269 not(any(feature = "x11", feature = "wayland"))
1270 ),
1271 allow(dead_code)
1272)]
1273pub(crate) struct WindowParams {
1274 pub bounds: Bounds<Pixels>,
1275
1276 #[cfg_attr(feature = "wayland", allow(dead_code))]
1278 pub titlebar: Option<TitlebarOptions>,
1279
1280 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1282 pub kind: WindowKind,
1283
1284 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1286 pub is_movable: bool,
1287
1288 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1290 pub is_resizable: bool,
1291
1292 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1294 pub is_minimizable: bool,
1295
1296 #[cfg_attr(
1297 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1298 allow(dead_code)
1299 )]
1300 pub focus: bool,
1301
1302 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1303 pub show: bool,
1304
1305 #[cfg_attr(feature = "wayland", allow(dead_code))]
1306 pub display_id: Option<DisplayId>,
1307
1308 pub window_min_size: Option<Size<Pixels>>,
1309 #[cfg(target_os = "macos")]
1310 pub tabbing_identifier: Option<String>,
1311}
1312
1313#[derive(Debug, Copy, Clone, PartialEq)]
1315pub enum WindowBounds {
1316 Windowed(Bounds<Pixels>),
1318 Maximized(Bounds<Pixels>),
1321 Fullscreen(Bounds<Pixels>),
1324}
1325
1326impl Default for WindowBounds {
1327 fn default() -> Self {
1328 WindowBounds::Windowed(Bounds::default())
1329 }
1330}
1331
1332impl WindowBounds {
1333 pub fn get_bounds(&self) -> Bounds<Pixels> {
1335 match self {
1336 WindowBounds::Windowed(bounds) => *bounds,
1337 WindowBounds::Maximized(bounds) => *bounds,
1338 WindowBounds::Fullscreen(bounds) => *bounds,
1339 }
1340 }
1341
1342 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1344 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1345 }
1346}
1347
1348impl Default for WindowOptions {
1349 fn default() -> Self {
1350 Self {
1351 window_bounds: None,
1352 titlebar: Some(TitlebarOptions {
1353 title: Default::default(),
1354 appears_transparent: Default::default(),
1355 traffic_light_position: Default::default(),
1356 }),
1357 focus: true,
1358 show: true,
1359 kind: WindowKind::Normal,
1360 is_movable: true,
1361 is_resizable: true,
1362 is_minimizable: true,
1363 display_id: None,
1364 window_background: WindowBackgroundAppearance::default(),
1365 app_id: None,
1366 window_min_size: None,
1367 window_decorations: None,
1368 tabbing_identifier: None,
1369 }
1370 }
1371}
1372
1373#[derive(Debug, Default)]
1375pub struct TitlebarOptions {
1376 pub title: Option<SharedString>,
1378
1379 pub appears_transparent: bool,
1382
1383 pub traffic_light_position: Option<Point<Pixels>>,
1385}
1386
1387#[derive(Clone, Debug, PartialEq, Eq)]
1389pub enum WindowKind {
1390 Normal,
1392
1393 PopUp,
1396
1397 Floating,
1399
1400 #[cfg(all(target_os = "linux", feature = "wayland"))]
1403 LayerShell(layer_shell::LayerShellOptions),
1404
1405 Dialog,
1408}
1409
1410#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1415pub enum WindowAppearance {
1416 #[default]
1420 Light,
1421
1422 VibrantLight,
1426
1427 Dark,
1431
1432 VibrantDark,
1436}
1437
1438#[derive(Copy, Clone, Debug, Default, PartialEq)]
1441pub enum WindowBackgroundAppearance {
1442 #[default]
1450 Opaque,
1451 Transparent,
1453 Blurred,
1457 MicaBackdrop,
1459 MicaAltBackdrop,
1461}
1462
1463#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1465pub enum TextRenderingMode {
1466 #[default]
1468 PlatformDefault,
1469 Subpixel,
1471 Grayscale,
1473}
1474
1475#[derive(Clone, Debug)]
1477pub struct PathPromptOptions {
1478 pub files: bool,
1480 pub directories: bool,
1482 pub multiple: bool,
1484 pub prompt: Option<SharedString>,
1486}
1487
1488#[derive(Copy, Clone, Debug, PartialEq)]
1490pub enum PromptLevel {
1491 Info,
1493
1494 Warning,
1496
1497 Critical,
1499}
1500
1501#[derive(Clone, Debug, PartialEq)]
1503pub enum PromptButton {
1504 Ok(SharedString),
1506 Cancel(SharedString),
1508 Other(SharedString),
1510}
1511
1512impl PromptButton {
1513 pub fn new(label: impl Into<SharedString>) -> Self {
1515 PromptButton::Other(label.into())
1516 }
1517
1518 pub fn ok(label: impl Into<SharedString>) -> Self {
1520 PromptButton::Ok(label.into())
1521 }
1522
1523 pub fn cancel(label: impl Into<SharedString>) -> Self {
1525 PromptButton::Cancel(label.into())
1526 }
1527
1528 #[allow(dead_code)]
1529 pub(crate) fn is_cancel(&self) -> bool {
1530 matches!(self, PromptButton::Cancel(_))
1531 }
1532
1533 pub fn label(&self) -> &SharedString {
1535 match self {
1536 PromptButton::Ok(label) => label,
1537 PromptButton::Cancel(label) => label,
1538 PromptButton::Other(label) => label,
1539 }
1540 }
1541}
1542
1543impl From<&str> for PromptButton {
1544 fn from(value: &str) -> Self {
1545 match value.to_lowercase().as_str() {
1546 "ok" => PromptButton::Ok("Ok".into()),
1547 "cancel" => PromptButton::Cancel("Cancel".into()),
1548 _ => PromptButton::Other(SharedString::from(value.to_owned())),
1549 }
1550 }
1551}
1552
1553#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1555pub enum CursorStyle {
1556 #[default]
1558 Arrow,
1559
1560 IBeam,
1563
1564 Crosshair,
1567
1568 ClosedHand,
1571
1572 OpenHand,
1575
1576 PointingHand,
1579
1580 ResizeLeft,
1583
1584 ResizeRight,
1587
1588 ResizeLeftRight,
1591
1592 ResizeUp,
1595
1596 ResizeDown,
1599
1600 ResizeUpDown,
1603
1604 ResizeUpLeftDownRight,
1607
1608 ResizeUpRightDownLeft,
1611
1612 ResizeColumn,
1615
1616 ResizeRow,
1619
1620 IBeamCursorForVerticalLayout,
1623
1624 OperationNotAllowed,
1627
1628 DragLink,
1631
1632 DragCopy,
1635
1636 ContextualMenu,
1639
1640 None,
1642}
1643
1644#[derive(Clone, Debug, Eq, PartialEq)]
1646pub struct ClipboardItem {
1647 entries: Vec<ClipboardEntry>,
1648}
1649
1650#[derive(Clone, Debug, Eq, PartialEq)]
1652pub enum ClipboardEntry {
1653 String(ClipboardString),
1655 Image(Image),
1657 ExternalPaths(crate::ExternalPaths),
1659}
1660
1661impl ClipboardItem {
1662 pub fn new_string(text: String) -> Self {
1664 Self {
1665 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1666 }
1667 }
1668
1669 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1671 Self {
1672 entries: vec![ClipboardEntry::String(ClipboardString {
1673 text,
1674 metadata: Some(metadata),
1675 })],
1676 }
1677 }
1678
1679 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1681 Self {
1682 entries: vec![ClipboardEntry::String(
1683 ClipboardString::new(text).with_json_metadata(metadata),
1684 )],
1685 }
1686 }
1687
1688 pub fn new_image(image: &Image) -> Self {
1690 Self {
1691 entries: vec![ClipboardEntry::Image(image.clone())],
1692 }
1693 }
1694
1695 pub fn text(&self) -> Option<String> {
1698 let mut answer = String::new();
1699
1700 for entry in self.entries.iter() {
1701 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1702 answer.push_str(text);
1703 }
1704 }
1705
1706 if answer.is_empty() {
1707 for entry in self.entries.iter() {
1708 if let ClipboardEntry::ExternalPaths(paths) = entry {
1709 for path in &paths.0 {
1710 use std::fmt::Write as _;
1711 _ = write!(answer, "{}", path.display());
1712 }
1713 }
1714 }
1715 }
1716
1717 if !answer.is_empty() {
1718 Some(answer)
1719 } else {
1720 None
1721 }
1722 }
1723
1724 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1726 pub fn metadata(&self) -> Option<&String> {
1727 match self.entries().first() {
1728 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1729 clipboard_string.metadata.as_ref()
1730 }
1731 _ => None,
1732 }
1733 }
1734
1735 pub fn entries(&self) -> &[ClipboardEntry] {
1737 &self.entries
1738 }
1739
1740 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1742 self.entries.into_iter()
1743 }
1744}
1745
1746impl From<ClipboardString> for ClipboardEntry {
1747 fn from(value: ClipboardString) -> Self {
1748 Self::String(value)
1749 }
1750}
1751
1752impl From<String> for ClipboardEntry {
1753 fn from(value: String) -> Self {
1754 Self::from(ClipboardString::from(value))
1755 }
1756}
1757
1758impl From<Image> for ClipboardEntry {
1759 fn from(value: Image) -> Self {
1760 Self::Image(value)
1761 }
1762}
1763
1764impl From<ClipboardEntry> for ClipboardItem {
1765 fn from(value: ClipboardEntry) -> Self {
1766 Self {
1767 entries: vec![value],
1768 }
1769 }
1770}
1771
1772impl From<String> for ClipboardItem {
1773 fn from(value: String) -> Self {
1774 Self::from(ClipboardEntry::from(value))
1775 }
1776}
1777
1778impl From<Image> for ClipboardItem {
1779 fn from(value: Image) -> Self {
1780 Self::from(ClipboardEntry::from(value))
1781 }
1782}
1783
1784#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1786pub enum ImageFormat {
1787 Png,
1792 Jpeg,
1794 Webp,
1796 Gif,
1798 Svg,
1800 Bmp,
1802 Tiff,
1804 Ico,
1806}
1807
1808impl ImageFormat {
1809 pub const fn mime_type(self) -> &'static str {
1811 match self {
1812 ImageFormat::Png => "image/png",
1813 ImageFormat::Jpeg => "image/jpeg",
1814 ImageFormat::Webp => "image/webp",
1815 ImageFormat::Gif => "image/gif",
1816 ImageFormat::Svg => "image/svg+xml",
1817 ImageFormat::Bmp => "image/bmp",
1818 ImageFormat::Tiff => "image/tiff",
1819 ImageFormat::Ico => "image/ico",
1820 }
1821 }
1822
1823 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1825 match mime_type {
1826 "image/png" => Some(Self::Png),
1827 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1828 "image/webp" => Some(Self::Webp),
1829 "image/gif" => Some(Self::Gif),
1830 "image/svg+xml" => Some(Self::Svg),
1831 "image/bmp" => Some(Self::Bmp),
1832 "image/tiff" | "image/tif" => Some(Self::Tiff),
1833 "image/ico" => Some(Self::Ico),
1834 _ => None,
1835 }
1836 }
1837}
1838
1839#[derive(Clone, Debug, PartialEq, Eq)]
1841pub struct Image {
1842 pub format: ImageFormat,
1844 pub bytes: Vec<u8>,
1846 id: u64,
1848}
1849
1850impl Hash for Image {
1851 fn hash<H: Hasher>(&self, state: &mut H) {
1852 state.write_u64(self.id);
1853 }
1854}
1855
1856impl Image {
1857 pub fn empty() -> Self {
1859 Self::from_bytes(ImageFormat::Png, Vec::new())
1860 }
1861
1862 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1864 Self {
1865 id: hash(&bytes),
1866 format,
1867 bytes,
1868 }
1869 }
1870
1871 pub fn id(&self) -> u64 {
1873 self.id
1874 }
1875
1876 pub fn use_render_image(
1878 self: Arc<Self>,
1879 window: &mut Window,
1880 cx: &mut App,
1881 ) -> Option<Arc<RenderImage>> {
1882 ImageSource::Image(self)
1883 .use_data(None, window, cx)
1884 .and_then(|result| result.ok())
1885 }
1886
1887 pub fn get_render_image(
1889 self: Arc<Self>,
1890 window: &mut Window,
1891 cx: &mut App,
1892 ) -> Option<Arc<RenderImage>> {
1893 ImageSource::Image(self)
1894 .get_data(None, window, cx)
1895 .and_then(|result| result.ok())
1896 }
1897
1898 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1900 ImageSource::Image(self).remove_asset(cx);
1901 }
1902
1903 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1905 fn frames_for_image(
1906 bytes: &[u8],
1907 format: image::ImageFormat,
1908 ) -> Result<SmallVec<[Frame; 1]>> {
1909 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1910
1911 for pixel in data.chunks_exact_mut(4) {
1913 pixel.swap(0, 2);
1914 }
1915
1916 Ok(SmallVec::from_elem(Frame::new(data), 1))
1917 }
1918
1919 let frames = match self.format {
1920 ImageFormat::Gif => {
1921 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1922 let mut frames = SmallVec::new();
1923
1924 for frame in decoder.into_frames() {
1925 let mut frame = frame?;
1926 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1928 pixel.swap(0, 2);
1929 }
1930 frames.push(frame);
1931 }
1932
1933 frames
1934 }
1935 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1936 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1937 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1938 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1939 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1940 ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
1941 ImageFormat::Svg => {
1942 return svg_renderer
1943 .render_single_frame(&self.bytes, 1.0, false)
1944 .map_err(Into::into);
1945 }
1946 };
1947
1948 Ok(Arc::new(RenderImage::new(frames)))
1949 }
1950
1951 pub fn format(&self) -> ImageFormat {
1953 self.format
1954 }
1955
1956 pub fn bytes(&self) -> &[u8] {
1958 self.bytes.as_slice()
1959 }
1960}
1961
1962#[derive(Clone, Debug, Eq, PartialEq)]
1964pub struct ClipboardString {
1965 pub(crate) text: String,
1966 pub(crate) metadata: Option<String>,
1967}
1968
1969impl ClipboardString {
1970 pub fn new(text: String) -> Self {
1972 Self {
1973 text,
1974 metadata: None,
1975 }
1976 }
1977
1978 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1981 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1982 self
1983 }
1984
1985 pub fn text(&self) -> &String {
1987 &self.text
1988 }
1989
1990 pub fn into_text(self) -> String {
1992 self.text
1993 }
1994
1995 pub fn metadata_json<T>(&self) -> Option<T>
1997 where
1998 T: for<'a> Deserialize<'a>,
1999 {
2000 self.metadata
2001 .as_ref()
2002 .and_then(|m| serde_json::from_str(m).ok())
2003 }
2004
2005 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
2006 pub(crate) fn text_hash(text: &str) -> u64 {
2007 let mut hasher = SeaHasher::new();
2008 text.hash(&mut hasher);
2009 hasher.finish()
2010 }
2011}
2012
2013impl From<String> for ClipboardString {
2014 fn from(value: String) -> Self {
2015 Self {
2016 text: value,
2017 metadata: None,
2018 }
2019 }
2020}