Skip to main content

lgui_core/core/view/
node.rs

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}