1use serde::{Deserialize, Serialize};
8
9pub type SurfaceId = String;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum Role {
17 Main,
19 Aside,
21 Float,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum Edge {
29 Left,
30 Right,
31 Top,
32 Bottom,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(tag = "kind", rename_all = "lowercase")]
39pub enum SurfaceContent {
40 Entry {
42 id: String,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 path: Option<String>,
46 },
47 Web {
51 url: String,
52 #[serde(
56 default = "default_reuse_by_url",
57 skip_serializing_if = "is_reuse_by_url"
58 )]
59 reuse_by_url: bool,
60 },
61}
62
63const fn default_reuse_by_url() -> bool {
64 true
65}
66
67fn is_reuse_by_url(value: &bool) -> bool {
68 *value
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub enum SlotKind {
77 Lxapp,
78 Browser,
79 Native,
80}
81
82impl SurfaceContent {
83 pub fn slot_kind(&self) -> SlotKind {
87 match self {
88 SurfaceContent::Web { .. } => SlotKind::Browser,
89 SurfaceContent::Entry { id, .. } if id == "terminal" => SlotKind::Native,
90 SurfaceContent::Entry { .. } => SlotKind::Lxapp,
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[serde(tag = "scope", rename_all = "camelCase")]
98pub enum SurfaceOwner {
99 Page { page_instance_id: String },
100 Lxapp { app_id: String },
101 Host,
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct Placement {
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub edge: Option<Edge>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub preferred_size: Option<f64>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "lowercase")]
117pub enum SurfaceState {
118 Mounted,
119 Hidden,
120 Minimized,
121}
122
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125#[serde(tag = "to", rename_all = "lowercase")]
126pub enum FloatAnchor {
127 Screen,
128 Surface { surface_id: SurfaceId },
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub enum FloatDismiss {
135 TapOutside,
136 Manual,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct SurfaceInteraction {
143 pub close_button: bool,
144 pub dismiss: FloatDismiss,
145 pub modal: bool,
146}
147
148impl SurfaceInteraction {
149 pub const fn standard() -> Self {
150 Self {
151 close_button: false,
152 dismiss: FloatDismiss::TapOutside,
153 modal: false,
154 }
155 }
156
157 pub const fn url_callback() -> Self {
158 Self {
159 close_button: true,
160 dismiss: FloatDismiss::Manual,
161 modal: true,
162 }
163 }
164
165 pub const fn window() -> Self {
166 Self {
167 close_button: false,
168 dismiss: FloatDismiss::Manual,
169 modal: false,
170 }
171 }
172}
173
174impl Default for SurfaceInteraction {
175 fn default() -> Self {
176 Self::standard()
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct FloatSpec {
184 pub anchor: FloatAnchor,
185 pub dismiss: FloatDismiss,
186 pub modal: bool,
188 pub close_button: bool,
190}
191
192impl Default for FloatSpec {
193 fn default() -> Self {
194 Self {
195 anchor: FloatAnchor::Screen,
196 dismiss: FloatDismiss::TapOutside,
197 modal: false,
198 close_button: false,
199 }
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct Surface {
207 pub id: SurfaceId,
208 pub role: Role,
209 pub content: SurfaceContent,
210 pub owner: SurfaceOwner,
211 #[serde(default)]
212 pub placement: Placement,
213 pub state: SurfaceState,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub float: Option<FloatSpec>,
217}
218
219impl Surface {
220 pub fn entry(id: impl Into<SurfaceId>, role: Role, entry_id: impl Into<String>) -> Self {
222 Self {
223 id: id.into(),
224 role,
225 content: SurfaceContent::Entry {
226 id: entry_id.into(),
227 path: None,
228 },
229 owner: SurfaceOwner::Host,
230 placement: Placement::default(),
231 state: SurfaceState::Mounted,
232 float: if role == Role::Float {
233 Some(FloatSpec::default())
234 } else {
235 None
236 },
237 }
238 }
239
240 pub fn is_modal_float(&self) -> bool {
241 self.role == Role::Float && self.float.as_ref().is_some_and(|f| f.modal)
242 }
243}