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}