mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 15:20:05 -04:00
Implement table row selection and hover highlighting (#3347)
* 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>
This commit is contained in:
@@ -26,6 +26,15 @@ pub(crate) enum CellDirection {
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Flags used by [`StripLayout::add`].
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) struct StripLayoutFlags {
|
||||
pub(crate) clip: bool,
|
||||
pub(crate) striped: bool,
|
||||
pub(crate) hovered: bool,
|
||||
pub(crate) selected: bool,
|
||||
}
|
||||
|
||||
/// Positions cells in [`CellDirection`] and starts a new line on [`StripLayout::end_line`]
|
||||
pub struct StripLayout<'l> {
|
||||
pub(crate) ui: &'l mut Ui,
|
||||
@@ -38,10 +47,16 @@ pub struct StripLayout<'l> {
|
||||
max: Pos2,
|
||||
|
||||
cell_layout: egui::Layout,
|
||||
sense: Sense,
|
||||
}
|
||||
|
||||
impl<'l> StripLayout<'l> {
|
||||
pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection, cell_layout: egui::Layout) -> Self {
|
||||
pub(crate) fn new(
|
||||
ui: &'l mut Ui,
|
||||
direction: CellDirection,
|
||||
cell_layout: egui::Layout,
|
||||
sense: Sense,
|
||||
) -> Self {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
let pos = rect.left_top();
|
||||
|
||||
@@ -52,6 +67,7 @@ impl<'l> StripLayout<'l> {
|
||||
cursor: pos,
|
||||
max: pos,
|
||||
cell_layout,
|
||||
sense,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,34 +110,53 @@ impl<'l> StripLayout<'l> {
|
||||
/// Return the used space (`min_rect`) plus the [`Response`] of the whole cell.
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
striped: bool,
|
||||
flags: StripLayoutFlags,
|
||||
width: CellSize,
|
||||
height: CellSize,
|
||||
add_cell_contents: impl FnOnce(&mut Ui),
|
||||
) -> (Rect, Response) {
|
||||
let max_rect = self.cell_rect(&width, &height);
|
||||
|
||||
if striped {
|
||||
// Make sure we don't have a gap in the stripe background:
|
||||
let stripe_rect = max_rect.expand2(0.5 * self.ui.spacing().item_spacing);
|
||||
// Make sure we don't have a gap in the stripe/frame/selection background:
|
||||
let item_spacing = self.ui.spacing().item_spacing;
|
||||
let gapless_rect = max_rect.expand2(0.5 * item_spacing);
|
||||
|
||||
self.ui
|
||||
.painter()
|
||||
.rect_filled(stripe_rect, 0.0, self.ui.visuals().faint_bg_color);
|
||||
if flags.striped {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
self.ui.visuals().faint_bg_color,
|
||||
);
|
||||
}
|
||||
|
||||
let used_rect = self.cell(clip, max_rect, add_cell_contents);
|
||||
if flags.selected {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
self.ui.visuals().selection.bg_fill,
|
||||
);
|
||||
}
|
||||
|
||||
if flags.hovered && !flags.selected && self.sense.interactive() {
|
||||
self.ui.painter().rect_filled(
|
||||
gapless_rect,
|
||||
egui::Rounding::ZERO,
|
||||
self.ui.visuals().widgets.hovered.bg_fill,
|
||||
);
|
||||
}
|
||||
|
||||
let response = self.ui.allocate_rect(max_rect, self.sense);
|
||||
let used_rect = self.cell(flags, max_rect, add_cell_contents);
|
||||
|
||||
self.set_pos(max_rect);
|
||||
|
||||
let allocation_rect = if clip {
|
||||
let allocation_rect = if flags.clip {
|
||||
max_rect
|
||||
} else {
|
||||
max_rect.union(used_rect)
|
||||
};
|
||||
|
||||
let response = self.ui.allocate_rect(allocation_rect, Sense::hover());
|
||||
let response = response.with_new_rect(allocation_rect);
|
||||
|
||||
(used_rect, response)
|
||||
}
|
||||
@@ -148,10 +183,15 @@ impl<'l> StripLayout<'l> {
|
||||
self.ui.allocate_rect(rect, Sense::hover());
|
||||
}
|
||||
|
||||
fn cell(&mut self, clip: bool, rect: Rect, add_cell_contents: impl FnOnce(&mut Ui)) -> Rect {
|
||||
fn cell(
|
||||
&mut self,
|
||||
flags: StripLayoutFlags,
|
||||
rect: Rect,
|
||||
add_cell_contents: impl FnOnce(&mut Ui),
|
||||
) -> Rect {
|
||||
let mut child_ui = self.ui.child_ui(rect, self.cell_layout);
|
||||
|
||||
if clip {
|
||||
if flags.clip {
|
||||
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
||||
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
||||
let clip_rect = rect.expand2(margin);
|
||||
|
||||
Reference in New Issue
Block a user