Skip to main content

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}