egui/
placer.rs

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