Skip to main content

banished_ui/
instantiate.rs

1//! Making elements from the game's dialog resources: one node per element, with its text, sprites
2//! and children.
3
4use super::*;
5
6impl Ui {
7    /// Build a node tree from an element reference such as `Dialog\MainMenu.rsc:main`.
8    pub fn instantiate(&mut self, reference: &str) -> Option<NodeId> {
9        let name = reference
10            .rsplit_once(':')
11            .map_or(reference, |(_, name)| name);
12        let reference = self
13            .overrides
14            .last()
15            .and_then(|o| o.get(name))
16            .cloned()
17            .unwrap_or_else(|| Some(reference.to_string()))?;
18        if self.stack.len() >= 128 || self.stack.contains(&reference) {
19            log::warn!("recursive UI reference: {reference}");
20            return None;
21        }
22        self.stack.push(reference.clone());
23        let result = self.instantiate_reference(&reference);
24        self.stack.pop();
25        result
26    }
27
28    pub(super) fn instantiate_reference(&mut self, reference: &str) -> Option<NodeId> {
29        let element = self.res.element(reference)?;
30        let name = reference
31            .rsplit_once(':')
32            .map_or(reference, |(_, n)| n)
33            .to_string();
34        let id = self.instantiate_element(&element, name);
35        self.nodes[id].reference = reference.to_string();
36        Some(id)
37    }
38
39    pub(super) fn text_data(&mut self, t: &banished_formats::ui::Text) -> TextData {
40        let font = t.font.as_deref().and_then(|f| self.res.font(f));
41        let text = self.res.string(t.string_table.as_deref(), &t.text);
42        TextData {
43            font,
44            text,
45            max_width: t.max_width,
46            justify: t.justify,
47            wrap: t.wrap,
48            shadow: t.shadow,
49            ellipsis_width: t.ellipsis_width,
50            layout: TextLayout::default(),
51        }
52    }
53
54    pub(super) fn instantiate_element(&mut self, e: &Element, name: String) -> NodeId {
55        let child = |ui: &mut Ui, r: &Option<String>| r.as_deref().and_then(|r| ui.instantiate(r));
56        let mut children = Vec::new();
57        let kind = match &e.kind {
58            ElementKind::Group { children: c } => {
59                children = c.iter().filter_map(|r| child(self, r)).collect();
60                Kind::Group
61            }
62            ElementKind::Ribbon {
63                vertical,
64                uniform,
65                spacing,
66                children: c,
67            } => {
68                children = c.iter().filter_map(|r| child(self, r)).collect();
69                Kind::Ribbon {
70                    vertical: *vertical,
71                    uniform: *uniform,
72                    spacing: *spacing,
73                    cells: Vec::new(),
74                }
75            }
76            ElementKind::Border {
77                sprite_sheet,
78                sprites,
79            } => {
80                let mut out = [None; 9];
81                for (o, s) in out.iter_mut().zip(sprites) {
82                    *o = self.res.sprite(sprite_sheet.as_deref(), s);
83                }
84                Kind::Border { sprites: out }
85            }
86            ElementKind::Image {
87                sprite_sheet,
88                sprite,
89                size,
90            } => Kind::Image {
91                sprite: self.res.sprite(sprite_sheet.as_deref(), sprite),
92                size: *size,
93            },
94            ElementKind::FrustumOutline {
95                sprite_sheet,
96                sprite,
97                size,
98            } => Kind::Lines {
99                sprite: self.res.sprite(sprite_sheet.as_deref(), sprite),
100                size: *size,
101                lines: Vec::new(),
102            },
103            ElementKind::AnimatedImage {
104                sprite_sheet,
105                sprite,
106                size,
107                frames,
108                rate,
109            } => {
110                let mut sprites = if frames.is_empty() {
111                    vec![self.res.sprite(sprite_sheet.as_deref(), sprite)]
112                } else {
113                    frames
114                        .iter()
115                        .map(|frame| self.res.sprite(sprite_sheet.as_deref(), frame))
116                        .collect()
117                };
118                if sprites.is_empty() {
119                    sprites.push(self.res.sprite(sprite_sheet.as_deref(), sprite));
120                }
121                Kind::AnimatedImage {
122                    sprites,
123                    size: *size,
124                    frames_per_second: *rate,
125                    frame: 0,
126                    clock: 0.0,
127                }
128            }
129            ElementKind::Button(b) => {
130                let mut states = [None; 4];
131                for (s, r) in states.iter_mut().zip(&b.states) {
132                    *s = child(self, r);
133                }
134                let content = child(self, &b.content);
135                Kind::Button {
136                    states,
137                    content,
138                    colors: b.colors,
139                    state: 0,
140                }
141            }
142            ElementKind::Text(t) => Kind::Text(self.text_data(t)),
143            ElementKind::Label(l) => Kind::Label {
144                text: self.text_data(&l.text),
145                icon: self.res.sprite(l.sprite_sheet.as_deref(), &l.sprite),
146                icon_size: l.icon_size,
147                text_alignment: l.text_alignment,
148                orientation: l.orientation,
149                min_text_width: l.min_text_width,
150                gap: l.gap,
151                icon_color: 0xFFFF_FFFF,
152            },
153            _ => Kind::Control(self.instantiate_control(&e.kind)),
154        };
155        let b = &e.base;
156        let mut node = Node::new(kind);
157        node.name = name;
158        node.margins = b.margins;
159        node.min_size = b.min_size;
160        node.alignment = b.alignment;
161        node.flags = b.flags;
162        node.color = b.color;
163        node.cursor = b.cursor;
164        node.children = children;
165        node.tooltip = self.res.string(b.string_table.as_deref(), &b.tooltip);
166        node.tooltip_dialog = b.tooltip_dialog.clone();
167        node.string_table = b.string_table.clone();
168        node.border = child(self, &b.border);
169
170        self.add(node)
171    }
172}