Skip to main content

banished_ui/
layout.rs

1//! Layout: each element's size (`ComputeSize`), then its rectangle (`ComputeLayout`).
2
3use super::*;
4
5impl Ui {
6    pub(super) fn padded_size(&mut self, id: NodeId) -> [i32; 2] {
7        self.compute_size(id);
8        let n = &self.nodes[id];
9        let mut w = (n.size[0] + n.margins[0] + n.margins[1]).max(n.min_size[0]);
10        let mut h = (n.size[1] + n.margins[2] + n.margins[3]).max(n.min_size[1]);
11        if n.flags & flags::SCREEN_WIDTH != 0 {
12            w = self.screen[0];
13        }
14        if n.flags & flags::SCREEN_HEIGHT != 0 {
15            h = self.screen[1];
16        }
17        [w, h]
18    }
19
20    /// Start a layout pass: every node is measured again, once.
21    pub(super) fn begin_size_pass(&mut self) {
22        self.size_pass = self.size_pass.wrapping_add(1).max(1);
23    }
24
25    /// `ComputeSize` for each element class. Within one layout pass a node's size is computed
26    /// once: a size depends only on the node and its children, which a pass does not change,
27    /// and nested layouts ask for their children's sizes both to measure and to arrange.
28    pub(super) fn compute_size(&mut self, id: NodeId) {
29        if self.nodes[id].sized_pass == self.size_pass {
30            return;
31        }
32        let children = self.nodes[id].children.clone();
33        let size = match self.nodes[id].kind.clone() {
34            Kind::Group => {
35                let mut s = [0, 0];
36                for c in children {
37                    if !self.nodes[c].visible() {
38                        continue;
39                    }
40                    let p = self.padded_size(c);
41                    s = [s[0].max(p[0]), s[1].max(p[1])];
42                }
43                s
44            }
45            Kind::Ribbon {
46                vertical,
47                uniform,
48                spacing,
49                ..
50            } => {
51                let (mut max, mut sum, mut cells, mut non_empty) = ([0, 0], [0, 0], Vec::new(), 0);
52                for &c in &children {
53                    if !self.nodes[c].visible() {
54                        cells.push(0);
55                        continue;
56                    }
57                    let p = self.padded_size(c);
58                    max = [max[0].max(p[0]), max[1].max(p[1])];
59                    sum = [sum[0] + p[0], sum[1] + p[1]];
60                    cells.push(if vertical { p[1] } else { p[0] });
61                    if p[0] > 0 && p[1] > 0 {
62                        non_empty += 1;
63                    }
64                }
65                if uniform {
66                    let cell = if vertical { max[1] } else { max[0] };
67                    cells.iter_mut().for_each(|c| *c = cell);
68                    sum = [
69                        children.len() as i32 * max[0],
70                        children.len() as i32 * max[1],
71                    ];
72                }
73                let gaps = spacing * (non_empty - 1).max(0);
74                if let Kind::Ribbon { cells: stored, .. } = &mut self.nodes[id].kind {
75                    *stored = cells;
76                }
77                if vertical {
78                    [max[0], sum[1] + gaps]
79                } else {
80                    [sum[0] + gaps, max[1]]
81                }
82            }
83            Kind::Border { sprites } => {
84                let w = |i: usize| sprites[i].map_or(0, |s| s.width);
85                let h = |i: usize| sprites[i].map_or(0, |s| s.height);
86                [
87                    (w(0) + w(2)).max(w(6) + w(8)),
88                    (h(0) + h(6)).max(h(2) + h(8)),
89                ]
90            }
91            Kind::Lines { size, .. } => size,
92            Kind::Image { sprite, size } => [
93                if size[0] > 0 {
94                    size[0]
95                } else {
96                    sprite.map_or(0, |s| s.width)
97                },
98                if size[1] > 0 {
99                    size[1]
100                } else {
101                    sprite.map_or(0, |s| s.height)
102                },
103            ],
104            Kind::AnimatedImage {
105                sprites,
106                size,
107                frame,
108                ..
109            } => {
110                let sprite = sprites.get(frame).copied().flatten();
111                [
112                    if size[0] > 0 {
113                        size[0]
114                    } else {
115                        sprite.map_or(0, |s| s.width)
116                    },
117                    if size[1] > 0 {
118                        size[1]
119                    } else {
120                        sprite.map_or(0, |s| s.height)
121                    },
122                ]
123            }
124            Kind::Button {
125                states,
126                content,
127                state,
128                ..
129            } => {
130                let mut s = content.map_or([0, 0], |c| self.padded_size(c));
131                if let Some(st) = states[state].or(states[0]) {
132                    let p = self.padded_size(st);
133                    s = [s[0].max(p[0]), s[1].max(p[1])];
134                }
135                let n = &self.nodes[id];
136                [s[0].max(n.min_size[0]), s[1].max(n.min_size[1])]
137            }
138            Kind::Text(mut t) => {
139                self.layout_text(&mut t);
140                let s = [t.layout.width, t.layout.height];
141                self.nodes[id].kind = Kind::Text(t);
142                s
143            }
144            Kind::Label {
145                mut text,
146                icon,
147                icon_size,
148                orientation,
149                min_text_width,
150                gap,
151                ..
152            } => {
153                self.layout_text(&mut text);
154                let (tw, th) = (
155                    text.layout.width.max(if text.text.is_empty() {
156                        0
157                    } else {
158                        min_text_width
159                    }),
160                    text.layout.height,
161                );
162                let iw = if icon_size[0] > 0 {
163                    icon_size[0]
164                } else {
165                    icon.map_or(0, |s| s.width)
166                };
167                let ih = if icon_size[1] > 0 {
168                    icon_size[1]
169                } else {
170                    icon.map_or(0, |s| s.height)
171                };
172                if let Kind::Label { text: t, .. } = &mut self.nodes[id].kind {
173                    *t = text;
174                }
175                if orientation > 1 {
176                    [iw.max(tw), gap + ih + th]
177                } else {
178                    [gap + iw + tw, ih.max(th)]
179                }
180            }
181            Kind::Picture { .. } | Kind::Viewport | Kind::List(_) => {
182                for c in children {
183                    self.compute_size(c);
184                }
185                self.nodes[id].min_size
186            }
187            Kind::Unsupported(_) => [0, 0],
188            Kind::Control(c) => self.measure_control(id, c),
189        };
190        self.nodes[id].size = [
191            size[0].max(self.nodes[id].min_size[0]),
192            size[1].max(self.nodes[id].min_size[1]),
193        ];
194        if let Some(b) = self.nodes[id].border {
195            self.compute_size(b);
196        }
197        self.nodes[id].sized_pass = self.size_pass;
198    }
199
200    pub(super) fn layout_text(&self, t: &mut TextData) {
201        let mut display = t.text.clone();
202        if !t.wrap && t.ellipsis_width > 0 {
203            if let Some(f) = t.font {
204                if TextLayout::compute(&self.res, f, &display, None, 0).width > t.ellipsis_width {
205                    while !display.is_empty()
206                        && TextLayout::compute(&self.res, f, &format!("{display}..."), None, 0)
207                            .width
208                            > t.ellipsis_width
209                    {
210                        display.pop();
211                    }
212                    display.push_str("...");
213                }
214            }
215        }
216        t.layout = match t.font {
217            Some(f) => TextLayout::compute(
218                &self.res,
219                f,
220                &display,
221                t.wrap.then_some(t.max_width),
222                t.justify,
223            ),
224            None => TextLayout::default(),
225        };
226    }
227
228    /// Generic placement shared by most `ComputeLayout` implementations.
229    pub(super) fn place(&mut self, id: NodeId, l: i32, t: i32, r: i32, b: i32) {
230        self.place_filling(id, l, t, r, b, 0);
231    }
232
233    /// [`Ui::place`] with `fill` (`FILL_WIDTH`, `FILL_HEIGHT`) added to the node's own flags.
234    pub(super) fn place_filling(
235        &mut self,
236        id: NodeId,
237        mut l: i32,
238        mut t: i32,
239        r: i32,
240        b: i32,
241        fill: u32,
242    ) {
243        let n = &mut self.nodes[id];
244        let fill = n.flags | fill;
245        let (mut w, mut h) = (n.size[0], n.size[1]);
246        if r < w + l {
247            l = r - w;
248        }
249        if b < h + t {
250            t = b - h;
251        }
252        let [ml, mr, mt, mb] = n.margins;
253        let (mut x, mut y) = (l + ml, t + mt);
254        let (aw, ah) = ((r - mr) - x, (b - mb) - y);
255        if fill & flags::FILL_WIDTH != 0 {
256            w = aw;
257        }
258        if fill & flags::FILL_HEIGHT != 0 {
259            h = ah;
260        }
261        match n.alignment % 3 {
262            1 => x += (aw >> 1) - (w >> 1),
263            2 => x = (r - mr) - w,
264            _ => {}
265        }
266        match n.alignment / 3 {
267            1 => y += (ah >> 1) - (h >> 1),
268            2 => y = (b - mb) - h,
269            _ => {}
270        }
271        n.rect = [x, y, x + w, y + h];
272    }
273
274    /// Size and lay out `root` inside the given rectangle.
275    pub fn layout(&mut self, root: NodeId, rect: [i32; 4]) {
276        self.begin_size_pass();
277        self.compute_size(root);
278        self.compute_layout(root, rect[0], rect[1], rect[2], rect[3]);
279    }
280
281    /// Lay out a dialog's root element: placed at its own size by its alignment within `rect`
282    /// (`DialogInstance::ComputeLayout`), unlike ordinary groups which fill.
283    ///
284    /// Original: Runtime `0x180116620`.
285    pub fn layout_dialog(&mut self, root: NodeId, rect: [i32; 4]) {
286        self.begin_size_pass();
287        self.compute_size(root);
288        if !matches!(self.nodes[root].kind, Kind::Group) {
289            self.compute_layout(root, rect[0], rect[1], rect[2], rect[3]);
290            return;
291        }
292        self.place(root, rect[0], rect[1], rect[2], rect[3]);
293        let rc = self.nodes[root].rect;
294        if let Some(border) = self.nodes[root].border {
295            self.compute_layout(border, rc[0], rc[1], rc[2], rc[3]);
296        }
297        for c in self.nodes[root].children.clone() {
298            if self.nodes[c].visible() {
299                self.compute_layout(c, rc[0], rc[1], rc[2], rc[3]);
300            }
301        }
302    }
303
304    /// `ComputeLayout` for each element class.
305    pub(super) fn compute_layout(&mut self, id: NodeId, l: i32, t: i32, r: i32, b: i32) {
306        let slot = [l, t, r, b];
307        if let Kind::Control(c) = &self.nodes[id].kind {
308            // `ScrollElement::ComputeLayout` (Runtime 0x18011d7b0): a bar always fills its own
309            // axis (a vertical bar the height, a horizontal one the width); the flags only
310            // decide the other axis.
311            let fill = match c {
312                Control::Scroll { vertical: true, .. } => flags::FILL_HEIGHT,
313                Control::Scroll {
314                    vertical: false, ..
315                } => flags::FILL_WIDTH,
316                _ => 0,
317            };
318            self.place_filling(id, l, t, r, b, fill);
319            self.layout_control(id);
320        } else {
321            match self.nodes[id].kind.clone() {
322                Kind::Group
323                | Kind::Viewport
324                | Kind::List(_)
325                | Kind::Picture { .. }
326                | Kind::Image { .. }
327                | Kind::Lines { .. }
328                | Kind::AnimatedImage { .. } => {
329                    // GroupElement fills the space offered, less its margins.
330                    let n = &mut self.nodes[id];
331                    let [ml, mr, mt, mb] = n.margins;
332                    if matches!(n.kind, Kind::Group)
333                        || n.flags & (flags::FILL_WIDTH | flags::FILL_HEIGHT) != 0
334                    {
335                        n.rect = [l + ml, t + mt, r - mr, b - mb];
336                        if !matches!(n.kind, Kind::Group) {
337                            let full = n.rect;
338                            self.place(id, l, t, r, b);
339                            let n = &mut self.nodes[id];
340                            if n.flags & flags::FILL_WIDTH != 0 {
341                                (n.rect[0], n.rect[2]) = (full[0], full[2]);
342                            }
343                            if n.flags & flags::FILL_HEIGHT != 0 {
344                                (n.rect[1], n.rect[3]) = (full[1], full[3]);
345                            }
346                        }
347                    } else {
348                        self.place(id, l, t, r, b);
349                    }
350                    self.reveal_selection(id);
351                    let rc = self.nodes[id].rect;
352                    for c in self.nodes[id].children.clone() {
353                        if self.nodes[c].visible() {
354                            self.compute_layout(c, rc[0], rc[1], rc[2], rc[3]);
355                        }
356                    }
357                }
358                Kind::Border { .. } => {
359                    let n = &mut self.nodes[id];
360                    let [ml, mr, mt, mb] = n.margins;
361                    n.rect = [l + ml, t + mt, r - mr, b - mb];
362                }
363                Kind::Ribbon {
364                    vertical,
365                    spacing,
366                    cells,
367                    ..
368                } => {
369                    self.place(id, l, t, r, b);
370                    let rc = self.nodes[id].rect;
371                    let fill_last = self.nodes[id].flags
372                        & if vertical {
373                            flags::FILL_HEIGHT
374                        } else {
375                            flags::FILL_WIDTH
376                        }
377                        != 0;
378                    let children = self.nodes[id].children.clone();
379                    let mut offset = 0;
380                    for (i, c) in children.iter().copied().enumerate() {
381                        if !self.nodes[c].visible() {
382                            continue;
383                        }
384                        let cell = cells.get(i).copied().unwrap_or(0);
385                        let last = i + 1 == children.len();
386                        let p = self.padded_size(c);
387                        if vertical {
388                            let bottom = if fill_last && last {
389                                rc[3]
390                            } else {
391                                rc[1] + offset + cell
392                            };
393                            self.compute_layout(c, rc[0], rc[1] + offset, rc[2], bottom);
394                        } else {
395                            let right = if fill_last && last {
396                                rc[2]
397                            } else {
398                                rc[0] + offset + cell
399                            };
400                            self.compute_layout(c, rc[0] + offset, rc[1], right, rc[3]);
401                        }
402                        offset += cell;
403                        if p[0] > 0 && p[1] > 0 {
404                            offset += spacing;
405                        }
406                    }
407                }
408                Kind::Button {
409                    states, content, ..
410                } => {
411                    self.place(id, l, t, r, b);
412                    let rc = self.nodes[id].rect;
413                    for c in states.iter().flatten().chain(content.iter()) {
414                        self.compute_layout(*c, rc[0], rc[1], rc[2], rc[3]);
415                    }
416                }
417                Kind::Text(_) | Kind::Label { .. } | Kind::Unsupported(_) => {
418                    self.place(id, l, t, r, b)
419                }
420                Kind::Control(_) => unreachable!(),
421            }
422        }
423        // `Element::ComputeLayout` (0x180119160) and its subclasses hand the border the slot
424        // the element was given, before the element's own margins: a padded text sits inside
425        // its box rather than the box inside the text.
426        if let Some(border) = self.nodes[id].border {
427            self.compute_layout(border, slot[0], slot[1], slot[2], slot[3]);
428        }
429    }
430}