Skip to main content

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}