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, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
43 ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task, TaskLabel, Window,
44 WindowControlArea, hash, point, px, size,
45};
46use anyhow::Result;
47use async_task::Runnable;
48use futures::channel::oneshot;
49use image::codecs::gif::GifDecoder;
50use image::{AnimationDecoder as _, Frame};
51use parking::Unparker;
52use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
53use schemars::JsonSchema;
54use seahash::SeaHasher;
55use serde::{Deserialize, Serialize};
56use smallvec::SmallVec;
57use std::borrow::Cow;
58use std::hash::{Hash, Hasher};
59use std::io::Cursor;
60use std::ops;
61use std::time::{Duration, Instant};
62use std::{
63 fmt::{self, Debug},
64 ops::Range,
65 path::{Path, PathBuf},
66 rc::Rc,
67 sync::Arc,
68};
69use strum::EnumIter;
70use uuid::Uuid;
71
72pub use app_menu::*;
73pub use keyboard::*;
74pub use keystroke::*;
75
76#[cfg(any(target_os = "linux", target_os = "freebsd"))]
77pub(crate) use linux::*;
78#[cfg(target_os = "macos")]
79pub(crate) use mac::*;
80pub use semantic_version::SemanticVersion;
81#[cfg(any(test, feature = "test-support"))]
82pub(crate) use test::*;
83#[cfg(target_os = "windows")]
84pub(crate) use windows::*;
85
86#[cfg(any(test, feature = "test-support"))]
87pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
88
89pub fn background_executor() -> BackgroundExecutor {
91 current_platform(true).background_executor()
92}
93
94#[cfg(target_os = "macos")]
95pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
96 Rc::new(MacPlatform::new(headless))
97}
98
99#[cfg(any(target_os = "linux", target_os = "freebsd"))]
100pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
101 #[cfg(feature = "x11")]
102 use anyhow::Context as _;
103
104 if headless {
105 return Rc::new(HeadlessClient::new());
106 }
107
108 match guess_compositor() {
109 #[cfg(feature = "wayland")]
110 "Wayland" => Rc::new(WaylandClient::new()),
111
112 #[cfg(feature = "x11")]
113 "X11" => Rc::new(
114 X11Client::new()
115 .context("Failed to initialize X11 client.")
116 .unwrap(),
117 ),
118
119 "Headless" => Rc::new(HeadlessClient::new()),
120 _ => unreachable!(),
121 }
122}
123
124#[cfg(any(target_os = "linux", target_os = "freebsd"))]
127#[inline]
128pub fn guess_compositor() -> &'static str {
129 if std::env::var_os("ZED_HEADLESS").is_some() {
130 return "Headless";
131 }
132
133 #[cfg(feature = "wayland")]
134 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
135 #[cfg(not(feature = "wayland"))]
136 let wayland_display: Option<std::ffi::OsString> = None;
137
138 #[cfg(feature = "x11")]
139 let x11_display = std::env::var_os("DISPLAY");
140 #[cfg(not(feature = "x11"))]
141 let x11_display: Option<std::ffi::OsString> = None;
142
143 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
144 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
145
146 if use_wayland {
147 "Wayland"
148 } else if use_x11 {
149 "X11"
150 } else {
151 "Headless"
152 }
153}
154
155#[cfg(target_os = "windows")]
156pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
157 Rc::new(
158 WindowsPlatform::new()
159 .inspect_err(|err| show_error("Failed to launch", err.to_string()))
160 .unwrap(),
161 )
162}
163
164pub(crate) trait Platform: 'static {
165 fn background_executor(&self) -> BackgroundExecutor;
166 fn foreground_executor(&self) -> ForegroundExecutor;
167 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
168
169 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
170 fn quit(&self);
171 fn restart(&self, binary_path: Option<PathBuf>);
172 fn activate(&self, ignoring_other_apps: bool);
173 fn hide(&self);
174 fn hide_other_apps(&self);
175 fn unhide_other_apps(&self);
176
177 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
178 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
179 fn active_window(&self) -> Option<AnyWindowHandle>;
180 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
181 None
182 }
183
184 #[cfg(feature = "screen-capture")]
185 fn is_screen_capture_supported(&self) -> bool;
186 #[cfg(not(feature = "screen-capture"))]
187 fn is_screen_capture_supported(&self) -> bool {
188 false
189 }
190 #[cfg(feature = "screen-capture")]
191 fn screen_capture_sources(&self)
192 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
193 #[cfg(not(feature = "screen-capture"))]
194 fn screen_capture_sources(
195 &self,
196 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
197 let (sources_tx, sources_rx) = oneshot::channel();
198 sources_tx
199 .send(Err(anyhow::anyhow!(
200 "gpui was compiled without the screen-capture feature"
201 )))
202 .ok();
203 sources_rx
204 }
205
206 fn open_window(
207 &self,
208 handle: AnyWindowHandle,
209 options: WindowParams,
210 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
211
212 fn window_appearance(&self) -> WindowAppearance;
214
215 fn open_url(&self, url: &str);
216 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
217 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
218
219 fn prompt_for_paths(
220 &self,
221 options: PathPromptOptions,
222 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
223 fn prompt_for_new_path(
224 &self,
225 directory: &Path,
226 suggested_name: Option<&str>,
227 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
228 fn can_select_mixed_files_and_dirs(&self) -> bool;
229 fn reveal_path(&self, path: &Path);
230 fn open_with_system(&self, path: &Path);
231
232 fn on_quit(&self, callback: Box<dyn FnMut()>);
233 fn on_reopen(&self, callback: Box<dyn FnMut()>);
234
235 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
236 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
237 None
238 }
239
240 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
241 fn perform_dock_menu_action(&self, _action: usize) {}
242 fn add_recent_document(&self, _path: &Path) {}
243 fn update_jump_list(
244 &self,
245 _menus: Vec<MenuItem>,
246 _entries: Vec<SmallVec<[PathBuf; 2]>>,
247 ) -> Vec<SmallVec<[PathBuf; 2]>> {
248 Vec::new()
249 }
250 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
251 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
252 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
253
254 fn compositor_name(&self) -> &'static str {
255 ""
256 }
257 fn app_path(&self) -> Result<PathBuf>;
258 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
259
260 fn set_cursor_style(&self, style: CursorStyle);
261 fn should_auto_hide_scrollbars(&self) -> bool;
262
263 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
264 fn write_to_primary(&self, item: ClipboardItem);
265 fn write_to_clipboard(&self, item: ClipboardItem);
266 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
267 fn read_from_primary(&self) -> Option<ClipboardItem>;
268 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
269
270 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
271 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
272 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
273
274 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
275 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
276 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
277}
278
279pub trait PlatformDisplay: Send + Sync + Debug {
281 fn id(&self) -> DisplayId;
283
284 fn uuid(&self) -> Result<Uuid>;
287
288 fn bounds(&self) -> Bounds<Pixels>;
290
291 fn default_bounds(&self) -> Bounds<Pixels> {
293 let center = self.bounds().center();
294 let offset = DEFAULT_WINDOW_SIZE / 2.0;
295 let origin = point(center.x - offset.width, center.y - offset.height);
296 Bounds::new(origin, DEFAULT_WINDOW_SIZE)
297 }
298}
299
300#[derive(Clone)]
302pub struct SourceMetadata {
303 pub id: u64,
305 pub label: Option<SharedString>,
307 pub is_main: Option<bool>,
309 pub resolution: Size<DevicePixels>,
311}
312
313pub trait ScreenCaptureSource {
315 fn metadata(&self) -> Result<SourceMetadata>;
317
318 fn stream(
321 &self,
322 foreground_executor: &ForegroundExecutor,
323 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
324 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
325}
326
327pub trait ScreenCaptureStream {
329 fn metadata(&self) -> Result<SourceMetadata>;
331}
332
333pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
335
336#[derive(PartialEq, Eq, Hash, Copy, Clone)]
338pub struct DisplayId(pub(crate) u32);
339
340impl From<DisplayId> for u32 {
341 fn from(id: DisplayId) -> Self {
342 id.0
343 }
344}
345
346impl Debug for DisplayId {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 write!(f, "DisplayId({})", self.0)
349 }
350}
351
352unsafe impl Send for DisplayId {}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub enum ResizeEdge {
357 Top,
359 TopRight,
361 Right,
363 BottomRight,
365 Bottom,
367 BottomLeft,
369 Left,
371 TopLeft,
373}
374
375#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
377pub enum WindowDecorations {
378 #[default]
379 Server,
381 Client,
383}
384
385#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
387pub enum Decorations {
388 #[default]
390 Server,
391 Client {
393 tiling: Tiling,
395 },
396}
397
398#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
400pub struct WindowControls {
401 pub fullscreen: bool,
403 pub maximize: bool,
405 pub minimize: bool,
407 pub window_menu: bool,
409}
410
411impl Default for WindowControls {
412 fn default() -> Self {
413 Self {
415 fullscreen: true,
416 maximize: true,
417 minimize: true,
418 window_menu: true,
419 }
420 }
421}
422
423#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
425pub struct Tiling {
426 pub top: bool,
428 pub left: bool,
430 pub right: bool,
432 pub bottom: bool,
434}
435
436impl Tiling {
437 pub fn tiled() -> Self {
439 Self {
440 top: true,
441 left: true,
442 right: true,
443 bottom: true,
444 }
445 }
446
447 pub fn is_tiled(&self) -> bool {
449 self.top || self.left || self.right || self.bottom
450 }
451}
452
453#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
454pub(crate) struct RequestFrameOptions {
455 pub(crate) require_presentation: bool,
456 pub(crate) force_render: bool,
458}
459
460pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
461 fn bounds(&self) -> Bounds<Pixels>;
462 fn is_maximized(&self) -> bool;
463 fn window_bounds(&self) -> WindowBounds;
464 fn content_size(&self) -> Size<Pixels>;
465 fn resize(&mut self, size: Size<Pixels>);
466 fn scale_factor(&self) -> f32;
467 fn appearance(&self) -> WindowAppearance;
468 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
469 fn mouse_position(&self) -> Point<Pixels>;
470 fn modifiers(&self) -> Modifiers;
471 fn capslock(&self) -> Capslock;
472 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
473 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
474 fn prompt(
475 &self,
476 level: PromptLevel,
477 msg: &str,
478 detail: Option<&str>,
479 answers: &[PromptButton],
480 ) -> Option<oneshot::Receiver<usize>>;
481 fn activate(&self);
482 fn is_active(&self) -> bool;
483 fn is_hovered(&self) -> bool;
484 fn set_title(&mut self, title: &str);
485 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
486 fn minimize(&self);
487 fn zoom(&self);
488 fn toggle_fullscreen(&self);
489 fn is_fullscreen(&self) -> bool;
490 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
491 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
492 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
493 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
494 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
495 fn on_moved(&self, callback: Box<dyn FnMut()>);
496 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
497 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
498 fn on_close(&self, callback: Box<dyn FnOnce()>);
499 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
500 fn draw(&self, scene: &Scene);
501 fn completed_frame(&self) {}
502 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
503
504 fn get_title(&self) -> String {
506 String::new()
507 }
508 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
509 None
510 }
511 fn tab_bar_visible(&self) -> bool {
512 false
513 }
514 fn set_edited(&mut self, _edited: bool) {}
515 fn show_character_palette(&self) {}
516 fn titlebar_double_click(&self) {}
517 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
518 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
519 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
520 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
521 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
522 fn merge_all_windows(&self) {}
523 fn move_tab_to_new_window(&self) {}
524 fn toggle_window_tab_overview(&self) {}
525 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
526
527 #[cfg(target_os = "windows")]
528 fn get_raw_handle(&self) -> windows::HWND;
529
530 fn inner_window_bounds(&self) -> WindowBounds {
532 self.window_bounds()
533 }
534 fn request_decorations(&self, _decorations: WindowDecorations) {}
535 fn show_window_menu(&self, _position: Point<Pixels>) {}
536 fn start_window_move(&self) {}
537 fn start_window_resize(&self, _edge: ResizeEdge) {}
538 fn window_decorations(&self) -> Decorations {
539 Decorations::Server
540 }
541 fn set_app_id(&mut self, _app_id: &str) {}
542 fn map_window(&mut self) -> anyhow::Result<()> {
543 Ok(())
544 }
545 fn window_controls(&self) -> WindowControls {
546 WindowControls::default()
547 }
548 fn set_client_inset(&self, _inset: Pixels) {}
549 fn gpu_specs(&self) -> Option<GpuSpecs>;
550
551 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
552
553 #[cfg(any(test, feature = "test-support"))]
554 fn as_test(&mut self) -> Option<&mut TestWindow> {
555 None
556 }
557}
558
559#[doc(hidden)]
562pub trait PlatformDispatcher: Send + Sync {
563 fn is_main_thread(&self) -> bool;
564 fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
565 fn dispatch_on_main_thread(&self, runnable: Runnable);
566 fn dispatch_after(&self, duration: Duration, runnable: Runnable);
567 fn park(&self, timeout: Option<Duration>) -> bool;
568 fn unparker(&self) -> Unparker;
569 fn now(&self) -> Instant {
570 Instant::now()
571 }
572
573 #[cfg(any(test, feature = "test-support"))]
574 fn as_test(&self) -> Option<&TestDispatcher> {
575 None
576 }
577}
578
579pub(crate) trait PlatformTextSystem: Send + Sync {
580 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
581 fn all_font_names(&self) -> Vec<String>;
582 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
583 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
584 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
585 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
586 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
587 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
588 fn rasterize_glyph(
589 &self,
590 params: &RenderGlyphParams,
591 raster_bounds: Bounds<DevicePixels>,
592 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
593 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
594}
595
596pub(crate) struct NoopTextSystem;
597
598impl NoopTextSystem {
599 #[allow(dead_code)]
600 pub fn new() -> Self {
601 Self
602 }
603}
604
605impl PlatformTextSystem for NoopTextSystem {
606 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
607 Ok(())
608 }
609
610 fn all_font_names(&self) -> Vec<String> {
611 Vec::new()
612 }
613
614 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
615 Ok(FontId(1))
616 }
617
618 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
619 FontMetrics {
620 units_per_em: 1000,
621 ascent: 1025.0,
622 descent: -275.0,
623 line_gap: 0.0,
624 underline_position: -95.0,
625 underline_thickness: 60.0,
626 cap_height: 698.0,
627 x_height: 516.0,
628 bounding_box: Bounds {
629 origin: Point {
630 x: -260.0,
631 y: -245.0,
632 },
633 size: Size {
634 width: 1501.0,
635 height: 1364.0,
636 },
637 },
638 }
639 }
640
641 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
642 Ok(Bounds {
643 origin: Point { x: 54.0, y: 0.0 },
644 size: size(392.0, 528.0),
645 })
646 }
647
648 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
649 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
650 }
651
652 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
653 Some(GlyphId(ch.len_utf16() as u32))
654 }
655
656 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
657 Ok(Default::default())
658 }
659
660 fn rasterize_glyph(
661 &self,
662 _params: &RenderGlyphParams,
663 raster_bounds: Bounds<DevicePixels>,
664 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
665 Ok((raster_bounds.size, Vec::new()))
666 }
667
668 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
669 let mut position = px(0.);
670 let metrics = self.font_metrics(FontId(0));
671 let em_width = font_size
672 * self
673 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
674 .unwrap()
675 .width
676 / metrics.units_per_em as f32;
677 let mut glyphs = Vec::new();
678 for (ix, c) in text.char_indices() {
679 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
680 glyphs.push(ShapedGlyph {
681 id: glyph,
682 position: point(position, px(0.)),
683 index: ix,
684 is_emoji: glyph.0 == 2,
685 });
686 if glyph.0 == 2 {
687 position += em_width * 2.0;
688 } else {
689 position += em_width;
690 }
691 } else {
692 position += em_width
693 }
694 }
695 let mut runs = Vec::default();
696 if !glyphs.is_empty() {
697 runs.push(ShapedRun {
698 font_id: FontId(0),
699 glyphs,
700 });
701 } else {
702 position = px(0.);
703 }
704
705 LineLayout {
706 font_size,
707 width: position,
708 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
709 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
710 runs,
711 len: text.len(),
712 }
713 }
714}
715
716#[derive(PartialEq, Eq, Hash, Clone)]
717pub(crate) enum AtlasKey {
718 Glyph(RenderGlyphParams),
719 Svg(RenderSvgParams),
720 Image(RenderImageParams),
721}
722
723impl AtlasKey {
724 #[cfg_attr(
725 all(
726 any(target_os = "linux", target_os = "freebsd"),
727 not(any(feature = "x11", feature = "wayland"))
728 ),
729 allow(dead_code)
730 )]
731 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
732 match self {
733 AtlasKey::Glyph(params) => {
734 if params.is_emoji {
735 AtlasTextureKind::Polychrome
736 } else {
737 AtlasTextureKind::Monochrome
738 }
739 }
740 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
741 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
742 }
743 }
744}
745
746impl From<RenderGlyphParams> for AtlasKey {
747 fn from(params: RenderGlyphParams) -> Self {
748 Self::Glyph(params)
749 }
750}
751
752impl From<RenderSvgParams> for AtlasKey {
753 fn from(params: RenderSvgParams) -> Self {
754 Self::Svg(params)
755 }
756}
757
758impl From<RenderImageParams> for AtlasKey {
759 fn from(params: RenderImageParams) -> Self {
760 Self::Image(params)
761 }
762}
763
764pub(crate) trait PlatformAtlas: Send + Sync {
765 fn get_or_insert_with<'a>(
766 &self,
767 key: &AtlasKey,
768 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
769 ) -> Result<Option<AtlasTile>>;
770 fn remove(&self, key: &AtlasKey);
771}
772
773struct AtlasTextureList<T> {
774 textures: Vec<Option<T>>,
775 free_list: Vec<usize>,
776}
777
778impl<T> Default for AtlasTextureList<T> {
779 fn default() -> Self {
780 Self {
781 textures: Vec::default(),
782 free_list: Vec::default(),
783 }
784 }
785}
786
787impl<T> ops::Index<usize> for AtlasTextureList<T> {
788 type Output = Option<T>;
789
790 fn index(&self, index: usize) -> &Self::Output {
791 &self.textures[index]
792 }
793}
794
795impl<T> AtlasTextureList<T> {
796 #[allow(unused)]
797 fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
798 self.free_list.clear();
799 self.textures.drain(..)
800 }
801
802 #[allow(dead_code)]
803 fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
804 self.textures.iter_mut().flatten()
805 }
806}
807
808#[derive(Clone, Debug, PartialEq, Eq)]
809#[repr(C)]
810pub(crate) struct AtlasTile {
811 pub(crate) texture_id: AtlasTextureId,
812 pub(crate) tile_id: TileId,
813 pub(crate) padding: u32,
814 pub(crate) bounds: Bounds<DevicePixels>,
815}
816
817#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
818#[repr(C)]
819pub(crate) struct AtlasTextureId {
820 pub(crate) index: u32,
822 pub(crate) kind: AtlasTextureKind,
823}
824
825#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
826#[repr(C)]
827#[cfg_attr(
828 all(
829 any(target_os = "linux", target_os = "freebsd"),
830 not(any(feature = "x11", feature = "wayland"))
831 ),
832 allow(dead_code)
833)]
834pub(crate) enum AtlasTextureKind {
835 Monochrome = 0,
836 Polychrome = 1,
837}
838
839#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
840#[repr(C)]
841pub(crate) struct TileId(pub(crate) u32);
842
843impl From<etagere::AllocId> for TileId {
844 fn from(id: etagere::AllocId) -> Self {
845 Self(id.serialize())
846 }
847}
848
849impl From<TileId> for etagere::AllocId {
850 fn from(id: TileId) -> Self {
851 Self::deserialize(id.0)
852 }
853}
854
855pub(crate) struct PlatformInputHandler {
856 cx: AsyncWindowContext,
857 handler: Box<dyn InputHandler>,
858}
859
860#[cfg_attr(
861 all(
862 any(target_os = "linux", target_os = "freebsd"),
863 not(any(feature = "x11", feature = "wayland"))
864 ),
865 allow(dead_code)
866)]
867impl PlatformInputHandler {
868 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
869 Self { cx, handler }
870 }
871
872 fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
873 self.cx
874 .update(|window, cx| {
875 self.handler
876 .selected_text_range(ignore_disabled_input, window, cx)
877 })
878 .ok()
879 .flatten()
880 }
881
882 #[cfg_attr(target_os = "windows", allow(dead_code))]
883 fn marked_text_range(&mut self) -> Option<Range<usize>> {
884 self.cx
885 .update(|window, cx| self.handler.marked_text_range(window, cx))
886 .ok()
887 .flatten()
888 }
889
890 #[cfg_attr(
891 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
892 allow(dead_code)
893 )]
894 fn text_for_range(
895 &mut self,
896 range_utf16: Range<usize>,
897 adjusted: &mut Option<Range<usize>>,
898 ) -> Option<String> {
899 self.cx
900 .update(|window, cx| {
901 self.handler
902 .text_for_range(range_utf16, adjusted, window, cx)
903 })
904 .ok()
905 .flatten()
906 }
907
908 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
909 self.cx
910 .update(|window, cx| {
911 self.handler
912 .replace_text_in_range(replacement_range, text, window, cx);
913 })
914 .ok();
915 }
916
917 pub fn replace_and_mark_text_in_range(
918 &mut self,
919 range_utf16: Option<Range<usize>>,
920 new_text: &str,
921 new_selected_range: Option<Range<usize>>,
922 ) {
923 self.cx
924 .update(|window, cx| {
925 self.handler.replace_and_mark_text_in_range(
926 range_utf16,
927 new_text,
928 new_selected_range,
929 window,
930 cx,
931 )
932 })
933 .ok();
934 }
935
936 #[cfg_attr(target_os = "windows", allow(dead_code))]
937 fn unmark_text(&mut self) {
938 self.cx
939 .update(|window, cx| self.handler.unmark_text(window, cx))
940 .ok();
941 }
942
943 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
944 self.cx
945 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
946 .ok()
947 .flatten()
948 }
949
950 #[allow(dead_code)]
951 fn apple_press_and_hold_enabled(&mut self) -> bool {
952 self.handler.apple_press_and_hold_enabled()
953 }
954
955 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
956 self.handler.replace_text_in_range(None, input, window, cx);
957 }
958
959 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
960 let selection = self.handler.selected_text_range(true, window, cx)?;
961 self.handler.bounds_for_range(
962 if selection.reversed {
963 selection.range.start..selection.range.start
964 } else {
965 selection.range.end..selection.range.end
966 },
967 window,
968 cx,
969 )
970 }
971
972 #[allow(unused)]
973 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
974 self.cx
975 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
976 .ok()
977 .flatten()
978 }
979}
980
981#[derive(Debug)]
984pub struct UTF16Selection {
985 pub range: Range<usize>,
988 pub reversed: bool,
991}
992
993pub trait InputHandler: 'static {
998 fn selected_text_range(
1003 &mut self,
1004 ignore_disabled_input: bool,
1005 window: &mut Window,
1006 cx: &mut App,
1007 ) -> Option<UTF16Selection>;
1008
1009 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1014
1015 fn text_for_range(
1020 &mut self,
1021 range_utf16: Range<usize>,
1022 adjusted_range: &mut Option<Range<usize>>,
1023 window: &mut Window,
1024 cx: &mut App,
1025 ) -> Option<String>;
1026
1027 fn replace_text_in_range(
1032 &mut self,
1033 replacement_range: Option<Range<usize>>,
1034 text: &str,
1035 window: &mut Window,
1036 cx: &mut App,
1037 );
1038
1039 fn replace_and_mark_text_in_range(
1046 &mut self,
1047 range_utf16: Option<Range<usize>>,
1048 new_text: &str,
1049 new_selected_range: Option<Range<usize>>,
1050 window: &mut Window,
1051 cx: &mut App,
1052 );
1053
1054 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1057
1058 fn bounds_for_range(
1063 &mut self,
1064 range_utf16: Range<usize>,
1065 window: &mut Window,
1066 cx: &mut App,
1067 ) -> Option<Bounds<Pixels>>;
1068
1069 fn character_index_for_point(
1073 &mut self,
1074 point: Point<Pixels>,
1075 window: &mut Window,
1076 cx: &mut App,
1077 ) -> Option<usize>;
1078
1079 #[allow(dead_code)]
1084 fn apple_press_and_hold_enabled(&mut self) -> bool {
1085 true
1086 }
1087}
1088
1089#[derive(Debug)]
1091pub struct WindowOptions {
1092 pub window_bounds: Option<WindowBounds>,
1096
1097 pub titlebar: Option<TitlebarOptions>,
1099
1100 pub focus: bool,
1102
1103 pub show: bool,
1105
1106 pub kind: WindowKind,
1108
1109 pub is_movable: bool,
1111
1112 pub is_resizable: bool,
1114
1115 pub is_minimizable: bool,
1117
1118 pub display_id: Option<DisplayId>,
1121
1122 pub window_background: WindowBackgroundAppearance,
1124
1125 pub app_id: Option<String>,
1127
1128 pub window_min_size: Option<Size<Pixels>>,
1130
1131 pub window_decorations: Option<WindowDecorations>,
1134
1135 pub tabbing_identifier: Option<String>,
1137}
1138
1139#[derive(Debug)]
1141#[cfg_attr(
1142 all(
1143 any(target_os = "linux", target_os = "freebsd"),
1144 not(any(feature = "x11", feature = "wayland"))
1145 ),
1146 allow(dead_code)
1147)]
1148pub(crate) struct WindowParams {
1149 pub bounds: Bounds<Pixels>,
1150
1151 #[cfg_attr(feature = "wayland", allow(dead_code))]
1153 pub titlebar: Option<TitlebarOptions>,
1154
1155 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1157 pub kind: WindowKind,
1158
1159 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1161 pub is_movable: bool,
1162
1163 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1165 pub is_resizable: bool,
1166
1167 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1169 pub is_minimizable: bool,
1170
1171 #[cfg_attr(
1172 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1173 allow(dead_code)
1174 )]
1175 pub focus: bool,
1176
1177 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1178 pub show: bool,
1179
1180 #[cfg_attr(feature = "wayland", allow(dead_code))]
1181 pub display_id: Option<DisplayId>,
1182
1183 pub window_min_size: Option<Size<Pixels>>,
1184 #[cfg(target_os = "macos")]
1185 pub tabbing_identifier: Option<String>,
1186}
1187
1188#[derive(Debug, Copy, Clone, PartialEq)]
1190pub enum WindowBounds {
1191 Windowed(Bounds<Pixels>),
1193 Maximized(Bounds<Pixels>),
1196 Fullscreen(Bounds<Pixels>),
1199}
1200
1201impl Default for WindowBounds {
1202 fn default() -> Self {
1203 WindowBounds::Windowed(Bounds::default())
1204 }
1205}
1206
1207impl WindowBounds {
1208 pub fn get_bounds(&self) -> Bounds<Pixels> {
1210 match self {
1211 WindowBounds::Windowed(bounds) => *bounds,
1212 WindowBounds::Maximized(bounds) => *bounds,
1213 WindowBounds::Fullscreen(bounds) => *bounds,
1214 }
1215 }
1216
1217 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1219 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1220 }
1221}
1222
1223impl Default for WindowOptions {
1224 fn default() -> Self {
1225 Self {
1226 window_bounds: None,
1227 titlebar: Some(TitlebarOptions {
1228 title: Default::default(),
1229 appears_transparent: Default::default(),
1230 traffic_light_position: Default::default(),
1231 }),
1232 focus: true,
1233 show: true,
1234 kind: WindowKind::Normal,
1235 is_movable: true,
1236 is_resizable: true,
1237 is_minimizable: true,
1238 display_id: None,
1239 window_background: WindowBackgroundAppearance::default(),
1240 app_id: None,
1241 window_min_size: None,
1242 window_decorations: None,
1243 tabbing_identifier: None,
1244 }
1245 }
1246}
1247
1248#[derive(Debug, Default)]
1250pub struct TitlebarOptions {
1251 pub title: Option<SharedString>,
1253
1254 pub appears_transparent: bool,
1257
1258 pub traffic_light_position: Option<Point<Pixels>>,
1260}
1261
1262#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1264pub enum WindowKind {
1265 Normal,
1267
1268 PopUp,
1271
1272 Floating,
1274}
1275
1276#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1281pub enum WindowAppearance {
1282 Light,
1286
1287 VibrantLight,
1291
1292 Dark,
1296
1297 VibrantDark,
1301}
1302
1303impl Default for WindowAppearance {
1304 fn default() -> Self {
1305 Self::Light
1306 }
1307}
1308
1309#[derive(Copy, Clone, Debug, Default, PartialEq)]
1312pub enum WindowBackgroundAppearance {
1313 #[default]
1321 Opaque,
1322 Transparent,
1324 Blurred,
1328}
1329
1330#[derive(Clone, Debug)]
1332pub struct PathPromptOptions {
1333 pub files: bool,
1335 pub directories: bool,
1337 pub multiple: bool,
1339 pub prompt: Option<SharedString>,
1341}
1342
1343#[derive(Copy, Clone, Debug, PartialEq)]
1345pub enum PromptLevel {
1346 Info,
1348
1349 Warning,
1351
1352 Critical,
1354}
1355
1356#[derive(Clone, Debug, PartialEq)]
1358pub enum PromptButton {
1359 Ok(SharedString),
1361 Cancel(SharedString),
1363 Other(SharedString),
1365}
1366
1367impl PromptButton {
1368 pub fn new(label: impl Into<SharedString>) -> Self {
1370 PromptButton::Other(label.into())
1371 }
1372
1373 pub fn ok(label: impl Into<SharedString>) -> Self {
1375 PromptButton::Ok(label.into())
1376 }
1377
1378 pub fn cancel(label: impl Into<SharedString>) -> Self {
1380 PromptButton::Cancel(label.into())
1381 }
1382
1383 #[allow(dead_code)]
1384 pub(crate) fn is_cancel(&self) -> bool {
1385 matches!(self, PromptButton::Cancel(_))
1386 }
1387
1388 pub fn label(&self) -> &SharedString {
1390 match self {
1391 PromptButton::Ok(label) => label,
1392 PromptButton::Cancel(label) => label,
1393 PromptButton::Other(label) => label,
1394 }
1395 }
1396}
1397
1398impl From<&str> for PromptButton {
1399 fn from(value: &str) -> Self {
1400 match value.to_lowercase().as_str() {
1401 "ok" => PromptButton::Ok("Ok".into()),
1402 "cancel" => PromptButton::Cancel("Cancel".into()),
1403 _ => PromptButton::Other(SharedString::from(value.to_owned())),
1404 }
1405 }
1406}
1407
1408#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1410pub enum CursorStyle {
1411 Arrow,
1413
1414 IBeam,
1417
1418 Crosshair,
1421
1422 ClosedHand,
1425
1426 OpenHand,
1429
1430 PointingHand,
1433
1434 ResizeLeft,
1437
1438 ResizeRight,
1441
1442 ResizeLeftRight,
1445
1446 ResizeUp,
1449
1450 ResizeDown,
1453
1454 ResizeUpDown,
1457
1458 ResizeUpLeftDownRight,
1461
1462 ResizeUpRightDownLeft,
1465
1466 ResizeColumn,
1469
1470 ResizeRow,
1473
1474 IBeamCursorForVerticalLayout,
1477
1478 OperationNotAllowed,
1481
1482 DragLink,
1485
1486 DragCopy,
1489
1490 ContextualMenu,
1493
1494 None,
1496}
1497
1498impl Default for CursorStyle {
1499 fn default() -> Self {
1500 Self::Arrow
1501 }
1502}
1503
1504#[derive(Clone, Debug, Eq, PartialEq)]
1506pub struct ClipboardItem {
1507 entries: Vec<ClipboardEntry>,
1508}
1509
1510#[derive(Clone, Debug, Eq, PartialEq)]
1512pub enum ClipboardEntry {
1513 String(ClipboardString),
1515 Image(Image),
1517}
1518
1519impl ClipboardItem {
1520 pub fn new_string(text: String) -> Self {
1522 Self {
1523 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1524 }
1525 }
1526
1527 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1529 Self {
1530 entries: vec![ClipboardEntry::String(ClipboardString {
1531 text,
1532 metadata: Some(metadata),
1533 })],
1534 }
1535 }
1536
1537 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1539 Self {
1540 entries: vec![ClipboardEntry::String(
1541 ClipboardString::new(text).with_json_metadata(metadata),
1542 )],
1543 }
1544 }
1545
1546 pub fn new_image(image: &Image) -> Self {
1548 Self {
1549 entries: vec![ClipboardEntry::Image(image.clone())],
1550 }
1551 }
1552
1553 pub fn text(&self) -> Option<String> {
1556 let mut answer = String::new();
1557 let mut any_entries = false;
1558
1559 for entry in self.entries.iter() {
1560 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1561 answer.push_str(text);
1562 any_entries = true;
1563 }
1564 }
1565
1566 if any_entries { Some(answer) } else { None }
1567 }
1568
1569 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1571 pub fn metadata(&self) -> Option<&String> {
1572 match self.entries().first() {
1573 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1574 clipboard_string.metadata.as_ref()
1575 }
1576 _ => None,
1577 }
1578 }
1579
1580 pub fn entries(&self) -> &[ClipboardEntry] {
1582 &self.entries
1583 }
1584
1585 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1587 self.entries.into_iter()
1588 }
1589}
1590
1591impl From<ClipboardString> for ClipboardEntry {
1592 fn from(value: ClipboardString) -> Self {
1593 Self::String(value)
1594 }
1595}
1596
1597impl From<String> for ClipboardEntry {
1598 fn from(value: String) -> Self {
1599 Self::from(ClipboardString::from(value))
1600 }
1601}
1602
1603impl From<Image> for ClipboardEntry {
1604 fn from(value: Image) -> Self {
1605 Self::Image(value)
1606 }
1607}
1608
1609impl From<ClipboardEntry> for ClipboardItem {
1610 fn from(value: ClipboardEntry) -> Self {
1611 Self {
1612 entries: vec![value],
1613 }
1614 }
1615}
1616
1617impl From<String> for ClipboardItem {
1618 fn from(value: String) -> Self {
1619 Self::from(ClipboardEntry::from(value))
1620 }
1621}
1622
1623impl From<Image> for ClipboardItem {
1624 fn from(value: Image) -> Self {
1625 Self::from(ClipboardEntry::from(value))
1626 }
1627}
1628
1629#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1631pub enum ImageFormat {
1632 Png,
1637 Jpeg,
1639 Webp,
1641 Gif,
1643 Svg,
1645 Bmp,
1647 Tiff,
1649}
1650
1651impl ImageFormat {
1652 pub const fn mime_type(self) -> &'static str {
1654 match self {
1655 ImageFormat::Png => "image/png",
1656 ImageFormat::Jpeg => "image/jpeg",
1657 ImageFormat::Webp => "image/webp",
1658 ImageFormat::Gif => "image/gif",
1659 ImageFormat::Svg => "image/svg+xml",
1660 ImageFormat::Bmp => "image/bmp",
1661 ImageFormat::Tiff => "image/tiff",
1662 }
1663 }
1664
1665 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1667 match mime_type {
1668 "image/png" => Some(Self::Png),
1669 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1670 "image/webp" => Some(Self::Webp),
1671 "image/gif" => Some(Self::Gif),
1672 "image/svg+xml" => Some(Self::Svg),
1673 "image/bmp" => Some(Self::Bmp),
1674 "image/tiff" | "image/tif" => Some(Self::Tiff),
1675 _ => None,
1676 }
1677 }
1678}
1679
1680#[derive(Clone, Debug, PartialEq, Eq)]
1682pub struct Image {
1683 pub format: ImageFormat,
1685 pub bytes: Vec<u8>,
1687 id: u64,
1689}
1690
1691impl Hash for Image {
1692 fn hash<H: Hasher>(&self, state: &mut H) {
1693 state.write_u64(self.id);
1694 }
1695}
1696
1697impl Image {
1698 pub fn empty() -> Self {
1700 Self::from_bytes(ImageFormat::Png, Vec::new())
1701 }
1702
1703 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1705 Self {
1706 id: hash(&bytes),
1707 format,
1708 bytes,
1709 }
1710 }
1711
1712 pub fn id(&self) -> u64 {
1714 self.id
1715 }
1716
1717 pub fn use_render_image(
1719 self: Arc<Self>,
1720 window: &mut Window,
1721 cx: &mut App,
1722 ) -> Option<Arc<RenderImage>> {
1723 ImageSource::Image(self)
1724 .use_data(None, window, cx)
1725 .and_then(|result| result.ok())
1726 }
1727
1728 pub fn get_render_image(
1730 self: Arc<Self>,
1731 window: &mut Window,
1732 cx: &mut App,
1733 ) -> Option<Arc<RenderImage>> {
1734 ImageSource::Image(self)
1735 .get_data(None, window, cx)
1736 .and_then(|result| result.ok())
1737 }
1738
1739 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1741 ImageSource::Image(self).remove_asset(cx);
1742 }
1743
1744 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1746 fn frames_for_image(
1747 bytes: &[u8],
1748 format: image::ImageFormat,
1749 ) -> Result<SmallVec<[Frame; 1]>> {
1750 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1751
1752 for pixel in data.chunks_exact_mut(4) {
1754 pixel.swap(0, 2);
1755 }
1756
1757 Ok(SmallVec::from_elem(Frame::new(data), 1))
1758 }
1759
1760 let frames = match self.format {
1761 ImageFormat::Gif => {
1762 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1763 let mut frames = SmallVec::new();
1764
1765 for frame in decoder.into_frames() {
1766 let mut frame = frame?;
1767 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1769 pixel.swap(0, 2);
1770 }
1771 frames.push(frame);
1772 }
1773
1774 frames
1775 }
1776 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1777 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1778 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1779 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1780 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1781 ImageFormat::Svg => {
1782 let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
1783
1784 let buffer =
1785 image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
1786 .unwrap();
1787
1788 SmallVec::from_elem(Frame::new(buffer), 1)
1789 }
1790 };
1791
1792 Ok(Arc::new(RenderImage::new(frames)))
1793 }
1794
1795 pub fn format(&self) -> ImageFormat {
1797 self.format
1798 }
1799
1800 pub fn bytes(&self) -> &[u8] {
1802 self.bytes.as_slice()
1803 }
1804}
1805
1806#[derive(Clone, Debug, Eq, PartialEq)]
1808pub struct ClipboardString {
1809 pub(crate) text: String,
1810 pub(crate) metadata: Option<String>,
1811}
1812
1813impl ClipboardString {
1814 pub fn new(text: String) -> Self {
1816 Self {
1817 text,
1818 metadata: None,
1819 }
1820 }
1821
1822 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1825 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1826 self
1827 }
1828
1829 pub fn text(&self) -> &String {
1831 &self.text
1832 }
1833
1834 pub fn into_text(self) -> String {
1836 self.text
1837 }
1838
1839 pub fn metadata_json<T>(&self) -> Option<T>
1841 where
1842 T: for<'a> Deserialize<'a>,
1843 {
1844 self.metadata
1845 .as_ref()
1846 .and_then(|m| serde_json::from_str(m).ok())
1847 }
1848
1849 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1850 pub(crate) fn text_hash(text: &str) -> u64 {
1851 let mut hasher = SeaHasher::new();
1852 text.hash(&mut hasher);
1853 hasher.finish()
1854 }
1855}
1856
1857impl From<String> for ClipboardString {
1858 fn from(value: String) -> Self {
1859 Self {
1860 text: value,
1861 metadata: None,
1862 }
1863 }
1864}