Skip to main content

banished_formats/
ui.rs

1//! `UI::` resources: dialogs, element descriptions, fonts, sprite sheets and string tables.
2//!
3//! Every class level of `Serialize` is its own block and derived classes serialize their base first.
4//! Field meanings come from `ElementDescription` defaults, `Element::Element` and the per-class
5//! `ComputeSize`/`ComputeLayout` code; names marked "unknown" have not been traced to behaviour yet.
6
7use crate::crc::type_id;
8use crate::crs::{self, Reader};
9use crate::error::{format_err, Error, Result};
10
11type Ref = Option<String>;
12
13fn refs(r: &mut Reader) -> Result<Vec<Ref>> {
14    (0..r.count()?).map(|_| r.external_ref()).collect()
15}
16
17fn strings<const N: usize>(r: &mut Reader) -> Result<[String; N]> {
18    let mut out: [String; N] = std::array::from_fn(|_| String::new());
19    for s in &mut out {
20        *s = r.string()?;
21    }
22    Ok(out)
23}
24
25fn i32s<const N: usize>(r: &mut Reader) -> Result<[i32; N]> {
26    let mut out = [0; N];
27    for v in &mut out {
28        *v = r.i32()?;
29    }
30    Ok(out)
31}
32
33/// Alignment within the space a parent offers: 0..8 = rows top/middle/bottom x columns left/centre/right.
34pub type Alignment = u32;
35
36/// The bits of an element's flags.
37pub mod flags {
38    /// The element is hidden.
39    pub const HIDDEN: u32 = 1 << 0;
40    /// The element fills the width it is given.
41    pub const FILL_WIDTH: u32 = 1 << 2;
42    /// The element fills the height it is given.
43    pub const FILL_HEIGHT: u32 = 1 << 3;
44    /// Height (0x10) / width (0x20) taken from the screen size times the UI scale.
45    pub const SCREEN_HEIGHT: u32 = 1 << 4;
46    /// The width is the screen's width times the interface scale.
47    pub const SCREEN_WIDTH: u32 = 1 << 5;
48    /// Halves the element's alpha (`Element::ComputeColor`).
49    pub const DIM: u32 = 1 << 18;
50    /// Takes the mouse (`SetFlagsOnOff(0x10000)`): a click on it is an event of its own.
51    pub const MOUSE: u32 = 1 << 16;
52    /// Disabled by the tutorial (input passes it by, like a disabled one).
53    ///
54    /// Original: `FUN_1400e7720`.
55    pub const LOCKED: u32 = 1 << 24;
56}
57
58/// Shared by every element.
59///
60/// Original: `UI::ElementDescription`, Runtime `0x180119390`.
61#[derive(Debug, Clone, Default)]
62pub struct ElementBase {
63    /// left, right, top, bottom
64    pub margins: [i32; 4],
65    /// The smallest size, in pixels.
66    pub min_size: [i32; 2],
67    /// How the element sits in the space it is given.
68    pub alignment: Alignment,
69    /// The element's flags ([`flags`]).
70    pub flags: u32,
71    /// ARGB, multiplied into everything the element draws.
72    pub color: u32,
73    // +0x3c
74    /// The mouse cursor shown over it (`Element::GetMouseCursorIndex`; an index into the game's
75    /// cursors: 0 Arrow, 1 Hand, 2 Hourglass, 3 Move, 4 Query, 5 Place). Only the StandardDialog
76    /// title bar sets one (3).
77    pub cursor: i32,
78    /// The element drawn behind it as a border.
79    pub border: Ref,
80    /// The dialog shown as the tooltip, when the tooltip is a dialog.
81    pub tooltip_dialog: Ref,
82    /// The string table the element's own strings come from.
83    pub string_table: Ref,
84    /// The tooltip text, or its key in the string table.
85    pub tooltip: String,
86}
87
88impl ElementBase {
89    fn read(r: &mut Reader) -> Result<Self> {
90        r.block(|r, _| {
91            let margins = i32s(r)?;
92            let min_size = i32s(r)?;
93            let alignment = r.u32()?;
94            let flags = r.u32()?;
95            let color = r.u32()?;
96            let cursor = r.i32()?;
97            Ok(ElementBase {
98                margins,
99                min_size,
100                alignment,
101                flags,
102                color,
103                cursor,
104                border: r.external_ref()?,
105                tooltip_dialog: r.external_ref()?,
106                string_table: r.external_ref()?,
107                tooltip: r.string()?,
108            })
109        })
110    }
111}
112
113/// A button's description.
114#[derive(Debug, Clone)]
115pub struct Button {
116    /// enabled, disabled, highlight, pressed state elements
117    pub states: [Ref; 4],
118    /// The content: a label or an image.
119    pub content: Ref,
120    /// Content colours per state (ARGB), same order as `states`.
121    pub colors: [u32; 4],
122}
123
124/// A text element's description.
125#[derive(Debug, Clone)]
126pub struct Text {
127    /// The font.
128    pub font: Ref,
129    /// The string table that `text` is a key of.
130    pub string_table: Ref,
131    /// String-table key, or literal text when no table is given.
132    pub text: String,
133    /// Draw a black shadow one pixel down and right (TextElement::Draw).
134    pub shadow: bool,
135    /// Whether long lines wrap.
136    pub wrap: bool,
137    /// Width limit passed to `Font::ComputeStringLayout` (wrapping / ellipsis).
138    pub max_width: i32,
139    /// `Font::Justify`: 0 left, 1 centre, 2 right.
140    pub justify: u32,
141    /// Non-wrapped line width at which the engine inserts "...". This is not a key code.
142    pub ellipsis_width: i32,
143}
144
145/// A label's description: text with an optional icon.
146#[derive(Debug, Clone)]
147pub struct Label {
148    /// The text.
149    pub text: Text,
150    /// The icon's sprite sheet.
151    pub sprite_sheet: Ref,
152    /// The icon's sprite name.
153    pub sprite: String,
154    /// The alignment of the text.
155    pub text_alignment: u32,
156    /// > 1 stacks icon above text, otherwise icon left of text (`LabelElement::ComputeSize`).
157    pub orientation: u32,
158    /// The smallest width of the text, in pixels.
159    pub min_text_width: i32,
160    /// The space between the icon and the text, in pixels.
161    pub gap: i32,
162    /// The icon's size, in pixels.
163    pub icon_size: [i32; 2],
164}
165
166/// What an element description is, with the fields of its class.
167#[derive(Debug, Clone)]
168pub enum ElementKind {
169    /// Children laid out on top of each other, each by its alignment.
170    Group {
171        /// The children.
172        children: Vec<Ref>,
173    },
174    /// Pages, of which one is shown.
175    Page {
176        /// The pages.
177        children: Vec<Ref>,
178        /// The page shown at first.
179        page: i32,
180    },
181    /// Radio buttons, of which one is selected.
182    Radio {
183        /// The buttons.
184        children: Vec<Ref>,
185        /// The button selected at first.
186        selected: i32,
187    },
188    /// Children in a row or a column.
189    Ribbon {
190        /// A column when true, a row when false.
191        vertical: bool,
192        /// Whether every cell has the size of the largest child.
193        uniform: bool,
194        /// The space between the cells, in pixels.
195        spacing: i32,
196        /// The children.
197        children: Vec<Ref>,
198    },
199    /// A frame drawn from nine sprites.
200    Border {
201        /// The sprite sheet.
202        sprite_sheet: Ref,
203        /// The nine sprites, row by row: corners, edges and centre.
204        sprites: [String; 9],
205    },
206    /// One sprite.
207    Image {
208        /// The sprite sheet.
209        sprite_sheet: Ref,
210        /// The sprite name.
211        sprite: String,
212        /// The size, in pixels.
213        size: [i32; 2],
214    },
215    /// The game's `FrustumOutlineDescription` (MapUI's camera outline): an image block whose
216    /// element draws lines instead of the sprite.
217    ///
218    /// Original: `FUN_14006c7f0`.
219    FrustumOutline {
220        /// The sprite sheet.
221        sprite_sheet: Ref,
222        /// The sprite the lines are drawn with.
223        sprite: String,
224        /// The size, in pixels.
225        size: [i32; 2],
226    },
227    /// A sequence of sprites played as an animation.
228    AnimatedImage {
229        /// The sprite sheet.
230        sprite_sheet: Ref,
231        /// The first frame's sprite.
232        sprite: String,
233        /// The size, in pixels.
234        size: [i32; 2],
235        /// The frames' sprite names.
236        frames: Vec<String>,
237        /// The frame rate.
238        rate: i32,
239    },
240    /// A button.
241    Button(Button),
242    /// A check box.
243    Check {
244        /// The box.
245        button: Button,
246        /// The tick, shown when checked.
247        check: Ref,
248    },
249    /// A combo box: a button that opens a list.
250    Combo {
251        /// The button that shows the choice.
252        button: Button,
253        /// The list's parts: the second is the text whose font the list uses, the third the
254        /// choices.
255        refs: [Ref; 3],
256    },
257    /// Text.
258    Text(Text),
259    /// Text with an optional icon.
260    Label(Label),
261    /// A window onto its content, cut at its edges.
262    ClipRegion {
263        /// The window's size, in pixels.
264        unknown: [i32; 2],
265        /// The content.
266        content: Ref,
267    },
268    /// A scrolling area.
269    Container {
270        /// The size (two values) and the space between the content and the bars.
271        unknown: [i32; 3],
272        /// The horizontal bar, the vertical bar and the content.
273        refs: [Ref; 3],
274        /// Not identified yet.
275        unknown_bools: [bool; 2],
276    },
277    /// An edit box.
278    Edit {
279        /// The font.
280        font: Ref,
281        /// The text at first.
282        text: String,
283        /// The most characters it takes (0: no limit).
284        max_length: i32,
285        /// Whether the text is drawn as `*`s (a password).
286        unknown_bool: bool,
287        /// The characters it takes, any when empty.
288        unknown_string: String,
289    },
290    /// A graph of values over time.
291    Graph {
292        /// The size (two values) and the length of the ticks.
293        unknown: [i32; 3],
294        /// Not identified yet.
295        unknown_u32: u32,
296        /// The sprite sheet of the lines.
297        sprite_sheet: Ref,
298        /// The sprite the lines are drawn with.
299        sprite: String,
300    },
301    /// Children placed by anchors relative to the element.
302    Layout {
303        /// The children and their anchors.
304        items: Vec<LayoutItem>,
305    },
306    /// A list of rows.
307    List {
308        /// The list's parts: the first is the text whose font the rows use.
309        refs: [Ref; 2],
310        /// The sprite sheet of the highlight.
311        sprite_sheet: Ref,
312        /// The highlight's sprite.
313        sprite: String,
314        /// Not identified yet.
315        unknown: [u32; 2],
316    },
317    /// An edit box for a number.
318    NumberEdit {
319        /// The font.
320        font: Ref,
321        /// The value at first, the smallest and largest value, and the most digits.
322        unknown: [i32; 4],
323        /// Whether the number may be below zero.
324        unknown_bool: bool,
325    },
326    /// A number shown as text.
327    Number {
328        /// The font.
329        font: Ref,
330        /// The colour, `0xAARRGGBB`.
331        color: u32,
332        /// Not identified yet.
333        unknown_int: i32,
334        /// The value at first.
335        unknown_float: f32,
336        /// Not identified yet.
337        unknown_bool: bool,
338    },
339    /// A slider.
340    Slider {
341        /// The number shown next to the slider.
342        track: Ref,
343        /// The sprite sheet.
344        sprite_sheet: Ref,
345        /// The sprites: the track's ends and middle, the knob, and the knob under the pointer.
346        sprites: [String; 5],
347        /// The track's vertical position and the inset of the knob's travel.
348        unknown: [i32; 2],
349        /// The smallest and the largest value.
350        range: [f32; 2],
351        /// The steps across the range: a click on the track moves the value one step.
352        unknown_b0: i32,
353    },
354    /// A number with up and down arrows.
355    Spinner {
356        /// The number and the two arrows.
357        refs: [Ref; 3],
358        /// How much an arrow changes the number.
359        unknown: i32,
360    },
361    /// Tabs: a header for each tab, and the page of the selected one.
362    Tab {
363        /// The elements drawn around the tabs.
364        refs: [Ref; 3],
365        /// The sprite sheet.
366        sprite_sheet: Ref,
367        /// The sprites of the tab headers.
368        sprites: [String; 3],
369        /// The space between the headers, in pixels.
370        unknown: i32,
371        /// Each tab: its header and its page.
372        tabs: Vec<(Ref, Ref)>,
373    },
374    /// A progress bar.
375    Progress {
376        /// The background's colour, `0xAARRGGBB`.
377        color: u32,
378        /// The sprite sheet.
379        sprite_sheet: Ref,
380        /// The sprites of the fill and the background.
381        sprites: [String; 6],
382        /// The element drawn on top (usually a label).
383        unknown_ref: Ref,
384    },
385    /// A scroll bar.
386    Scroll {
387        /// The sprite sheet.
388        sprite_sheet: Ref,
389        /// The sprites of the track, the thumb and the arrows.
390        sprites: [String; 9],
391        /// A vertical bar when true.
392        vertical: bool,
393        /// The thumb's inset at both ends of the track.
394        unknown: i32,
395        /// The thumb's colour, and a value not identified yet.
396        unknown_u32: [u32; 2],
397    },
398    /// A table of cells in rows and columns.
399    Table {
400        /// The number of columns, the number of rows, and the space between the cells.
401        unknown: [i32; 3],
402        /// The cells.
403        cells: Vec<TableCell>,
404    },
405    /// A document with markup (the help pages and the tutorials).
406    DocumentText(DocumentText),
407}
408
409/// Rich text configuration (`DocumentTextDescription::Serialize`).
410///
411/// Original: Runtime `0x1800d2110`.
412#[derive(Debug, Clone)]
413pub struct DocumentText {
414    /// The string table that `text` is a key of.
415    pub string_table: Ref,
416    /// The text with markup, or its key.
417    pub text: String,
418    /// The sprite sheet of the bullets and the underline.
419    pub sprite_sheet: Ref,
420    /// The bullets' sprite names.
421    pub bullets: Vec<String>,
422    /// The underline's sprite name.
423    pub underline_sprite: String,
424    /// The colours of links: normal, under the pointer, pressed.
425    pub link_colors: [u32; 3],
426    /// The indent of list items, in pixels.
427    pub indent: i32,
428    /// The width at which the text wraps, in pixels.
429    pub width: i32,
430    /// The fonts the markup can select.
431    pub fonts: Vec<Ref>,
432    /// The sprite sheets the markup can show images from.
433    pub sheets: Vec<Ref>,
434    /// The colours the markup can select.
435    pub colors: Vec<u32>,
436    /// The resource with the document's pictures.
437    pub images: Ref,
438}
439
440/// A child of a layout element, with its anchors.
441#[derive(Debug, Clone)]
442pub struct LayoutItem {
443    /// The child.
444    pub element: Ref,
445    /// The names of the anchors.
446    pub strings: [String; 3],
447    /// Not identified yet.
448    pub unknown_u32: [u32; 2],
449    /// The offsets from the anchors, in pixels.
450    pub unknown_i32: [i32; 2],
451    /// Not identified yet.
452    pub flag: bool,
453}
454
455/// A cell of a table element.
456#[derive(Debug, Clone)]
457pub struct TableCell {
458    /// The column.
459    pub column: i32,
460    /// The row.
461    pub row: i32,
462    /// The cell's element.
463    pub element: Ref,
464}
465
466/// An element description: its class, the fields every element has, and its class's fields.
467#[derive(Debug, Clone)]
468pub struct Element {
469    /// The element's class name, for example `ButtonDescription`.
470    pub class: &'static str,
471    /// The fields every element has.
472    pub base: ElementBase,
473    /// The class and its own fields.
474    pub kind: ElementKind,
475}
476
477impl Element {
478    /// The references of its child elements.
479    pub fn children(&self) -> &[Ref] {
480        match &self.kind {
481            ElementKind::Group { children }
482            | ElementKind::Page { children, .. }
483            | ElementKind::Radio { children, .. }
484            | ElementKind::Ribbon { children, .. } => children,
485            _ => &[],
486        }
487    }
488}
489
490fn read_button(r: &mut Reader) -> Result<Button> {
491    r.block(|r, _| {
492        let mut states: [Ref; 4] = Default::default();
493        for s in &mut states {
494            *s = r.external_ref()?;
495        }
496        let content = r.external_ref()?;
497        let colors = [r.u32()?, r.u32()?, r.u32()?, r.u32()?];
498        Ok(Button {
499            states,
500            content,
501            colors,
502        })
503    })
504}
505
506fn read_text(r: &mut Reader) -> Result<Text> {
507    r.block(|r, _| {
508        Ok(Text {
509            font: r.external_ref()?,
510            string_table: r.external_ref()?,
511            text: r.string()?,
512            shadow: r.bool()?,
513            wrap: r.bool()?,
514            max_width: r.i32()?,
515            justify: r.u32()?,
516            ellipsis_width: r.i32()?,
517        })
518    })
519}
520
521fn read_group(r: &mut Reader) -> Result<Vec<Ref>> {
522    r.block(|r, _| refs(r))
523}
524
525fn read_image(r: &mut Reader) -> Result<(Ref, String, [i32; 2])> {
526    r.block(|r, _| Ok((r.external_ref()?, r.string()?, i32s(r)?)))
527}
528
529/// Decode any `UI::*Description` element given its class name.
530fn read_element(class: &'static str, r: &mut Reader) -> Result<Element> {
531    let base = ElementBase::read(r)?;
532    let kind = match class {
533        "GroupDescription" => ElementKind::Group {
534            children: read_group(r)?,
535        },
536        "PageDescription" => {
537            let children = read_group(r)?;
538            ElementKind::Page {
539                children,
540                page: r.block(|r, _| r.i32())?,
541            }
542        }
543        "RadioDescription" => {
544            let children = read_group(r)?;
545            ElementKind::Radio {
546                children,
547                selected: r.block(|r, _| r.i32())?,
548            }
549        }
550        "RibbonDescription" => r.block(|r, _| {
551            Ok(ElementKind::Ribbon {
552                vertical: r.bool()?,
553                uniform: r.bool()?,
554                spacing: r.i32()?,
555                children: refs(r)?,
556            })
557        })?,
558        "BorderDescription" => r.block(|r, _| {
559            Ok(ElementKind::Border {
560                sprite_sheet: r.external_ref()?,
561                sprites: strings(r)?,
562            })
563        })?,
564        "ImageDescription" => {
565            let (sprite_sheet, sprite, size) = read_image(r)?;
566            ElementKind::Image {
567                sprite_sheet,
568                sprite,
569                size,
570            }
571        }
572        "FrustumOutlineDescription" => {
573            let (sprite_sheet, sprite, size) = read_image(r)?;
574            ElementKind::FrustumOutline {
575                sprite_sheet,
576                sprite,
577                size,
578            }
579        }
580        "AnimatedImageDescription" => {
581            let (sprite_sheet, sprite, size) = read_image(r)?;
582            r.block(|r, _| {
583                let frames = (0..r.count()?).map(|_| r.string()).collect::<Result<_>>()?;
584                Ok(ElementKind::AnimatedImage {
585                    sprite_sheet,
586                    sprite,
587                    size,
588                    frames,
589                    rate: r.i32()?,
590                })
591            })?
592        }
593        "ButtonDescription" => ElementKind::Button(read_button(r)?),
594        "CheckDescription" => {
595            let button = read_button(r)?;
596            ElementKind::Check {
597                button,
598                check: r.block(|r, _| r.external_ref())?,
599            }
600        }
601        "ComboDescription" => {
602            let button = read_button(r)?;
603            r.block(|r, _| {
604                Ok(ElementKind::Combo {
605                    button,
606                    refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
607                })
608            })?
609        }
610        "TextDescription" => ElementKind::Text(read_text(r)?),
611        "LabelDescription" => {
612            let text = read_text(r)?;
613            r.block(|r, _| {
614                let sprite_sheet = r.external_ref()?;
615                let sprite = r.string()?;
616                let text_alignment = r.u32()?;
617                let orientation = r.u32()?;
618                let [min_text_width, gap, w, h] = i32s(r)?;
619                Ok(ElementKind::Label(Label {
620                    text,
621                    sprite_sheet,
622                    sprite,
623                    text_alignment,
624                    orientation,
625                    min_text_width,
626                    gap,
627                    icon_size: [w, h],
628                }))
629            })?
630        }
631        "ClipRegionDescription" => r.block(|r, _| {
632            Ok(ElementKind::ClipRegion {
633                unknown: i32s(r)?,
634                content: r.external_ref()?,
635            })
636        })?,
637        "ContainerDescription" => r.block(|r, _| {
638            Ok(ElementKind::Container {
639                unknown: i32s(r)?,
640                refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
641                unknown_bools: [r.bool()?, r.bool()?],
642            })
643        })?,
644        "EditDescription" => r.block(|r, _| {
645            Ok(ElementKind::Edit {
646                font: r.external_ref()?,
647                text: r.string()?,
648                max_length: r.i32()?,
649                unknown_bool: r.bool()?,
650                unknown_string: r.string()?,
651            })
652        })?,
653        "GraphDescription" => r.block(|r, _| {
654            Ok(ElementKind::Graph {
655                unknown: i32s(r)?,
656                unknown_u32: r.u32()?,
657                sprite_sheet: r.external_ref()?,
658                sprite: r.string()?,
659            })
660        })?,
661        "LayoutDescription" => r.block(|r, _| {
662            let items = (0..r.count()?)
663                .map(|_| {
664                    Ok(LayoutItem {
665                        element: r.external_ref()?,
666                        strings: strings(r)?,
667                        unknown_u32: [r.u32()?, r.u32()?],
668                        unknown_i32: i32s(r)?,
669                        flag: r.bool()?,
670                    })
671                })
672                .collect::<Result<_>>()?;
673            Ok(ElementKind::Layout { items })
674        })?,
675        "ListDescription" => r.block(|r, _| {
676            Ok(ElementKind::List {
677                refs: [r.external_ref()?, r.external_ref()?],
678                sprite_sheet: r.external_ref()?,
679                sprite: r.string()?,
680                unknown: [r.u32()?, r.u32()?],
681            })
682        })?,
683        "NumberEditDescription" => r.block(|r, _| {
684            Ok(ElementKind::NumberEdit {
685                font: r.external_ref()?,
686                unknown: i32s(r)?,
687                unknown_bool: r.bool()?,
688            })
689        })?,
690        "NumberDescription" => r.block(|r, _| {
691            Ok(ElementKind::Number {
692                font: r.external_ref()?,
693                color: r.u32()?,
694                unknown_int: r.i32()?,
695                unknown_float: r.f32()?,
696                unknown_bool: r.bool()?,
697            })
698        })?,
699        "SliderDescription" => r.block(|r, _| {
700            Ok(ElementKind::Slider {
701                track: r.external_ref()?,
702                sprite_sheet: r.external_ref()?,
703                sprites: strings(r)?,
704                unknown: i32s(r)?,
705                range: [r.f32()?, r.f32()?],
706                unknown_b0: r.i32()?,
707            })
708        })?,
709        "SpinnerDescription" => r.block(|r, _| {
710            Ok(ElementKind::Spinner {
711                refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
712                unknown: r.i32()?,
713            })
714        })?,
715        "TabDescription" => r.block(|r, _| {
716            Ok(ElementKind::Tab {
717                refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
718                sprite_sheet: r.external_ref()?,
719                sprites: strings(r)?,
720                unknown: r.i32()?,
721                tabs: (0..r.count()?)
722                    .map(|_| Ok((r.external_ref()?, r.external_ref()?)))
723                    .collect::<Result<_>>()?,
724            })
725        })?,
726        "ProgressDescription" => r.block(|r, _| {
727            Ok(ElementKind::Progress {
728                color: r.u32()?,
729                sprite_sheet: r.external_ref()?,
730                sprites: strings(r)?,
731                unknown_ref: r.external_ref()?,
732            })
733        })?,
734        "ScrollDescription" => r.block(|r, _| {
735            Ok(ElementKind::Scroll {
736                sprite_sheet: r.external_ref()?,
737                sprites: strings(r)?,
738                vertical: r.bool()?,
739                unknown: r.i32()?,
740                unknown_u32: [r.u32()?, r.u32()?],
741            })
742        })?,
743        "TableDescription" => r.block(|r, _| {
744            let unknown = i32s(r)?;
745            let cells = (0..r.count()?)
746                .map(|_| {
747                    Ok(TableCell {
748                        column: r.i32()?,
749                        row: r.i32()?,
750                        element: r.external_ref()?,
751                    })
752                })
753                .collect::<Result<_>>()?;
754            Ok(ElementKind::Table { unknown, cells })
755        })?,
756        "DocumentTextDescription" => r.block(|r, _| {
757            Ok(ElementKind::DocumentText(DocumentText {
758                string_table: r.external_ref()?,
759                text: r.string()?,
760                sprite_sheet: r.external_ref()?,
761                bullets: (0..r.count()?).map(|_| r.string()).collect::<Result<_>>()?,
762                underline_sprite: r.string()?,
763                link_colors: [r.u32()?, r.u32()?, r.u32()?],
764                indent: r.i32()?,
765                width: r.i32()?,
766                fonts: refs(r)?,
767                sheets: refs(r)?,
768                colors: (0..r.count()?).map(|_| r.u32()).collect::<Result<_>>()?,
769                images: r.external_ref()?,
770            }))
771        })?,
772        _ => return Err(Error::Unsupported(format!("element class {class}"))),
773    };
774    Ok(Element { class, base, kind })
775}
776
777/// The class names of every element description.
778pub const ELEMENT_CLASSES: &[&str] = &[
779    "GroupDescription",
780    "PageDescription",
781    "RadioDescription",
782    "RibbonDescription",
783    "BorderDescription",
784    "ImageDescription",
785    "AnimatedImageDescription",
786    "FrustumOutlineDescription",
787    "ButtonDescription",
788    "CheckDescription",
789    "ComboDescription",
790    "TextDescription",
791    "LabelDescription",
792    "ClipRegionDescription",
793    "ContainerDescription",
794    "EditDescription",
795    "GraphDescription",
796    "LayoutDescription",
797    "ListDescription",
798    "NumberEditDescription",
799    "NumberDescription",
800    "SliderDescription",
801    "SpinnerDescription",
802    "TabDescription",
803    "ProgressDescription",
804    "ScrollDescription",
805    "TableDescription",
806    "DocumentTextDescription",
807];
808
809/// Decode a resource if it is any element description.
810pub fn element(data: &[u8]) -> Result<Element> {
811    let (_, element) = crs::parse(data, |r, h| {
812        let class = ELEMENT_CLASSES
813            .iter()
814            .find(|c| type_id(c) == h.type_id)
815            .ok_or_else(|| {
816                Error::Format(format!("type id {:#010x} is not an element", h.type_id))
817            })?;
818        read_element(class, r)
819    })?;
820    Ok(element)
821}
822
823/// Whether a type id is one of the element classes.
824pub fn is_element(type_id_value: u32) -> bool {
825    ELEMENT_CLASSES.iter().any(|c| type_id(c) == type_id_value)
826}
827
828// ---------------------------------------------------------------- Dialog
829
830/// A controller attached to a dialog's element.
831#[derive(Debug, Clone)]
832pub struct DialogBinding {
833    /// Application controller type ID (CRC32 of its class name).
834    pub unknown: u32,
835    /// Optional element description created beneath the named host.
836    pub source: Ref,
837    /// Optional controller configuration resource.
838    pub target: Ref,
839    /// Host element path identifier, or empty for the dialog root.
840    pub name: String,
841}
842
843/// Original: `UI::Dialog`, Runtime `0x180115950`.
844#[derive(Debug, Clone)]
845pub struct Dialog {
846    /// Not identified yet.
847    pub unknown_40: u32,
848    /// The root element.
849    pub root: Ref,
850    /// The sprite sheet of the background drawn behind the dialog.
851    pub background_sheet: Ref,
852    /// The background's sprite name.
853    pub background_sprite: String,
854    /// Not identified yet.
855    pub unknown_30: u32,
856    /// Elements replaced by name: the replacement resource and the name of the element it replaces.
857    pub named: Vec<(Ref, String)>,
858    /// The dialog's elements.
859    pub children: Vec<Ref>,
860    /// The controllers attached to elements.
861    pub bindings: Vec<DialogBinding>,
862    /// Associated resources, including nested dialogs and string tables.
863    pub dialogs: Vec<Ref>,
864    /// Not identified yet.
865    pub unknown_34: i32,
866}
867
868/// Read a [`Dialog`] from the bytes of its resource.
869pub fn dialog(data: &[u8]) -> Result<Dialog> {
870    expect(data, "Dialog", |r| {
871        r.block(|r, _| {
872            let unknown_40 = r.u32()?;
873            let root = r.external_ref()?;
874            let background_sheet = r.external_ref()?;
875            let background_sprite = r.string()?;
876            let unknown_30 = r.u32()?;
877            let named = (0..r.count()?)
878                .map(|_| Ok((r.external_ref()?, r.string()?)))
879                .collect::<Result<_>>()?;
880            let children = refs(r)?;
881            let bindings = (0..r.count()?)
882                .map(|_| {
883                    Ok(DialogBinding {
884                        unknown: r.u32()?,
885                        source: r.external_ref()?,
886                        target: r.external_ref()?,
887                        name: r.string()?,
888                    })
889                })
890                .collect::<Result<_>>()?;
891            let dialogs = refs(r)?;
892            Ok(Dialog {
893                unknown_40,
894                root,
895                background_sheet,
896                background_sprite,
897                unknown_30,
898                named,
899                children,
900                bindings,
901                dialogs,
902                unknown_34: r.i32()?,
903            })
904        })
905    })
906}
907
908// ---------------------------------------------------------------- SpriteSheet, Font, StringTable
909
910/// A sprite of a sprite sheet.
911#[derive(Debug, Clone)]
912pub struct Sprite {
913    /// The sprite's name.
914    pub name: String,
915    /// The image file it came from.
916    pub source: String,
917    /// Offset of the trimmed image inside the original source image.
918    pub offset: [i32; 2],
919    /// The width in pixels.
920    pub width: i32,
921    /// The height in pixels.
922    pub height: i32,
923    /// Not identified yet.
924    pub unknown: i32,
925    /// u0, v0, u1, v1 in the sheet image.
926    pub uv: [f32; 4],
927}
928
929/// Original: `UI::SpriteSheet`, Runtime `0x18011ffc0`.
930#[derive(Debug, Clone)]
931pub struct SpriteSheet {
932    /// The material's path.
933    pub material_path: String,
934    /// The image file of the sheet.
935    pub source_image: String,
936    /// The sheet's width in pixels.
937    pub width: i32,
938    /// The sheet's height in pixels.
939    pub height: i32,
940    /// Not identified yet.
941    pub unknown_flag: bool,
942    /// The sprites.
943    pub sprites: Vec<Sprite>,
944    /// Material instance whose `diffuse` texture is the sheet image.
945    pub material: Ref,
946}
947
948impl SpriteSheet {
949    /// A sprite by name.
950    pub fn sprite(&self, name: &str) -> Option<&Sprite> {
951        self.sprites.iter().find(|s| s.name == name)
952    }
953}
954
955/// Read a [`SpriteSheet`] from the bytes of its resource.
956pub fn sprite_sheet(data: &[u8]) -> Result<SpriteSheet> {
957    expect(data, "SpriteSheet", |r| {
958        r.block(|r, _| {
959            let material_path = r.string()?;
960            let source_image = r.string()?;
961            let width = r.i32()?;
962            let height = r.i32()?;
963            let unknown_flag = r.bool()?;
964            let sprites = (0..r.count()?)
965                .map(|_| {
966                    let name = r.string()?;
967                    let source = r.string()?;
968                    let offset = i32s(r)?;
969                    let width = r.i32()?;
970                    let height = r.i32()?;
971                    let unknown = r.i32()?;
972                    Ok(Sprite {
973                        name,
974                        source,
975                        offset,
976                        width,
977                        height,
978                        unknown,
979                        uv: r.f32s()?,
980                    })
981                })
982                .collect::<Result<_>>()?;
983            Ok(SpriteSheet {
984                material_path,
985                source_image,
986                width,
987                height,
988                unknown_flag,
989                sprites,
990                material: r.external_ref()?,
991            })
992        })
993    })
994}
995
996/// A mouse pointer: the sprite sheet whose image is the pointer, and its hot spot.
997///
998/// Original: `UI::Cursor::Serialize`, Runtime `0x1800cf980` (version 1).
999#[derive(Debug, Clone)]
1000pub struct Cursor {
1001    /// The sprite sheet whose image is the pointer.
1002    pub sprite_sheet: Ref,
1003    /// The point of the image that is the pointer's position, in pixels.
1004    pub hotspot: [i32; 2],
1005}
1006
1007/// Read a [`Cursor`] from the bytes of its resource.
1008pub fn cursor(data: &[u8]) -> Result<Cursor> {
1009    expect(data, "Cursor", |r| {
1010        r.block(|r, _| {
1011            Ok(Cursor {
1012                sprite_sheet: r.external_ref()?,
1013                hotspot: [r.i32()?, r.i32()?],
1014            })
1015        })
1016    })
1017}
1018
1019/// `UI::Font`, block version 2.
1020///
1021/// Original: Runtime `0x180119f60`.
1022#[derive(Debug, Clone)]
1023pub struct Font {
1024    /// The font's name.
1025    pub name: String,
1026    /// The font's size, in pixels.
1027    pub size: i32,
1028    /// The glyph sheet's width.
1029    pub sheet_width: i32,
1030    /// The glyph sheet's height.
1031    pub sheet_height: i32,
1032    /// The character set resource.
1033    pub character_set: Ref,
1034    /// Sprite sheet holding one sprite per glyph, in glyph order.
1035    pub sprite_sheet: Ref,
1036    /// The material the glyphs are drawn with.
1037    pub material: Ref,
1038    /// Code point -> glyph index; negative or 0xffff means no glyph.
1039    pub char_map: Vec<i32>,
1040    /// Per glyph: (a, c, b, d): advance = a + b, with the previous glyph's c added;
1041    /// d raises the bottom of the sprite above the baseline.
1042    pub glyphs: Vec<[i32; 4]>,
1043    // +0x54
1044    /// Serialized descent, ellipsis advance, and space advance (Runtime Font/58/5c).
1045    pub unknown: [i32; 3],
1046}
1047
1048impl Font {
1049    /// Baseline relative to a text element's top, as used by TextElement::Draw.
1050    ///
1051    /// Original: Runtime `0x180121b10`.
1052    pub fn baseline_offset(&self) -> i32 {
1053        self.size - self.unknown[0]
1054    }
1055
1056    /// The glyph of a character.
1057    pub fn glyph_index(&self, ch: char) -> Option<usize> {
1058        let g = *self.char_map.get(ch as usize)?;
1059        (g >= 0 && g != 0xFFFF && (g as usize) < self.glyphs.len()).then_some(g as usize)
1060    }
1061}
1062
1063/// Read a [`Font`] from the bytes of its resource.
1064pub fn font(data: &[u8]) -> Result<Font> {
1065    expect(data, "Font", |r| {
1066        r.block(|r, version| {
1067            if version < 2 {
1068                return Err(Error::Unsupported("Font block version < 2".into()));
1069            }
1070            let name = r.string()?;
1071            let size = r.i32()?;
1072            let _sheet_path = r.string()?;
1073            let sheet_width = r.i32()?;
1074            let sheet_height = r.i32()?;
1075            let _material_path = r.string()?;
1076            let _image_path = r.string()?;
1077            let character_set = r.external_ref()?;
1078            let sprite_sheet = r.external_ref()?;
1079            let material = r.external_ref()?;
1080            let char_map = (0..r.count()?).map(|_| r.i32()).collect::<Result<_>>()?;
1081            let glyphs = (0..r.count()?).map(|_| i32s(r)).collect::<Result<_>>()?;
1082            Ok(Font {
1083                name,
1084                size,
1085                sheet_width,
1086                sheet_height,
1087                character_set,
1088                sprite_sheet,
1089                material,
1090                char_map,
1091                glyphs,
1092                unknown: i32s(r)?,
1093            })
1094        })
1095    })
1096}
1097
1098/// Key -> display string.
1099///
1100/// Original: `UI::StringTable`, Runtime `0x1801201d0`.
1101pub fn string_table(data: &[u8]) -> Result<Vec<(String, String)>> {
1102    expect(data, "StringTable", |r| {
1103        r.block(|r, _| {
1104            (0..r.count()?)
1105                .map(|_| Ok((r.string()?, r.string()?)))
1106                .collect()
1107        })
1108    })
1109}
1110
1111fn expect<T>(data: &[u8], class: &str, read: impl FnOnce(&mut Reader) -> Result<T>) -> Result<T> {
1112    let (_, value) = crs::parse(data, |r, h| {
1113        if h.type_id != type_id(class) {
1114            return format_err(format!(
1115                "expected {class}, found type id {:#010x}",
1116                h.type_id
1117            ));
1118        }
1119        read(r)
1120    })?;
1121    Ok(value)
1122}