banished_ui/controls.rs
1//! The controls with state: sliders, scroll bars, progress bars, edit boxes, graphs, tables, tabs,
2//! containers, check boxes, combo boxes, spinners, pages, radio groups and documents.
3//!
4//! The order of the sprites and the layout follow the engine's own element classes. The
5//! application supplies list rows, graph samples and combo choices through the setters on
6//! [`Ui`](crate::Ui).
7mod measure;
8mod paint;
9mod setup;
10mod values;
11pub(crate) use paint::scroll_thumb;
12
13use crate::*;
14use banished_formats::ui::{self, ElementKind};
15
16#[derive(Debug, Clone)]
17/// The state of a slider.
18pub struct SliderData {
19 /// The sprites: the track's ends and middle, the knob, and the knob under the pointer.
20 pub sprites: Vec<Option<SpriteRef>>,
21 /// The smallest and the largest value.
22 pub range: [f32; 2],
23 /// The value.
24 pub value: f32,
25 // +0x120; +0xb0
26 /// Steps across the range (SliderDescription): a click on the track moves the value one of
27 /// them.
28 pub steps: i32,
29 /// The space between the track's ends and the knob's travel, in pixels.
30 pub inset: i32,
31 /// The track's vertical position in the element, in pixels.
32 pub track_y: i32,
33 /// The number shown next to the slider, if it has one.
34 pub number: Option<NodeId>,
35 // +0x124
36 /// The pointer is over the knob (it draws the fifth sprite).
37 pub over_knob: bool,
38 // +0x12c
39 /// A drag from the knob (flag `0x200000`): the value and pointer x when it began.
40 pub drag: Option<(f32, i32)>,
41}
42
43impl SliderData {
44 /// One step of the range (a track click), a hundredth when the slider has none.
45 pub fn step(&self) -> f32 {
46 let range = self.range[1] - self.range[0];
47 if 0 < self.steps {
48 range / self.steps as f32
49 } else {
50 range.abs() / 100.0
51 }
52 }
53
54 /// The knob's width and the length it travels along `rect` (`SliderElement::Update` and
55 /// `Draw`: the width less twice the inset and twice half the knob's width).
56 fn travel(&self, rect: [i32; 4]) -> (i32, i32) {
57 let width = self.sprites.get(3).and_then(|s| *s).map_or(12, |s| s.width);
58 (width, rect[2] - (width / 2 + self.inset) * 2 - rect[0])
59 }
60
61 /// The knob's rectangle in `rect`: its sprite's size, from the inset at the minimum.
62 pub fn knob(&self, rect: [i32; 4]) -> [i32; 4] {
63 let (width, travel) = self.travel(rect);
64 let height = self
65 .sprites
66 .get(3)
67 .and_then(|s| *s)
68 .map_or(rect[3] - rect[1], |s| s.height);
69 let range = self.range[1] - self.range[0];
70 let offset = if range != 0.0 {
71 (travel as f32 * ((self.value - self.range[0]) / range)) as i32
72 } else {
73 0
74 };
75 let x = rect[0] + self.inset + offset;
76 [x, rect[1], x + width, rect[1] + height]
77 }
78
79 /// The value a drag from the knob gives with the pointer at `x`.
80 pub fn dragged(&self, rect: [i32; 4], x: i32) -> Option<f32> {
81 let (start, from) = self.drag?;
82 let (_, travel) = self.travel(rect);
83 Some(start - ((from - x) as f32 / travel as f32) * (self.range[1] - self.range[0]))
84 }
85}
86
87#[derive(Debug, Clone)]
88/// The state of an edit box.
89pub struct EditData {
90 /// The text, and its layout.
91 pub text: TextData,
92 /// Cursor in Unicode scalar values, never byte offsets.
93 pub cursor: usize,
94 /// The most characters it takes.
95 pub max_length: usize,
96 /// Whether it takes a number.
97 pub numeric: bool,
98 /// The smallest and the largest number, for a number box.
99 pub range: Option<[f32; 2]>,
100 // +0xd4; +0x7c
101 /// Drawn as `*`s (EditDescription).
102 pub password: bool,
103 // +0xd8
104 /// The characters it takes, any when empty (a NumberEdit's digits, and `-` when it may go below
105 /// zero).
106 pub allowed: String,
107 // +0xe8; +0xf0
108 /// The caret's blink (flipping every half second).
109 pub caret: bool,
110 /// The time since the caret last changed, in seconds.
111 pub blink: f32,
112}
113
114#[derive(Debug, Clone)]
115/// A child of a layout element, with its anchors.
116pub struct LayoutChild {
117 /// The child.
118 pub node: NodeId,
119 /// How it is placed: its anchors and offsets.
120 pub spec: ui::LayoutItem,
121}
122
123#[derive(Debug, Clone)]
124/// A control and its state.
125pub enum Control {
126 /// A slider.
127 Slider(SliderData),
128 /// A scroll bar.
129 Scroll {
130 /// The sprites of the track and the thumb.
131 sprites: Vec<Option<SpriteRef>>,
132 /// A vertical bar when true.
133 vertical: bool,
134 /// The position, from 0 to 1.
135 value: f32,
136 /// The share of the content that is visible, from 0 to 1.
137 page: f32,
138 // +0x15c; +0xbc
139 /// The bar's inset at both ends of the track (ScrollDescription).
140 inset: i32,
141 // +0x154
142 /// The thumb's colour.
143 thumb_color: u32,
144 // +0x16c
145 /// A drag from the thumb (flag `0x200000`): the value and the pointer's coordinate along
146 /// the bar when it began.
147 drag: Option<(f32, i32)>,
148 },
149 /// A progress bar.
150 Progress {
151 /// The sprites of the fill and the background.
152 sprites: Vec<Option<SpriteRef>>,
153 /// The background's colour, `0xAARRGGBB`.
154 background_color: u32,
155 /// The fill, from 0 to 1.
156 value: f32,
157 /// The element drawn on top (usually a label).
158 content: Option<NodeId>,
159 },
160 /// An edit box.
161 Edit(EditData),
162 /// `GraphElement`: a frame, `y_divisions + 1` lines across (with ticks of `tick` px to
163 /// the left) and `x_divisions + 1` down (ticks below), then each graph's points from the
164 /// left edge to the right, `y_range` at the top.
165 Graph {
166 /// The size in pixels.
167 size: [i32; 2],
168 /// The sprite the lines are drawn with.
169 sprite: Option<SpriteRef>,
170 /// The length of the ticks, in pixels.
171 tick: i32,
172 /// The value at the top of the graph.
173 y_range: f32,
174 /// The number of divisions across.
175 x_divisions: i32,
176 /// The number of divisions down.
177 y_divisions: i32,
178 /// `AddGraph(color)` / `AddGraphPoint`.
179 series: Vec<(u32, Vec<i32>)>,
180 },
181 /// A table of cells in rows and columns.
182 Table {
183 /// Each cell: its row, its column and its element.
184 cells: Vec<(usize, usize, NodeId)>,
185 /// The width of each column after the last layout.
186 columns: Vec<i32>,
187 /// The height of each row after the last layout.
188 rows: Vec<i32>,
189 /// The space between the cells, in pixels.
190 spacing: i32,
191 },
192 /// Children placed by anchors relative to the element.
193 Layout(Vec<LayoutChild>),
194 /// Tabs: a header for each tab, and the page of the selected one.
195 Tabs {
196 /// The elements drawn around the tabs.
197 decoration: [Option<NodeId>; 3],
198 /// Each tab: its header and its page.
199 tabs: Vec<(NodeId, NodeId)>,
200 /// The selected tab.
201 selected: usize,
202 /// The space between the headers, in pixels.
203 gap: i32,
204 /// The headers' height, in pixels.
205 header_height: i32,
206 },
207 /// A window onto its content, cut at its edges.
208 Clip {
209 /// The content.
210 content: Option<NodeId>,
211 /// The window's size, in pixels.
212 size: [i32; 2],
213 /// The content's offset, in pixels.
214 offset: [i32; 2],
215 },
216 /// A scrolling area: its content and its scroll bars.
217 Container {
218 /// The content.
219 content: Option<NodeId>,
220 /// The horizontal and the vertical scroll bar.
221 bars: [Option<NodeId>; 2],
222 /// The size, in pixels.
223 size: [i32; 2],
224 /// The space between the content and the bars, in pixels.
225 spacing: i32,
226 /// How far the content is scrolled, in pixels.
227 offset: [i32; 2],
228 /// The visible part of the content: x0, y0, x1, y1.
229 view: [i32; 4],
230 // +0xd0; ScrollElement +0x160
231 /// `ContainerElement::Update`: the wheel's frames still to scroll (±7 a notch) and the
232 /// vertical bar's value (`ScrollElement`, in pixels).
233 wheel: i32,
234 /// The vertical bar's position, in pixels.
235 fine: f32,
236 },
237 /// A check box.
238 Check {
239 /// The box.
240 button: NodeId,
241 /// The tick, shown when checked.
242 mark: Option<NodeId>,
243 /// Whether it is checked.
244 checked: bool,
245 },
246 /// A combo box: a button that opens a list.
247 Combo {
248 /// The button that shows the choice.
249 button: NodeId,
250 /// The list of choices.
251 list: NodeId,
252 /// Whether the list is open.
253 open: bool,
254 },
255 /// A number with up and down arrows.
256 Spinner {
257 /// The number and the two arrows.
258 parts: [Option<NodeId>; 3],
259 /// The number.
260 value: i32,
261 /// How much an arrow changes it.
262 step: i32,
263 },
264 /// Pages, of which one is shown.
265 Page {
266 /// The pages.
267 children: Vec<NodeId>,
268 /// The page shown.
269 selected: usize,
270 },
271 /// Radio buttons, of which one is selected.
272 Radio {
273 /// The buttons.
274 children: Vec<NodeId>,
275 /// The selected button.
276 selected: usize,
277 },
278 /// A document with markup: the help pages and the tutorials.
279 Document(document::Document),
280}
281
282impl Control {
283 pub(crate) fn children(&self) -> Vec<NodeId> {
284 match self {
285 Self::Slider(s) => s.number.into_iter().collect(),
286 Self::Progress { content, .. } | Self::Clip { content, .. } => {
287 content.iter().copied().collect()
288 }
289 Self::Container { content, bars, .. } => content
290 .iter()
291 .chain(bars.iter().flatten())
292 .copied()
293 .collect(),
294 Self::Table { cells, .. } => cells.iter().map(|c| c.2).collect(),
295 Self::Layout(items) => items.iter().map(|i| i.node).collect(),
296 Self::Tabs {
297 decoration, tabs, ..
298 } => decoration
299 .iter()
300 .flatten()
301 .copied()
302 .chain(tabs.iter().flat_map(|&(a, b)| [a, b]))
303 .collect(),
304 Self::Check { button, mark, .. } => std::iter::once(*button).chain(*mark).collect(),
305 Self::Combo { button, list, .. } => vec![*button, *list],
306 Self::Spinner { parts, .. } => parts.iter().flatten().copied().collect(),
307 Self::Page { children, .. } | Self::Radio { children, .. } => children.clone(),
308 _ => Vec::new(),
309 }
310 }
311 pub(crate) fn active_children(&self) -> Vec<NodeId> {
312 match self {
313 Self::Page { children, selected } => {
314 children.get(*selected).copied().into_iter().collect()
315 }
316 Self::Tabs {
317 decoration,
318 tabs,
319 selected,
320 ..
321 } => decoration
322 .iter()
323 .flatten()
324 .copied()
325 .chain(tabs.iter().map(|t| t.0))
326 .chain(tabs.get(*selected).map(|t| t.1))
327 .collect(),
328 Self::Combo { button, list, open } => std::iter::once(*button)
329 .chain(open.then_some(*list))
330 .collect(),
331 Self::Check {
332 button,
333 mark,
334 checked,
335 } => std::iter::once(*button)
336 .chain(if *checked { *mark } else { None })
337 .collect(),
338 _ => self.children(),
339 }
340 }
341}
342
343impl Ui {}
344
345pub(crate) fn intersect(a: Option<[i32; 4]>, b: [i32; 4]) -> Option<[i32; 4]> {
346 Some(if let Some(a) = a {
347 [
348 a[0].max(b[0]),
349 a[1].max(b[1]),
350 a[2].min(b[2]),
351 a[3].min(b[3]),
352 ]
353 } else {
354 b
355 })
356}
357pub(crate) fn format_number(v: f32) -> String {
358 if v.fract().abs() < 0.00001 {
359 format!("{v:.0}")
360 } else {
361 format!("{v:.2}")
362 }
363}
364pub(crate) fn fraction(v: f32, range: [f32; 2]) -> f32 {
365 if range[0] == range[1] {
366 0.0
367 } else {
368 ((v - range[0]) / (range[1] - range[0])).clamp(0.0, 1.0)
369 }
370}