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 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
266 fn write_to_primary(&self, item: ClipboardItem);
267 fn write_to_clipboard(&self, item: ClipboardItem);
268 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
269 fn read_from_primary(&self) -> Option<ClipboardItem>;
270 fn read_from_clipboard(&self) -> Option<ClipboardItem>;
271
272 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
273 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
274 fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
275
276 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
277 fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
278 fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
279}
280
281pub trait PlatformDisplay: Send + Sync + Debug {
283 fn id(&self) -> DisplayId;
285
286 fn uuid(&self) -> Result<Uuid>;
289
290 fn bounds(&self) -> Bounds<Pixels>;
292
293 fn visible_bounds(&self) -> Bounds<Pixels> {
297 self.bounds()
298 }
299
300 fn default_bounds(&self) -> Bounds<Pixels> {
302 let bounds = self.bounds();
303 let center = bounds.center();
304 let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
305
306 let offset = clipped_window_size / 2.0;
307 let origin = point(center.x - offset.width, center.y - offset.height);
308 Bounds::new(origin, clipped_window_size)
309 }
310}
311
312#[derive(Clone)]
314pub struct SourceMetadata {
315 pub id: u64,
317 pub label: Option<SharedString>,
319 pub is_main: Option<bool>,
321 pub resolution: Size<DevicePixels>,
323}
324
325pub trait ScreenCaptureSource {
327 fn metadata(&self) -> Result<SourceMetadata>;
329
330 fn stream(
333 &self,
334 foreground_executor: &ForegroundExecutor,
335 frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
336 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
337}
338
339pub trait ScreenCaptureStream {
341 fn metadata(&self) -> Result<SourceMetadata>;
343}
344
345pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
347
348#[derive(PartialEq, Eq, Hash, Copy, Clone)]
350pub struct DisplayId(pub(crate) u32);
351
352impl From<DisplayId> for u32 {
353 fn from(id: DisplayId) -> Self {
354 id.0
355 }
356}
357
358impl Debug for DisplayId {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 write!(f, "DisplayId({})", self.0)
361 }
362}
363
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub enum ResizeEdge {
367 Top,
369 TopRight,
371 Right,
373 BottomRight,
375 Bottom,
377 BottomLeft,
379 Left,
381 TopLeft,
383}
384
385#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
387pub enum WindowDecorations {
388 #[default]
389 Server,
391 Client,
393}
394
395#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
397pub enum Decorations {
398 #[default]
400 Server,
401 Client {
403 tiling: Tiling,
405 },
406}
407
408#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
410pub struct WindowControls {
411 pub fullscreen: bool,
413 pub maximize: bool,
415 pub minimize: bool,
417 pub window_menu: bool,
419}
420
421impl Default for WindowControls {
422 fn default() -> Self {
423 Self {
425 fullscreen: true,
426 maximize: true,
427 minimize: true,
428 window_menu: true,
429 }
430 }
431}
432
433#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
435pub struct Tiling {
436 pub top: bool,
438 pub left: bool,
440 pub right: bool,
442 pub bottom: bool,
444}
445
446impl Tiling {
447 pub fn tiled() -> Self {
449 Self {
450 top: true,
451 left: true,
452 right: true,
453 bottom: true,
454 }
455 }
456
457 pub fn is_tiled(&self) -> bool {
459 self.top || self.left || self.right || self.bottom
460 }
461}
462
463#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
464pub(crate) struct RequestFrameOptions {
465 pub(crate) require_presentation: bool,
466 pub(crate) force_render: bool,
468}
469
470pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
471 fn bounds(&self) -> Bounds<Pixels>;
472 fn is_maximized(&self) -> bool;
473 fn window_bounds(&self) -> WindowBounds;
474 fn content_size(&self) -> Size<Pixels>;
475 fn resize(&mut self, size: Size<Pixels>);
476 fn scale_factor(&self) -> f32;
477 fn appearance(&self) -> WindowAppearance;
478 fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
479 fn mouse_position(&self) -> Point<Pixels>;
480 fn modifiers(&self) -> Modifiers;
481 fn capslock(&self) -> Capslock;
482 fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
483 fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
484 fn prompt(
485 &self,
486 level: PromptLevel,
487 msg: &str,
488 detail: Option<&str>,
489 answers: &[PromptButton],
490 ) -> Option<oneshot::Receiver<usize>>;
491 fn activate(&self);
492 fn is_active(&self) -> bool;
493 fn is_hovered(&self) -> bool;
494 fn set_title(&mut self, title: &str);
495 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
496 fn minimize(&self);
497 fn zoom(&self);
498 fn toggle_fullscreen(&self);
499 fn is_fullscreen(&self) -> bool;
500 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>);
501 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
502 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
503 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
504 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
505 fn on_moved(&self, callback: Box<dyn FnMut()>);
506 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
507 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
508 fn on_close(&self, callback: Box<dyn FnOnce()>);
509 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
510 fn draw(&self, scene: &Scene);
511 fn completed_frame(&self) {}
512 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
513
514 fn get_title(&self) -> String {
516 String::new()
517 }
518 fn tabbed_windows(&self) -> Option<Vec<SystemWindowTab>> {
519 None
520 }
521 fn tab_bar_visible(&self) -> bool {
522 false
523 }
524 fn set_edited(&mut self, _edited: bool) {}
525 fn show_character_palette(&self) {}
526 fn titlebar_double_click(&self) {}
527 fn on_move_tab_to_new_window(&self, _callback: Box<dyn FnMut()>) {}
528 fn on_merge_all_windows(&self, _callback: Box<dyn FnMut()>) {}
529 fn on_select_previous_tab(&self, _callback: Box<dyn FnMut()>) {}
530 fn on_select_next_tab(&self, _callback: Box<dyn FnMut()>) {}
531 fn on_toggle_tab_bar(&self, _callback: Box<dyn FnMut()>) {}
532 fn merge_all_windows(&self) {}
533 fn move_tab_to_new_window(&self) {}
534 fn toggle_window_tab_overview(&self) {}
535 fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
536
537 #[cfg(target_os = "windows")]
538 fn get_raw_handle(&self) -> windows::HWND;
539
540 fn inner_window_bounds(&self) -> WindowBounds {
542 self.window_bounds()
543 }
544 fn request_decorations(&self, _decorations: WindowDecorations) {}
545 fn show_window_menu(&self, _position: Point<Pixels>) {}
546 fn start_window_move(&self) {}
547 fn start_window_resize(&self, _edge: ResizeEdge) {}
548 fn window_decorations(&self) -> Decorations {
549 Decorations::Server
550 }
551 fn set_app_id(&mut self, _app_id: &str) {}
552 fn map_window(&mut self) -> anyhow::Result<()> {
553 Ok(())
554 }
555 fn window_controls(&self) -> WindowControls {
556 WindowControls::default()
557 }
558 fn set_client_inset(&self, _inset: Pixels) {}
559 fn gpu_specs(&self) -> Option<GpuSpecs>;
560
561 fn update_ime_position(&self, _bounds: Bounds<Pixels>);
562
563 #[cfg(any(test, feature = "test-support"))]
564 fn as_test(&mut self) -> Option<&mut TestWindow> {
565 None
566 }
567}
568
569#[doc(hidden)]
572#[derive(Debug)]
573pub struct RunnableMeta {
574 pub location: &'static core::panic::Location<'static>,
576}
577
578#[doc(hidden)]
579pub enum RunnableVariant {
580 Meta(Runnable<RunnableMeta>),
581 Compat(Runnable),
582}
583
584#[doc(hidden)]
587pub trait PlatformDispatcher: Send + Sync {
588 fn get_all_timings(&self) -> Vec<ThreadTaskTimings>;
589 fn get_current_thread_timings(&self) -> Vec<TaskTiming>;
590 fn is_main_thread(&self) -> bool;
591 fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, priority: Priority);
592 fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
593 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
594 fn spawn_realtime(&self, priority: RealtimePriority, f: Box<dyn FnOnce() + Send>);
595
596 fn now(&self) -> Instant {
597 Instant::now()
598 }
599
600 #[cfg(any(test, feature = "test-support"))]
601 fn as_test(&self) -> Option<&TestDispatcher> {
602 None
603 }
604}
605
606pub(crate) trait PlatformTextSystem: Send + Sync {
607 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
608 fn all_font_names(&self) -> Vec<String>;
609 fn font_id(&self, descriptor: &Font) -> Result<FontId>;
610 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
611 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
612 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
613 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
614 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
615 fn rasterize_glyph(
616 &self,
617 params: &RenderGlyphParams,
618 raster_bounds: Bounds<DevicePixels>,
619 ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
620 fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
621}
622
623pub(crate) struct NoopTextSystem;
624
625impl NoopTextSystem {
626 #[allow(dead_code)]
627 pub fn new() -> Self {
628 Self
629 }
630}
631
632impl PlatformTextSystem for NoopTextSystem {
633 fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
634 Ok(())
635 }
636
637 fn all_font_names(&self) -> Vec<String> {
638 Vec::new()
639 }
640
641 fn font_id(&self, _descriptor: &Font) -> Result<FontId> {
642 Ok(FontId(1))
643 }
644
645 fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
646 FontMetrics {
647 units_per_em: 1000,
648 ascent: 1025.0,
649 descent: -275.0,
650 line_gap: 0.0,
651 underline_position: -95.0,
652 underline_thickness: 60.0,
653 cap_height: 698.0,
654 x_height: 516.0,
655 bounding_box: Bounds {
656 origin: Point {
657 x: -260.0,
658 y: -245.0,
659 },
660 size: Size {
661 width: 1501.0,
662 height: 1364.0,
663 },
664 },
665 }
666 }
667
668 fn typographic_bounds(&self, _font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
669 Ok(Bounds {
670 origin: Point { x: 54.0, y: 0.0 },
671 size: size(392.0, 528.0),
672 })
673 }
674
675 fn advance(&self, _font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
676 Ok(size(600.0 * glyph_id.0 as f32, 0.0))
677 }
678
679 fn glyph_for_char(&self, _font_id: FontId, ch: char) -> Option<GlyphId> {
680 Some(GlyphId(ch.len_utf16() as u32))
681 }
682
683 fn glyph_raster_bounds(&self, _params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
684 Ok(Default::default())
685 }
686
687 fn rasterize_glyph(
688 &self,
689 _params: &RenderGlyphParams,
690 raster_bounds: Bounds<DevicePixels>,
691 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
692 Ok((raster_bounds.size, Vec::new()))
693 }
694
695 fn layout_line(&self, text: &str, font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
696 let mut position = px(0.);
697 let metrics = self.font_metrics(FontId(0));
698 let em_width = font_size
699 * self
700 .advance(FontId(0), self.glyph_for_char(FontId(0), 'm').unwrap())
701 .unwrap()
702 .width
703 / metrics.units_per_em as f32;
704 let mut glyphs = Vec::new();
705 for (ix, c) in text.char_indices() {
706 if let Some(glyph) = self.glyph_for_char(FontId(0), c) {
707 glyphs.push(ShapedGlyph {
708 id: glyph,
709 position: point(position, px(0.)),
710 index: ix,
711 is_emoji: glyph.0 == 2,
712 });
713 if glyph.0 == 2 {
714 position += em_width * 2.0;
715 } else {
716 position += em_width;
717 }
718 } else {
719 position += em_width
720 }
721 }
722 let mut runs = Vec::default();
723 if !glyphs.is_empty() {
724 runs.push(ShapedRun {
725 font_id: FontId(0),
726 glyphs,
727 });
728 } else {
729 position = px(0.);
730 }
731
732 LineLayout {
733 font_size,
734 width: position,
735 ascent: font_size * (metrics.ascent / metrics.units_per_em as f32),
736 descent: font_size * (metrics.descent / metrics.units_per_em as f32),
737 runs,
738 len: text.len(),
739 }
740 }
741}
742
743#[allow(dead_code)]
747pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
748 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
749 [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], ];
763
764 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
765 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
766
767 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
768 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
769
770 [
771 ratios[0] * NORM13,
772 ratios[1] * NORM24,
773 ratios[2] * NORM13,
774 ratios[3] * NORM24,
775 ]
776}
777
778#[derive(PartialEq, Eq, Hash, Clone)]
779pub(crate) enum AtlasKey {
780 Glyph(RenderGlyphParams),
781 Svg(RenderSvgParams),
782 Image(RenderImageParams),
783}
784
785impl AtlasKey {
786 #[cfg_attr(
787 all(
788 any(target_os = "linux", target_os = "freebsd"),
789 not(any(feature = "x11", feature = "wayland"))
790 ),
791 allow(dead_code)
792 )]
793 pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
794 match self {
795 AtlasKey::Glyph(params) => {
796 if params.is_emoji {
797 AtlasTextureKind::Polychrome
798 } else {
799 AtlasTextureKind::Monochrome
800 }
801 }
802 AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
803 AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
804 }
805 }
806}
807
808impl From<RenderGlyphParams> for AtlasKey {
809 fn from(params: RenderGlyphParams) -> Self {
810 Self::Glyph(params)
811 }
812}
813
814impl From<RenderSvgParams> for AtlasKey {
815 fn from(params: RenderSvgParams) -> Self {
816 Self::Svg(params)
817 }
818}
819
820impl From<RenderImageParams> for AtlasKey {
821 fn from(params: RenderImageParams) -> Self {
822 Self::Image(params)
823 }
824}
825
826pub(crate) trait PlatformAtlas: Send + Sync {
827 fn get_or_insert_with<'a>(
828 &self,
829 key: &AtlasKey,
830 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
831 ) -> Result<Option<AtlasTile>>;
832 fn remove(&self, key: &AtlasKey);
833}
834
835struct AtlasTextureList<T> {
836 textures: Vec<Option<T>>,
837 free_list: Vec<usize>,
838}
839
840impl<T> Default for AtlasTextureList<T> {
841 fn default() -> Self {
842 Self {
843 textures: Vec::default(),
844 free_list: Vec::default(),
845 }
846 }
847}
848
849impl<T> ops::Index<usize> for AtlasTextureList<T> {
850 type Output = Option<T>;
851
852 fn index(&self, index: usize) -> &Self::Output {
853 &self.textures[index]
854 }
855}
856
857impl<T> AtlasTextureList<T> {
858 #[allow(unused)]
859 fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
860 self.free_list.clear();
861 self.textures.drain(..)
862 }
863
864 #[allow(dead_code)]
865 fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
866 self.textures.iter_mut().flatten()
867 }
868}
869
870#[derive(Clone, Debug, PartialEq, Eq)]
871#[repr(C)]
872pub(crate) struct AtlasTile {
873 pub(crate) texture_id: AtlasTextureId,
874 pub(crate) tile_id: TileId,
875 pub(crate) padding: u32,
876 pub(crate) bounds: Bounds<DevicePixels>,
877}
878
879#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
880#[repr(C)]
881pub(crate) struct AtlasTextureId {
882 pub(crate) index: u32,
884 pub(crate) kind: AtlasTextureKind,
885}
886
887#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
888#[repr(C)]
889#[cfg_attr(
890 all(
891 any(target_os = "linux", target_os = "freebsd"),
892 not(any(feature = "x11", feature = "wayland"))
893 ),
894 allow(dead_code)
895)]
896pub(crate) enum AtlasTextureKind {
897 Monochrome = 0,
898 Polychrome = 1,
899}
900
901#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
902#[repr(C)]
903pub(crate) struct TileId(pub(crate) u32);
904
905impl From<etagere::AllocId> for TileId {
906 fn from(id: etagere::AllocId) -> Self {
907 Self(id.serialize())
908 }
909}
910
911impl From<TileId> for etagere::AllocId {
912 fn from(id: TileId) -> Self {
913 Self::deserialize(id.0)
914 }
915}
916
917pub(crate) struct PlatformInputHandler {
918 cx: AsyncWindowContext,
919 handler: Box<dyn InputHandler>,
920}
921
922#[cfg_attr(
923 all(
924 any(target_os = "linux", target_os = "freebsd"),
925 not(any(feature = "x11", feature = "wayland"))
926 ),
927 allow(dead_code)
928)]
929impl PlatformInputHandler {
930 pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
931 Self { cx, handler }
932 }
933
934 fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
935 self.cx
936 .update(|window, cx| {
937 self.handler
938 .selected_text_range(ignore_disabled_input, window, cx)
939 })
940 .ok()
941 .flatten()
942 }
943
944 #[cfg_attr(target_os = "windows", allow(dead_code))]
945 fn marked_text_range(&mut self) -> Option<Range<usize>> {
946 self.cx
947 .update(|window, cx| self.handler.marked_text_range(window, cx))
948 .ok()
949 .flatten()
950 }
951
952 #[cfg_attr(
953 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
954 allow(dead_code)
955 )]
956 fn text_for_range(
957 &mut self,
958 range_utf16: Range<usize>,
959 adjusted: &mut Option<Range<usize>>,
960 ) -> Option<String> {
961 self.cx
962 .update(|window, cx| {
963 self.handler
964 .text_for_range(range_utf16, adjusted, window, cx)
965 })
966 .ok()
967 .flatten()
968 }
969
970 fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
971 self.cx
972 .update(|window, cx| {
973 self.handler
974 .replace_text_in_range(replacement_range, text, window, cx);
975 })
976 .ok();
977 }
978
979 pub fn replace_and_mark_text_in_range(
980 &mut self,
981 range_utf16: Option<Range<usize>>,
982 new_text: &str,
983 new_selected_range: Option<Range<usize>>,
984 ) {
985 self.cx
986 .update(|window, cx| {
987 self.handler.replace_and_mark_text_in_range(
988 range_utf16,
989 new_text,
990 new_selected_range,
991 window,
992 cx,
993 )
994 })
995 .ok();
996 }
997
998 #[cfg_attr(target_os = "windows", allow(dead_code))]
999 fn unmark_text(&mut self) {
1000 self.cx
1001 .update(|window, cx| self.handler.unmark_text(window, cx))
1002 .ok();
1003 }
1004
1005 fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
1006 self.cx
1007 .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
1008 .ok()
1009 .flatten()
1010 }
1011
1012 #[allow(dead_code)]
1013 fn apple_press_and_hold_enabled(&mut self) -> bool {
1014 self.handler.apple_press_and_hold_enabled()
1015 }
1016
1017 pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
1018 self.handler.replace_text_in_range(None, input, window, cx);
1019 }
1020
1021 pub fn selected_bounds(&mut self, window: &mut Window, cx: &mut App) -> Option<Bounds<Pixels>> {
1022 let selection = self.handler.selected_text_range(true, window, cx)?;
1023 self.handler.bounds_for_range(
1024 if selection.reversed {
1025 selection.range.start..selection.range.start
1026 } else {
1027 selection.range.end..selection.range.end
1028 },
1029 window,
1030 cx,
1031 )
1032 }
1033
1034 #[allow(unused)]
1035 pub fn character_index_for_point(&mut self, point: Point<Pixels>) -> Option<usize> {
1036 self.cx
1037 .update(|window, cx| self.handler.character_index_for_point(point, window, cx))
1038 .ok()
1039 .flatten()
1040 }
1041
1042 #[allow(dead_code)]
1043 pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
1044 self.handler.accepts_text_input(window, cx)
1045 }
1046}
1047
1048#[derive(Debug)]
1051pub struct UTF16Selection {
1052 pub range: Range<usize>,
1055 pub reversed: bool,
1058}
1059
1060pub trait InputHandler: 'static {
1065 fn selected_text_range(
1070 &mut self,
1071 ignore_disabled_input: bool,
1072 window: &mut Window,
1073 cx: &mut App,
1074 ) -> Option<UTF16Selection>;
1075
1076 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>>;
1081
1082 fn text_for_range(
1087 &mut self,
1088 range_utf16: Range<usize>,
1089 adjusted_range: &mut Option<Range<usize>>,
1090 window: &mut Window,
1091 cx: &mut App,
1092 ) -> Option<String>;
1093
1094 fn replace_text_in_range(
1099 &mut self,
1100 replacement_range: Option<Range<usize>>,
1101 text: &str,
1102 window: &mut Window,
1103 cx: &mut App,
1104 );
1105
1106 fn replace_and_mark_text_in_range(
1113 &mut self,
1114 range_utf16: Option<Range<usize>>,
1115 new_text: &str,
1116 new_selected_range: Option<Range<usize>>,
1117 window: &mut Window,
1118 cx: &mut App,
1119 );
1120
1121 fn unmark_text(&mut self, window: &mut Window, cx: &mut App);
1124
1125 fn bounds_for_range(
1130 &mut self,
1131 range_utf16: Range<usize>,
1132 window: &mut Window,
1133 cx: &mut App,
1134 ) -> Option<Bounds<Pixels>>;
1135
1136 fn character_index_for_point(
1140 &mut self,
1141 point: Point<Pixels>,
1142 window: &mut Window,
1143 cx: &mut App,
1144 ) -> Option<usize>;
1145
1146 #[allow(dead_code)]
1151 fn apple_press_and_hold_enabled(&mut self) -> bool {
1152 true
1153 }
1154
1155 fn accepts_text_input(&mut self, _window: &mut Window, _cx: &mut App) -> bool {
1157 true
1158 }
1159}
1160
1161#[derive(Debug)]
1163pub struct WindowOptions {
1164 pub window_bounds: Option<WindowBounds>,
1168
1169 pub titlebar: Option<TitlebarOptions>,
1171
1172 pub focus: bool,
1174
1175 pub show: bool,
1177
1178 pub kind: WindowKind,
1180
1181 pub is_movable: bool,
1183
1184 pub is_resizable: bool,
1186
1187 pub is_minimizable: bool,
1189
1190 pub display_id: Option<DisplayId>,
1193
1194 pub window_background: WindowBackgroundAppearance,
1196
1197 pub app_id: Option<String>,
1199
1200 pub window_min_size: Option<Size<Pixels>>,
1202
1203 pub window_decorations: Option<WindowDecorations>,
1206
1207 pub tabbing_identifier: Option<String>,
1209}
1210
1211#[derive(Debug)]
1213#[cfg_attr(
1214 all(
1215 any(target_os = "linux", target_os = "freebsd"),
1216 not(any(feature = "x11", feature = "wayland"))
1217 ),
1218 allow(dead_code)
1219)]
1220pub(crate) struct WindowParams {
1221 pub bounds: Bounds<Pixels>,
1222
1223 #[cfg_attr(feature = "wayland", allow(dead_code))]
1225 pub titlebar: Option<TitlebarOptions>,
1226
1227 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1229 pub kind: WindowKind,
1230
1231 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1233 pub is_movable: bool,
1234
1235 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1237 pub is_resizable: bool,
1238
1239 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1241 pub is_minimizable: bool,
1242
1243 #[cfg_attr(
1244 any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
1245 allow(dead_code)
1246 )]
1247 pub focus: bool,
1248
1249 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1250 pub show: bool,
1251
1252 #[cfg_attr(feature = "wayland", allow(dead_code))]
1253 pub display_id: Option<DisplayId>,
1254
1255 pub window_min_size: Option<Size<Pixels>>,
1256 #[cfg(target_os = "macos")]
1257 pub tabbing_identifier: Option<String>,
1258}
1259
1260#[derive(Debug, Copy, Clone, PartialEq)]
1262pub enum WindowBounds {
1263 Windowed(Bounds<Pixels>),
1265 Maximized(Bounds<Pixels>),
1268 Fullscreen(Bounds<Pixels>),
1271}
1272
1273impl Default for WindowBounds {
1274 fn default() -> Self {
1275 WindowBounds::Windowed(Bounds::default())
1276 }
1277}
1278
1279impl WindowBounds {
1280 pub fn get_bounds(&self) -> Bounds<Pixels> {
1282 match self {
1283 WindowBounds::Windowed(bounds) => *bounds,
1284 WindowBounds::Maximized(bounds) => *bounds,
1285 WindowBounds::Fullscreen(bounds) => *bounds,
1286 }
1287 }
1288
1289 pub fn centered(size: Size<Pixels>, cx: &App) -> Self {
1291 WindowBounds::Windowed(Bounds::centered(None, size, cx))
1292 }
1293}
1294
1295impl Default for WindowOptions {
1296 fn default() -> Self {
1297 Self {
1298 window_bounds: None,
1299 titlebar: Some(TitlebarOptions {
1300 title: Default::default(),
1301 appears_transparent: Default::default(),
1302 traffic_light_position: Default::default(),
1303 }),
1304 focus: true,
1305 show: true,
1306 kind: WindowKind::Normal,
1307 is_movable: true,
1308 is_resizable: true,
1309 is_minimizable: true,
1310 display_id: None,
1311 window_background: WindowBackgroundAppearance::default(),
1312 app_id: None,
1313 window_min_size: None,
1314 window_decorations: None,
1315 tabbing_identifier: None,
1316 }
1317 }
1318}
1319
1320#[derive(Debug, Default)]
1322pub struct TitlebarOptions {
1323 pub title: Option<SharedString>,
1325
1326 pub appears_transparent: bool,
1329
1330 pub traffic_light_position: Option<Point<Pixels>>,
1332}
1333
1334#[derive(Clone, Debug, PartialEq, Eq)]
1336pub enum WindowKind {
1337 Normal,
1339
1340 PopUp,
1343
1344 Floating,
1346
1347 #[cfg(all(target_os = "linux", feature = "wayland"))]
1350 LayerShell(layer_shell::LayerShellOptions),
1351}
1352
1353#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1358pub enum WindowAppearance {
1359 #[default]
1363 Light,
1364
1365 VibrantLight,
1369
1370 Dark,
1374
1375 VibrantDark,
1379}
1380
1381#[derive(Copy, Clone, Debug, Default, PartialEq)]
1384pub enum WindowBackgroundAppearance {
1385 #[default]
1393 Opaque,
1394 Transparent,
1396 Blurred,
1400 MicaBackdrop,
1402 MicaAltBackdrop,
1404}
1405
1406#[derive(Clone, Debug)]
1408pub struct PathPromptOptions {
1409 pub files: bool,
1411 pub directories: bool,
1413 pub multiple: bool,
1415 pub prompt: Option<SharedString>,
1417}
1418
1419#[derive(Copy, Clone, Debug, PartialEq)]
1421pub enum PromptLevel {
1422 Info,
1424
1425 Warning,
1427
1428 Critical,
1430}
1431
1432#[derive(Clone, Debug, PartialEq)]
1434pub enum PromptButton {
1435 Ok(SharedString),
1437 Cancel(SharedString),
1439 Other(SharedString),
1441}
1442
1443impl PromptButton {
1444 pub fn new(label: impl Into<SharedString>) -> Self {
1446 PromptButton::Other(label.into())
1447 }
1448
1449 pub fn ok(label: impl Into<SharedString>) -> Self {
1451 PromptButton::Ok(label.into())
1452 }
1453
1454 pub fn cancel(label: impl Into<SharedString>) -> Self {
1456 PromptButton::Cancel(label.into())
1457 }
1458
1459 #[allow(dead_code)]
1460 pub(crate) fn is_cancel(&self) -> bool {
1461 matches!(self, PromptButton::Cancel(_))
1462 }
1463
1464 pub fn label(&self) -> &SharedString {
1466 match self {
1467 PromptButton::Ok(label) => label,
1468 PromptButton::Cancel(label) => label,
1469 PromptButton::Other(label) => label,
1470 }
1471 }
1472}
1473
1474impl From<&str> for PromptButton {
1475 fn from(value: &str) -> Self {
1476 match value.to_lowercase().as_str() {
1477 "ok" => PromptButton::Ok("Ok".into()),
1478 "cancel" => PromptButton::Cancel("Cancel".into()),
1479 _ => PromptButton::Other(SharedString::from(value.to_owned())),
1480 }
1481 }
1482}
1483
1484#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1486pub enum CursorStyle {
1487 #[default]
1489 Arrow,
1490
1491 IBeam,
1494
1495 Crosshair,
1498
1499 ClosedHand,
1502
1503 OpenHand,
1506
1507 PointingHand,
1510
1511 ResizeLeft,
1514
1515 ResizeRight,
1518
1519 ResizeLeftRight,
1522
1523 ResizeUp,
1526
1527 ResizeDown,
1530
1531 ResizeUpDown,
1534
1535 ResizeUpLeftDownRight,
1538
1539 ResizeUpRightDownLeft,
1542
1543 ResizeColumn,
1546
1547 ResizeRow,
1550
1551 IBeamCursorForVerticalLayout,
1554
1555 OperationNotAllowed,
1558
1559 DragLink,
1562
1563 DragCopy,
1566
1567 ContextualMenu,
1570
1571 None,
1573}
1574
1575#[derive(Clone, Debug, Eq, PartialEq)]
1577pub struct ClipboardItem {
1578 entries: Vec<ClipboardEntry>,
1579}
1580
1581#[derive(Clone, Debug, Eq, PartialEq)]
1583pub enum ClipboardEntry {
1584 String(ClipboardString),
1586 Image(Image),
1588 ExternalPaths(crate::ExternalPaths),
1590}
1591
1592impl ClipboardItem {
1593 pub fn new_string(text: String) -> Self {
1595 Self {
1596 entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1597 }
1598 }
1599
1600 pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1602 Self {
1603 entries: vec![ClipboardEntry::String(ClipboardString {
1604 text,
1605 metadata: Some(metadata),
1606 })],
1607 }
1608 }
1609
1610 pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1612 Self {
1613 entries: vec![ClipboardEntry::String(
1614 ClipboardString::new(text).with_json_metadata(metadata),
1615 )],
1616 }
1617 }
1618
1619 pub fn new_image(image: &Image) -> Self {
1621 Self {
1622 entries: vec![ClipboardEntry::Image(image.clone())],
1623 }
1624 }
1625
1626 pub fn text(&self) -> Option<String> {
1629 let mut answer = String::new();
1630
1631 for entry in self.entries.iter() {
1632 if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1633 answer.push_str(text);
1634 }
1635 }
1636
1637 if answer.is_empty() {
1638 for entry in self.entries.iter() {
1639 if let ClipboardEntry::ExternalPaths(paths) = entry {
1640 for path in &paths.0 {
1641 use std::fmt::Write as _;
1642 _ = write!(answer, "{}", path.display());
1643 }
1644 }
1645 }
1646 }
1647
1648 if !answer.is_empty() {
1649 Some(answer)
1650 } else {
1651 None
1652 }
1653 }
1654
1655 #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1657 pub fn metadata(&self) -> Option<&String> {
1658 match self.entries().first() {
1659 Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1660 clipboard_string.metadata.as_ref()
1661 }
1662 _ => None,
1663 }
1664 }
1665
1666 pub fn entries(&self) -> &[ClipboardEntry] {
1668 &self.entries
1669 }
1670
1671 pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1673 self.entries.into_iter()
1674 }
1675}
1676
1677impl From<ClipboardString> for ClipboardEntry {
1678 fn from(value: ClipboardString) -> Self {
1679 Self::String(value)
1680 }
1681}
1682
1683impl From<String> for ClipboardEntry {
1684 fn from(value: String) -> Self {
1685 Self::from(ClipboardString::from(value))
1686 }
1687}
1688
1689impl From<Image> for ClipboardEntry {
1690 fn from(value: Image) -> Self {
1691 Self::Image(value)
1692 }
1693}
1694
1695impl From<ClipboardEntry> for ClipboardItem {
1696 fn from(value: ClipboardEntry) -> Self {
1697 Self {
1698 entries: vec![value],
1699 }
1700 }
1701}
1702
1703impl From<String> for ClipboardItem {
1704 fn from(value: String) -> Self {
1705 Self::from(ClipboardEntry::from(value))
1706 }
1707}
1708
1709impl From<Image> for ClipboardItem {
1710 fn from(value: Image) -> Self {
1711 Self::from(ClipboardEntry::from(value))
1712 }
1713}
1714
1715#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1717pub enum ImageFormat {
1718 Png,
1723 Jpeg,
1725 Webp,
1727 Gif,
1729 Svg,
1731 Bmp,
1733 Tiff,
1735 Ico,
1737}
1738
1739impl ImageFormat {
1740 pub const fn mime_type(self) -> &'static str {
1742 match self {
1743 ImageFormat::Png => "image/png",
1744 ImageFormat::Jpeg => "image/jpeg",
1745 ImageFormat::Webp => "image/webp",
1746 ImageFormat::Gif => "image/gif",
1747 ImageFormat::Svg => "image/svg+xml",
1748 ImageFormat::Bmp => "image/bmp",
1749 ImageFormat::Tiff => "image/tiff",
1750 ImageFormat::Ico => "image/ico",
1751 }
1752 }
1753
1754 pub fn from_mime_type(mime_type: &str) -> Option<Self> {
1756 match mime_type {
1757 "image/png" => Some(Self::Png),
1758 "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
1759 "image/webp" => Some(Self::Webp),
1760 "image/gif" => Some(Self::Gif),
1761 "image/svg+xml" => Some(Self::Svg),
1762 "image/bmp" => Some(Self::Bmp),
1763 "image/tiff" | "image/tif" => Some(Self::Tiff),
1764 "image/ico" => Some(Self::Ico),
1765 _ => None,
1766 }
1767 }
1768}
1769
1770#[derive(Clone, Debug, PartialEq, Eq)]
1772pub struct Image {
1773 pub format: ImageFormat,
1775 pub bytes: Vec<u8>,
1777 id: u64,
1779}
1780
1781impl Hash for Image {
1782 fn hash<H: Hasher>(&self, state: &mut H) {
1783 state.write_u64(self.id);
1784 }
1785}
1786
1787impl Image {
1788 pub fn empty() -> Self {
1790 Self::from_bytes(ImageFormat::Png, Vec::new())
1791 }
1792
1793 pub fn from_bytes(format: ImageFormat, bytes: Vec<u8>) -> Self {
1795 Self {
1796 id: hash(&bytes),
1797 format,
1798 bytes,
1799 }
1800 }
1801
1802 pub fn id(&self) -> u64 {
1804 self.id
1805 }
1806
1807 pub fn use_render_image(
1809 self: Arc<Self>,
1810 window: &mut Window,
1811 cx: &mut App,
1812 ) -> Option<Arc<RenderImage>> {
1813 ImageSource::Image(self)
1814 .use_data(None, window, cx)
1815 .and_then(|result| result.ok())
1816 }
1817
1818 pub fn get_render_image(
1820 self: Arc<Self>,
1821 window: &mut Window,
1822 cx: &mut App,
1823 ) -> Option<Arc<RenderImage>> {
1824 ImageSource::Image(self)
1825 .get_data(None, window, cx)
1826 .and_then(|result| result.ok())
1827 }
1828
1829 pub fn remove_asset(self: Arc<Self>, cx: &mut App) {
1831 ImageSource::Image(self).remove_asset(cx);
1832 }
1833
1834 pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
1836 fn frames_for_image(
1837 bytes: &[u8],
1838 format: image::ImageFormat,
1839 ) -> Result<SmallVec<[Frame; 1]>> {
1840 let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1841
1842 for pixel in data.chunks_exact_mut(4) {
1844 pixel.swap(0, 2);
1845 }
1846
1847 Ok(SmallVec::from_elem(Frame::new(data), 1))
1848 }
1849
1850 let frames = match self.format {
1851 ImageFormat::Gif => {
1852 let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1853 let mut frames = SmallVec::new();
1854
1855 for frame in decoder.into_frames() {
1856 let mut frame = frame?;
1857 for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1859 pixel.swap(0, 2);
1860 }
1861 frames.push(frame);
1862 }
1863
1864 frames
1865 }
1866 ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1867 ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1868 ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1869 ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1870 ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1871 ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
1872 ImageFormat::Svg => {
1873 return svg_renderer
1874 .render_single_frame(&self.bytes, 1.0, false)
1875 .map_err(Into::into);
1876 }
1877 };
1878
1879 Ok(Arc::new(RenderImage::new(frames)))
1880 }
1881
1882 pub fn format(&self) -> ImageFormat {
1884 self.format
1885 }
1886
1887 pub fn bytes(&self) -> &[u8] {
1889 self.bytes.as_slice()
1890 }
1891}
1892
1893#[derive(Clone, Debug, Eq, PartialEq)]
1895pub struct ClipboardString {
1896 pub(crate) text: String,
1897 pub(crate) metadata: Option<String>,
1898}
1899
1900impl ClipboardString {
1901 pub fn new(text: String) -> Self {
1903 Self {
1904 text,
1905 metadata: None,
1906 }
1907 }
1908
1909 pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1912 self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1913 self
1914 }
1915
1916 pub fn text(&self) -> &String {
1918 &self.text
1919 }
1920
1921 pub fn into_text(self) -> String {
1923 self.text
1924 }
1925
1926 pub fn metadata_json<T>(&self) -> Option<T>
1928 where
1929 T: for<'a> Deserialize<'a>,
1930 {
1931 self.metadata
1932 .as_ref()
1933 .and_then(|m| serde_json::from_str(m).ok())
1934 }
1935
1936 #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
1937 pub(crate) fn text_hash(text: &str) -> u64 {
1938 let mut hasher = SeaHasher::new();
1939 text.hash(&mut hasher);
1940 hasher.finish()
1941 }
1942}
1943
1944impl From<String> for ClipboardString {
1945 fn from(value: String) -> Self {
1946 Self {
1947 text: value,
1948 metadata: None,
1949 }
1950 }
1951}