inkanim_types/ink/widget/
mod.rs

1//! All widgets in Cyberpunk 2077 UI
2//! are similar to web and traditional UI frameworks.
3
4pub mod font;
5pub mod image;
6pub(crate) mod implementation;
7pub mod layout;
8pub mod properties;
9
10use enum_dispatch::enum_dispatch;
11pub use implementation::*;
12
13use serde::{Deserialize, Serialize, ser::SerializeSeq};
14use serde_aux::prelude::deserialize_bool_from_anything;
15
16use crate::{DepotPath, Name, Vector2, is_any_default_localization_string};
17
18use self::{
19    font::{
20        fontStyle, inkFontFamilyResource, textHorizontalAlignment, textLetterCase,
21        textOverflowPolicy, textVerticalAlignment,
22    },
23    image::{inkBrushMirrorType, inkBrushTileType, inkTextureAtlas},
24    layout::{inkEHorizontalAlign, inkEVerticalAlign, inkMargin, textJustificationType},
25};
26
27use super::{HandleId, InkWrapper, LocalizationString};
28
29/// belongs to the same level or is nested below, in a tree hierarchy
30pub trait SiblingOrNested {
31    fn sibling_or_nested(&self, searched: &[usize]) -> bool;
32}
33
34#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
35#[non_exhaustive]
36pub enum Flags {
37    #[default]
38    Default,
39    Soft,
40    Hard,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
44pub struct Pivot(Vector2);
45
46impl Default for Pivot {
47    fn default() -> Self {
48        Self(Vector2 { x: 0.5, y: 0.5 })
49    }
50}
51
52fn is_default<T: Default + PartialEq>(value: &T) -> bool {
53    *value == T::default()
54}
55
56macro_rules! native_compound_widget {
57    ($ty:ident) => {
58        #[doc=concat!("see [NativeDB](https://nativedb.red4ext.com/", stringify!($ty), ")")]
59        #[allow(non_camel_case_types)]
60        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61        #[serde(rename_all = "camelCase")]
62        pub struct $ty {
63            #[serde(default, skip_serializing_if = "is_default")]
64            pub children: InkWrapper<inkMultiChildren>,
65            #[serde(default, skip_serializing_if = "is_default")]
66            pub name: $crate::Name,
67            #[serde(default, skip_serializing_if = "is_default")]
68            pub child_order: self::layout::inkEChildOrder,
69            #[serde(default, skip_serializing_if = "is_default")]
70            pub child_margin: self::layout::inkMargin,
71        }
72    };
73}
74
75macro_rules! native_leaf_widget {
76    ($ty:ident { $($tt:tt)* }) => {
77        #[doc=concat!("🌿 see [NativeDB](https://nativedb.red4ext.com/", stringify!($ty), ")")]
78        #[allow(non_camel_case_types)]
79        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
80        #[serde(rename_all = "camelCase")]
81        pub struct $ty {
82            #[serde(default, skip_serializing_if = "is_default")]
83            pub name: $crate::Name,
84            #[serde(default, skip_serializing_if = "is_default")]
85            pub layout: self::layout::inkWidgetLayout,
86            #[serde(default, skip_serializing_if = "is_default")]
87            pub property_manager: Option<self::properties::PropertyManager>,
88            #[serde(default, skip_serializing_if = "is_default")]
89            pub render_transform_pivot: self::Pivot,
90            #[serde(default, skip_serializing_if = "is_default")]
91            pub render_transform: self::layout::inkUITransform,
92            #[serde(default, skip_serializing_if = "is_default")]
93            pub size: crate::Vector2,
94            $($tt)*
95        }
96    };
97    ($ty:ident) => {
98        native_leaf_widget!($ty {});
99    };
100}
101
102native_compound_widget!(inkCanvasWidget);
103native_compound_widget!(inkHorizontalPanelWidget);
104native_compound_widget!(inkVerticalPanelWidget);
105native_compound_widget!(inkScrollAreaWidget);
106native_compound_widget!(inkUniformGridWidget);
107native_compound_widget!(inkVirtualCompoundWidget);
108native_compound_widget!(inkFlexWidget);
109native_compound_widget!(inkCacheWidget);
110
111/// see [NativeDB](https://nativedb.red4ext.com/inkMultiChildren)
112#[allow(non_camel_case_types)]
113#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
114pub struct inkMultiChildren {
115    pub children: Vec<InkWrapper<Widget>>,
116}
117
118impl Serialize for inkMultiChildren {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: serde::Serializer,
122    {
123        let mut seq = serializer.serialize_seq(Some(self.children.len()))?;
124        for elem in self.children.iter() {
125            seq.serialize_element(elem)?;
126        }
127        seq.end()
128    }
129}
130
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
132#[serde(transparent)]
133pub struct ScrollDelay(u16);
134
135impl Default for ScrollDelay {
136    fn default() -> Self {
137        Self(30)
138    }
139}
140
141#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
142#[serde(transparent)]
143pub struct ScrollTextSpeed(f32);
144
145impl Default for ScrollTextSpeed {
146    fn default() -> Self {
147        Self(0.2)
148    }
149}
150
151native_leaf_widget!(inkTextWidget {
152  #[serde(default, skip_serializing_if = "is_any_default_localization_string",)]
153  pub localization_string: LocalizationString,
154  #[serde(default, skip_serializing_if = "is_default")]
155  pub text: String,
156  #[serde(default, skip_serializing_if = "is_default")]
157  pub font_family: inkFontFamilyResource,
158  #[serde(default, skip_serializing_if = "is_default")]
159  pub font_style: fontStyle,
160  #[serde(default, skip_serializing_if = "is_default")]
161  pub justification: textJustificationType,
162  #[serde(default, skip_serializing_if = "is_default")]
163  pub text_letter_case: Option<textLetterCase>,
164  #[serde(default, skip_serializing_if = "is_default")]
165  pub line_height_percentage: f32,
166  #[serde(default, skip_serializing_if = "is_default")]
167  pub text_horizontal_alignment: textHorizontalAlignment,
168  #[serde(default, skip_serializing_if = "is_default")]
169  pub text_vertical_alignment: textVerticalAlignment,
170  #[serde(default, skip_serializing_if = "is_default")]
171  pub text_overflow_policy: textOverflowPolicy,
172  #[serde(default, skip_serializing_if = "is_default")]
173  pub content_h_align: inkEHorizontalAlign,
174  #[serde(default, skip_serializing_if = "is_default")]
175  pub content_v_align: inkEVerticalAlign,
176  #[serde(default, skip_serializing_if = "is_default")]
177  pub scroll_delay: self::ScrollDelay,
178  #[serde(default, skip_serializing_if = "is_default")]
179  pub scroll_text_speed: self::ScrollTextSpeed,
180});
181native_leaf_widget!(inkImageWidget {
182    #[serde(deserialize_with = "deserialize_bool_from_anything")]
183    pub use_external_dynamic_texture: bool,
184    pub external_dynamic_texture: Name,
185    #[serde(deserialize_with = "deserialize_bool_from_anything")]
186    pub use_nine_slice_scale: bool,
187    pub nine_slice_scale: inkMargin,
188    pub mirror_type: inkBrushMirrorType,
189    pub tile_type: inkBrushTileType,
190    pub horizontal_tile_crop: f32,
191    pub vertical_tile_crop: f32,
192    pub texture_atlas: inkTextureAtlas,
193    pub texture_part: Name,
194    pub content_h_align: inkEHorizontalAlign,
195    pub content_v_align: inkEVerticalAlign,
196    pub tile_h_align: inkEHorizontalAlign,
197    pub tile_v_align: inkEVerticalAlign,
198});
199native_leaf_widget!(inkVideoWidget);
200native_leaf_widget!(inkMaskWidget);
201native_leaf_widget!(inkBorderWidget);
202native_leaf_widget!(inkShapeWidget);
203native_leaf_widget!(inkCircleWidget);
204native_leaf_widget!(inkRectangleWidget);
205native_leaf_widget!(inkVectorGraphicWidget);
206
207/// any widget
208#[allow(clippy::enum_variant_names)]
209#[allow(non_camel_case_types)]
210#[non_exhaustive]
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
212#[enum_dispatch(Classname)]
213#[serde(tag = "$type")]
214pub enum Widget {
215    inkMultiChildren(inkMultiChildren),
216
217    inkCanvasWidget(inkCanvasWidget),
218    inkHorizontalPanelWidget(inkHorizontalPanelWidget),
219    inkVerticalPanelWidget(inkVerticalPanelWidget),
220    inkScrollAreaWidget(inkScrollAreaWidget),
221    inkUniformGridWidget(inkUniformGridWidget),
222    inkVirtualCompoundWidget(inkVirtualCompoundWidget),
223    inkFlexWidget(inkFlexWidget),
224    inkCacheWidget(inkCacheWidget),
225
226    inkTextWidget(inkTextWidget),
227    inkImageWidget(inkImageWidget),
228    inkVideoWidget(inkVideoWidget),
229    inkMaskWidget(inkMaskWidget),
230    inkBorderWidget(inkBorderWidget),
231    inkShapeWidget(inkShapeWidget),
232    inkCircleWidget(inkCircleWidget),
233    inkRectangleWidget(inkRectangleWidget),
234    inkVectorGraphicWidget(inkVectorGraphicWidget),
235}
236
237/// see [NativeDB](https://nativedb.red4ext.com/inkWidgetLibraryItemInstance)
238#[allow(non_camel_case_types)]
239#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct inkWidgetLibraryItemInstance {
242    pub root_widget: InkWrapper<inkCanvasWidget>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "PascalCase")]
247pub struct Data {
248    pub file: crate::Data<inkWidgetLibraryItemInstance>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "PascalCase")]
253pub struct Package {
254    pub data: self::Data,
255}
256
257/// see [NativeDB](https://nativedb.red4ext.com/inkWidgetLibraryItem)
258#[allow(non_camel_case_types)]
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct inkWidgetLibraryItem {
261    pub name: Name,
262    pub package: Package,
263}
264
265/// see [NativeDB](https://nativedb.red4ext.com/inkanimAnimationLibraryResource)
266#[allow(non_camel_case_types)]
267#[derive(Debug, Clone, Serialize, Deserialize)]
268#[serde(rename_all = "PascalCase")]
269pub struct inkanimAnimationLibraryResource {
270    depot_path: DepotPath,
271    flags: Flags,
272}
273
274/// see [NativeDB](https://nativedb.red4ext.com/inkWidgetLibraryResource)
275#[allow(non_camel_case_types)]
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct inkWidgetLibraryResource {
279    pub animation_library_res_ref: inkanimAnimationLibraryResource,
280    pub library_items: Vec<inkWidgetLibraryItem>,
281}
282
283/// widget aggregated informations summary
284#[allow(non_snake_case)]
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct WidgetSummary {
287    /// unique handle ID
288    pub HandleId: HandleId,
289    /// widget name
290    pub Name: Name,
291}