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;
50use image::codecs::gif::GifDecoder;
51use image::{AnimationDecoder as _, Frame};
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::*;
80#[cfg(any(test, feature = "test-support"))]
81pub(crate) use test::*;
82#[cfg(target_os = "windows")]
83pub(crate) use windows::*;
84
85#[cfg(all(target_os = "linux", feature = "wayland"))]
86pub use linux::layer_shell;
87
88#[cfg(any(test, feature = "test-support"))]
89pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
90
91pub fn background_executor() -> BackgroundExecutor {
93 current_platform(true).background_executor()
94}
95
96#[cfg(target_os = "macos")]
97pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
98 Rc::new(MacPlatform::new(headless))
99}
100
101#[cfg(any(target_os = "linux", target_os = "freebsd"))]
102pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
103 #[cfg(feature = "x11")]
104 use anyhow::Context as _;
105
106 if headless {
107 return Rc::new(HeadlessClient::new());
108 }
109
110 match guess_compositor() {
111 #[cfg(feature = "wayland")]
112 "Wayland" => Rc::new(WaylandClient::new()),
113
114 #[cfg(feature = "x11")]
115 "X11" => Rc::new(
116 X11Client::new()
117 .context("Failed to initialize X11 client.")
118 .unwrap(),
119 ),
120
121 "Headless" => Rc::new(HeadlessClient::new()),
122 _ => unreachable!(),
123 }
124}
125
126#[cfg(target_os = "windows")]
127pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
128 Rc::new(
129 WindowsPlatform::new()
130 .inspect_err(|err| show_error("Failed to launch", err.to_string()))
131 .unwrap(),
132 )
133}
134
135#[cfg(any(target_os = "linux", target_os = "freebsd"))]
138#[inline]
139pub fn guess_compositor() -> &'static str {
140 if std::env::var_os("ZED_HEADLESS").is_some() {
141 return "Headless";
142 }
143
144 #[cfg(feature = "wayland")]
145 let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
146 #[cfg(not(feature = "wayland"))]
147 let wayland_display: Option<std::ffi::OsString> = None;
148
149 #[cfg(feature = "x11")]
150 let x11_display = std::env::var_os("DISPLAY");
151 #[cfg(not(feature = "x11"))]
152 let x11_display: Option<std::ffi::OsString> = None;
153
154 let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
155 let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
156
157 if use_wayland {
158 "Wayland"
159 } else if use_x11 {
160 "X11"
161 } else {
162 "Headless"
163 }
164}
165
166pub(crate) trait Platform: 'static {
167 fn background_executor(&self) -> BackgroundExecutor;
168 fn foreground_executor(&self) -> ForegroundExecutor;
169 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
170
171 fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
172 fn quit(&self);
173 fn restart(&self, binary_path: Option<PathBuf>);
174 fn activate(&self, ignoring_other_apps: bool);
175 fn hide(&self);
176 fn hide_other_apps(&self);
177 fn unhide_other_apps(&self);
178
179 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
180 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
181 fn active_window(&self) -> Option<AnyWindowHandle>;
182 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
183 None
184 }
185
186 #[cfg(feature = "screen-capture")]
187 fn is_screen_capture_supported(&self) -> bool;
188 #[cfg(not(feature = "screen-capture"))]
189 fn is_screen_capture_supported(&self) -> bool {
190 false
191 }
192 #[cfg(feature = "screen-capture")]
193 fn screen_capture_sources(&self)
194 -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
195 #[cfg(not(feature = "screen-capture"))]
196 fn screen_capture_sources(
197 &self,
198 ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
199 let (sources_tx, sources_rx) = oneshot::channel();
200 sources_tx
201 .send(Err(anyhow::anyhow!(
202 "gpui was compiled without the screen-capture feature"
203 )))
204 .ok();
205 sources_rx
206 }
207
208 fn open_window(
209 &self,
210 handle: AnyWindowHandle,
211 options: WindowParams,
212 ) -> anyhow::Result<Box<dyn PlatformWindow>>;
213
214 fn window_appearance(&self) -> WindowAppearance;
216
217 fn open_url(&self, url: &str);
218 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
219 fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
220
221 fn prompt_for_paths(
222 &self,
223 options: PathPromptOptions,
224 ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
225 fn prompt_for_new_path(
226 &self,
227 directory: &Path,
228 suggested_name: Option<&str>,
229 ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
230 fn can_select_mixed_files_and_dirs(&self) -> bool;
231 fn reveal_path(&self, path: &Path);
232 fn open_with_system(&self, path: &Path);
233
234 fn on_quit(&self, callback: Box<dyn FnMut()>);
235 fn on_reopen(&self, callback: Box<dyn FnMut()>);
236
237 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
238 fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
239 None
240 }
241
242 fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
243 fn perform_dock_menu_action(&self, _action: usize) {}
244 fn add_recent_document(&self, _path: &Path) {}
245 fn update_jump_list(
246 &self,
247 _menus: Vec<MenuItem>,
248 _entries: Vec<SmallVec<[PathBuf; 2]>>,
249 ) -> Vec<SmallVec<[PathBuf; 2]>> {
250 Vec::new()
251 }
252 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
253 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
254 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
255
256 fn compositor_name(&self) -> &'static str {
257 ""
258 }
259 fn app_path(&self) -> Result<PathBuf>;
260 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
261
262 fn set_cursor_style(&self, style: CursorStyle);
263 fn should_auto_hide_scrollbars(&self) -> bool;
264
265 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
266 fn write_to_clipboard(&self, item: ClipboardItem);
267
268 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
269 fn read_from_primary(&self) -> Option<ClipboardItem>;
270 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
271 fn write_to_primary(&self, item: ClipboardItem);
272
273 #[cfg(target_os = "macos")]
274 fn read_from_find_pasteboard(&self) -> Option<ClipboardItem>;
275 #[cfg(target_os = "macos")]
276 fn write_to_find_pasteboard(&self, item: ClipboardItem);
277
278 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
279 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
280 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
281
282 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
283 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
284 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
285}
286
287pub trait PlatformDisplay: Send + Sync + Debug {
289 fn id(&self) -> DisplayId;
291
292 fn uuid(&self) -> Result<Uuid>;
295
296 fn bounds(&self) -> Bounds<Pixels>;
298
299 fn visible_bounds(&self) -> Bounds<Pixels> {
303 self.bounds()
304 }
305
306 fn default_bounds(&self) -> Bounds<Pixels> {
308 let bounds = self.bounds();
309 let center = bounds.center();
310 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
311
312 let offset = clipped_window_size / 2.0;
313 let origin = point(center.x - offset.width, center.y - offset.height);
314 Bounds::new(origin, clipped_window_size)
315 }
316}
317
318#[derive(Clone)]
320pub struct SourceMetadata {
321 pub id: u64,
323 pub label: Option<SharedString>,
325 pub is_main: Option<bool>,
327 pub resolution: Size<DevicePixels>,
329}
330
331pub trait ScreenCaptureSource {
333 fn metadata(&self) -> Result<SourceMetadata>;
335
336 fn stream(
339 &self,
340 foreground_executor: &ForegroundExecutor,
341 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
342 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
343}
344
345pub trait ScreenCaptureStream {
347 fn metadata(&self) -> Result<SourceMetadata>;
349}
350
351pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
353
354#[derive(PartialEq, Eq, Hash, Copy, Clone)]
356pub struct DisplayId(pub(crate) u32);
357
358impl From<DisplayId> for u32 {
359 fn from(id: DisplayId) -> Self {
360 id.0
361 }
362}
363
364impl Debug for DisplayId {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 write!(f, "DisplayId({})", self.0)
367 }
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum ResizeEdge {
373 Top,
375 TopRight,
377 Right,
379 BottomRight,
381 Bottom,
383 BottomLeft,
385 Left,
387 TopLeft,
389}
390
391#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
393pub enum WindowDecorations {
394 #[default]
395 Server,
397 Client,
399}
400
401#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
403pub enum Decorations {
404 #[default]
406 Server,
407 Client {
409 tiling: Tiling,
411 },
412}
413
414#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
416pub struct WindowControls {
417 pub fullscreen: bool,
419 pub maximize: bool,
421 pub minimize: bool,
423 pub window_menu: bool,
425}
426
427impl Default for WindowControls {
428 fn default() -> Self {
429 Self {
431 fullscreen: true,
432 maximize: true,
433 minimize: true,
434 window_menu: true,
435 }
436 }
437}
438
439#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
441pub struct Tiling {
442 pub top: bool,
444 pub left: bool,
446 pub right: bool,
448 pub bottom: bool,
450}
451
452impl Tiling {
453 pub fn tiled() -> Self {
455 Self {
456 top: true,
457 left: true,
458 right: true,
459 bottom: true,
460 }
461 }
462
463 pub fn is_tiled(&self) -> bool {
465 self.top || self.left || self.right || self.bottom
466 }
467}
468
469#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
470pub(crate) struct RequestFrameOptions {
471 pub(crate) require_presentation: bool,
472 pub(crate) force_render: bool,
474}
475
476pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
477 fn bounds(&self) -> Bounds<Pixels>;
478 fn is_maximized(&self) -> bool;
479 fn window_bounds(&self) -> WindowBounds;
480 fn content_size(&self) -> Size<Pixels>;
481 fn resize(&mut self, size: Size<Pixels>);
482 fn scale_factor(&self) -> f32;
483 fn appearance(&self) -> WindowAppearance;
484 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
485 fn mouse_position(&self) -> Point<Pixels>;
486 fn modifiers(&self) -> Modifiers;
487 fn capslock(&self) -> Capslock;
488 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
489 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
490 fn prompt(
491 &self,
492 level: PromptLevel,
493 msg: &str,
494 detail: Option<&str>,
495 answers: &[PromptButton],
496 ) -> Option<oneshot::Receiver<usize>>;
497 fn activate(&self);
498 fn is_active(&self) -> bool;
499 fn is_hovered(&self) -> bool;
500 fn set_title(&mut self, title: &str);
501 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
502 fn minimize(&self);
503 fn zoom(&self);
504 fn toggle_fullscreen(&self);
505 fn is_fullscreen(&self) -> bool;
506 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
507 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
508 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
509 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
510 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
511 fn on_moved(&self, callback: Box<dyn FnMut()>);
512 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
513 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
514 fn on_close(&self, callback: Box<dyn FnOnce()>);
515 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
516 fn draw(&self, scene: &Scene);
517 fn completed_frame(&self) {}
518 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
519
520 fn get_title(&self) -> String {
522 String::new()
523 }
524 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
525 None
526 }
527 fn tab_bar_visible(&self) -> bool {
528 false
529 }
530 fn set_edited(&mut self, _edited: bool) {}
531 fn show_character_palette(&self) {}
532 fn titlebar_double_click(&self) {}
533 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
534 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
535 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
536 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
537 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
538 fn merge_all_windows(&self) {}
539 fn move_tab_to_new_window(&self) {}
540 fn toggle_window_tab_overview(&self) {}
541 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
542
543 #[cfg(target_os = "windows")]
544 fn get_raw_handle(&self) -> windows::HWND;
545
546 fn inner_window_bounds(&self) -> WindowBounds {
548 self.window_bounds()
549 }
550 fn request_decorations(&self, _decorations: WindowDecorations) {}
551 fn show_window_menu(&self, _position: Point<Pixels>) {}
552 fn start_window_move(&self) {}
553 fn start_window_resize(&self, _edge: ResizeEdge) {}
554 fn window_decorations(&self) -> Decorations {
555 Decorations::Server
556 }
557 fn set_app_id(&mut self, _app_id: &str) {}
558 fn map_window(&mut self) -> anyhow::Result<()> {
559 Ok(())
560 }
561 fn window_controls(&self) -> WindowControls {
562 WindowControls::default()
563 }
564 fn set_client_inset(&self, _inset: Pixels) {}
565 fn gpu_specs(&self) -> Option<GpuSpecs>;
566
567 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
568
569 #[cfg(any(test, feature = "test-support"))]
570 fn as_test(&mut self) -> Option<&mut TestWindow> {
571 None
572 }
573}
574
575#[doc(hidden)]
578#[derive(Debug)]
579pub struct RunnableMeta {
580 pub location: &'static core::panic::Location<'static>,
582}
583
584#[doc(hidden)]
585pub enum RunnableVariant {
586 Meta(Runnable<RunnableMeta>),
587 Compat(Runnable),
588}
589
590#[doc(hidden)]
593pub trait PlatformDispatcher: Send + Sync {
594 fn get_all_timings(&self) -> Vec<ThreadTaskTimings>;
595 fn get_current_thread_timings(&self) -> Vec<TaskTiming>;
596 fn is_main_thread(&self) -> bool;
597 fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, priority: Priority);
598 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
599 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
600 fn spawn_realtime(&self, priority: RealtimePriority, f: Box<dyn FnOnce() + Send>);
601
602 fn now(&self) -> Instant {
603 Instant::now()
604 }
605
606 #[cfg(any(test, feature = "test-support"))]
607 fn as_test(&self) -> Option<&TestDispatcher> {
608 None
609 }
610}
611
612pub(crate) trait PlatformTextSystem: Send + Sync {
613 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
614 fn all_font_names(&self) -> Vec<String>;
615 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
616 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
617 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
618 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
619 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
620 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
621 fn rasterize_glyph(
622 &self,
623 params: &RenderGlyphParams,
624 raster_bounds: Bounds<DevicePixels>,
625 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
626 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
627}
628
629pub(crate) struct NoopTextSystem;
630
631impl NoopTextSystem {
632 #[allow(dead_code)]
633 pub fn new() -> Self {
634 Self
635 }
636}
637
638impl PlatformTextSystem for NoopTextSystem {
639 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
640 Ok(())
641 }
642
643 fn all_font_names(&self) -> Vec<String> {
644 Vec::new()
645 }
646
647 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
648 Ok(FontId(1))
649 }
650
651 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
652 FontMetrics {
653 units_per_em: 1000,
654 ascent: 1025.0,
655 descent: -275.0,
656 line_gap: 0.0,
657 underline_position: -95.0,
658 underline_thickness: 60.0,
659 cap_height: 698.0,
660 x_height: 516.0,
661 bounding_box: Bounds {
662 origin: Point {
663 x: -260.0,
664 y: -245.0,
665 },
666 size: Size {
667 width: 1501.0,
668 height: 1364.0,
669 },
670 },
671 }
672 }
673
674 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
675 Ok(Bounds {
676 origin: Point { x: 54.0, y: 0.0 },
677 size: size(392.0, 528.0),
678 })
679 }
680
681 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
682 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
683 }
684
685 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
686 Some(GlyphId(ch.len_utf16() as u32))
687 }
688
689 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
690 Ok(Default::default())
691 }
692
693 fn rasterize_glyph(
694 &self,
695 _params: &RenderGlyphParams,
696 raster_bounds: Bounds<DevicePixels>,
697 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
698 Ok((raster_bounds.size, Vec::new()))
699 }
700
701 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
702 let mut position = px(0.);
703 let metrics = self.font_metrics(FontId(0));
704 let em_width = font_size
705 * self
706 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
707 .unwrap()
708 .width
709 / metrics.units_per_em as f32;
710 let mut glyphs = Vec::new();
711 for (ix, c) in text.char_indices() {
712 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
713 glyphs.push(ShapedGlyph {
714 id: glyph,
715 position: point(position, px(0.)),
716 index: ix,
717 is_emoji: glyph.0 == 2,
718 });
719 if glyph.0 == 2 {
720 position += em_width * 2.0;
721 } else {
722 position += em_width;
723 }
724 } else {
725 position += em_width
726 }
727 }
728 let mut runs = Vec::default();
729 if !glyphs.is_empty() {
730 runs.push(ShapedRun {
731 font_id: FontId(0),
732 glyphs,
733 });
734 } else {
735 position = px(0.);
736 }
737
738 LineLayout {
739 font_size,
740 width: position,
741 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
742 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
743 runs,
744 len: text.len(),
745 }
746 }
747}
748
749#[allow(dead_code)]
753pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
754 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
755 [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], ];
769
770 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
771 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
772
773 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
774 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
775
776 [
777 ratios[0] * NORM13,
778 ratios[1] * NORM24,
779 ratios[2] * NORM13,
780 ratios[3] * NORM24,
781 ]
782}
783
784#[derive(PartialEq, Eq, Hash, Clone)]
785pub(crate) enum AtlasKey {
786 Glyph(RenderGlyphParams),
787 Svg(RenderSvgParams),
788 Image(RenderImageParams),
789}
790
791impl AtlasKey {
792 #[cfg_attr(
793 all(
794 any(target_os = "linux", target_os = "freebsd"),
795 not(any(feature = "x11", feature = "wayland"))
796 ),
797 allow(dead_code)
798 )]
799 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
800 match self {
801 AtlasKey::Glyph(params) => {
802 if params.is_emoji {
803 AtlasTextureKind::Polychrome
804 } else {
805 AtlasTextureKind::Monochrome
806 }
807 }
808 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
809 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
810 }
811 }
812}
813
814impl From<RenderGlyphParams> for AtlasKey {
815 fn from(params: RenderGlyphParams) -> Self {
816 Self::Glyph(params)
817 }
818}
819
820impl From<RenderSvgParams> for AtlasKey {
821 fn from(params: RenderSvgParams) -> Self {
822 Self::Svg(params)
823 }
824}
825
826impl From<RenderImageParams> for AtlasKey {
827 fn from(params: RenderImageParams) -> Self {
828 Self::Image(params)
829 }
830}
831
832pub(crate) trait PlatformAtlas: Send + Sync {
833 fn get_or_insert_with<'a>(
834 &self,
835 key: &AtlasKey,
836 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
837 ) -> Result<Option<AtlasTile>>;
838 fn remove(&self, key: &AtlasKey);
839}
840
841struct AtlasTextureList<T> {
842 textures: Vec<Option<T>>,
843 free_list: Vec<usize>,
844}
845
846impl<T> Default for AtlasTextureList<T> {
847 fn default() -> Self {
848 Self {
849 textures: Vec::default(),
850 free_list: Vec::default(),
851 }
852 }
853}
854
855impl<T> ops::Index<usize> for AtlasTextureList<T> {
856 type Output = Option<T>;
857
858 fn index(&self, index: usize) -> &Self::Output {
859 &self.textures[index]
860 }
861}
862
863impl<T> AtlasTextureList<T> {
864 #[allow(unused)]
865 fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
866 self.free_list.clear();
867 self.textures.drain(..)
868 }
869
870 #[allow(dead_code)]
871 fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
872 self.textures.iter_mut().flatten()
873 }
874}
875
876#[derive(Clone, Debug, PartialEq, Eq)]
877#[repr(C)]
878pub(crate) struct AtlasTile {
879 pub(crate) texture_id: AtlasTextureId,
880 pub(crate) tile_id: TileId,
881 pub(crate) padding: u32,
882 pub(crate) bounds: Bounds<DevicePixels>,
883}
884
885#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
886#[repr(C)]
887pub(crate) struct AtlasTextureId {
888 pub(crate) index: u32,
890 pub(crate) kind: AtlasTextureKind,
891}
892
893#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
894#[repr(C)]
895#[cfg_attr(
896 all(
897 any(target_os = "linux", target_os = "freebsd"),
898 not(any(feature = "x11", feature = "wayland"))
899 ),
900 allow(dead_code)
901)]
902pub(crate) enum AtlasTextureKind {
903 Monochrome = 0,
904 Polychrome = 1,
905}
906
907#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
908#[repr(C)]
909pub(crate) struct TileId(pub(crate) u32);
910
911impl From<etagere::AllocId> for TileId {
912 fn from(id: etagere::AllocId) -> Self {
913 Self(id.serialize())
914 }
915}
916
917impl From<TileId> for etagere::AllocId {
918 fn from(id: TileId) -> Self {
919 Self::deserialize(id.0)
920 }
921}
922
923pub(crate) struct PlatformInputHandler {
924 cx: AsyncWindowContext,
925 handler: Box<dyn InputHandler>,
926}
927
928#[cfg_attr(
929 all(
930 any(target_os = "linux", target_os = "freebsd"),
931 not(any(feature = "x11", feature = "wayland"))
932 ),
933 allow(dead_code)
934)]
935impl PlatformInputHandler {
936 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
937 Self { cx, handler }
938 }
939
940 fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
941 self.cx
942 .update(|window, cx| {
943 self.handler
944 .selected_text_range(ignore_disabled_input, window, cx)
945 })
946 .ok()
947 .flatten()
948 }
949
950 #[cfg_attr(target_os = "windows", allow(dead_code))]
951 fn marked_text_range(&mut self) -> Option<Range<usize>> {
952 self.cx
953 .update(|window, cx| self.handler.marked_text_range(window, cx))
954 .ok()
955 .flatten()
956 }
957
958 #[cfg_attr(
959 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
960 allow(dead_code)
961 )]
962 fn text_for_range(
963 &mut self,
964 range_utf16: Range<usize>,
965 adjusted: &mut Option<Range<usize>>,
966 ) -> Option<String> {
967 self.cx
968 .update(|window, cx| {
969 self.handler
970 .text_for_range(range_utf16, adjusted, window, cx)
971 })
972 .ok()
973 .flatten()
974 }
975
976 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
977 self.cx
978 .update(|window, cx| {
979 self.handler
980 .replace_text_in_range(replacement_range, text, window, cx);
981 })
982 .ok();
983 }
984
985 pub fn replace_and_mark_text_in_range(
986 &mut self,
987 range_utf16: Option<Range<usize>>,
988 new_text: &str,
989 new_selected_range: Option<Range<usize>>,
990 ) {
991 self.cx
992 .update(|window, cx| {
993 self.handler.replace_and_mark_text_in_range(
994 range_utf16,
995 new_text,
996 new_selected_range,
997 window,
998 cx,
999 )
1000 })
1001 .ok();
1002 }
1003
1004 #[cfg_attr(target_os = "windows", allow(dead_code))]
1005 fn unmark_text(&mut self) {
1006 self.cx
1007 .update(|window, cx| self.handler.unmark_text(window, cx))
1008 .ok();
1009 }
1010
1011 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1012 self.cx
1013 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1014 .ok()
1015 .flatten()
1016 }
1017
1018 #[allow(dead_code)]
1019 fn apple_press_and_hold_enabled(&mut self) -> bool {
1020 self.handler.apple_press_and_hold_enabled()
1021 }
1022
1023 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1024 self.handler.replace_text_in_range(None, input, window, cx);
1025 }
1026
1027 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1028 let selection = self.handler.selected_text_range(true, window, cx)?;
1029 self.handler.bounds_for_range(
1030 if selection.reversed {
1031 selection.range.start..selection.range.start
1032 } else {
1033 selection.range.end..selection.range.end
1034 },
1035 window,
1036 cx,
1037 )
1038 }
1039
1040 #[allow(unused)]
1041 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1042 self.cx
1043 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1044 .ok()
1045 .flatten()
1046 }
1047
1048 #[allow(dead_code)]
1049 pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1050 self.handler.accepts_text_input(window, cx)
1051 }
1052}
1053
1054#[derive(Debug)]
1057pub struct UTF16Selection {
1058 pub range: Range<usize>,
1061 pub reversed: bool,
1064}
1065
1066pub trait InputHandler: 'static {
1071 fn selected_text_range(
1076 &mut self,
1077 ignore_disabled_input: bool,
1078 window: &mut Window,
1079 cx: &mut App,
1080 ) -> Option<UTF16Selection>;
1081
1082 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1087
1088 fn text_for_range(
1093 &mut self,
1094 range_utf16: Range<usize>,
1095 adjusted_range: &mut Option<Range<usize>>,
1096 window: &mut Window,
1097 cx: &mut App,
1098 ) -> Option<String>;
1099
1100 fn replace_text_in_range(
1105 &mut self,
1106 replacement_range: Option<Range<usize>>,
1107 text: &str,
1108 window: &mut Window,
1109 cx: &mut App,
1110 );
1111
1112 fn replace_and_mark_text_in_range(
1119 &mut self,
1120 range_utf16: Option<Range<usize>>,
1121 new_text: &str,
1122 new_selected_range: Option<Range<usize>>,
1123 window: &mut Window,
1124 cx: &mut App,
1125 );
1126
1127 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1130
1131 fn bounds_for_range(
1136 &mut self,
1137 range_utf16: Range<usize>,
1138 window: &mut Window,
1139 cx: &mut App,
1140 ) -> Option<Bounds<Pixels>>;
1141
1142 fn character_index_for_point(
1146 &mut self,
1147 point: Point<Pixels>,
1148 window: &mut Window,
1149 cx: &mut App,
1150 ) -> Option<usize>;
1151
1152 #[allow(dead_code)]
1157 fn apple_press_and_hold_enabled(&mut self) -> bool {
1158 true
1159 }
1160
1161 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1163 true
1164 }
1165}
1166
1167#[derive(Debug)]
1169pub struct WindowOptions {
1170 pub window_bounds: Option<WindowBounds>,
1174
1175 pub titlebar: Option<TitlebarOptions>,
1177
1178 pub focus: bool,
1180
1181 pub show: bool,
1183
1184 pub kind: WindowKind,
1186
1187 pub is_movable: bool,
1189
1190 pub is_resizable: bool,
1192
1193 pub is_minimizable: bool,
1195
1196 pub display_id: Option<DisplayId>,
1199
1200 pub window_background: WindowBackgroundAppearance,
1202
1203 pub app_id: Option<String>,
1205
1206 pub window_min_size: Option<Size<Pixels>>,
1208
1209 pub window_decorations: Option<WindowDecorations>,
1212
1213 pub tabbing_identifier: Option<String>,
1215}
1216
1217#[derive(Debug)]
1219#[cfg_attr(
1220 all(
1221 any(target_os = "linux", target_os = "freebsd"),
1222 not(any(feature = "x11", feature = "wayland"))
1223 ),
1224 allow(dead_code)
1225)]
1226pub(crate) struct WindowParams {
1227 pub bounds: Bounds<Pixels>,
1228
1229 #[cfg_attr(feature = "wayland", allow(dead_code))]
1231 pub titlebar: Option<TitlebarOptions>,
1232
1233 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1235 pub kind: WindowKind,
1236
1237 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1239 pub is_movable: bool,
1240
1241 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1243 pub is_resizable: bool,
1244
1245 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1247 pub is_minimizable: bool,
1248
1249 #[cfg_attr(
1250 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1251 allow(dead_code)
1252 )]
1253 pub focus: bool,
1254
1255 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1256 pub show: bool,
1257
1258 #[cfg_attr(feature = "wayland", allow(dead_code))]
1259 pub display_id: Option<DisplayId>,
1260
1261 pub window_min_size: Option<Size<Pixels>>,
1262 #[cfg(target_os = "macos")]
1263 pub tabbing_identifier: Option<String>,
1264}
1265
1266#[derive(Debug, Copy, Clone, PartialEq)]
1268pub enum WindowBounds {
1269 Windowed(Bounds<Pixels>),
1271 Maximized(Bounds<Pixels>),
1274 Fullscreen(Bounds<Pixels>),
1277}
1278
1279impl Default for WindowBounds {
1280 fn default() -> Self {
1281 WindowBounds::Windowed(Bounds::default())
1282 }
1283}
1284
1285impl WindowBounds {
1286 pub fn get_bounds(&self) -> Bounds<Pixels> {
1288 match self {
1289 WindowBounds::Windowed(bounds) => *bounds,
1290 WindowBounds::Maximized(bounds) => *bounds,
1291 WindowBounds::Fullscreen(bounds) => *bounds,
1292 }
1293 }
1294
1295 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1297 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1298 }
1299}
1300
1301impl Default for WindowOptions {
1302 fn default() -> Self {
1303 Self {
1304 window_bounds: None,
1305 titlebar: Some(TitlebarOptions {
1306 title: Default::default(),
1307 appears_transparent: Default::default(),
1308 traffic_light_position: Default::default(),
1309 }),
1310 focus: true,
1311 show: true,
1312 kind: WindowKind::Normal,
1313 is_movable: true,
1314 is_resizable: true,
1315 is_minimizable: true,
1316 display_id: None,
1317 window_background: WindowBackgroundAppearance::default(),
1318 app_id: None,
1319 window_min_size: None,
1320 window_decorations: None,
1321 tabbing_identifier: None,
1322 }
1323 }
1324}
1325
1326#[derive(Debug, Default)]
1328pub struct TitlebarOptions {
1329 pub title: Option<SharedString>,
1331
1332 pub appears_transparent: bool,
1335
1336 pub traffic_light_position: Option<Point<Pixels>>,
1338}
1339
1340#[derive(Clone, Debug, PartialEq, Eq)]
1342pub enum WindowKind {
1343 Normal,
1345
1346 PopUp,
1349
1350 Floating,
1352
1353 #[cfg(all(target_os = "linux", feature = "wayland"))]
1356 LayerShell(layer_shell::LayerShellOptions),
1357
1358 Dialog,
1361}
1362
1363#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1368pub enum WindowAppearance {
1369 #[default]
1373 Light,
1374
1375 VibrantLight,
1379
1380 Dark,
1384
1385 VibrantDark,
1389}
1390
1391#[derive(Copy, Clone, Debug, Default, PartialEq)]
1394pub enum WindowBackgroundAppearance {
1395 #[default]
1403 Opaque,
1404 Transparent,
1406 Blurred,
1410 MicaBackdrop,
1412 MicaAltBackdrop,
1414}
1415
1416#[derive(Clone, Debug)]
1418pub struct PathPromptOptions {
1419 pub files: bool,
1421 pub directories: bool,
1423 pub multiple: bool,
1425 pub prompt: Option<SharedString>,
1427}
1428
1429#[derive(Copy, Clone, Debug, PartialEq)]
1431pub enum PromptLevel {
1432 Info,
1434
1435 Warning,
1437
1438 Critical,
1440}
1441
1442#[derive(Clone, Debug, PartialEq)]
1444pub enum PromptButton {
1445 Ok(SharedString),
1447 Cancel(SharedString),
1449 Other(SharedString),
1451}
1452
1453impl PromptButton {
1454 pub fn new(label: impl Into<SharedString>) -> Self {
1456 PromptButton::Other(label.into())
1457 }
1458
1459 pub fn ok(label: impl Into<SharedString>) -> Self {
1461 PromptButton::Ok(label.into())
1462 }
1463
1464 pub fn cancel(label: impl Into<SharedString>) -> Self {
1466 PromptButton::Cancel(label.into())
1467 }
1468
1469 #[allow(dead_code)]
1470 pub(crate) fn is_cancel(&self) -> bool {
1471 matches!(self, PromptButton::Cancel(_))
1472 }
1473
1474 pub fn label(&self) -> &SharedString {
1476 match self {
1477 PromptButton::Ok(label) => label,
1478 PromptButton::Cancel(label) => label,
1479 PromptButton::Other(label) => label,
1480 }
1481 }
1482}
1483
1484impl From<&str> for PromptButton {
1485 fn from(value: &str) -> Self {
1486 match value.to_lowercase().as_str() {
1487 "ok" => PromptButton::Ok("Ok".into()),
1488 "cancel" => PromptButton::Cancel("Cancel".into()),
1489 _ => PromptButton::Other(SharedString::from(value.to_owned())),
1490 }
1491 }
1492}
1493
1494#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1496pub enum CursorStyle {
1497 #[default]
1499 Arrow,
1500
1501 IBeam,
1504
1505 Crosshair,
1508
1509 ClosedHand,
1512
1513 OpenHand,
1516
1517 PointingHand,
1520
1521 ResizeLeft,
1524
1525 ResizeRight,
1528
1529 ResizeLeftRight,
1532
1533 ResizeUp,
1536
1537 ResizeDown,
1540
1541 ResizeUpDown,
1544
1545 ResizeUpLeftDownRight,
1548
1549 ResizeUpRightDownLeft,
1552
1553 ResizeColumn,
1556
1557 ResizeRow,
1560
1561 IBeamCursorForVerticalLayout,
1564
1565 OperationNotAllowed,
1568
1569 DragLink,
1572
1573 DragCopy,
1576
1577 ContextualMenu,
1580
1581 None,
1583}
1584
1585#[derive(Clone, Debug, Eq, PartialEq)]
1587pub struct ClipboardItem {
1588 entries: Vec<ClipboardEntry>,
1589}
1590
1591#[derive(Clone, Debug, Eq, PartialEq)]
1593pub enum ClipboardEntry {
1594 String(ClipboardString),
1596 Image(Image),
1598 ExternalPaths(crate::ExternalPaths),
1600}
1601
1602impl ClipboardItem {
1603 pub fn new_string(text: String) -> Self {
1605 Self {
1606 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1607 }
1608 }
1609
1610 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1612 Self {
1613 entries: vec![ClipboardEntry::String(ClipboardString {
1614 text,
1615 metadata: Some(metadata),
1616 })],
1617 }
1618 }
1619
1620 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1622 Self {
1623 entries: vec![ClipboardEntry::String(
1624 ClipboardString::new(text).with_json_metadata(metadata),
1625 )],
1626 }
1627 }
1628
1629 pub fn new_image(image: &Image) -> Self {
1631 Self {
1632 entries: vec![ClipboardEntry::Image(image.clone())],
1633 }
1634 }
1635
1636 pub fn text(&self) -> Option<String> {
1639 let mut answer = String::new();
1640
1641 for entry in self.entries.iter() {
1642 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1643 answer.push_str(text);
1644 }
1645 }
1646
1647 if answer.is_empty() {
1648 for entry in self.entries.iter() {
1649 if let ClipboardEntry::ExternalPaths(paths) = entry {
1650 for path in &paths.0 {
1651 use std::fmt::Write as _;
1652 _ = write!(answer, "{}", path.display());
1653 }
1654 }
1655 }
1656 }
1657
1658 if !answer.is_empty() {
1659 Some(answer)
1660 } else {
1661 None
1662 }
1663 }
1664
1665 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1667 pub fn metadata(&self) -> Option<&String> {
1668 match self.entries().first() {
1669 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1670 clipboard_string.metadata.as_ref()
1671 }
1672 _ => None,
1673 }
1674 }
1675
1676 pub fn entries(&self) -> &[ClipboardEntry] {
1678 &self.entries
1679 }
1680
1681 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1683 self.entries.into_iter()
1684 }
1685}
1686
1687impl From<ClipboardString> for ClipboardEntry {
1688 fn from(value: ClipboardString) -> Self {
1689 Self::String(value)
1690 }
1691}
1692
1693impl From<String> for ClipboardEntry {
1694 fn from(value: String) -> Self {
1695 Self::from(ClipboardString::from(value))
1696 }
1697}
1698
1699impl From<Image> for ClipboardEntry {
1700 fn from(value: Image) -> Self {
1701 Self::Image(value)
1702 }
1703}
1704
1705impl From<ClipboardEntry> for ClipboardItem {
1706 fn from(value: ClipboardEntry) -> Self {
1707 Self {
1708 entries: vec![value],
1709 }
1710 }
1711}
1712
1713impl From<String> for ClipboardItem {
1714 fn from(value: String) -> Self {
1715 Self::from(ClipboardEntry::from(value))
1716 }
1717}
1718
1719impl From<Image> for ClipboardItem {
1720 fn from(value: Image) -> Self {
1721 Self::from(ClipboardEntry::from(value))
1722 }
1723}
1724
1725#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1727pub enum ImageFormat {
1728 Png,
1733 Jpeg,
1735 Webp,
1737 Gif,
1739 Svg,
1741 Bmp,
1743 Tiff,
1745 Ico,
1747}
1748
1749impl ImageFormat {
1750 pub const fn mime_type(self) -> &'static str {
1752 match self {
1753 ImageFormat::Png => "image/png",
1754 ImageFormat::Jpeg => "image/jpeg",
1755 ImageFormat::Webp => "image/webp",
1756 ImageFormat::Gif => "image/gif",
1757 ImageFormat::Svg => "image/svg+xml",
1758 ImageFormat::Bmp => "image/bmp",
1759 ImageFormat::Tiff => "image/tiff",
1760 ImageFormat::Ico => "image/ico",
1761 }
1762 }
1763
1764 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1766 match mime_type {
1767 "image/png" => Some(Self::Png),
1768 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1769 "image/webp" => Some(Self::Webp),
1770 "image/gif" => Some(Self::Gif),
1771 "image/svg+xml" => Some(Self::Svg),
1772 "image/bmp" => Some(Self::Bmp),
1773 "image/tiff" | "image/tif" => Some(Self::Tiff),
1774 "image/ico" => Some(Self::Ico),
1775 _ => None,
1776 }
1777 }
1778}
1779
1780#[derive(Clone, Debug, PartialEq, Eq)]
1782pub struct Image {
1783 pub format: ImageFormat,
1785 pub bytes: Vec<u8>,
1787 id: u64,
1789}
1790
1791impl Hash for Image {
1792 fn hash<H: Hasher>(&self, state: &mut H) {
1793 state.write_u64(self.id);
1794 }
1795}
1796
1797impl Image {
1798 pub fn empty() -> Self {
1800 Self::from_bytes(ImageFormat::Png, Vec::new())
1801 }
1802
1803 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1805 Self {
1806 id: hash(&bytes),
1807 format,
1808 bytes,
1809 }
1810 }
1811
1812 pub fn id(&self) -> u64 {
1814 self.id
1815 }
1816
1817 pub fn use_render_image(
1819 self: Arc<Self>,
1820 window: &mut Window,
1821 cx: &mut App,
1822 ) -> Option<Arc<RenderImage>> {
1823 ImageSource::Image(self)
1824 .use_data(None, window, cx)
1825 .and_then(|result| result.ok())
1826 }
1827
1828 pub fn get_render_image(
1830 self: Arc<Self>,
1831 window: &mut Window,
1832 cx: &mut App,
1833 ) -> Option<Arc<RenderImage>> {
1834 ImageSource::Image(self)
1835 .get_data(None, window, cx)
1836 .and_then(|result| result.ok())
1837 }
1838
1839 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1841 ImageSource::Image(self).remove_asset(cx);
1842 }
1843
1844 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1846 fn frames_for_image(
1847 bytes: &[u8],
1848 format: image::ImageFormat,
1849 ) -> Result<SmallVec<[Frame; 1]>> {
1850 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1851
1852 for pixel in data.chunks_exact_mut(4) {
1854 pixel.swap(0, 2);
1855 }
1856
1857 Ok(SmallVec::from_elem(Frame::new(data), 1))
1858 }
1859
1860 let frames = match self.format {
1861 ImageFormat::Gif => {
1862 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1863 let mut frames = SmallVec::new();
1864
1865 for frame in decoder.into_frames() {
1866 let mut frame = frame?;
1867 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1869 pixel.swap(0, 2);
1870 }
1871 frames.push(frame);
1872 }
1873
1874 frames
1875 }
1876 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1877 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1878 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1879 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1880 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1881 ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
1882 ImageFormat::Svg => {
1883 return svg_renderer
1884 .render_single_frame(&self.bytes, 1.0, false)
1885 .map_err(Into::into);
1886 }
1887 };
1888
1889 Ok(Arc::new(RenderImage::new(frames)))
1890 }
1891
1892 pub fn format(&self) -> ImageFormat {
1894 self.format
1895 }
1896
1897 pub fn bytes(&self) -> &[u8] {
1899 self.bytes.as_slice()
1900 }
1901}
1902
1903#[derive(Clone, Debug, Eq, PartialEq)]
1905pub struct ClipboardString {
1906 pub(crate) text: String,
1907 pub(crate) metadata: Option<String>,
1908}
1909
1910impl ClipboardString {
1911 pub fn new(text: String) -> Self {
1913 Self {
1914 text,
1915 metadata: None,
1916 }
1917 }
1918
1919 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1922 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1923 self
1924 }
1925
1926 pub fn text(&self) -> &String {
1928 &self.text
1929 }
1930
1931 pub fn into_text(self) -> String {
1933 self.text
1934 }
1935
1936 pub fn metadata_json<T>(&self) -> Option<T>
1938 where
1939 T: for<'a> Deserialize<'a>,
1940 {
1941 self.metadata
1942 .as_ref()
1943 .and_then(|m| serde_json::from_str(m).ok())
1944 }
1945
1946 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1947 pub(crate) fn text_hash(text: &str) -> u64 {
1948 let mut hasher = SeaHasher::new();
1949 text.hash(&mut hasher);
1950 hasher.finish()
1951 }
1952}
1953
1954impl From<String> for ClipboardString {
1955 fn from(value: String) -> Self {
1956 Self {
1957 text: value,
1958 metadata: None,
1959 }
1960 }
1961}