Truncate text in clipped `Table` columns (#5023)
* Closes https://github.com/emilk/egui/issues/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) 
This commit is contained in:
parent
343c3d16c3
commit
3777b8d274
|
|
@ -11,7 +11,7 @@ use crate::Ui;
|
||||||
/// except for `max_rect` which by default is set to
|
/// except for `max_rect` which by default is set to
|
||||||
/// the parent [`Ui::available_rect_before_wrap`].
|
/// the parent [`Ui::available_rect_before_wrap`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UiBuilder {
|
pub struct UiBuilder {
|
||||||
pub id_source: Option<Id>,
|
pub id_source: Option<Id>,
|
||||||
pub ui_stack_info: UiStackInfo,
|
pub ui_stack_info: UiStackInfo,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ pub(crate) struct StripLayoutFlags {
|
||||||
pub(crate) striped: bool,
|
pub(crate) striped: bool,
|
||||||
pub(crate) hovered: bool,
|
pub(crate) hovered: bool,
|
||||||
pub(crate) selected: 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`]
|
/// Positions cells in [`CellDirection`] and starts a new line on [`StripLayout::end_line`]
|
||||||
|
|
@ -197,19 +200,27 @@ impl<'l> StripLayout<'l> {
|
||||||
child_ui_id_source: egui::Id,
|
child_ui_id_source: egui::Id,
|
||||||
add_cell_contents: impl FnOnce(&mut Ui),
|
add_cell_contents: impl FnOnce(&mut Ui),
|
||||||
) -> Ui {
|
) -> Ui {
|
||||||
let mut child_ui = self.ui.new_child(
|
let mut ui_builder = UiBuilder::new()
|
||||||
UiBuilder::new()
|
.id_source(child_ui_id_source)
|
||||||
.id_source(child_ui_id_source)
|
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
|
||||||
.ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
|
.max_rect(max_rect)
|
||||||
.max_rect(max_rect)
|
.layout(self.cell_layout);
|
||||||
.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 {
|
if flags.clip {
|
||||||
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
||||||
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
||||||
let clip_rect = max_rect.expand2(margin);
|
let clip_rect = max_rect.expand2(margin);
|
||||||
child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect()));
|
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 {
|
if flags.selected {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ pub struct Column {
|
||||||
clip: bool,
|
clip: bool,
|
||||||
|
|
||||||
resizable: Option<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 {
|
impl Column {
|
||||||
|
|
@ -86,6 +90,7 @@ impl Column {
|
||||||
width_range: Rangef::new(0.0, f32::INFINITY),
|
width_range: Rangef::new(0.0, f32::INFINITY),
|
||||||
resizable: None,
|
resizable: None,
|
||||||
clip: false,
|
clip: false,
|
||||||
|
auto_size_this_frame: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,6 +143,15 @@ impl Column {
|
||||||
self
|
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 {
|
fn is_auto(&self) -> bool {
|
||||||
match self.initial_width {
|
match self.initial_width {
|
||||||
InitialColumnSize::Automatic(_) => true,
|
InitialColumnSize::Automatic(_) => true,
|
||||||
|
|
@ -446,10 +460,11 @@ impl<'a> TableBuilder<'a> {
|
||||||
let mut max_used_widths = vec![0.0; columns.len()];
|
let mut max_used_widths = vec![0.0; columns.len()];
|
||||||
let table_top = ui.cursor().top();
|
let table_top = ui.cursor().top();
|
||||||
|
|
||||||
ui.scope(|ui| {
|
let mut ui_builder = egui::UiBuilder::new();
|
||||||
if is_sizing_pass {
|
if is_sizing_pass {
|
||||||
ui.set_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 layout = StripLayout::new(ui, CellDirection::Horizontal, cell_layout, sense);
|
||||||
let mut response: Option<Response> = None;
|
let mut response: Option<Response> = None;
|
||||||
add_header_row(TableRow {
|
add_header_row(TableRow {
|
||||||
|
|
@ -671,7 +686,7 @@ impl<'a> Table<'a> {
|
||||||
ui,
|
ui,
|
||||||
table_top,
|
table_top,
|
||||||
state_id,
|
state_id,
|
||||||
columns,
|
mut columns,
|
||||||
resizable,
|
resizable,
|
||||||
mut available_width,
|
mut available_width,
|
||||||
mut state,
|
mut state,
|
||||||
|
|
@ -695,6 +710,15 @@ impl<'a> Table<'a> {
|
||||||
scroll_bar_visibility,
|
scroll_bar_visibility,
|
||||||
} = scroll_options;
|
} = 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 cursor_position = ui.cursor().min;
|
||||||
|
|
||||||
let mut scroll_area = ScrollArea::new([false, vscroll])
|
let mut scroll_area = ScrollArea::new([false, vscroll])
|
||||||
|
|
@ -718,11 +742,11 @@ impl<'a> Table<'a> {
|
||||||
|
|
||||||
let clip_rect = ui.clip_rect();
|
let clip_rect = ui.clip_rect();
|
||||||
|
|
||||||
ui.scope(|ui| {
|
let mut ui_builder = egui::UiBuilder::new();
|
||||||
if is_sizing_pass {
|
if is_sizing_pass {
|
||||||
ui.set_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_id = self.state_id.with("__table_hovered_row");
|
||||||
let hovered_row_index =
|
let hovered_row_index =
|
||||||
ui.data_mut(|data| data.remove_temp::<usize>(hovered_row_index_id));
|
ui.data_mut(|data| data.remove_temp::<usize>(hovered_row_index_id));
|
||||||
|
|
@ -736,8 +760,7 @@ impl<'a> Table<'a> {
|
||||||
max_used_widths: max_used_widths_ref,
|
max_used_widths: max_used_widths_ref,
|
||||||
striped,
|
striped,
|
||||||
row_index: 0,
|
row_index: 0,
|
||||||
start_y: clip_rect.top(),
|
y_range: clip_rect.y_range(),
|
||||||
end_y: clip_rect.bottom(),
|
|
||||||
scroll_to_row: scroll_to_row.map(|(r, _)| r),
|
scroll_to_row: scroll_to_row.map(|(r, _)| r),
|
||||||
scroll_to_y_range: &mut scroll_to_y_range,
|
scroll_to_y_range: &mut scroll_to_y_range,
|
||||||
hovered_row_index,
|
hovered_row_index,
|
||||||
|
|
@ -745,7 +768,7 @@ impl<'a> Table<'a> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if scroll_to_row.is_some() && scroll_to_y_range.is_none() {
|
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));
|
scroll_to_y_range = Some(Rangef::new(f32::INFINITY, f32::INFINITY));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -811,9 +834,8 @@ impl<'a> Table<'a> {
|
||||||
let resize_response =
|
let resize_response =
|
||||||
ui.interact(line_rect, column_resize_id, egui::Sense::click_and_drag());
|
ui.interact(line_rect, column_resize_id, egui::Sense::click_and_drag());
|
||||||
|
|
||||||
if resize_response.double_clicked() {
|
if column.auto_size_this_frame {
|
||||||
// Resize to the minimum of what is needed.
|
// Auto-size: resize to what is needed.
|
||||||
|
|
||||||
*column_width = width_range.clamp(max_used_widths[i]);
|
*column_width = width_range.clamp(max_used_widths[i]);
|
||||||
} else if resize_response.dragged() {
|
} else if resize_response.dragged() {
|
||||||
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
|
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
|
||||||
|
|
@ -884,8 +906,7 @@ pub struct TableBody<'a> {
|
||||||
|
|
||||||
striped: bool,
|
striped: bool,
|
||||||
row_index: usize,
|
row_index: usize,
|
||||||
start_y: f32,
|
y_range: Rangef,
|
||||||
end_y: f32,
|
|
||||||
|
|
||||||
/// Look for this row to scroll to.
|
/// Look for this row to scroll to.
|
||||||
scroll_to_row: Option<usize>,
|
scroll_to_row: Option<usize>,
|
||||||
|
|
@ -916,7 +937,7 @@ impl<'a> TableBody<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_offset_y(&self) -> f32 {
|
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.
|
/// Return a vector containing all column widths for this table body.
|
||||||
|
|
@ -1002,7 +1023,7 @@ impl<'a> TableBody<'a> {
|
||||||
let scroll_offset_y = self
|
let scroll_offset_y = self
|
||||||
.scroll_offset_y()
|
.scroll_offset_y()
|
||||||
.min(total_rows as f32 * row_height_with_spacing);
|
.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;
|
let mut min_row = 0;
|
||||||
|
|
||||||
if scroll_offset_y > 0.0 {
|
if scroll_offset_y > 0.0 {
|
||||||
|
|
@ -1074,7 +1095,7 @@ impl<'a> TableBody<'a> {
|
||||||
let spacing = self.layout.ui.spacing().item_spacing;
|
let spacing = self.layout.ui.spacing().item_spacing;
|
||||||
let mut enumerated_heights = heights.enumerate();
|
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_offset_y = self.scroll_offset_y() as f64;
|
||||||
|
|
||||||
let scroll_to_y_range_offset = self.layout.cursor.y as f64;
|
let scroll_to_y_range_offset = self.layout.cursor.y as f64;
|
||||||
|
|
@ -1221,7 +1242,7 @@ pub struct TableRow<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> 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.
|
/// Returns the used space (`min_rect`) plus the [`Response`] of the whole cell.
|
||||||
#[cfg_attr(debug_assertions, track_caller)]
|
#[cfg_attr(debug_assertions, track_caller)]
|
||||||
|
|
@ -1229,6 +1250,10 @@ impl<'a, 'b> TableRow<'a, 'b> {
|
||||||
let col_index = self.col_index;
|
let col_index = self.col_index;
|
||||||
|
|
||||||
let clip = self.columns.get(col_index).map_or(false, |c| c.clip);
|
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) {
|
let width = if let Some(width) = self.widths.get(col_index) {
|
||||||
self.col_index += 1;
|
self.col_index += 1;
|
||||||
|
|
@ -1249,6 +1274,7 @@ impl<'a, 'b> TableRow<'a, 'b> {
|
||||||
striped: self.striped,
|
striped: self.striped,
|
||||||
hovered: self.hovered,
|
hovered: self.hovered,
|
||||||
selected: self.selected,
|
selected: self.selected,
|
||||||
|
sizing_pass: auto_size_this_frame || self.layout.ui.is_sizing_pass(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (used_rect, response) = self.layout.add(
|
let (used_rect, response) = self.layout.add(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue