azul_layout/managers/selection.rs
1//! Clipboard content types for copy/paste operations
2//!
3//! Contains `ClipboardContent` and `StyledTextRun`, used by clipboard and
4//! changeset modules.
5//!
6//! **Rich-text status:** `StyledTextRun`, `StyledTextRunVec` and the
7//! `ClipboardContent.styled_runs` field are FFI-exported (api.json), but the
8//! rich path is only half-wired: the live clipboard producers build
9//! `styled_runs` empty (`window.rs::get_selected_content_for_clipboard`,
10//! paste in `common/event.rs`) and the platform clipboard backends write only
11//! `plain_text`. Fully wiring it means (a) extracting per-run style from the
12//! styled DOM when copying and (b) adding an HTML/RTF format to each platform's
13//! clipboard write (and reading it back on paste). `to_html()` below is the
14//! retained consumer for that future format. Until then the FFI surface is
15//! kept (it is public API) but `styled_runs` stays empty.
16
17use azul_css::{impl_option, impl_option_inner, AzString, OptionString};
18
19// Clipboard Content Extraction
20
21/// Styled text run for rich clipboard content
22#[derive(Debug, Clone, PartialEq)]
23#[repr(C)]
24pub struct StyledTextRun {
25 /// The actual text content
26 pub text: AzString,
27 /// Font family name
28 pub font_family: OptionString,
29 /// Font size in pixels
30 pub font_size_px: f32,
31 /// Text color
32 pub color: azul_css::props::basic::ColorU,
33 /// Whether text is bold
34 pub is_bold: bool,
35 /// Whether text is italic
36 pub is_italic: bool,
37}
38
39azul_css::impl_option!(StyledTextRun, OptionStyledTextRun, copy = false, [Debug, Clone, PartialEq]);
40azul_css::impl_vec!(StyledTextRun, StyledTextRunVec, StyledTextRunVecDestructor, StyledTextRunVecDestructorType, StyledTextRunVecSlice, OptionStyledTextRun);
41azul_css::impl_vec_debug!(StyledTextRun, StyledTextRunVec);
42azul_css::impl_vec_clone!(StyledTextRun, StyledTextRunVec, StyledTextRunVecDestructor);
43azul_css::impl_vec_partialeq!(StyledTextRun, StyledTextRunVec);
44
45/// Clipboard content with both plain text and styled (HTML) representation
46#[derive(Debug, Clone, PartialEq)]
47#[repr(C)]
48pub struct ClipboardContent {
49 /// Plain text representation (UTF-8)
50 pub plain_text: AzString,
51 /// Rich text runs with styling information
52 pub styled_runs: StyledTextRunVec,
53}
54
55impl_option!(
56 ClipboardContent,
57 OptionClipboardContent,
58 copy = false,
59 [Debug, Clone, PartialEq]
60);
61
62impl ClipboardContent {
63 /// Convert styled runs to HTML for rich clipboard formats.
64 ///
65 /// Retained consumer of the FFI-exported `styled_runs`: returns an empty
66 /// `<div></div>` until `styled_runs` is populated and the platform clipboard
67 /// backends gain an HTML format (see module docs). Kept as public API.
68 #[must_use] pub fn to_html(&self) -> String {
69 use core::fmt::Write as _;
70 let mut html = String::from("<div>");
71
72 for run in self.styled_runs.as_slice() {
73 html.push_str("<span style=\"");
74
75 if let Some(font_family) = run.font_family.as_ref() {
76 let _ = write!(html, "font-family: {}; ", font_family.as_str());
77 }
78 let _ = write!(html, "font-size: {}px; ", run.font_size_px);
79 let _ = write!(
80 html,
81 "color: rgba({}, {}, {}, {}); ",
82 run.color.r,
83 run.color.g,
84 run.color.b,
85 f32::from(run.color.a) / 255.0
86 );
87 if run.is_bold {
88 html.push_str("font-weight: bold; ");
89 }
90 if run.is_italic {
91 html.push_str("font-style: italic; ");
92 }
93
94 html.push_str("\">");
95 // Escape HTML entities
96 let escaped = run
97 .text
98 .as_str()
99 .replace('&', "&")
100 .replace('<', "<")
101 .replace('>', ">");
102 html.push_str(&escaped);
103 html.push_str("</span>");
104 }
105
106 html.push_str("</div>");
107 html
108 }
109}
110