Skip to main content

khora_data/ui/
scene.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Per-frame intermediate UI scene types.
16//!
17//! `UiScene` is published into the [`LaneBus`](khora_core::lane::LaneBus)
18//! each frame by [`UiFlow`](crate::flow::UiFlow). Atlas allocation is **not**
19//! part of the View — the UI agent maintains a per-texture
20//! [`UiAtlasMap`](super::UiAtlasMap) and passes it to the lane separately
21//! so the immutable bus invariant is preserved.
22
23use khora_core::asset::AssetUUID;
24use khora_core::math::{Vec2, Vec4};
25use khora_core::renderer::api::text::TextLayout;
26use khora_core::renderer::api::util::AtlasRect;
27use std::collections::HashMap;
28use std::sync::Arc;
29
30use crate::ui::components::{UiBorder, UiColor, UiImage};
31
32/// A flat, GPU-friendly representation of a single UI node.
33#[derive(Debug, Clone)]
34pub struct ExtractedUiNode {
35    /// Screen-space position.
36    pub pos: Vec2,
37    /// Screen-space size.
38    pub size: Vec2,
39    /// Optional background color.
40    pub color: Option<UiColor>,
41    /// Optional border properties.
42    pub border: Option<UiBorder>,
43    /// Optional image asset reference.
44    pub image: Option<UiImage>,
45    /// Z-index for sorting.
46    pub z_index: i32,
47}
48
49/// Extracted text data for rendering.
50#[derive(Clone)]
51pub struct ExtractedUiText {
52    /// Screen-space position.
53    pub pos: Vec2,
54    /// Pre-computed text layout. `Arc` (immutable, shared) rather than
55    /// `Box` so the containing `UiScene` view is cheaply clonable.
56    pub layout: Arc<dyn TextLayout>,
57    /// Text color RGBA.
58    pub color: Vec4,
59    /// Z-index for sorting.
60    pub z_index: i32,
61}
62
63/// All UI data extracted from the main `World` for a single frame.
64#[derive(Default, Clone)]
65pub struct UiScene {
66    /// UI nodes to render.
67    pub nodes: Vec<ExtractedUiNode>,
68    /// UI text elements to render.
69    pub texts: Vec<ExtractedUiText>,
70    /// Surface size at the time of extraction.
71    pub surface_size: (u32, u32),
72}
73
74impl UiScene {
75    /// Creates a new, empty `UiScene`.
76    pub fn new() -> Self {
77        Self::default()
78    }
79}
80
81/// Per-frame UI atlas lookup: texture UUID → allocated `AtlasRect`.
82///
83/// Maintained by the UI agent (which owns the atlas allocator) and passed
84/// to the UI render lane through `LaneContext`. Replaces the old in-place
85/// `ExtractedUiNode.atlas_rect` mutation, which violated the immutable
86/// `LaneBus` invariant once the bus replaced `UiSceneStore`.
87#[derive(Debug, Default, Clone)]
88pub struct UiAtlasMap(pub HashMap<AssetUUID, AtlasRect>);
89
90impl UiAtlasMap {
91    /// Creates an empty atlas map.
92    pub fn new() -> Self {
93        Self::default()
94    }
95
96    /// Looks up the rect for a texture UUID, if allocated this frame.
97    pub fn get(&self, uuid: &AssetUUID) -> Option<AtlasRect> {
98        self.0.get(uuid).copied()
99    }
100
101    /// Inserts or replaces the rect for a texture UUID.
102    pub fn insert(&mut self, uuid: AssetUUID, rect: AtlasRect) {
103        self.0.insert(uuid, rect);
104    }
105}