mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
* Based on #3105 by @vvv. ## Additions and Changes - Add `TableBuilder::sense()` and `StripBuilder::sense()` to enable detecting clicks or drags on table and strip cells. - Add `TableRow::select()` which takes a boolean that sets the highlight state for all cells added after a call to it. This allows highlighting an entire row or specific cells. - Add `TableRow::response()` which returns the union of the `Response` of all cells added to the row up to that point. This makes it easy to detect interactions with an entire row. See below for an alternative design. - Add `TableRow::index()` and `TableRow::col_index()` helpers. - Remove explicit `row_index` from callback passed to `TableBody::rows()` and `TableBody::heterogeneous_rows()`, possible due to the above. This is a breaking change but makes the callback compatible with `TableBody::row()`. - Update Table example to demonstrate all of the above. ## Design Decisions An alternative design to `TableRow::response()` would be to return the row response from `TableBody`s `row()`, `rows()` and `heterogeneous_rows()` functions. `row()` could just return the response. `rows()` and `heterogeneous_rows()` could return a tuple of the hovered row index and that rows response. I feel like this might be the cleaner soluction if only returning the hovered rows response isn't too limiting. I didn't implement `TableBuilder::select_rows()` as described [here](https://github.com/emilk/egui/pull/3105#issuecomment-1618062533) because it requires an immutable borrow of the selection state for the lifetime of the `TableBuilder`. This makes updating the selection state from within the body unnecessarily complicated. Additionally the current design allows for selecting specific cells, though that could be possible by modifying `TableBuilder::select_rows()` to provide row and column indices like below. ```rust pub fn select_cells(is_selected: impl Fn(usize, usize) -> bool) -> Self ``` ## Hover Highlighting EDIT: Thanks to @samitbasu we now have hover highlighting too. ~This is not implemented yet. Ideally we'd have an api that allows to choose between highlighting the hovered cell, column or row. Should cells containing interactive widgets, be highlighted when hovering over the widget or only when hovering over the cell itself? I'd like to implement that before this gets merged though.~ Feedback is more than welcome. I'd be happy to make any changes necessary to get this merged. * Closes #1519 * Closes #1553 * Closes #3069 --------- Co-authored-by: Samit Basu <basu.samit@gmail.com> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
223 lines
6.7 KiB
Rust
223 lines
6.7 KiB
Rust
use crate::{
|
|
layout::{CellDirection, CellSize, StripLayout, StripLayoutFlags},
|
|
sizing::Sizing,
|
|
Size,
|
|
};
|
|
use egui::{Response, Ui};
|
|
|
|
/// Builder for creating a new [`Strip`].
|
|
///
|
|
/// This can be used to do dynamic layouts.
|
|
///
|
|
/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
|
|
///
|
|
/// First use [`Self::size`] and [`Self::sizes`] to allocate space for the rows or columns will follow.
|
|
/// Then build the strip with [`Self::horizontal`]/[`Self::vertical`], and add 'cells'
|
|
/// to it using [`Strip::cell`]. The number of cells MUST match the number of pre-allocated sizes.
|
|
///
|
|
/// ### Example
|
|
/// ```
|
|
/// # egui::__run_test_ui(|ui| {
|
|
/// use egui_extras::{StripBuilder, Size};
|
|
/// StripBuilder::new(ui)
|
|
/// .size(Size::remainder().at_least(100.0)) // top cell
|
|
/// .size(Size::exact(40.0)) // bottom cell
|
|
/// .vertical(|mut strip| {
|
|
/// // Add the top 'cell'
|
|
/// strip.cell(|ui| {
|
|
/// ui.label("Fixed");
|
|
/// });
|
|
/// // We add a nested strip in the bottom cell:
|
|
/// strip.strip(|builder| {
|
|
/// builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
|
/// strip.cell(|ui| {
|
|
/// ui.label("Top Left");
|
|
/// });
|
|
/// strip.cell(|ui| {
|
|
/// ui.label("Top Right");
|
|
/// });
|
|
/// });
|
|
/// });
|
|
/// });
|
|
/// # });
|
|
/// ```
|
|
pub struct StripBuilder<'a> {
|
|
ui: &'a mut Ui,
|
|
sizing: Sizing,
|
|
clip: bool,
|
|
cell_layout: egui::Layout,
|
|
sense: egui::Sense,
|
|
}
|
|
|
|
impl<'a> StripBuilder<'a> {
|
|
/// Create new strip builder.
|
|
pub fn new(ui: &'a mut Ui) -> Self {
|
|
let cell_layout = *ui.layout();
|
|
Self {
|
|
ui,
|
|
sizing: Default::default(),
|
|
clip: false,
|
|
cell_layout,
|
|
sense: egui::Sense::hover(),
|
|
}
|
|
}
|
|
|
|
/// Should we clip the contents of each cell? Default: `false`.
|
|
#[inline]
|
|
pub fn clip(mut self, clip: bool) -> Self {
|
|
self.clip = clip;
|
|
self
|
|
}
|
|
|
|
/// What layout should we use for the individual cells?
|
|
#[inline]
|
|
pub fn cell_layout(mut self, cell_layout: egui::Layout) -> Self {
|
|
self.cell_layout = cell_layout;
|
|
self
|
|
}
|
|
|
|
/// What should strip cells sense for? Default: [`egui::Sense::hover()`].
|
|
#[inline]
|
|
pub fn sense(mut self, sense: egui::Sense) -> Self {
|
|
self.sense = sense;
|
|
self
|
|
}
|
|
|
|
/// Allocate space for one column/row.
|
|
#[inline]
|
|
pub fn size(mut self, size: Size) -> Self {
|
|
self.sizing.add(size);
|
|
self
|
|
}
|
|
|
|
/// Allocate space for several columns/rows at once.
|
|
#[inline]
|
|
pub fn sizes(mut self, size: Size, count: usize) -> Self {
|
|
for _ in 0..count {
|
|
self.sizing.add(size);
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Build horizontal strip: Cells are positions from left to right.
|
|
/// Takes the available horizontal width, so there can't be anything right of the strip or the container will grow slowly!
|
|
///
|
|
/// Returns a [`egui::Response`] for hover events.
|
|
pub fn horizontal<F>(self, strip: F) -> Response
|
|
where
|
|
F: for<'b> FnOnce(Strip<'a, 'b>),
|
|
{
|
|
let widths = self.sizing.to_lengths(
|
|
self.ui.available_rect_before_wrap().width(),
|
|
self.ui.spacing().item_spacing.x,
|
|
);
|
|
let mut layout = StripLayout::new(
|
|
self.ui,
|
|
CellDirection::Horizontal,
|
|
self.cell_layout,
|
|
self.sense,
|
|
);
|
|
strip(Strip {
|
|
layout: &mut layout,
|
|
direction: CellDirection::Horizontal,
|
|
clip: self.clip,
|
|
sizes: widths,
|
|
size_index: 0,
|
|
});
|
|
layout.allocate_rect()
|
|
}
|
|
|
|
/// Build vertical strip: Cells are positions from top to bottom.
|
|
/// Takes the full available vertical height, so there can't be anything below of the strip or the container will grow slowly!
|
|
///
|
|
/// Returns a [`egui::Response`] for hover events.
|
|
pub fn vertical<F>(self, strip: F) -> Response
|
|
where
|
|
F: for<'b> FnOnce(Strip<'a, 'b>),
|
|
{
|
|
let heights = self.sizing.to_lengths(
|
|
self.ui.available_rect_before_wrap().height(),
|
|
self.ui.spacing().item_spacing.y,
|
|
);
|
|
let mut layout = StripLayout::new(
|
|
self.ui,
|
|
CellDirection::Vertical,
|
|
self.cell_layout,
|
|
self.sense,
|
|
);
|
|
strip(Strip {
|
|
layout: &mut layout,
|
|
direction: CellDirection::Vertical,
|
|
clip: self.clip,
|
|
sizes: heights,
|
|
size_index: 0,
|
|
});
|
|
layout.allocate_rect()
|
|
}
|
|
}
|
|
|
|
/// A Strip of cells which go in one direction. Each cell has a fixed size.
|
|
/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
|
|
pub struct Strip<'a, 'b> {
|
|
layout: &'b mut StripLayout<'a>,
|
|
direction: CellDirection,
|
|
clip: bool,
|
|
sizes: Vec<f32>,
|
|
size_index: usize,
|
|
}
|
|
|
|
impl<'a, 'b> Strip<'a, 'b> {
|
|
#[cfg_attr(debug_assertions, track_caller)]
|
|
fn next_cell_size(&mut self) -> (CellSize, CellSize) {
|
|
let size = if let Some(size) = self.sizes.get(self.size_index) {
|
|
self.size_index += 1;
|
|
*size
|
|
} else {
|
|
crate::log_or_panic!(
|
|
"Added more `Strip` cells than were pre-allocated ({} pre-allocated)",
|
|
self.sizes.len()
|
|
);
|
|
8.0 // anything will look wrong, so pick something that is obviously wrong
|
|
};
|
|
|
|
match self.direction {
|
|
CellDirection::Horizontal => (CellSize::Absolute(size), CellSize::Remainder),
|
|
CellDirection::Vertical => (CellSize::Remainder, CellSize::Absolute(size)),
|
|
}
|
|
}
|
|
|
|
/// Add cell contents.
|
|
#[cfg_attr(debug_assertions, track_caller)]
|
|
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
|
let (width, height) = self.next_cell_size();
|
|
let flags = StripLayoutFlags {
|
|
clip: self.clip,
|
|
..Default::default()
|
|
};
|
|
self.layout.add(flags, width, height, add_contents);
|
|
}
|
|
|
|
/// Add an empty cell.
|
|
#[cfg_attr(debug_assertions, track_caller)]
|
|
pub fn empty(&mut self) {
|
|
let (width, height) = self.next_cell_size();
|
|
self.layout.empty(width, height);
|
|
}
|
|
|
|
/// Add a strip as cell.
|
|
pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) {
|
|
let clip = self.clip;
|
|
self.cell(|ui| {
|
|
strip_builder(StripBuilder::new(ui).clip(clip));
|
|
});
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> Drop for Strip<'a, 'b> {
|
|
fn drop(&mut self) {
|
|
while self.size_index < self.sizes.len() {
|
|
self.empty();
|
|
}
|
|
}
|
|
}
|