Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/clipboard.rs
StratoSDK / crates / strato-ui-core / src / clipboard.rs
1use parking_lot::Mutex;
2 
3/// A Clipboard that can read and write strings. Each platform must implement this trait to support
4/// writing to a clipboard.
5pub trait Clipboard: 'static {
6 fn write(&mut self, contents: ClipboardContent);
7 
8 fn read(&mut self) -> ClipboardContent;
9 
10 /// Writes to the primary clipboard, used to support the Primary Selection Protocol (middle-click paste).
11 ///
12 /// NOTE: For platforms that don't support the primary clipboard, it writes to the default clipboard instead.
13 fn write_to_primary_clipboard(&mut self, contents: ClipboardContent) {
14 self.write(contents)
15 }
16 
17 /// Reads the primary clipboard, used to support the Primary Selection Protocol (middle-click paste).
18 /// This reads from the default clipboard on platforms other than Linux.
19 fn read_from_primary_clipboard(&mut self) -> ClipboardContent {
20 self.read()
21 }
22 
23 #[cfg(target_family = "wasm")]
24 fn save(&mut self, content: ClipboardContent);
25}
26 
27// Clipboard could contain content with multiple data types at the same type.
28#[derive(Debug, Clone, Default)]
29pub struct ClipboardContent {
30 // Clipboard contains plain string.
31 pub plain_text: String,
32 // Clipboard contains a list of file paths.
33 // Parsed direct from OS clipboard on Mac/Windows, from plain_text on Linux.
34 // On Mac/Linux, plain_text may also be populated.
35 pub paths: Option<Vec<String>>,
36 // Clipboard contains HTML content.
37 pub html: Option<String>,
38 // Clipboard contains image data (can be multiple images).
39 pub images: Option<Vec<ImageData>>,
40}
41 
42/// Represents image data from the clipboard.
43///
44/// Contains the raw image bytes and associated MIME type information.
45#[derive(Debug, Clone)]
46pub struct ImageData {
47 /// Raw image data as bytes.
48 pub data: Vec<u8>,
49 /// MIME type of the image (e.g., "image/png", "image/jpeg").
50 pub mime_type: String,
51 /// Original filename if available (e.g., "photo.jpg").
52 pub filename: Option<String>,
53}
54 
55impl ClipboardContent {
56 pub fn plain_text(text: String) -> Self {
57 Self {
58 plain_text: text,
59 paths: Default::default(),
60 html: Default::default(),
61 images: Default::default(),
62 }
63 }
64 
65 pub fn is_empty(&self) -> bool {
66 let Self {
67 plain_text,
68 paths,
69 html,
70 images,
71 } = self;
72 plain_text.is_empty() && paths.is_none() && html.is_none() && images.is_none()
73 }
74 
75 pub fn has_image_data(&self) -> bool {
76 self.images
77 .as_ref()
78 .map(|images| !images.is_empty())
79 .unwrap_or(false)
80 }
81 
82 pub fn num_paths(&self) -> usize {
83 self.paths.as_ref().map(|paths| paths.len()).unwrap_or(0)
84 }
85 
86 /// Check if clipboard contains file paths that are not images
87 pub fn has_non_image_filepaths(&self) -> bool {
88 self.paths
89 .as_ref()
90 .map(|paths| {
91 paths
92 .iter()
93 .any(|path| !crate::clipboard_utils::has_image_extension(path))
94 })
95 .unwrap_or(false)
96 }
97}
98 
99pub fn should_insert_text_on_paste(content: &ClipboardContent) -> bool {
100 // Insert any text content present when:
101 // 1. No images at all (neither data nor paths)
102 // 2. Has non-image files (mixed content)
103 // 3. Has image data but no file paths (direct image paste)
104 if !content.has_image_data() && content.num_paths() == 0 {
105 return true; // No images at all
106 }
107 if content.has_non_image_filepaths() {
108 return true; // Mixed content - user likely wants text paths
109 }
110 // Direct image paste - user would still want any text content present (unless paths)
111 content.has_image_data() && content.num_paths() == 0
112}
113 
114/// Stores clipboard content in the heap of this process. Therefore, it is scoped to this process.
115/// This is not a proper implementation for a "real" platform. It's useful in tests or as a
116/// temporary substitute.
117pub struct InMemoryClipboard {
118 clipboard_content: Mutex<ClipboardContent>,
119}
120 
121impl Default for InMemoryClipboard {
122 fn default() -> Self {
123 Self {
124 clipboard_content: Mutex::new(ClipboardContent::plain_text(String::new())),
125 }
126 }
127}
128 
129impl Clipboard for InMemoryClipboard {
130 fn write(&mut self, contents: ClipboardContent) {
131 *self.clipboard_content.lock() = contents;
132 }
133 
134 fn read(&mut self) -> ClipboardContent {
135 self.clipboard_content.lock().clone()
136 }
137 
138 #[cfg(target_family = "wasm")]
139 fn save(&mut self, _content: ClipboardContent) {}
140}
141