Skip to main content

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}