1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct ElementId(Uuid);
9
10impl ElementId {
11 #[must_use]
13 pub fn new() -> Self {
14 Self(Uuid::new_v4())
15 }
16
17 #[must_use]
19 pub fn from_uuid(uuid: Uuid) -> Self {
20 Self(uuid)
21 }
22
23 pub fn parse(s: &str) -> Result<Self, uuid::Error> {
29 Uuid::parse_str(s).map(Self)
30 }
31}
32
33impl Default for ElementId {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl std::fmt::Display for ElementId {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}", self.0)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[serde(tag = "type", content = "data")]
48pub enum ElementKind {
49 Chart {
51 chart_type: String,
53 data: serde_json::Value,
55 },
56
57 Image {
59 src: String,
61 format: ImageFormat,
63 },
64
65 Model3D {
67 src: String,
69 rotation: [f32; 3],
71 scale: f32,
73 },
74
75 Video {
77 stream_id: String,
79 is_live: bool,
81 mirror: bool,
83 crop: Option<CropRect>,
85 media_config: Option<MediaConfig>,
87 },
88
89 OverlayLayer {
91 children: Vec<ElementId>,
93 opacity: f32,
95 },
96
97 Text {
99 content: String,
101 font_size: f32,
103 color: String,
105 },
106
107 Group {
109 children: Vec<ElementId>,
111 },
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "lowercase")]
117pub enum ImageFormat {
118 Png,
120 Jpeg,
122 Svg,
124 WebP,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
131pub struct CropRect {
132 pub x: f32,
134 pub y: f32,
136 pub width: f32,
138 pub height: f32,
140}
141
142impl Default for CropRect {
143 fn default() -> Self {
144 Self {
145 x: 0.0,
146 y: 0.0,
147 width: 1.0,
148 height: 1.0,
149 }
150 }
151}
152
153impl CropRect {
154 #[must_use]
156 pub fn full() -> Self {
157 Self::default()
158 }
159
160 #[must_use]
162 pub fn center_square() -> Self {
163 Self {
164 x: 0.25,
165 y: 0.0,
166 width: 0.5,
167 height: 1.0,
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
174#[serde(rename_all = "lowercase")]
175pub enum Resolution {
176 R240p,
178 R360p,
180 R480p,
182 #[default]
184 R720p,
185 R1080p,
187}
188
189impl Resolution {
190 #[must_use]
192 pub const fn dimensions(&self) -> (u32, u32) {
193 match self {
194 Self::R240p => (426, 240),
195 Self::R360p => (640, 360),
196 Self::R480p => (854, 480),
197 Self::R720p => (1280, 720),
198 Self::R1080p => (1920, 1080),
199 }
200 }
201
202 #[must_use]
204 pub const fn suggested_bitrate_kbps(&self) -> u32 {
205 match self {
206 Self::R240p => 400,
207 Self::R360p => 800,
208 Self::R480p => 1200,
209 Self::R720p => 2500,
210 Self::R1080p => 5000,
211 }
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
217#[serde(rename_all = "lowercase")]
218pub enum QualityPreset {
219 #[default]
221 Auto,
222 Low,
224 Medium,
226 High,
228 Ultra,
230}
231
232impl QualityPreset {
233 #[must_use]
235 pub const fn resolution(&self) -> Resolution {
236 match self {
237 Self::Auto | Self::High => Resolution::R720p,
238 Self::Low => Resolution::R360p,
239 Self::Medium => Resolution::R480p,
240 Self::Ultra => Resolution::R1080p,
241 }
242 }
243
244 #[must_use]
246 pub const fn bitrate_kbps(&self) -> u32 {
247 match self {
248 Self::Low => 400,
249 Self::Medium => 1200,
250 Self::Auto | Self::High => 2500,
251 Self::Ultra => 5000,
252 }
253 }
254
255 #[must_use]
257 pub const fn framerate(&self) -> u8 {
258 match self {
259 Self::Low => 15,
260 Self::Medium => 24,
261 Self::Auto | Self::High | Self::Ultra => 30,
262 }
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct MediaConfig {
269 pub bitrate_kbps: Option<u32>,
271 pub max_resolution: Option<Resolution>,
273 pub quality_preset: QualityPreset,
275 pub target_fps: Option<u8>,
277 pub audio_enabled: bool,
279}
280
281impl Default for MediaConfig {
282 fn default() -> Self {
283 Self {
284 bitrate_kbps: None,
285 max_resolution: None,
286 quality_preset: QualityPreset::Auto,
287 target_fps: None,
288 audio_enabled: false,
289 }
290 }
291}
292
293impl MediaConfig {
294 #[must_use]
296 pub fn from_preset(preset: QualityPreset) -> Self {
297 Self {
298 bitrate_kbps: Some(preset.bitrate_kbps()),
299 max_resolution: Some(preset.resolution()),
300 quality_preset: preset,
301 target_fps: Some(preset.framerate()),
302 audio_enabled: false,
303 }
304 }
305
306 #[must_use]
308 pub fn effective_bitrate_kbps(&self) -> u32 {
309 self.bitrate_kbps
310 .unwrap_or_else(|| self.quality_preset.bitrate_kbps())
311 }
312
313 #[must_use]
315 pub fn effective_resolution(&self) -> Resolution {
316 self.max_resolution
317 .unwrap_or_else(|| self.quality_preset.resolution())
318 }
319
320 #[must_use]
322 pub fn effective_fps(&self) -> u8 {
323 self.target_fps
324 .unwrap_or_else(|| self.quality_preset.framerate())
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
330pub struct MediaStats {
331 pub rtt_ms: Option<f64>,
333 pub jitter_ms: Option<f64>,
335 pub packet_loss_percent: Option<f64>,
337 pub fps: Option<f64>,
339 pub bitrate_kbps: Option<f64>,
341 pub timestamp_ms: u64,
343}
344
345impl MediaStats {
346 #[must_use]
348 pub fn is_quality_good(&self) -> bool {
349 let loss_ok = self.packet_loss_percent.is_none_or(|l| l < 2.0);
350 let rtt_ok = self.rtt_ms.is_none_or(|r| r < 150.0);
351 loss_ok && rtt_ok
352 }
353
354 #[must_use]
356 pub fn should_downgrade(&self) -> bool {
357 let high_loss = self.packet_loss_percent.is_some_and(|l| l > 5.0);
358 let high_rtt = self.rtt_ms.is_some_and(|r| r > 300.0);
359 high_loss || high_rtt
360 }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
365pub struct Transform {
366 pub x: f32,
368 pub y: f32,
370 pub width: f32,
372 pub height: f32,
374 pub rotation: f32,
376 pub z_index: i32,
378}
379
380impl Default for Transform {
381 fn default() -> Self {
382 Self {
383 x: 0.0,
384 y: 0.0,
385 width: 100.0,
386 height: 100.0,
387 rotation: 0.0,
388 z_index: 0,
389 }
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
395pub struct Element {
396 pub id: ElementId,
398 pub kind: ElementKind,
400 pub transform: Transform,
402 pub selected: bool,
404 pub interactive: bool,
406 pub parent: Option<ElementId>,
408}
409
410impl Element {
411 #[must_use]
413 pub fn new(kind: ElementKind) -> Self {
414 Self {
415 id: ElementId::new(),
416 kind,
417 transform: Transform::default(),
418 selected: false,
419 interactive: true,
420 parent: None,
421 }
422 }
423
424 #[must_use]
426 pub fn with_transform(mut self, transform: Transform) -> Self {
427 self.transform = transform;
428 self
429 }
430
431 #[must_use]
433 pub fn with_interactive(mut self, interactive: bool) -> Self {
434 self.interactive = interactive;
435 self
436 }
437
438 #[must_use]
440 pub fn contains_point(&self, x: f32, y: f32) -> bool {
441 let t = &self.transform;
442 x >= t.x && x <= t.x + t.width && y >= t.y && y <= t.y + t.height
443 }
444}