Skip to content

Commit

Permalink
Truncate text in clipped Table columns (#5023)
Browse files Browse the repository at this point in the history
* Closes #5013
* Columns with `clip = true` will have `TextWrapMode::Truncate` set
* Added setting `Column::auto_size_this_frame` (acts like a double-click
on column resizer)
* Set `sizing_pass` on all cells in a column that is being auto-sized
(e.g. on double-click)


![image](https://github.com/user-attachments/assets/360f6b59-c9a9-468b-8919-4b7e4fc6661a)
  • Loading branch information
emilk authored Aug 29, 2024
1 parent 343c3d1 commit 3777b8d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 30 deletions.
2 changes: 1 addition & 1 deletion crates/egui/src/ui_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::Ui;
/// except for `max_rect` which by default is set to
/// the parent [`Ui::available_rect_before_wrap`].
#[must_use]
#[derive(Default)]
#[derive(Clone, Default)]
pub struct UiBuilder {
pub id_source: Option<Id>,
pub ui_stack_info: UiStackInfo,
Expand Down
25 changes: 18 additions & 7 deletions crates/egui_extras/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub(crate) struct StripLayoutFlags {
pub(crate) striped: bool,
pub(crate) hovered: bool,
pub(crate) selected: bool,

/// Used when we want to accruately measure the size of this cell.
pub(crate) sizing_pass: bool,
}

/// Positions cells in [`CellDirection`] and starts a new line on [`StripLayout::end_line`]
Expand Down Expand Up @@ -197,19 +200,27 @@ impl<'l> StripLayout<'l> {
child_ui_id_source: egui::Id,
add_cell_contents: impl FnOnce(&mut Ui),
) -> Ui {
let mut child_ui = self.ui.new_child(
UiBuilder::new()
.id_source(child_ui_id_source)
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
.max_rect(max_rect)
.layout(self.cell_layout),
);
let mut ui_builder = UiBuilder::new()
.id_source(child_ui_id_source)
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
.max_rect(max_rect)
.layout(self.cell_layout);
if flags.sizing_pass {
ui_builder = ui_builder.sizing_pass();
}

let mut child_ui = self.ui.new_child(ui_builder);

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 = max_rect.expand2(margin);
child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect()));

if !child_ui.is_sizing_pass() {
// Better to truncate (if we can), rather than hard clipping:
child_ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
}
}

if flags.selected {
Expand Down
70 changes: 48 additions & 22 deletions crates/egui_extras/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ pub struct Column {
clip: bool,

resizable: Option<bool>,

/// If set, we should acurately measure the size of this column this frame
/// so that we can correctly auto-size it. This is done as a `sizing_pass`.
auto_size_this_frame: bool,
}

impl Column {
Expand Down Expand Up @@ -86,6 +90,7 @@ impl Column {
width_range: Rangef::new(0.0, f32::INFINITY),
resizable: None,
clip: false,
auto_size_this_frame: false,
}
}

Expand Down Expand Up @@ -138,6 +143,15 @@ impl Column {
self
}

/// If set, the column will be automatically sized based on the content this frame.
///
/// Do not set this every frame, just on a specific action.
#[inline]
pub fn auto_size_this_frame(mut self, auto_size_this_frame: bool) -> Self {
self.auto_size_this_frame = auto_size_this_frame;
self
}

fn is_auto(&self) -> bool {
match self.initial_width {
InitialColumnSize::Automatic(_) => true,
Expand Down Expand Up @@ -446,10 +460,11 @@ impl<'a> TableBuilder<'a> {
let mut max_used_widths = vec![0.0; columns.len()];
let table_top = ui.cursor().top();

ui.scope(|ui| {
if is_sizing_pass {
ui.set_sizing_pass();
}
let mut ui_builder = egui::UiBuilder::new();
if is_sizing_pass {
ui_builder = ui_builder.sizing_pass();
}
ui.scope_builder(ui_builder, |ui| {
let mut layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);
let mut response: Option<Response> = None;
add_header_row(TableRow {
Expand Down Expand Up @@ -671,7 +686,7 @@ impl<'a> Table<'a> {
ui,
table_top,
state_id,
columns,
mut columns,
resizable,
mut available_width,
mut state,
Expand All @@ -695,6 +710,15 @@ impl<'a> Table<'a> {
scroll_bar_visibility,
} = scroll_options;

for (i, column) in columns.iter_mut().enumerate() {
let column_resize_id = ui.id().with("resize_column").with(i);
if let Some(response) = ui.ctx().read_response(column_resize_id) {
if response.double_clicked() {
column.auto_size_this_frame = true;
}
}
}

let cursor_position = ui.cursor().min;

let mut scroll_area = ScrollArea::new([false, vscroll])
Expand All @@ -718,11 +742,11 @@ impl<'a> Table<'a> {

let clip_rect = ui.clip_rect();

ui.scope(|ui| {
if is_sizing_pass {
ui.set_sizing_pass();
}

let mut ui_builder = egui::UiBuilder::new();
if is_sizing_pass {
ui_builder = ui_builder.sizing_pass();
}
ui.scope_builder(ui_builder, |ui| {
let hovered_row_index_id = self.state_id.with("__table_hovered_row");
let hovered_row_index =
ui.data_mut(|data| data.remove_temp::<usize>(hovered_row_index_id));
Expand All @@ -736,16 +760,15 @@ impl<'a> Table<'a> {
max_used_widths: max_used_widths_ref,
striped,
row_index: 0,
start_y: clip_rect.top(),
end_y: clip_rect.bottom(),
y_range: clip_rect.y_range(),
scroll_to_row: scroll_to_row.map(|(r, _)| r),
scroll_to_y_range: &mut scroll_to_y_range,
hovered_row_index,
hovered_row_index_id,
});

if scroll_to_row.is_some() && scroll_to_y_range.is_none() {
// TableBody::row didn't find the right row, so scroll to the bottom:
// TableBody::row didn't find the correct row, so scroll to the bottom:
scroll_to_y_range = Some(Rangef::new(f32::INFINITY, f32::INFINITY));
}
});
Expand Down Expand Up @@ -811,9 +834,8 @@ impl<'a> Table<'a> {
let resize_response =
ui.interact(line_rect, column_resize_id, egui::Sense::click_and_drag());

if resize_response.double_clicked() {
// Resize to the minimum of what is needed.

if column.auto_size_this_frame {
// Auto-size: resize to what is needed.
*column_width = width_range.clamp(max_used_widths[i]);
} else if resize_response.dragged() {
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
Expand Down Expand Up @@ -884,8 +906,7 @@ pub struct TableBody<'a> {

striped: bool,
row_index: usize,
start_y: f32,
end_y: f32,
y_range: Rangef,

/// Look for this row to scroll to.
scroll_to_row: Option<usize>,
Expand Down Expand Up @@ -916,7 +937,7 @@ impl<'a> TableBody<'a> {
}

fn scroll_offset_y(&self) -> f32 {
self.start_y - self.layout.rect.top()
self.y_range.min - self.layout.rect.top()
}

/// Return a vector containing all column widths for this table body.
Expand Down Expand Up @@ -1002,7 +1023,7 @@ impl<'a> TableBody<'a> {
let scroll_offset_y = self
.scroll_offset_y()
.min(total_rows as f32 * row_height_with_spacing);
let max_height = self.end_y - self.start_y;
let max_height = self.y_range.span();
let mut min_row = 0;

if scroll_offset_y > 0.0 {
Expand Down Expand Up @@ -1074,7 +1095,7 @@ impl<'a> TableBody<'a> {
let spacing = self.layout.ui.spacing().item_spacing;
let mut enumerated_heights = heights.enumerate();

let max_height = self.end_y - self.start_y;
let max_height = self.y_range.span();
let scroll_offset_y = self.scroll_offset_y() as f64;

let scroll_to_y_range_offset = self.layout.cursor.y as f64;
Expand Down Expand Up @@ -1221,14 +1242,18 @@ pub struct TableRow<'a, 'b> {
}

impl<'a, 'b> TableRow<'a, 'b> {
/// Add the contents of a column.
/// Add the contents of a column on this row (i.e. a cell).
///
/// Returns the used space (`min_rect`) plus the [`Response`] of the whole cell.
#[cfg_attr(debug_assertions, track_caller)]
pub fn col(&mut self, add_cell_contents: impl FnOnce(&mut Ui)) -> (Rect, Response) {
let col_index = self.col_index;

let clip = self.columns.get(col_index).map_or(false, |c| c.clip);
let auto_size_this_frame = self
.columns
.get(col_index)
.map_or(false, |c| c.auto_size_this_frame);

let width = if let Some(width) = self.widths.get(col_index) {
self.col_index += 1;
Expand All @@ -1249,6 +1274,7 @@ impl<'a, 'b> TableRow<'a, 'b> {
striped: self.striped,
hovered: self.hovered,
selected: self.selected,
sizing_pass: auto_size_this_frame || self.layout.ui.is_sizing_pass(),
};

let (used_rect, response) = self.layout.add(
Expand Down

0 comments on commit 3777b8d

Please sign in to comment.