rpfm_lib/files/uic/mod.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! UI Component (UIC) files for Total War games.
12//!
13//! UIC files define the layout, appearance, and behaviour of UI elements in the game.
14//! They contain hierarchical component definitions with states, images, animations,
15//! and callback bindings.
16//!
17//! # Format History
18//!
19//! - **Pre-Three Kingdoms**: Binary format (not yet implemented)
20//! - **Three Kingdoms onwards**: XML format (partially implemented)
21//!
22//! # Status
23//!
24//! **This module is incomplete and experimental.** Only XML parsing for version 138
25//! is partially implemented. Binary format support is not yet available.
26
27use serde_derive::{Serialize, Deserialize};
28
29use std::collections::HashMap;
30use std::io::SeekFrom;
31
32use crate::binary::{ReadBytes, WriteBytes};
33use crate::error::Result;
34use crate::files::{DecodeableExtraData, Decodeable, EncodeableExtraData, Encodeable};
35
36/// Path where all uics are in.
37pub const BASE_PATH: &str = "ui";
38
39/// Extension of UIC files in some games (they don't have extensions in some games).
40pub const EXTENSIONS: [&str; 2] = [".cml", ".xml"];
41
42mod xml;
43
44//#[cfg(test)] mod uic_test;
45
46//---------------------------------------------------------------------------//
47// Enum & Structs
48//---------------------------------------------------------------------------//
49
50/// In-memory representation of a decoded UI Component file.
51///
52/// Contains the complete UI component definition including its hierarchy,
53/// component definitions, and metadata.
54///
55/// # Fields
56///
57/// * `version` - Format version number.
58/// * `source_is_xml` - `true` if decoded from XML format, `false` if binary.
59/// * `comment` - Optional comment/description for the component.
60/// * `precache_condition` - Condition for precaching this component.
61/// * `hierarchy` - Tree structure of UI element relationships.
62/// * `components` - Map of component IDs to their definitions.
63#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
64pub struct UIC {
65 version: u32,
66 source_is_xml: bool,
67 comment: String,
68 precache_condition: String,
69 hierarchy: HashMap<String, HierarchyItem>,
70 components: HashMap<String, Component>,
71}
72
73/// A node in the UI component hierarchy tree.
74#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
75pub struct HierarchyItem {
76 /// Unique identifier for this hierarchy node.
77 this: String,
78 /// Child nodes in the hierarchy.
79 childs: HashMap<String, HierarchyItem>,
80}
81
82/// A UI component definition with all its properties and sub-elements.
83#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
84pub struct Component {
85 /// Unique identifier for this component.
86 this: String,
87 /// Human-readable component ID.
88 id: String,
89 /// Whether horizontal resizing is allowed.
90 allowhorizontalresize: Option<bool>,
91 /// Rendering priority.
92 priority: Option<u8>,
93 /// Whether tooltips should be localised.
94 tooltipslocalised: Option<bool>,
95 /// Globally unique identifier (typically same as `this`).
96 uniqueguid: String,
97 /// Whether to update this component when not visible.
98 update_when_not_visible: Option<bool>,
99 /// GUID of the current visual state.
100 current_state: Option<String>,
101 /// GUID of the default visual state.
102 default_state: Option<String>,
103
104 /// Event callbacks with context bindings.
105 callbackwithcontextlist: Option<HashMap<String, CallbackWithContext>>,
106 /// Image resources used by this component.
107 componentimages: Option<HashMap<String, ComponentImage>>,
108 /// Visual states (e.g., normal, hover, pressed).
109 states: Option<HashMap<String, State>>,
110 /// Animation definitions.
111 animations: Option<HashMap<String, Animation>>,
112 /// Layout engine configuration.
113 layout_engine: Option<LayoutEngine>,
114}
115
116/// An event callback binding with optional context.
117#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
118pub struct CallbackWithContext {
119 /// Identifier of the callback function.
120 callback_id: String,
121 /// Optional context object for the callback.
122 context_object_id: Option<String>,
123 /// Optional context function for the callback.
124 context_function_id: Option<String>,
125 /// User-defined properties for this callback.
126 child_m_user_properties: Option<HashMap<String, Property>>,
127}
128
129/// A key-value property pair.
130#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
131pub struct Property {
132 /// Property name.
133 name: String,
134 /// Property value.
135 value: String,
136}
137
138/// An image resource reference for a UI component.
139#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
140pub struct ComponentImage {
141 /// Unique identifier for this image reference.
142 this: String,
143 /// Globally unique identifier.
144 uniqueguid: String,
145 /// Path to the image file.
146 imagepath: Option<String>,
147}
148
149/// A visual state definition for a UI component.
150///
151/// States define how a component appears under different conditions
152/// (e.g., normal, hovered, pressed, disabled).
153#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
154pub struct State {
155 /// Unique identifier for this state.
156 this: String,
157 /// Human-readable state name.
158 name: String,
159 /// Component width in this state.
160 width: Option<u32>,
161 /// Text content to display.
162 text: Option<String>,
163 /// Horizontal text alignment.
164 text_h_align: Option<String>,
165 /// Vertical text offset.
166 text_y_offset: Option<String>,
167 /// Horizontal text behaviour.
168 text_h_behaviour: Option<String>,
169 /// Whether the text should be localised.
170 text_localised: Option<bool>,
171 /// Localisation key for the text.
172 text_label: Option<String>,
173 /// Font family name.
174 font_m_font_name: Option<String>,
175 /// Font size.
176 font_m_size: Option<u8>,
177 /// Font colour (hex format).
178 font_m_colour: Option<String>,
179 /// Line spacing (leading).
180 font_m_leading: Option<u8>,
181 /// Font category name.
182 fontcat_name: Option<String>,
183 /// Whether this state is interactive.
184 interactive: Option<bool>,
185 /// Globally unique identifier (typically same as `this`).
186 uniqueguid: String,
187 /// Image metrics for this state.
188 imagemetrics: Option<HashMap<String, Image>>,
189}
190
191/// Image display properties within a UI state.
192#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
193pub struct Image {
194 /// Unique identifier for this image instance.
195 this: String,
196 /// Globally unique identifier.
197 uniqueguid: String,
198 /// Reference to a ComponentImage.
199 componentimage: String,
200 /// Position offset.
201 offset: Option<String>,
202 /// Colour tint (hex format).
203 colour: Option<String>,
204 /// Docking anchor point.
205 dockpoint: Option<String>,
206 /// Offset from dock point.
207 dock_offset: Option<String>,
208 /// Whether height can be resized.
209 canresizeheight: Option<bool>,
210 /// Whether width can be resized.
211 canresizewidth: Option<bool>,
212 /// Colour preset key reference.
213 ui_colour_preset_type_key: Option<String>,
214}
215
216/// An animation definition with keyframes.
217#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
218pub struct Animation {
219 /// Animation identifier.
220 id: String,
221 /// Keyframes in this animation.
222 frames: Vec<Frame>,
223}
224
225/// A single keyframe in an animation.
226#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
227pub struct Frame {
228 /// Time to interpolate to this frame (milliseconds).
229 interpolationtime: Option<u32>,
230 /// Bitmask of properties to interpolate.
231 interpolationpropertymask: Option<u8>,
232 /// Target height at this keyframe.
233 targetmetrics_m_height: Option<i32>,
234 /// Target width at this keyframe.
235 targetmetrics_m_width: Option<i32>
236}
237
238/// Layout engine configuration for arranging child components.
239#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
240pub struct LayoutEngine {
241 /// Layout type (e.g., "horizontal", "vertical", "grid").
242 r#type: String,
243 /// Spacing between child elements.
244 spacing: String,
245 /// Whether to resize to fit content.
246 sizetocontent: bool,
247 /// Margin values.
248 margins: String,
249 /// Fixed column widths for grid layouts.
250 columnwidths: Vec<i32>,
251}
252
253//---------------------------------------------------------------------------//
254// Implementation of Text
255//---------------------------------------------------------------------------//
256
257impl Decodeable for UIC {
258
259 fn decode<R: ReadBytes>(data: &mut R, extra_data: &Option<DecodeableExtraData>) -> Result<Self> {
260 let mut data_local = vec![];
261 let read = data.read_to_end(&mut data_local)?;
262 data.seek(SeekFrom::Current(-(read as i64)))?;
263
264 // TODO: Unhardcode this version.
265 if content_inspector::inspect(&data_local).is_text() {
266 Ok(Self::from(xml::v138::XmlLayout::decode(data, extra_data)?))
267 } else {
268 todo!()
269 }
270 }
271}
272
273impl Encodeable for UIC {
274
275 fn encode<W: WriteBytes>(&mut self, _buffer: &mut W, _extra_data: &Option<EncodeableExtraData>) -> Result<()> {
276 if self.source_is_xml {
277 //xml::v138::XmlLayout::encode(&mut self, buffer, extra_data)
278 todo!()
279 } else {
280 todo!()
281 }
282 }
283}