egui/
placer.rs

1use crate::{grid, vec2, Layout, Painter, Pos2, Rect, Region, Vec2};
2
3#[cfg(debug_assertions)]
4use crate::{Align2, Color32, Stroke};
5
6pub(crate) struct Placer {
7    /// If set this will take precedence over [`crate::layout`].
8    grid: Option<grid::GridLayout>,
9    layout: Layout,
10    region: Region,
11}
12
13impl Placer {
14    pub(crate) fn new(max_rect: Rect, layout: Layout) -> Self {
15        let region = layout.region_from_max_rect(max_rect);
16        Self {
17            grid: None,
18            layout,
19            region,
20        }
21    }
22
23    #[inline(always)]
24    pub(crate) fn set_grid(&mut self, grid: grid::GridLayout) {
25        self.grid = Some(grid);
26    }
27
28    pub(crate) fn save_grid(&mut self) {
29        if let Some(grid) = &mut self.grid {
30            grid.save();
31        }
32    }
33
34    #[inline(always)]
35    pub(crate) fn grid(&self) -> Option<&grid::GridLayout> {
36        self.grid.as_ref()
37    }
38
39    #[inline(always)]
40    pub(crate) fn is_grid(&self) -> bool {
41        self.grid.is_some()
42    }
43
44    #[inline(always)]
45    pub(crate) fn layout(&self) -> &Layout {
46        &self.layout
47    }
48
49    #[inline(always)]
50    pub(crate) fn prefer_right_to_left(&self) -> bool {
51        self.layout.prefer_right_to_left()
52    }
53
54    #[inline(always)]
55    pub(crate) fn min_rect(&self) -> Rect {
56        self.region.min_rect
57    }
58
59    #[inline(always)]
60    pub(crate) fn max_rect(&self) -> Rect {
61        self.region.max_rect
62    }
63
64    #[inline(always)]
65    pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
66        self.region.min_rect = min_rect;
67    }
68
69    #[inline(always)]
70    pub(crate) fn cursor(&self) -> Rect {
71        self.region.cursor
72    }
73
74    #[inline(always)]
75    pub(crate) fn set_cursor(&mut self, cursor: Rect) {
76        self.region.cursor = cursor;
77    }
78}
79
80impl Placer {
81    pub(crate) fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
82        if let Some(grid) = &self.grid {
83            grid.align_size_within_rect(size, outer)
84        } else {
85            self.layout.align_size_within_rect(size, outer)
86        }
87    }
88
89    pub(crate) fn available_rect_before_wrap(&self) -> Rect {
90        if let Some(grid) = &self.grid {
91            grid.available_rect(&self.region)
92        } else {
93            self.layout.available_rect_before_wrap(&self.region)
94        }
95    }
96
97    /// Amount of space available for a widget.
98    /// For wrapping layouts, this is the maximum (after wrap).
99    pub(crate) fn available_size(&self) -> Vec2 {
100        if let Some(grid) = &self.grid {
101            grid.available_rect(&self.region).size()
102        } else {
103            self.layout.available_size(&self.region)
104        }
105    }
106
107    /// Returns where to put the next widget that is of the given size.
108    /// The returned `frame_rect` will always be justified along the cross axis.
109    /// This is what you then pass to `advance_after_rects`.
110    /// Use `justify_and_align` to get the inner `widget_rect`.
111    pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect {
112        debug_assert!(
113            0.0 <= child_size.x && 0.0 <= child_size.y,
114            "Negative child size: {child_size:?}"
115        );
116        self.region.sanity_check();
117        if let Some(grid) = &self.grid {
118            grid.next_cell(self.region.cursor, child_size)
119        } else {
120            self.layout
121                .next_frame(&self.region, child_size, item_spacing)
122        }
123    }
124
125    /// Where do we expect a zero-sized widget to be placed?
126    pub(crate) fn next_widget_position(&self) -> Pos2 {
127        if let Some(grid) = &self.grid {
128            grid.next_cell(self.region.cursor, Vec2::ZERO).center()
129        } else {
130            self.layout.next_widget_position(&self.region)
131        }
132    }
133
134    /// Apply justify or alignment after calling `next_space`.
135    pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect {
136        debug_assert!(!rect.any_nan());
137        debug_assert!(!child_size.any_nan());
138
139        if let Some(grid) = &self.grid {
140            grid.justify_and_align(rect, child_size)
141        } else {
142            self.layout.justify_and_align(rect, child_size)
143        }
144    }
145
146    /// Advance the cursor by this many points.
147    /// [`Self::min_rect`] will expand to contain the cursor.
148    pub(crate) fn advance_cursor(&mut self, amount: f32) {
149        debug_assert!(
150            self.grid.is_none(),
151            "You cannot advance the cursor when in a grid layout"
152        );
153        self.layout.advance_cursor(&mut self.region, amount);
154    }
155
156    /// Advance cursor after a widget was added to a specific rectangle
157    /// and expand the region `min_rect`.
158    ///
159    /// * `frame_rect`: the frame inside which a widget was e.g. centered
160    /// * `widget_rect`: the actual rect used by the widget
161    pub(crate) fn advance_after_rects(
162        &mut self,
163        frame_rect: Rect,
164        widget_rect: Rect,
165        item_spacing: Vec2,
166    ) {
167        debug_assert!(!frame_rect.any_nan());
168        debug_assert!(!widget_rect.any_nan());
169        self.region.sanity_check();
170
171        if let Some(grid) = &mut self.grid {
172            grid.advance(&mut self.region.cursor, frame_rect, widget_rect);
173        } else {
174            self.layout.advance_after_rects(
175                &mut self.region.cursor,
176                frame_rect,
177                widget_rect,
178                item_spacing,
179            );
180        }
181
182        self.expand_to_include_rect(frame_rect); // e.g. for centered layouts: pretend we used whole frame
183
184        self.region.sanity_check();
185    }
186
187    /// Move to the next row in a grid layout or wrapping layout.
188    /// Otherwise does nothing.
189    pub(crate) fn end_row(&mut self, item_spacing: Vec2, painter: &Painter) {
190        if let Some(grid) = &mut self.grid {
191            grid.end_row(&mut self.region.cursor, painter);
192        } else {
193            self.layout.end_row(&mut self.region, item_spacing);
194        }
195    }
196
197    /// Set row height in horizontal wrapping layout.
198    pub(crate) fn set_row_height(&mut self, height: f32) {
199        self.layout.set_row_height(&mut self.region, height);
200    }
201}
202
203impl Placer {
204    /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
205    pub(crate) fn expand_to_include_rect(&mut self, rect: Rect) {
206        self.region.expand_to_include_rect(rect);
207    }
208
209    /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given x-coordinate.
210    pub(crate) fn expand_to_include_x(&mut self, x: f32) {
211        self.region.expand_to_include_x(x);
212    }
213
214    /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given y-coordinate.
215    pub(crate) fn expand_to_include_y(&mut self, y: f32) {
216        self.region.expand_to_include_y(y);
217    }
218
219    fn next_widget_space_ignore_wrap_justify(&self, size: Vec2) -> Rect {
220        self.layout
221            .next_widget_space_ignore_wrap_justify(&self.region, size)
222    }
223
224    /// Set the maximum width of the ui.
225    /// You won't be able to shrink it below the current minimum size.
226    pub(crate) fn set_max_width(&mut self, width: f32) {
227        let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
228        let region = &mut self.region;
229        region.max_rect.min.x = rect.min.x;
230        region.max_rect.max.x = rect.max.x;
231        region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
232
233        region.cursor.min.x = region.max_rect.min.x;
234        region.cursor.max.x = region.max_rect.max.x;
235
236        region.sanity_check();
237    }
238
239    /// Set the maximum height of the ui.
240    /// You won't be able to shrink it below the current minimum size.
241    pub(crate) fn set_max_height(&mut self, height: f32) {
242        let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
243        let region = &mut self.region;
244        region.max_rect.min.y = rect.min.y;
245        region.max_rect.max.y = rect.max.y;
246        region.max_rect = region.max_rect.union(region.min_rect); // make sure we didn't shrink too much
247
248        region.cursor.min.y = region.max_rect.min.y;
249        region.cursor.max.y = region.max_rect.max.y;
250
251        region.sanity_check();
252    }
253
254    /// Set the minimum width of the ui.
255    /// This can't shrink the ui, only make it larger.
256    pub(crate) fn set_min_width(&mut self, width: f32) {
257        if width <= 0.0 {
258            return;
259        }
260        let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0));
261        self.region.expand_to_include_x(rect.min.x);
262        self.region.expand_to_include_x(rect.max.x);
263    }
264
265    /// Set the minimum height of the ui.
266    /// This can't shrink the ui, only make it larger.
267    pub(crate) fn set_min_height(&mut self, height: f32) {
268        if height <= 0.0 {
269            return;
270        }
271        let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height));
272        self.region.expand_to_include_y(rect.min.y);
273        self.region.expand_to_include_y(rect.max.y);
274    }
275}
276
277impl Placer {
278    #[cfg(debug_assertions)]
279    pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter, text: impl ToString) {
280        let stroke = Stroke::new(1.0, Color32::DEBUG_COLOR);
281
282        if let Some(grid) = &self.grid {
283            let rect = grid.next_cell(self.cursor(), Vec2::splat(0.0));
284            painter.rect_stroke(rect, 1.0, stroke);
285            let align = Align2::CENTER_CENTER;
286            painter.debug_text(align.pos_in_rect(&rect), align, stroke.color, text);
287        } else {
288            self.layout
289                .paint_text_at_cursor(painter, &self.region, stroke, text);
290        }
291    }
292}