Skip to main content

banished_ui/controls/
setup.rs

1//! Making controls from their resource descriptions (`instantiate_control`), and tabs added in
2//! code.
3
4use super::*;
5
6impl Ui {
7    /// Runtime UI::TabElement::AddTab: retain header/content order and select the first tab.
8    pub fn add_tab(&mut self, parent: NodeId, header: NodeId, content: NodeId) {
9        if let Kind::Control(Control::Tabs { tabs, .. }) = &mut self.nodes[parent].kind {
10            tabs.push((header, content));
11        }
12    }
13
14    pub(crate) fn make_text(&mut self, font: Option<&str>, text: String) -> TextData {
15        TextData {
16            font: font.and_then(|f| self.res.font(f)),
17            text,
18            max_width: 0,
19            justify: 0,
20            wrap: false,
21            shadow: false,
22            ellipsis_width: 0,
23            layout: TextLayout::default(),
24        }
25    }
26
27    pub(super) fn control_button(&mut self, b: &ui::Button) -> NodeId {
28        let states =
29            std::array::from_fn(|i| b.states[i].as_deref().and_then(|r| self.instantiate(r)));
30        let content = b.content.as_deref().and_then(|r| self.instantiate(r));
31        self.add(Node::new(Kind::Button {
32            states,
33            content,
34            colors: b.colors,
35            state: 0,
36        }))
37    }
38
39    pub(crate) fn instantiate_control(&mut self, kind: &ElementKind) -> Control {
40        let child = |ui: &mut Ui, r: &Option<String>| r.as_deref().and_then(|r| ui.instantiate(r));
41        let sprites = |ui: &mut Ui, sheet: &Option<String>, names: &[String]| {
42            names
43                .iter()
44                .map(|s| ui.res.sprite(sheet.as_deref(), s))
45                .collect::<Vec<_>>()
46        };
47        match kind {
48            ElementKind::Slider {
49                track,
50                sprite_sheet,
51                sprites: names,
52                unknown,
53                range,
54                unknown_b0,
55            } => Control::Slider(SliderData {
56                sprites: sprites(self, sprite_sheet, names),
57                range: *range,
58                value: range[0],
59                steps: *unknown_b0,
60                inset: unknown[1],
61                track_y: unknown[0],
62                number: child(self, track),
63                over_knob: false,
64                drag: None,
65            }),
66            ElementKind::Scroll {
67                sprite_sheet,
68                sprites: names,
69                vertical,
70                unknown,
71                unknown_u32,
72            } => Control::Scroll {
73                sprites: sprites(self, sprite_sheet, names),
74                vertical: *vertical,
75                value: 0.0,
76                page: 0.25,
77                inset: *unknown,
78                thumb_color: unknown_u32[0],
79                drag: None,
80            },
81            ElementKind::Progress {
82                color,
83                sprite_sheet,
84                sprites: names,
85                unknown_ref,
86            } => Control::Progress {
87                sprites: sprites(self, sprite_sheet, names),
88                background_color: *color,
89                value: 0.0,
90                content: child(self, unknown_ref),
91            },
92            ElementKind::Edit {
93                font,
94                text,
95                max_length,
96                unknown_bool,
97                unknown_string,
98            } => Control::Edit(EditData {
99                text: self.make_text(font.as_deref(), text.clone()),
100                cursor: text.chars().count(),
101                max_length: if *max_length > 0 {
102                    *max_length as usize
103                } else {
104                    usize::MAX
105                },
106                numeric: false,
107                range: None,
108                password: *unknown_bool,
109                allowed: unknown_string.clone(),
110                caret: true,
111                blink: 0.0,
112            }),
113            ElementKind::NumberEdit {
114                font,
115                unknown,
116                unknown_bool,
117            } => Control::Edit(EditData {
118                text: self.make_text(font.as_deref(), unknown[0].to_string()),
119                cursor: 1,
120                max_length: unknown[3].max(1) as usize,
121                numeric: true,
122                range: Some([unknown[1] as f32, unknown[2] as f32]),
123                password: false,
124                allowed: if *unknown_bool {
125                    "0123456789-".into()
126                } else {
127                    "0123456789".into()
128                },
129                caret: true,
130                blink: 0.0,
131            }),
132            ElementKind::Number {
133                font,
134                unknown_float,
135                ..
136            } => Control::Edit(EditData {
137                text: self.make_text(font.as_deref(), format_number(*unknown_float)),
138                cursor: 0,
139                max_length: 0,
140                numeric: true,
141                range: None,
142                password: false,
143                allowed: String::new(),
144                caret: true,
145                blink: 0.0,
146            }),
147            ElementKind::Graph {
148                unknown,
149                unknown_u32,
150                sprite_sheet,
151                sprite,
152            } => Control::Graph {
153                // `GraphDescription::CreateInstance`: size `+0xc0`, tick `+0xc8` (and an unused
154                // colour, `+0xcc`); `GraphElement::GraphElement`: range 1, 12 by 4 divisions.
155                size: [unknown[0], unknown[1]],
156                sprite: self.res.sprite(sprite_sheet.as_deref(), sprite),
157                tick: {
158                    let _ = unknown_u32;
159                    unknown[2]
160                },
161                y_range: 1.0,
162                x_divisions: 12,
163                y_divisions: 4,
164                series: Vec::new(),
165            },
166            ElementKind::Table { unknown, cells } => {
167                let cols = unknown[0].clamp(0, 256) as usize;
168                let rows = unknown[1].clamp(0, 4096) as usize;
169                let mut out = Vec::new();
170                for y in 0..rows {
171                    for x in 0..cols {
172                        // Specific cell > column/row template > table default.
173                        let template = cells
174                            .iter()
175                            .filter(|cell| {
176                                (cell.column < 0 || cell.column as usize == x)
177                                    && (cell.row < 0 || cell.row as usize == y)
178                            })
179                            .max_by_key(|cell| (cell.column >= 0) as u8 + (cell.row >= 0) as u8);
180                        if let Some(c) = template.and_then(|cell| child(self, &cell.element)) {
181                            out.push((x, y, c));
182                        }
183                    }
184                }
185                Control::Table {
186                    cells: out,
187                    columns: vec![0; cols],
188                    rows: vec![0; rows],
189                    spacing: unknown[2],
190                }
191            }
192            ElementKind::Layout { items } => Control::Layout(
193                items
194                    .iter()
195                    .filter_map(|spec| {
196                        child(self, &spec.element).map(|node| LayoutChild {
197                            node,
198                            spec: spec.clone(),
199                        })
200                    })
201                    .collect(),
202            ),
203            ElementKind::Tab {
204                refs,
205                tabs,
206                unknown,
207                ..
208            } => Control::Tabs {
209                decoration: std::array::from_fn(|i| child(self, &refs[i])),
210                tabs: tabs
211                    .iter()
212                    .filter_map(|(a, b)| Some((child(self, a)?, child(self, b)?)))
213                    .collect(),
214                selected: 0,
215                gap: *unknown,
216                header_height: 0,
217            },
218            ElementKind::ClipRegion { unknown, content } => Control::Clip {
219                content: child(self, content),
220                size: *unknown,
221                offset: [0, 0],
222            },
223            ElementKind::Container { unknown, refs, .. } => Control::Container {
224                content: child(self, &refs[2]),
225                bars: [child(self, &refs[0]), child(self, &refs[1])],
226                size: [unknown[0], unknown[1]],
227                spacing: unknown[2],
228                offset: [0, 0],
229                view: [0; 4],
230                wheel: 0,
231                fine: 0.0,
232            },
233            ElementKind::Check { button, check } => Control::Check {
234                button: self.control_button(button),
235                mark: child(self, check),
236                checked: false,
237            },
238            ElementKind::Combo { button, refs } => {
239                let b = self.control_button(button);
240                // ComboElement derives from ButtonElement: the button is the whole combo.
241                self.nodes[b].flags |= crate::flags::FILL_WIDTH | crate::flags::FILL_HEIGHT;
242                self.set_text(b, "");
243                let font = refs[1]
244                    .as_deref()
245                    .and_then(|r| self.res.element(r))
246                    .and_then(|e| match &e.kind {
247                        ElementKind::Text(t) => t.font.clone(),
248                        _ => None,
249                    })
250                    .unwrap_or_else(|| r"Font\FontSmall.rsc:resource".into());
251                let list = self.list(&font);
252                // The third reference is an optional string table whose entries are the items
253                // (e.g. `Dialog\StringTable.rsc:terrainSize`); code fills the other combos.
254                if let Some(items) = refs[2].as_deref().and_then(|r| {
255                    let data = self.res.archive.read_ref(r).ok()?;
256                    banished_formats::ui::string_table(&data).ok()
257                }) {
258                    self.set_list_items(list, items.into_iter().map(|(_, v)| v).collect());
259                }
260                Control::Combo {
261                    button: b,
262                    list,
263                    open: false,
264                }
265            }
266            ElementKind::Spinner { refs, unknown } => Control::Spinner {
267                parts: std::array::from_fn(|i| child(self, &refs[i])),
268                value: 0,
269                step: *unknown,
270            },
271            ElementKind::Page { children, page } => Control::Page {
272                children: children.iter().filter_map(|r| child(self, r)).collect(),
273                selected: (*page).max(0) as usize,
274            },
275            ElementKind::Radio { children, selected } => Control::Radio {
276                children: children.iter().filter_map(|r| child(self, r)).collect(),
277                selected: (*selected).max(0) as usize,
278            },
279            ElementKind::DocumentText(d) => Control::Document(self.load_document(d)),
280            ElementKind::List {
281                refs,
282                sprite_sheet,
283                sprite,
284                ..
285            } => {
286                let f = refs[0]
287                    .as_deref()
288                    .and_then(|r| self.res.element(r))
289                    .and_then(|e| match &e.kind {
290                        ElementKind::Text(t) => t.font.clone(),
291                        _ => None,
292                    });
293                let font = f.as_deref().and_then(|f| self.res.font(f));
294                let highlight = self.res.sprite(sprite_sheet.as_deref(), sprite);
295                let id = self.add(Node::new(Kind::List(ListData {
296                    items: Vec::new(),
297                    icons: Vec::new(),
298                    selected: None,
299                    hovered: None,
300                    scroll: 0,
301                    font,
302                    row_height: font.map_or(20, |f| self.res.fonts[f].font.size + 4),
303                    highlight,
304                    rows: Vec::new(),
305                    reveal: false,
306                })));
307                Control::Clip {
308                    content: Some(id),
309                    size: [0, 0],
310                    offset: [0, 0],
311                }
312            }
313            _ => unreachable!("ordinary element handled by Ui"),
314        }
315    }
316}