Skip to main content

khora_data/ui/
components.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//! UI components for the Khora Engine.
16
17use bincode::{Decode, Encode};
18use khora_core::asset::AssetUUID;
19use khora_core::math::{Vec2, Vec4};
20pub use khora_core::ui::types::{UiFlexDirection, UiRect, UiVal};
21use khora_macros::Component;
22use serde::{Deserialize, Serialize};
23
24/// Represents the layout definition of a UI element.
25/// Fully Send/Sync ECS component that is later translated to a layout engine (like taffy) internally.
26#[derive(Debug, Clone, PartialEq, Component, Default)]
27#[component(domain = Ui)]
28pub struct UiNode {
29    /// The width of the UI element.
30    pub width: UiVal,
31    /// The height of the UI element.
32    pub height: UiVal,
33
34    /// The minimum width of the UI element.
35    pub min_width: UiVal,
36    /// The minimum height of the UI element.
37    pub min_height: UiVal,
38
39    /// The maximum width of the UI element.
40    pub max_width: UiVal,
41    /// The maximum height of the UI element.
42    pub max_height: UiVal,
43
44    /// The padding of the UI element.
45    pub padding: UiRect<UiVal>,
46    /// The margin of the UI element.
47    pub margin: UiRect<UiVal>,
48
49    /// The flex direction of the UI element.
50    pub flex_direction: UiFlexDirection,
51    /// The flex grow factor of the UI element.
52    pub flex_grow: f32,
53    /// The flex shrink factor of the UI element.
54    pub flex_shrink: f32,
55}
56
57impl From<UiNode> for khora_core::ui::types::UiNode {
58    fn from(node: UiNode) -> Self {
59        Self {
60            width: node.width,
61            height: node.height,
62            min_width: node.min_width,
63            min_height: node.min_height,
64            max_width: node.max_width,
65            max_height: node.max_height,
66            padding: node.padding,
67            margin: node.margin,
68            flex_direction: node.flex_direction,
69            flex_grow: node.flex_grow,
70            flex_shrink: node.flex_shrink,
71        }
72    }
73}
74
75/// The computed screen-space transform of a UI element.
76/// This is populated by the layout system (e.g. `UiAgent`) after evaluating the `taffy` tree.
77#[derive(Debug, Clone, Copy, PartialEq, Component)]
78#[component(domain = Ui)]
79pub struct UiTransform {
80    /// Absolute position of the top-left corner in screen coordinates
81    pub pos: Vec2,
82    /// Absolute size in screen coordinates
83    pub size: Vec2,
84    /// The Z-index for rendering order. Higher values are rendered on top.
85    pub z_index: i32,
86}
87
88impl From<UiTransform> for khora_core::ui::types::UiTransform {
89    fn from(t: UiTransform) -> Self {
90        Self {
91            pos: t.pos,
92            size: t.size,
93            z_index: t.z_index,
94        }
95    }
96}
97
98impl From<khora_core::ui::types::UiTransform> for UiTransform {
99    fn from(t: khora_core::ui::types::UiTransform) -> Self {
100        Self {
101            pos: t.pos,
102            size: t.size,
103            z_index: t.z_index,
104        }
105    }
106}
107
108impl Default for UiTransform {
109    fn default() -> Self {
110        Self {
111            pos: Vec2::ZERO,
112            size: Vec2::ZERO,
113            z_index: 0,
114        }
115    }
116}
117
118impl UiTransform {
119    /// Get the bounding rect for this transform
120    pub fn rect(&self) -> (Vec2, Vec2) {
121        (self.pos, self.pos + self.size)
122    }
123
124    /// Check if a point is within this transform's bounds
125    pub fn contains(&self, point: Vec2) -> bool {
126        point.x >= self.pos.x
127            && point.x <= self.pos.x + self.size.x
128            && point.y >= self.pos.y
129            && point.y <= self.pos.y + self.size.y
130    }
131}
132
133/// The visual style of a UI element.
134#[derive(Debug, Clone, PartialEq, Component)]
135#[component(domain = Ui)]
136pub struct UiStyle {
137    /// Background color in RGBA format (0.0 to 1.0)
138    pub background_color: Vec4,
139    /// Border radius for rounded corners
140    pub border_radius: Vec4, // (top-left, top-right, bottom-right, bottom-left)
141    /// Border color
142    pub border_color: Vec4,
143    /// Border width
144    pub border_width: f32,
145    /// Optional texture ID for rendering images or offscreen viewports
146    pub texture_id: Option<u64>,
147}
148
149impl Default for UiStyle {
150    fn default() -> Self {
151        Self {
152            background_color: Vec4::new(1.0, 1.0, 1.0, 1.0),
153            border_radius: Vec4::ZERO,
154            border_color: Vec4::ZERO,
155            border_width: 0.0,
156            texture_id: None,
157        }
158    }
159}
160
161/// Visual color of a UI element.
162#[derive(
163    Debug, Clone, Copy, PartialEq, Component, Default, Serialize, Deserialize, Encode, Decode,
164)]
165#[component(domain = Ui)]
166pub struct UiColor(pub Vec4);
167
168impl UiColor {
169    /// Convert to core UI color type.
170    pub fn to_core(&self) -> khora_core::ui::types::UiColor {
171        khora_core::ui::types::UiColor(self.0)
172    }
173}
174
175/// Visual image of a UI element.
176#[derive(Debug, Clone, Copy, Default, PartialEq, Component)]
177#[component(domain = Ui)]
178pub struct UiImage {
179    /// The ID of the image asset.
180    pub texture: AssetUUID,
181}
182
183impl UiImage {
184    /// Convert to core UI image type.
185    pub fn to_core(&self) -> khora_core::ui::types::UiImage {
186        khora_core::ui::types::UiImage {
187            texture: self.texture,
188        }
189    }
190}
191
192/// Border specification for a UI element.
193#[derive(
194    Debug, Clone, Copy, PartialEq, Component, Default, Serialize, Deserialize, Encode, Decode,
195)]
196#[component(domain = Ui)]
197pub struct UiBorder {
198    /// Width of the border for each side.
199    pub width: UiRect<f32>,
200    /// Color of the border.
201    pub color: UiColor,
202    /// Corner radius for rounded borders.
203    pub radius: f32,
204}
205
206impl UiBorder {
207    /// Convert to core UI border type.
208    pub fn to_core(&self) -> khora_core::ui::types::UiBorder {
209        khora_core::ui::types::UiBorder {
210            width: self.width,
211            color: self.color.to_core(),
212            radius: self.radius,
213        }
214    }
215}
216
217/// Represents the interaction state of a UI element.
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Encode, Decode)]
219pub enum UiInteractionState {
220    #[default]
221    /// The element is not being interacted with.
222    Normal,
223    /// The element is being hovered over.
224    Hovered,
225    /// The element is being pressed.
226    Pressed,
227    /// The element is focused.
228    Focused,
229}
230
231/// Track the interaction capabilities and current state of a UI element.
232#[derive(Debug, Clone, Default, Component)]
233#[component(domain = Ui)]
234pub struct UiInteraction {
235    /// The current interaction state, updated continuously by the input system
236    pub state: UiInteractionState,
237
238    /// If true, this element can receive focus (e.g., text inputs)
239    pub focusable: bool,
240
241    /// If true, this element blocks clicks/hovers from passing through to elements below it
242    pub blocks_input: bool,
243}
244
245/// Typography settings for a text element.
246#[derive(Debug, Clone, PartialEq, Component)]
247#[component(domain = Ui)]
248pub struct UiText {
249    /// The string to display.
250    pub content: String,
251    /// The font asset ID.
252    pub font: AssetUUID,
253    /// Font size in pixels.
254    pub size: f32,
255    /// Text color RGBA.
256    pub color: Vec4,
257}
258
259impl UiText {
260    /// Convert to core UI text type.
261    pub fn to_core(&self) -> khora_core::ui::types::UiText {
262        khora_core::ui::types::UiText {
263            content: self.content.clone(),
264            font: self.font,
265            size: self.size,
266            color: self.color,
267        }
268    }
269}
270
271impl Default for UiText {
272    fn default() -> Self {
273        Self {
274            content: String::new(),
275            font: AssetUUID::default(),
276            size: 16.0,
277            color: Vec4::new(0.0, 0.0, 0.0, 1.0), // Black
278        }
279    }
280}