banished_ui/lib.rs
1#![doc = include_str!("../README.md")]
2#![doc(
3 html_logo_url = "https://tanepiper.github.io/unbanished/assets/un-banished-icon.svg",
4 html_favicon_url = "https://tanepiper.github.io/unbanished/favicon.svg"
5)]
6
7mod animation;
8mod controls;
9mod dialog;
10mod document;
11mod draw;
12mod input;
13mod instantiate;
14mod layout;
15mod query;
16mod resources;
17mod text;
18mod tree;
19
20use draw::modulate;
21
22use std::rc::Rc;
23
24use banished_formats::pkg::Archive;
25use banished_formats::ui::{flags, Element, ElementKind};
26
27pub use controls::{Control, EditData, SliderData};
28pub use dialog::{Binding, DialogInstance};
29pub use input::{tooltip_controller, Key};
30pub use query::{ElementInfo, Query, Step};
31pub use resources::{FontId, LoadedFont, LoadedSheet, Resources, SheetId, SpriteRef};
32pub use text::TextLayout;
33
34/// The index of an element in [`Ui::nodes`].
35pub type NodeId = usize;
36
37/// What a quad samples from.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub enum Texture {
40 /// A sprite sheet of the game, loaded by [`Resources`].
41 Sheet(SheetId),
42 /// Application-supplied texture (for example a texture preview).
43 External(u32),
44}
45
46/// One textured rectangle to draw. [`Ui::draw`] returns them in drawing order.
47#[derive(Debug, Clone, Copy, PartialEq)]
48pub struct Quad {
49 /// What the quad samples from.
50 pub texture: Texture,
51 /// The rectangle in pixels: x0, y0, x1, y1.
52 pub rect: [f32; 4],
53 /// The texture coordinates: u0, v0, u1, v1.
54 pub uv: [f32; 4],
55 /// The colour that multiplies the texture: red, green, blue, alpha.
56 pub color: [u8; 4],
57 /// Scissor rectangle (x0, y0, x1, y1) in pixels.
58 pub clip: Option<[i32; 4]>,
59 /// Degrees about the rectangle's centre, clockwise on screen (`ImageElement::SetRotation`).
60 pub rotation: f32,
61}
62
63/// The text of a text element or a label, with its font and the result of its layout.
64#[derive(Debug, Clone)]
65pub struct TextData {
66 /// The font, or `None` when it could not be loaded.
67 pub font: Option<FontId>,
68 /// The text.
69 pub text: String,
70 /// The width at which the text wraps, in pixels (0: no limit).
71 pub max_width: i32,
72 /// The justification of the lines (left, centre or right).
73 pub justify: u32,
74 /// Whether long lines wrap.
75 pub wrap: bool,
76 /// Whether the text has a shadow.
77 pub shadow: bool,
78 /// The width below which the text ends with an ellipsis (0: never).
79 pub ellipsis_width: i32,
80 /// The laid-out lines and glyphs.
81 pub layout: TextLayout,
82}
83
84/// The rows of a list.
85#[derive(Debug, Clone)]
86pub struct ListData {
87 /// The text of each row.
88 pub items: Vec<String>,
89 /// Each item's sprite (`ComboElement::AddItem`'s `Sprite`), drawn before its text.
90 pub icons: Vec<Option<SpriteRef>>,
91 /// The selected row.
92 pub selected: Option<usize>,
93 /// The row under the pointer.
94 pub hovered: Option<usize>,
95 /// First visible pixel row.
96 pub scroll: i32,
97 /// The font of the rows.
98 pub font: Option<FontId>,
99 /// The height of a row, in pixels.
100 pub row_height: i32,
101 /// The sprite drawn behind the selected row.
102 pub highlight: Option<SpriteRef>,
103 rows: Vec<TextLayout>,
104 /// Scroll the selection into view at the next layout.
105 reveal: bool,
106}
107
108/// What an element is, with the state of its class.
109#[derive(Debug, Clone)]
110pub enum Kind {
111 /// A group: its children are laid out on top of each other, each by its alignment.
112 Group,
113 /// A ribbon: its children in a row or a column.
114 Ribbon {
115 /// A column when true, a row when false.
116 vertical: bool,
117 /// Whether every cell has the size of the largest child.
118 uniform: bool,
119 /// The space between the cells, in pixels.
120 spacing: i32,
121 /// The size of each cell after the last layout.
122 cells: Vec<i32>,
123 },
124 /// A frame drawn from nine sprites: four corners, four edges and the centre.
125 Border {
126 /// The nine sprites, row by row.
127 sprites: [Option<SpriteRef>; 9],
128 },
129 /// One sprite.
130 Image {
131 /// The sprite.
132 sprite: Option<SpriteRef>,
133 /// Its size in pixels.
134 size: [i32; 2],
135 },
136 /// A sequence of sprites played as an animation (for example the loading screen).
137 AnimatedImage {
138 /// The frames.
139 sprites: Vec<Option<SpriteRef>>,
140 /// The size in pixels.
141 size: [i32; 2],
142 /// The frame rate.
143 frames_per_second: i32,
144 /// The frame shown now.
145 frame: usize,
146 // +0xf8
147 /// Its own clock, from its creation, kept within one loop of the frames.
148 clock: f64,
149 },
150 /// A button: one child element for each state, and its content.
151 Button {
152 /// The element shown in each state: normal, hot (under the pointer), pressed, disabled.
153 states: [Option<NodeId>; 4],
154 /// The content (a label or an image).
155 content: Option<NodeId>,
156 /// The content's colour in each state.
157 colors: [u32; 4],
158 /// The current state, 0 to 3.
159 state: usize,
160 },
161 /// Text.
162 Text(TextData),
163 /// Text with an optional icon.
164 Label {
165 /// The text.
166 text: TextData,
167 /// The icon.
168 icon: Option<SpriteRef>,
169 /// The icon's size in pixels.
170 icon_size: [i32; 2],
171 /// The alignment of the text.
172 text_alignment: u32,
173 /// Whether the icon is beside the text or above it.
174 orientation: u32,
175 /// The smallest width of the text, in pixels.
176 min_text_width: i32,
177 /// The space between the icon and the text, in pixels.
178 gap: i32,
179 // +0x128
180 /// The icon's colour (`LabelElement::SetSpriteColor`), drawn as it is.
181 icon_color: u32,
182 },
183 /// Reserves space the application draws into (3D view, previews).
184 Viewport,
185 /// Scrolling list of text rows (tool widget, styled with game assets).
186 List(ListData),
187 /// Line segments in element pixels, drawn with the sprite in the element colour and clipped
188 /// to the element (the game's `FrustumOutlineElement`, `SpriteContext::AddLine`).
189 Lines {
190 /// The sprite the lines are drawn with.
191 sprite: Option<SpriteRef>,
192 /// The element's size in pixels.
193 size: [i32; 2],
194 /// The lines: x0, y0, x1, y1 in element pixels.
195 lines: Vec<[i32; 4]>,
196 },
197 /// Application texture drawn letterboxed inside the element.
198 Picture {
199 /// The application's texture number ([`Texture::External`]).
200 texture: u32,
201 /// The texture's size in pixels.
202 size: [i32; 2],
203 },
204 /// A control with state: a check box, a slider, an edit box, a combo box and others.
205 Control(Control),
206 /// An element class that is not implemented, by name. It is laid out but not drawn.
207 Unsupported(&'static str),
208}
209
210/// One element of the interface tree.
211#[derive(Debug, Clone)]
212pub struct Node {
213 /// The element's name in its resource, for example `buttonNew`. The game finds elements by
214 /// it.
215 pub name: String,
216 /// The reference of the resource the element was made from.
217 pub reference: String,
218 /// The element's border, drawn behind it.
219 pub border: Option<NodeId>,
220 /// The key that clicks the element.
221 pub shortcut: Option<u32>,
222 /// The margins: left, right, top, bottom.
223 pub margins: [i32; 4],
224 /// The smallest size, in pixels.
225 pub min_size: [i32; 2],
226 /// How the element sits in the space it is given ([`flags`] has the bits).
227 pub alignment: u32,
228 /// The element's flags ([`flags`]).
229 pub flags: u32,
230 /// The colour that multiplies the element, `0xAARRGGBB`.
231 pub color: u32,
232 /// What the element is.
233 pub kind: Kind,
234 /// The children, in drawing order.
235 pub children: Vec<NodeId>,
236 /// The tooltip text.
237 pub tooltip: String,
238 /// The dialog shown as the tooltip, if the tooltip is a dialog.
239 pub tooltip_dialog: Option<String>,
240 // +0x48
241 /// Element: the string table the element's own strings come from.
242 pub string_table: Option<String>,
243 // Element +0xc0
244 /// Name of the element's key binding (`Element` resolved through `keyNames`), for
245 /// `HotKeyToolTipUI`.
246 pub hotkey: Option<String>,
247 /// Title and description user data, for `TitleDescToolTipUI`.
248 pub title_desc: Option<(String, String)>,
249 /// Whether the element takes input.
250 pub enabled: bool,
251 // +0x3c
252 /// Element: the mouse cursor index over it (`Element::GetMouseCursorIndex`).
253 pub cursor: i32,
254 /// An image's rotation in degrees (`ImageElement::SetRotation`).
255 pub rotation: f32,
256 /// Content size from the last size pass.
257 pub size: [i32; 2],
258 /// Layout rectangle: left, top, right, bottom.
259 pub rect: [i32; 4],
260 /// The layout pass whose `size` this is (`Ui::size_pass`): a node is measured once per
261 /// pass, however many of its ancestors ask for its size.
262 sized_pass: u32,
263}
264
265impl Node {
266 /// An element of this kind, with the default values.
267 pub fn new(kind: Kind) -> Node {
268 Node {
269 name: String::new(),
270 reference: String::new(),
271 border: None,
272 shortcut: None,
273 margins: [0; 4],
274 min_size: [0; 2],
275 alignment: 0,
276 flags: 0,
277 color: 0xFFFF_FFFF,
278 kind,
279 children: Vec::new(),
280 tooltip: String::new(),
281 tooltip_dialog: None,
282 string_table: None,
283 hotkey: None,
284 title_desc: None,
285 enabled: true,
286 cursor: 0,
287 rotation: 0.0,
288 size: [0; 2],
289 rect: [0; 4],
290 sized_pass: 0,
291 }
292 }
293
294 /// Whether the element is shown.
295 pub fn visible(&self) -> bool {
296 self.flags & flags::HIDDEN == 0
297 }
298
299 // +0x34 & 0x1800000
300 /// Enabled and not locked by the tutorial (`& 0x1800000` clear).
301 pub fn usable(&self) -> bool {
302 self.enabled && self.flags & flags::LOCKED == 0
303 }
304
305 /// Whether a point is inside the element's rectangle.
306 pub fn contains(&self, x: i32, y: i32) -> bool {
307 x >= self.rect[0] && x < self.rect[2] && y >= self.rect[1] && y < self.rect[3]
308 }
309}
310
311/// Something the player did to an element.
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum Event {
314 /// A button was clicked.
315 Clicked(NodeId),
316 /// A row of a list or a combo box was selected.
317 Selected(NodeId, usize),
318 /// A check box, a slider or an edit box changed.
319 Changed(NodeId),
320 /// Enter was pressed in an edit box.
321 Submitted(NodeId),
322 /// A link in a document was clicked (its index).
323 Link(NodeId, usize),
324}
325
326/// The interface: the element tree, the resources it uses, and the state of the pointer and the
327/// keyboard.
328pub struct Ui {
329 /// The sprite sheets, fonts, element descriptions and strings, loaded once.
330 pub res: Resources,
331 /// Every element. A [`NodeId`] is an index into this list.
332 pub nodes: Vec<Node>,
333 /// The screen's size in pixels.
334 pub screen: [i32; 2],
335 pointer: (i32, i32),
336 pointer_down: bool,
337 hot: Option<NodeId>,
338 pressed: Option<NodeId>,
339 focus: Option<NodeId>,
340 input_root: Option<NodeId>,
341 /// The current layout pass, for [`Node::sized_pass`].
342 size_pass: u32,
343 tooltip_instance: Option<(NodeId, NodeId)>,
344 /// `UserInterface::Play` requests (sound slots: 4 button down, 5 button up/click, 3 drop),
345 /// drained by the application.
346 pub sounds: Vec<usize>,
347 /// A tooltip dialog opened since the application last asked (`new_tooltip`), with its
348 /// controller ids, for application-defined tooltip controllers.
349 tooltip_opened: Option<(NodeId, NodeId, Vec<u32>)>,
350 pending: Vec<Event>,
351 stack: Vec<String>,
352 overrides: Vec<std::collections::HashMap<String, Option<String>>>,
353}
354
355impl Ui {
356 /// An empty interface that reads its resources from `archive`.
357 pub fn new(archive: Rc<Archive>) -> Ui {
358 Ui {
359 res: Resources::new(archive),
360 nodes: Vec::new(),
361 screen: [1280, 720],
362 pointer: (-1, -1),
363 pointer_down: false,
364 hot: None,
365 pressed: None,
366 focus: None,
367 input_root: None,
368 size_pass: 1,
369 tooltip_instance: None,
370 tooltip_opened: None,
371 sounds: Vec::new(),
372 pending: Vec::new(),
373 stack: Vec::new(),
374 overrides: Vec::new(),
375 }
376 }
377
378 /// Add an element, and return its id.
379 pub fn add(&mut self, mut node: Node) -> NodeId {
380 node.sized_pass = 0;
381 self.nodes.push(node);
382 self.nodes.len() - 1
383 }
384
385 /// Make `child` the last child of `parent`.
386 pub fn add_child(&mut self, parent: NodeId, child: NodeId) {
387 self.nodes[parent].children.push(child);
388 }
389
390 /// An element.
391 pub fn node(&self, id: NodeId) -> &Node {
392 &self.nodes[id]
393 }
394
395 /// An element, to change it.
396 pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
397 &mut self.nodes[id]
398 }
399
400 // ------------------------------------------------------------ instantiation
401
402 // ------------------------------------------------------------ tool widgets
403
404 // ------------------------------------------------------------ layout
405
406 // ------------------------------------------------------------ drawing
407}