1use std::{borrow::Cow, path::PathBuf, sync::Arc};
2
3use crate::memory::ImageCachePolicy;
4
5use super::{
6 ActionId, AnimationBinding, BackdropBlurStyle, Color, ComponentId, CompositingLayerSpec,
7 CustomPaintStyle, IconStyle, ImageFit, LayoutSpec, OverlayStyle, PathStyle, PhysicalSize,
8 RenderPhase, ScrollRasterSpec, Semantics, StaticLayerSpec, TextStyle, UiAction,
9 UiActionBinding, UiActionHandler, UiEventContext, UiEventHandler, UiEventKind, UiEventPayload,
10 UiId, UiInputEventBinding, UiInputEventHandler, UiPath, UiRect, VisualStyle,
11};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum UiNodeKind {
15 Root,
16 Group,
17 Text,
18 Image,
19 Icon,
20 Glow,
21 BackdropBlur,
22 BackdropBlurPath,
23 Overlay,
24 Line,
25 Path,
26 Ellipse,
27 Panel,
28 Button,
29 Table,
30 TableRow,
31 CompositingLayer,
32 StaticLayer,
33 ScrollRaster,
34 Clip,
35 ClipPath,
36 Custom(&'static str),
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum InteractionRole {
41 None,
42 Button,
43 Navigation,
44 Row,
45 DragHandle,
46 WindowDragRegion,
47 Custom(&'static str),
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, Hash)]
51pub enum UiImageSource {
52 Static(&'static str),
53 File(PathBuf),
54 Url(String),
55 Bytes {
56 key: String,
57 version: u64,
58 bytes: Arc<Vec<u8>>,
59 },
60}
61
62impl UiImageSource {
63 pub fn static_asset(source: &'static str) -> Self {
64 Self::Static(source)
65 }
66
67 pub fn file(path: impl Into<PathBuf>) -> Self {
68 Self::File(path.into())
69 }
70
71 pub fn url(url: impl Into<String>) -> Self {
72 Self::Url(url.into())
73 }
74
75 pub fn bytes(key: impl Into<String>, version: u64, bytes: impl Into<Arc<Vec<u8>>>) -> Self {
76 Self::Bytes {
77 key: key.into(),
78 version,
79 bytes: bytes.into(),
80 }
81 }
82}
83
84impl From<&'static str> for UiImageSource {
85 fn from(value: &'static str) -> Self {
86 Self::Static(value)
87 }
88}
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
91pub enum ImageDecodePolicy {
92 #[default]
93 Original,
94 FitTarget(PhysicalSize),
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Hash)]
98pub struct ImageRequest {
99 source: UiImageSource,
100 cache_policy: ImageCachePolicy,
101 decode_policy: ImageDecodePolicy,
102 priority: crate::memory::CachePriority,
103 namespace: String,
104 version: u64,
105 sensitive: bool,
106}
107
108impl ImageRequest {
109 pub fn new(source: impl Into<UiImageSource>) -> Self {
110 Self {
111 source: source.into(),
112 cache_policy: ImageCachePolicy::ApplicationDefault,
113 decode_policy: ImageDecodePolicy::Original,
114 priority: crate::memory::CachePriority::Normal,
115 namespace: "images".to_owned(),
116 version: 1,
117 sensitive: false,
118 }
119 }
120
121 pub fn cache_policy(mut self, policy: ImageCachePolicy) -> Self {
122 self.cache_policy = policy;
123 self
124 }
125
126 pub fn decode_policy(mut self, policy: ImageDecodePolicy) -> Self {
127 self.decode_policy = policy;
128 self
129 }
130
131 pub fn priority(mut self, priority: crate::memory::CachePriority) -> Self {
132 self.priority = priority;
133 self
134 }
135
136 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
137 self.namespace = namespace.into();
138 self
139 }
140
141 pub fn version(mut self, version: u64) -> Self {
142 self.version = version;
143 self
144 }
145
146 pub fn sensitive(mut self, sensitive: bool) -> Self {
147 self.sensitive = sensitive;
148 self
149 }
150
151 pub fn source(&self) -> &UiImageSource {
152 &self.source
153 }
154
155 pub const fn cache_policy_value(
156 &self,
157 application_default: ImageCachePolicy,
158 ) -> ImageCachePolicy {
159 match self.cache_policy {
160 ImageCachePolicy::ApplicationDefault => application_default,
161 policy => policy,
162 }
163 }
164
165 pub const fn decode_policy_value(&self) -> ImageDecodePolicy {
166 self.decode_policy
167 }
168
169 pub const fn priority_value(&self) -> crate::memory::CachePriority {
170 self.priority
171 }
172
173 pub fn namespace_value(&self) -> &str {
174 &self.namespace
175 }
176
177 pub const fn version_value(&self) -> u64 {
178 self.version
179 }
180
181 pub const fn is_sensitive(&self) -> bool {
182 self.sensitive
183 }
184
185 pub fn cache_key(&self) -> String {
186 let source = match &self.source {
187 UiImageSource::Static(key) => format!("asset:{key}"),
188 UiImageSource::File(path) => format!("file:{}", path.display()),
189 UiImageSource::Url(url) => format!("url:{url}"),
190 UiImageSource::Bytes { key, version, .. } => format!("bytes:{key}:{version}"),
191 };
192 format!("{}:{source}:{}", self.namespace, self.version)
193 }
194}
195
196impl From<UiImageSource> for ImageRequest {
197 fn from(source: UiImageSource) -> Self {
198 Self::new(source)
199 }
200}
201
202impl From<&'static str> for ImageRequest {
203 fn from(source: &'static str) -> Self {
204 Self::new(source)
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct EventPolicy {
210 pub hover: bool,
211 pub press: bool,
212 pub focus: bool,
213}
214
215impl EventPolicy {
216 pub const NONE: Self = Self {
217 hover: false,
218 press: false,
219 focus: false,
220 };
221
222 pub const INTERACTIVE: Self = Self {
223 hover: true,
224 press: true,
225 focus: true,
226 };
227}
228
229#[derive(Clone)]
230pub struct UiNode {
231 pub id: UiId,
232 pub component_owner: Option<ComponentId>,
233 pub parent: Option<UiId>,
234 pub kind: UiNodeKind,
235 pub layout_rect: UiRect,
236 pub hit_rect: UiRect,
237 pub paint_bounds: UiRect,
238 pub ime_cursor_rect: Option<UiRect>,
239 pub interaction: InteractionRole,
240 pub semantics: Option<Semantics>,
241 pub click_capture_handler: Option<UiEventHandler>,
242 pub click_handler: Option<UiEventHandler>,
243 pub input_event_handlers: Vec<UiInputEventBinding>,
244 pub click_action: Option<UiAction>,
245 pub wheel_action: Option<UiAction>,
246 pub action_target: Option<UiId>,
247 pub action_handlers: Vec<UiActionBinding>,
248 pub event_policy: EventPolicy,
249 pub auto_focus: bool,
250 pub focus_scope: bool,
251 pub animation_bindings: Vec<AnimationBinding>,
252 pub animation_targets: Vec<(super::AnimProperty, bool)>,
253 pub animation_outset: (f32, f32),
254 pub layout: LayoutSpec,
255 pub style: VisualStyle,
256 pub path: Option<UiPath>,
257 pub path_style: PathStyle,
258 pub image_request: Option<ImageRequest>,
259 pub image_fit: ImageFit,
260 pub icon_key: Option<&'static str>,
261 pub icon_style: IconStyle,
262 pub glow: Option<(Color, u8)>,
263 pub backdrop_blur_style: Option<BackdropBlurStyle>,
264 pub overlay_style: Option<OverlayStyle>,
265 pub custom_style: Option<CustomPaintStyle>,
266 pub compositing_layer: Option<CompositingLayerSpec>,
267 pub shadow: Option<super::ShadowStyle>,
268 pub static_layer: Option<StaticLayerSpec>,
269 pub scroll_raster: Option<ScrollRasterSpec>,
270 pub clip_rect: Option<UiRect>,
271 pub content_offset: (f32, f32),
272 pub text: Option<Cow<'static, str>>,
273 pub text_style: Option<TextStyle>,
274 pub render_phase: RenderPhase,
275 pub children: Vec<UiId>,
276}
277
278impl UiNode {
279 pub fn new(id: UiId, kind: UiNodeKind, layout_rect: UiRect) -> Self {
280 Self {
281 id,
282 component_owner: None,
283 parent: None,
284 kind,
285 layout_rect,
286 hit_rect: layout_rect,
287 paint_bounds: layout_rect,
288 ime_cursor_rect: None,
289 interaction: InteractionRole::None,
290 semantics: None,
291 click_capture_handler: None,
292 click_handler: None,
293 input_event_handlers: Vec::new(),
294 click_action: None,
295 wheel_action: None,
296 action_target: None,
297 action_handlers: Vec::new(),
298 event_policy: EventPolicy::NONE,
299 auto_focus: false,
300 focus_scope: false,
301 animation_bindings: Vec::new(),
302 animation_targets: Vec::new(),
303 animation_outset: (0.0, 0.0),
304 layout: LayoutSpec::default(),
305 style: VisualStyle::default(),
306 path: None,
307 path_style: PathStyle::default(),
308 image_request: None,
309 image_fit: ImageFit::Contain,
310 icon_key: None,
311 icon_style: IconStyle::new(Color::WHITE),
312 glow: None,
313 backdrop_blur_style: None,
314 overlay_style: None,
315 custom_style: None,
316 compositing_layer: None,
317 shadow: None,
318 static_layer: None,
319 scroll_raster: None,
320 clip_rect: None,
321 content_offset: (0.0, 0.0),
322 text: None,
323 text_style: None,
324 render_phase: RenderPhase::Content,
325 children: Vec::new(),
326 }
327 }
328
329 pub(crate) fn estimated_bytes(&self) -> usize {
330 std::mem::size_of::<Self>()
331 .saturating_add(self.id.as_str().len())
332 .saturating_add(
333 self.parent
334 .as_ref()
335 .map_or(0, |parent| parent.as_str().len()),
336 )
337 .saturating_add(
338 self.children
339 .capacity()
340 .saturating_mul(std::mem::size_of::<UiId>()),
341 )
342 .saturating_add(self.children.iter().map(|id| id.as_str().len()).sum())
343 .saturating_add(self.text.as_deref().map_or(0, str::len))
344 .saturating_add(
345 self.input_event_handlers
346 .capacity()
347 .saturating_mul(std::mem::size_of::<UiInputEventBinding>()),
348 )
349 .saturating_add(
350 self.action_handlers
351 .capacity()
352 .saturating_mul(std::mem::size_of::<UiActionBinding>()),
353 )
354 .saturating_add(
355 self.animation_bindings
356 .capacity()
357 .saturating_mul(std::mem::size_of::<AnimationBinding>()),
358 )
359 .saturating_add(
360 self.animation_targets
361 .capacity()
362 .saturating_mul(std::mem::size_of::<(super::AnimProperty, bool)>()),
363 )
364 }
365
366 pub(crate) fn projection_eq(&self, other: &Self) -> bool {
367 self.parent == other.parent
368 && self.kind == other.kind
369 && self.layout_rect == other.layout_rect
370 && self.hit_rect == other.hit_rect
371 && self.paint_bounds == other.paint_bounds
372 && self.ime_cursor_rect == other.ime_cursor_rect
373 && self.interaction == other.interaction
374 && self.semantics == other.semantics
375 && self.event_policy == other.event_policy
376 && self.auto_focus == other.auto_focus
377 && self.focus_scope == other.focus_scope
378 && self.animation_bindings == other.animation_bindings
379 && self.animation_targets == other.animation_targets
380 && self.animation_outset == other.animation_outset
381 && self.layout == other.layout
382 && self.style == other.style
383 && self.path == other.path
384 && self.path_style == other.path_style
385 && self.image_request == other.image_request
386 && self.image_fit == other.image_fit
387 && self.icon_key == other.icon_key
388 && self.icon_style == other.icon_style
389 && self.glow == other.glow
390 && self.backdrop_blur_style == other.backdrop_blur_style
391 && self.overlay_style == other.overlay_style
392 && self.custom_style == other.custom_style
393 && self.compositing_layer == other.compositing_layer
394 && self.shadow == other.shadow
395 && self.static_layer == other.static_layer
396 && self.scroll_raster == other.scroll_raster
397 && self.clip_rect == other.clip_rect
398 && self.content_offset == other.content_offset
399 && self.text == other.text
400 && self.text_style == other.text_style
401 && self.render_phase == other.render_phase
402 }
403
404 pub fn parent(mut self, parent: UiId) -> Self {
405 self.parent = Some(parent);
406 self
407 }
408
409 pub fn hit_rect(mut self, rect: UiRect) -> Self {
410 self.hit_rect = rect;
411 self
412 }
413
414 pub fn paint_bounds(mut self, rect: UiRect) -> Self {
415 self.paint_bounds = rect;
416 self
417 }
418
419 pub fn ime_cursor_rect(mut self, rect: UiRect) -> Self {
420 self.ime_cursor_rect = Some(rect);
421 self
422 }
423
424 pub fn interaction(mut self, interaction: InteractionRole) -> Self {
425 self.interaction = interaction;
426 if interaction != InteractionRole::None {
427 self.event_policy = EventPolicy::INTERACTIVE;
428 }
429 self
430 }
431
432 pub fn semantics(mut self, semantics: Semantics) -> Self {
433 self.semantics = Some(semantics);
434 self
435 }
436
437 pub fn on_click<F>(self, handler: F) -> Self
438 where
439 F: Fn(&mut UiEventContext) + Send + Sync + 'static,
440 {
441 self.on_click_handler(std::sync::Arc::new(handler))
442 }
443
444 pub fn on_click_handler(mut self, handler: UiEventHandler) -> Self {
445 self.click_handler = Some(handler);
446 self
447 }
448
449 pub(crate) fn component_owner(mut self, owner: ComponentId) -> Self {
450 self.component_owner = Some(owner);
451 self
452 }
453
454 pub fn on_click_capture_handler(mut self, handler: UiEventHandler) -> Self {
455 self.click_capture_handler = Some(handler);
456 self
457 }
458
459 pub fn on_click_capture<F>(self, handler: F) -> Self
460 where
461 F: Fn(&mut UiEventContext) + Send + Sync + 'static,
462 {
463 self.on_click_capture_handler(Arc::new(handler))
464 }
465
466 pub fn on_event_handler(
467 mut self,
468 kind: UiEventKind,
469 capture: bool,
470 handler: UiInputEventHandler,
471 ) -> Self {
472 self.input_event_handlers.push(UiInputEventBinding {
473 kind,
474 capture,
475 handler,
476 });
477 match kind {
478 UiEventKind::PointerMove => self.event_policy.hover = true,
479 UiEventKind::Click | UiEventKind::PointerDown | UiEventKind::PointerUp => {
480 self.event_policy.press = true;
481 }
482 UiEventKind::KeyDown
483 | UiEventKind::KeyUp
484 | UiEventKind::Input
485 | UiEventKind::CompositionStart
486 | UiEventKind::CompositionUpdate
487 | UiEventKind::CompositionEnd
488 | UiEventKind::Focus
489 | UiEventKind::Blur
490 | UiEventKind::Change => self.event_policy.focus = true,
491 UiEventKind::Wheel => {}
492 }
493 self
494 }
495
496 pub fn on_event<F>(self, kind: UiEventKind, handler: F) -> Self
497 where
498 F: Fn(&mut UiEventContext, &UiEventPayload) + Send + Sync + 'static,
499 {
500 self.on_event_handler(kind, false, Arc::new(handler))
501 }
502
503 pub fn on_event_capture<F>(self, kind: UiEventKind, handler: F) -> Self
504 where
505 F: Fn(&mut UiEventContext, &UiEventPayload) + Send + Sync + 'static,
506 {
507 self.on_event_handler(kind, true, Arc::new(handler))
508 }
509
510 pub fn wheel_action(mut self, action: UiAction) -> Self {
511 self.wheel_action = Some(action);
512 self
513 }
514
515 pub fn click_action(mut self, action: UiAction) -> Self {
516 self.click_action = Some(action);
517 self
518 }
519
520 pub fn action_target(mut self, target: UiId) -> Self {
521 self.action_target = Some(target);
522 self
523 }
524
525 pub fn on_action<F>(self, id: impl Into<ActionId>, handler: F) -> Self
526 where
527 F: Fn(&mut UiEventContext, &UiAction) + Send + Sync + 'static,
528 {
529 self.on_action_handler(id, Arc::new(handler))
530 }
531
532 pub fn on_action_handler(mut self, id: impl Into<ActionId>, handler: UiActionHandler) -> Self {
533 self.action_handlers.push(UiActionBinding {
534 id: id.into(),
535 handler,
536 });
537 self
538 }
539
540 pub fn event_policy(mut self, policy: EventPolicy) -> Self {
541 self.event_policy = policy;
542 self
543 }
544
545 pub fn auto_focus(mut self, enabled: bool) -> Self {
546 self.auto_focus = enabled;
547 self
548 }
549
550 pub fn focus_scope(mut self, enabled: bool) -> Self {
551 self.focus_scope = enabled;
552 self
553 }
554
555 pub fn animation(mut self, binding: AnimationBinding) -> Self {
556 self.animation_bindings.push(binding);
557 self
558 }
559
560 pub fn animation_target(mut self, property: super::AnimProperty, active: bool) -> Self {
561 self.animation_targets.push((property, active));
562 self
563 }
564
565 pub fn animation_outset(mut self, x: f32, y: f32) -> Self {
566 self.animation_outset = (x, y);
567 self
568 }
569
570 pub fn layout(mut self, layout: LayoutSpec) -> Self {
571 self.layout = layout;
572 self
573 }
574
575 pub fn style(mut self, style: VisualStyle) -> Self {
576 self.style = style;
577 self
578 }
579
580 pub fn path(mut self, path: UiPath, style: PathStyle) -> Self {
581 self.path = Some(path);
582 self.path_style = style;
583 self
584 }
585
586 pub fn text(mut self, value: impl Into<Cow<'static, str>>, style: TextStyle) -> Self {
587 self.text = Some(value.into());
588 self.text_style = Some(style);
589 self
590 }
591
592 pub fn image(mut self, source: impl Into<UiImageSource>, fit: ImageFit) -> Self {
593 self.image_request = Some(ImageRequest::new(source));
594 self.image_fit = fit;
595 self
596 }
597
598 pub fn image_request(mut self, request: impl Into<ImageRequest>, fit: ImageFit) -> Self {
599 self.image_request = Some(request.into());
600 self.image_fit = fit;
601 self
602 }
603
604 pub fn icon(mut self, key: &'static str) -> Self {
605 self.icon_key = Some(key);
606 self
607 }
608
609 pub fn icon_style(mut self, style: IconStyle) -> Self {
610 self.icon_style = style;
611 self
612 }
613
614 pub fn glow(mut self, color: Color, alpha: u8) -> Self {
615 self.glow = Some((color, alpha));
616 self
617 }
618
619 pub fn overlay(mut self, style: OverlayStyle) -> Self {
620 self.overlay_style = Some(style);
621 self
622 }
623
624 pub fn backdrop_blur(mut self, style: BackdropBlurStyle) -> Self {
625 self.backdrop_blur_style = Some(style);
626 self
627 }
628
629 pub fn custom_style(mut self, style: CustomPaintStyle) -> Self {
630 self.custom_style = Some(style);
631 self
632 }
633
634 pub fn static_layer(mut self, spec: StaticLayerSpec) -> Self {
635 self.static_layer = Some(spec);
636 self
637 }
638
639 pub fn compositing_layer(mut self, spec: CompositingLayerSpec) -> Self {
640 self.compositing_layer = Some(spec);
641 self
642 }
643
644 pub fn shadow(mut self, style: super::ShadowStyle) -> Self {
645 self.shadow = (style.alpha > 0).then_some(style);
646 self
647 }
648
649 pub fn scroll_raster(mut self, spec: ScrollRasterSpec) -> Self {
650 self.clip_rect = Some(self.layout_rect);
651 self.content_offset = (0.0, -spec.scroll_y);
652 self.scroll_raster = Some(spec);
653 self
654 }
655
656 pub fn clip(mut self, rect: UiRect, offset_x: f32, offset_y: f32) -> Self {
657 self.clip_rect = Some(rect);
658 self.content_offset = (offset_x, offset_y);
659 self
660 }
661
662 pub fn render_phase(mut self, phase: RenderPhase) -> Self {
663 self.render_phase = phase;
664 self
665 }
666
667 pub fn translate(mut self, x: f32, y: f32) -> Self {
668 self.layout_rect = self.layout_rect.translate(x, y);
669 self.hit_rect = self.hit_rect.translate(x, y);
670 self.paint_bounds = self.paint_bounds.translate(x, y);
671 self.ime_cursor_rect = self.ime_cursor_rect.map(|rect| rect.translate(x, y));
672 self.clip_rect = self.clip_rect.map(|rect| rect.translate(x, y));
673 self.path = self.path.map(|path| translate_path(&path, x, y));
674 self
675 }
676}
677
678fn translate_path(path: &UiPath, x: f32, y: f32) -> UiPath {
679 let translate = |point: super::Point| super::Point::new(point.x + x, point.y + y);
680 UiPath::new(path.commands().iter().map(|command| match *command {
681 super::UiPathCommand::MoveTo(point) => super::UiPathCommand::MoveTo(translate(point)),
682 super::UiPathCommand::LineTo(point) => super::UiPathCommand::LineTo(translate(point)),
683 super::UiPathCommand::QuadraticTo { control, to } => super::UiPathCommand::QuadraticTo {
684 control: translate(control),
685 to: translate(to),
686 },
687 super::UiPathCommand::CubicTo {
688 control1,
689 control2,
690 to,
691 } => super::UiPathCommand::CubicTo {
692 control1: translate(control1),
693 control2: translate(control2),
694 to: translate(to),
695 },
696 super::UiPathCommand::Close => super::UiPathCommand::Close,
697 }))
698}