egui_extras/
strip.rs

1use crate::{
2    layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags},
3    sizing::Sizing,
4    Size,
5};
6use egui::{Response, Ui};
7
8/// Builder for creating a new [`Strip`].
9///
10/// This can be used to do dynamic layouts.
11///
12/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
13///
14/// First use [`Self::size`] and [`Self::sizes`] to allocate space for the rows or columns will follow.
15/// Then build the strip with [`Self::horizontal`]/[`Self::vertical`], and add 'cells'
16/// to it using [`Strip::cell`]. The number of cells MUST match the number of pre-allocated sizes.
17///
18/// ### Example
19/// ```
20/// # egui::__run_test_ui(|ui| {
21/// use egui_extras::{StripBuilder, Size};
22/// StripBuilder::new(ui)
23///     .size(Size::remainder().at_least(100.0)) // top cell
24///     .size(Size::exact(40.0)) // bottom cell
25///     .vertical(|mut strip| {
26///         // Add the top 'cell'
27///         strip.cell(|ui| {
28///             ui.label("Fixed");
29///         });
30///         // We add a nested strip in the bottom cell:
31///         strip.strip(|builder| {
32///             builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
33///                 strip.cell(|ui| {
34///                     ui.label("Top Left");
35///                 });
36///                 strip.cell(|ui| {
37///                     ui.label("Top Right");
38///                 });
39///             });
40///         });
41///     });
42/// # });
43/// ```
44pub struct StripBuilder<'a> {
45    ui: &'a mut Ui,
46    sizing: Sizing,
47    clip: bool,
48    cell_layout: egui::Layout,
49    sense: egui::Sense,
50}
51
52impl<'a> StripBuilder<'a> {
53    /// Create new strip builder.
54    pub fn new(ui: &'a mut Ui) -> Self {
55        let cell_layout = *ui.layout();
56        Self {
57            ui,
58            sizing: Default::default(),
59            clip: false,
60            cell_layout,
61            sense: egui::Sense::hover(),
62        }
63    }
64
65    /// Should we clip the contents of each cell? Default: `false`.
66    #[inline]
67    pub fn clip(mut self, clip: bool) -> Self {
68        self.clip = clip;
69        self
70    }
71
72    /// What layout should we use for the individual cells?
73    #[inline]
74    pub fn cell_layout(mut self, cell_layout: egui::Layout) -> Self {
75        self.cell_layout = cell_layout;
76        self
77    }
78
79    /// What should strip cells sense for? Default: [`egui::Sense::hover()`].
80    #[inline]
81    pub fn sense(mut self, sense: egui::Sense) -> Self {
82        self.sense = sense;
83        self
84    }
85
86    /// Allocate space for one column/row.
87    #[inline]
88    pub fn size(mut self, size: Size) -> Self {
89        self.sizing.add(size);
90        self
91    }
92
93    /// Allocate space for several columns/rows at once.
94    #[inline]
95    pub fn sizes(mut self, size: Size, count: usize) -> Self {
96        for _ in 0..count {
97            self.sizing.add(size);
98        }
99        self
100    }
101
102    /// Build horizontal strip: Cells are positions from left to right.
103    /// Takes the available horizontal width, so there can't be anything right of the strip or the container will grow slowly!
104    ///
105    /// Returns a [`egui::Response`] for hover events.
106    pub fn horizontal<F>(self, strip: F) -> Response
107    where
108        F: for<'b> FnOnce(Strip<'a, 'b>),
109    {
110        let widths = self.sizing.to_lengths(
111            self.ui.available_rect_before_wrap().width(),
112            self.ui.spacing().item_spacing.x,
113        );
114        let mut layout = StripLayout::new(
115            self.ui,
116            CellDirection::Horizontal,
117            self.cell_layout,
118            self.sense,
119        );
120        strip(Strip {
121            layout: &mut layout,
122            direction: CellDirection::Horizontal,
123            clip: self.clip,
124            sizes: widths,
125            size_index: 0,
126        });
127        layout.allocate_rect()
128    }
129
130    /// Build vertical strip: Cells are positions from top to bottom.
131    /// Takes the full available vertical height, so there can't be anything below of the strip or the container will grow slowly!
132    ///
133    /// Returns a [`egui::Response`] for hover events.
134    pub fn vertical<F>(self, strip: F) -> Response
135    where
136        F: for<'b> FnOnce(Strip<'a, 'b>),
137    {
138        let heights = self.sizing.to_lengths(
139            self.ui.available_rect_before_wrap().height(),
140            self.ui.spacing().item_spacing.y,
141        );
142        let mut layout = StripLayout::new(
143            self.ui,
144            CellDirection::Vertical,
145            self.cell_layout,
146            self.sense,
147        );
148        strip(Strip {
149            layout: &mut layout,
150            direction: CellDirection::Vertical,
151            clip: self.clip,
152            sizes: heights,
153            size_index: 0,
154        });
155        layout.allocate_rect()
156    }
157}
158
159/// A Strip of cells which go in one direction. Each cell has a fixed size.
160/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
161pub struct Strip<'a, 'b> {
162    layout: &'b mut StripLayout<'a>,
163    direction: CellDirection,
164    clip: bool,
165    sizes: Vec<f32>,
166    size_index: usize,
167}
168
169impl<'a, 'b> Strip<'a, 'b> {
170    #[cfg_attr(debug_assertions, track_caller)]
171    fn next_cell_size(&mut self) -> (CellSize, CellSize) {
172        let size = if let Some(size) = self.sizes.get(self.size_index) {
173            self.size_index += 1;
174            *size
175        } else {
176            crate::log_or_panic!(
177                "Added more `Strip` cells than were pre-allocated ({} pre-allocated)",
178                self.sizes.len()
179            );
180            8.0 // anything will look wrong, so pick something that is obviously wrong
181        };
182
183        match self.direction {
184            CellDirection::Horizontal => (CellSize::Absolute(size), CellSize::Remainder),
185            CellDirection::Vertical => (CellSize::Remainder, CellSize::Absolute(size)),
186        }
187    }
188
189    /// Add cell contents.
190    #[cfg_attr(debug_assertions, track_caller)]
191    pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
192        let (width, height) = self.next_cell_size();
193        let flags = StripLayoutFlags {
194            clip: self.clip,
195            ..Default::default()
196        };
197        self.layout.add(
198            flags,
199            width,
200            height,
201            egui::Id::new(self.size_index),
202            add_contents,
203        );
204    }
205
206    /// Add an empty cell.
207    #[cfg_attr(debug_assertions, track_caller)]
208    pub fn empty(&mut self) {
209        let (width, height) = self.next_cell_size();
210        self.layout.empty(width, height);
211    }
212
213    /// Add a strip as cell.
214    pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) {
215        let clip = self.clip;
216        self.cell(|ui| {
217            strip_builder(StripBuilder::new(ui).clip(clip));
218        });
219    }
220}
221
222impl<'a, 'b> Drop for Strip<'a, 'b> {
223    fn drop(&mut self) {
224        while self.size_index < self.sizes.len() {
225            self.empty();
226        }
227    }
228}