egui_extras: improve Table/Strip docs, and only panic in debug builds
This commit is contained in:
parent
65d16695ae
commit
c88e1f8b29
5 changed files with 136 additions and 104 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1091,6 +1091,7 @@ dependencies = [
|
||||||
"resvg",
|
"resvg",
|
||||||
"serde",
|
"serde",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
|
"tracing",
|
||||||
"usvg",
|
"usvg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,10 @@ datepicker = ["chrono"]
|
||||||
# Persistence
|
# Persistence
|
||||||
persistence = ["serde"]
|
persistence = ["serde"]
|
||||||
|
|
||||||
|
# Log warnings using `tracing` crate
|
||||||
|
tracing = ["dep:tracing", "egui/tracing"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||||
|
|
||||||
|
@ -55,3 +59,6 @@ usvg = { version = "0.22", optional = true }
|
||||||
|
|
||||||
# feature "persistence":
|
# feature "persistence":
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
# feature "tracing"
|
||||||
|
tracing = { version = "0.1", optional = true }
|
||||||
|
|
|
@ -76,16 +76,12 @@ impl Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Sizing {
|
pub struct Sizing {
|
||||||
pub(crate) sizes: Vec<Size>,
|
pub(crate) sizes: Vec<Size>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sizing {
|
impl Sizing {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { sizes: vec![] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, size: Size) {
|
pub fn add(&mut self, size: Size) {
|
||||||
self.sizes.push(size);
|
self.sizes.push(size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,23 @@ use egui::{Response, Ui};
|
||||||
///
|
///
|
||||||
/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
|
/// In contrast to normal egui behavior, strip cells do *not* grow with its children!
|
||||||
///
|
///
|
||||||
/// After adding size hints with `[Self::column]`/`[Self::columns]` the strip can be build with `[Self::horizontal]`/`[Self::vertical]`.
|
/// 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
|
/// ### Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # egui::__run_test_ui(|ui| {
|
/// # egui::__run_test_ui(|ui| {
|
||||||
/// use egui_extras::{StripBuilder, Size};
|
/// use egui_extras::{StripBuilder, Size};
|
||||||
/// StripBuilder::new(ui)
|
/// StripBuilder::new(ui)
|
||||||
/// .size(Size::remainder().at_least(100.0))
|
/// .size(Size::remainder().at_least(100.0)) // top cell
|
||||||
/// .size(Size::exact(40.0))
|
/// .size(Size::exact(40.0)) // bottom cell
|
||||||
/// .vertical(|mut strip| {
|
/// .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| {
|
/// strip.strip(|builder| {
|
||||||
/// builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
/// builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
||||||
/// strip.cell(|ui| {
|
/// strip.cell(|ui| {
|
||||||
|
@ -31,9 +38,6 @@ use egui::{Response, Ui};
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// strip.cell(|ui| {
|
|
||||||
/// ui.label("Fixed");
|
|
||||||
/// });
|
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -46,11 +50,9 @@ pub struct StripBuilder<'a> {
|
||||||
impl<'a> StripBuilder<'a> {
|
impl<'a> StripBuilder<'a> {
|
||||||
/// Create new strip builder.
|
/// Create new strip builder.
|
||||||
pub fn new(ui: &'a mut Ui) -> Self {
|
pub fn new(ui: &'a mut Ui) -> Self {
|
||||||
let sizing = Sizing::new();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ui,
|
ui,
|
||||||
sizing,
|
sizing: Default::default(),
|
||||||
clip: true,
|
clip: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,13 +63,13 @@ impl<'a> StripBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add size hint for one column/row.
|
/// Allocate space for for one column/row.
|
||||||
pub fn size(mut self, size: Size) -> Self {
|
pub fn size(mut self, size: Size) -> Self {
|
||||||
self.sizing.add(size);
|
self.sizing.add(size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add size hint for several columns/rows at once.
|
/// Allocate space for for several columns/rows at once.
|
||||||
pub fn sizes(mut self, size: Size, count: usize) -> Self {
|
pub fn sizes(mut self, size: Size, count: usize) -> Self {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
self.sizing.add(size);
|
self.sizing.add(size);
|
||||||
|
@ -128,12 +130,21 @@ pub struct Strip<'a, 'b> {
|
||||||
|
|
||||||
impl<'a, 'b> Strip<'a, 'b> {
|
impl<'a, 'b> Strip<'a, 'b> {
|
||||||
fn next_cell_size(&mut self) -> (CellSize, CellSize) {
|
fn next_cell_size(&mut self) -> (CellSize, CellSize) {
|
||||||
assert!(
|
let size = if self.sizes.is_empty() {
|
||||||
!self.sizes.is_empty(),
|
if cfg!(debug_assertions) {
|
||||||
"Tried using more strip cells than available."
|
panic!("Added more `Strip` cells than were allocated.");
|
||||||
);
|
} else {
|
||||||
let size = self.sizes[0];
|
#[cfg(feature = "tracing")]
|
||||||
self.sizes = &self.sizes[1..];
|
tracing::error!("Added more `Strip` cells than were allocated");
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
eprintln!("egui_extras: Added more `Strip` cells than were allocated");
|
||||||
|
8.0 // anything will look wrong, so pick something that is obviously wrong
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let size = self.sizes[0];
|
||||||
|
self.sizes = &self.sizes[1..];
|
||||||
|
size
|
||||||
|
};
|
||||||
|
|
||||||
match self.direction {
|
match self.direction {
|
||||||
CellDirection::Horizontal => (CellSize::Absolute(size), CellSize::Remainder),
|
CellDirection::Horizontal => (CellSize::Absolute(size), CellSize::Remainder),
|
||||||
|
@ -141,19 +152,19 @@ impl<'a, 'b> Strip<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add empty cell
|
|
||||||
pub fn empty(&mut self) {
|
|
||||||
let (width, height) = self.next_cell_size();
|
|
||||||
self.layout.empty(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add cell contents.
|
/// Add cell contents.
|
||||||
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
let (width, height) = self.next_cell_size();
|
let (width, height) = self.next_cell_size();
|
||||||
self.layout.add(width, height, add_contents);
|
self.layout.add(width, height, add_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add strip as cell
|
/// Add an empty cell.
|
||||||
|
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<'_>)) {
|
pub fn strip(&mut self, strip_builder: impl FnOnce(StripBuilder<'_>)) {
|
||||||
let clip = self.layout.clip;
|
let clip = self.layout.clip;
|
||||||
self.cell(|ui| {
|
self.cell(|ui| {
|
||||||
|
|
|
@ -13,13 +13,15 @@ use egui::{Rect, Response, Ui, Vec2};
|
||||||
|
|
||||||
/// Builder for a [`Table`] with (optional) fixed header and scrolling body.
|
/// Builder for a [`Table`] with (optional) fixed header and scrolling body.
|
||||||
///
|
///
|
||||||
/// Cell widths are precalculated with given size hints so we can have tables like this:
|
/// Cell widths are precalculated so we can have tables like this:
|
||||||
///
|
///
|
||||||
/// | fixed size | all available space/minimum | 30% of available width | fixed size |
|
/// | fixed size | all available space/minimum | 30% of available width | fixed size |
|
||||||
///
|
///
|
||||||
/// In contrast to normal egui behavior, columns/rows do *not* grow with its children!
|
/// In contrast to normal egui behavior, columns/rows do *not* grow with its children!
|
||||||
/// Takes all available height, so if you want something below the table, put it in a strip.
|
/// Takes all available height, so if you want something below the table, put it in a strip.
|
||||||
///
|
///
|
||||||
|
/// You must pre-allocate all columns with [`Self::column`]/[`Self::columns`].
|
||||||
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```
|
/// ```
|
||||||
/// # egui::__run_test_ui(|ui| {
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
@ -58,11 +60,9 @@ pub struct TableBuilder<'a> {
|
||||||
|
|
||||||
impl<'a> TableBuilder<'a> {
|
impl<'a> TableBuilder<'a> {
|
||||||
pub fn new(ui: &'a mut Ui) -> Self {
|
pub fn new(ui: &'a mut Ui) -> Self {
|
||||||
let sizing = Sizing::new();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ui,
|
ui,
|
||||||
sizing,
|
sizing: Default::default(),
|
||||||
scroll: true,
|
scroll: true,
|
||||||
striped: false,
|
striped: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
@ -102,13 +102,13 @@ impl<'a> TableBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add size hint for column
|
/// Allocate space for one column.
|
||||||
pub fn column(mut self, width: Size) -> Self {
|
pub fn column(mut self, width: Size) -> Self {
|
||||||
self.sizing.add(width);
|
self.sizing.add(width);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add size hint for several columns at once.
|
/// Allocate space for several columns at once.
|
||||||
pub fn columns(mut self, size: Size, count: usize) -> Self {
|
pub fn columns(mut self, size: Size, count: usize) -> Self {
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
self.sizing.add(size);
|
self.sizing.add(size);
|
||||||
|
@ -367,6 +367,76 @@ impl<'a> TableBody<'a> {
|
||||||
&self.widths
|
&self.widths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a single row with the given height.
|
||||||
|
///
|
||||||
|
/// If you have many thousands of row it can be more performant to instead use [`Self::rows]` or [`Self::heterogeneous_rows`].
|
||||||
|
pub fn row(&mut self, height: f32, row: impl FnOnce(TableRow<'a, '_>)) {
|
||||||
|
row(TableRow {
|
||||||
|
layout: &mut self.layout,
|
||||||
|
widths: &self.widths,
|
||||||
|
striped: self.striped && self.row_nr % 2 == 0,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.row_nr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add many rows with same height.
|
||||||
|
///
|
||||||
|
/// Is a lot more performant than adding each individual row as non visible rows must not be rendered.
|
||||||
|
///
|
||||||
|
/// If you need many rows with different heights, use [`Self::heterogeneous_rows`] instead.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
/// ```
|
||||||
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
/// use egui_extras::{TableBuilder, Size};
|
||||||
|
/// TableBuilder::new(ui)
|
||||||
|
/// .column(Size::remainder().at_least(100.0))
|
||||||
|
/// .body(|mut body| {
|
||||||
|
/// let row_height = 18.0;
|
||||||
|
/// let num_rows = 10_000;
|
||||||
|
/// body.rows(row_height, num_rows, |row_index, mut row| {
|
||||||
|
/// row.col(|ui| {
|
||||||
|
/// ui.label("First column");
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// });
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) {
|
||||||
|
let y_progress = self.y_progress();
|
||||||
|
let mut start = 0;
|
||||||
|
|
||||||
|
if y_progress > 0.0 {
|
||||||
|
start = (y_progress / height).floor() as usize;
|
||||||
|
|
||||||
|
self.add_buffer(y_progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_height = self.end_y - self.start_y;
|
||||||
|
let count = (max_height / height).ceil() as usize;
|
||||||
|
let end = rows.min(start + count);
|
||||||
|
|
||||||
|
for idx in start..end {
|
||||||
|
row(
|
||||||
|
idx,
|
||||||
|
TableRow {
|
||||||
|
layout: &mut self.layout,
|
||||||
|
widths: &self.widths,
|
||||||
|
striped: self.striped && idx % 2 == 0,
|
||||||
|
height,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows - end > 0 {
|
||||||
|
let skip_height = (rows - end) as f32 * height;
|
||||||
|
|
||||||
|
self.add_buffer(skip_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add rows with varying heights.
|
/// Add rows with varying heights.
|
||||||
///
|
///
|
||||||
/// This takes a very slight performance hit compared to [`TableBody::rows`] due to the need to
|
/// This takes a very slight performance hit compared to [`TableBody::rows`] due to the need to
|
||||||
|
@ -395,7 +465,7 @@ impl<'a> TableBody<'a> {
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn heterogeneous_rows(
|
pub fn heterogeneous_rows(
|
||||||
&mut self,
|
mut self,
|
||||||
heights: impl Iterator<Item = f32>,
|
heights: impl Iterator<Item = f32>,
|
||||||
mut populate_row: impl FnMut(usize, TableRow<'_, '_>),
|
mut populate_row: impl FnMut(usize, TableRow<'_, '_>),
|
||||||
) {
|
) {
|
||||||
|
@ -463,72 +533,6 @@ impl<'a> TableBody<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add rows with same height.
|
|
||||||
///
|
|
||||||
/// Is a lot more performant than adding each individual row as non visible rows must not be rendered
|
|
||||||
///
|
|
||||||
/// ### Example
|
|
||||||
/// ```
|
|
||||||
/// # egui::__run_test_ui(|ui| {
|
|
||||||
/// use egui_extras::{TableBuilder, Size};
|
|
||||||
/// TableBuilder::new(ui)
|
|
||||||
/// .column(Size::remainder().at_least(100.0))
|
|
||||||
/// .body(|mut body| {
|
|
||||||
/// let row_height = 18.0;
|
|
||||||
/// let num_rows = 10_000;
|
|
||||||
/// body.rows(row_height, num_rows, |row_index, mut row| {
|
|
||||||
/// row.col(|ui| {
|
|
||||||
/// ui.label("First column");
|
|
||||||
/// });
|
|
||||||
/// });
|
|
||||||
/// });
|
|
||||||
/// # });
|
|
||||||
/// ```
|
|
||||||
pub fn rows(mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow<'_, '_>)) {
|
|
||||||
let y_progress = self.y_progress();
|
|
||||||
let mut start = 0;
|
|
||||||
|
|
||||||
if y_progress > 0.0 {
|
|
||||||
start = (y_progress / height).floor() as usize;
|
|
||||||
|
|
||||||
self.add_buffer(y_progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_height = self.end_y - self.start_y;
|
|
||||||
let count = (max_height / height).ceil() as usize;
|
|
||||||
let end = rows.min(start + count);
|
|
||||||
|
|
||||||
for idx in start..end {
|
|
||||||
row(
|
|
||||||
idx,
|
|
||||||
TableRow {
|
|
||||||
layout: &mut self.layout,
|
|
||||||
widths: &self.widths,
|
|
||||||
striped: self.striped && idx % 2 == 0,
|
|
||||||
height,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows - end > 0 {
|
|
||||||
let skip_height = (rows - end) as f32 * height;
|
|
||||||
|
|
||||||
self.add_buffer(skip_height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add row with individual height
|
|
||||||
pub fn row(&mut self, height: f32, row: impl FnOnce(TableRow<'a, '_>)) {
|
|
||||||
row(TableRow {
|
|
||||||
layout: &mut self.layout,
|
|
||||||
widths: &self.widths,
|
|
||||||
striped: self.striped && self.row_nr % 2 == 0,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.row_nr += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a table row buffer of the given height to represent the non-visible portion of the
|
// Create a table row buffer of the given height to represent the non-visible portion of the
|
||||||
// table.
|
// table.
|
||||||
fn add_buffer(&mut self, height: f32) {
|
fn add_buffer(&mut self, height: f32) {
|
||||||
|
@ -564,9 +568,22 @@ impl<'a, 'b> TableRow<'a, 'b> {
|
||||||
!self.widths.is_empty(),
|
!self.widths.is_empty(),
|
||||||
"Tried using more table columns than available."
|
"Tried using more table columns than available."
|
||||||
);
|
);
|
||||||
|
let width = if self.widths.is_empty() {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
panic!("Added more `Table` columns than were allocated.");
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::error!("Added more `Table` columns than were allocated");
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
eprintln!("egui_extras: Added more `Table` columns than were allocated");
|
||||||
|
8.0 // anything will look wrong, so pick something that is obviously wrong
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let width = self.widths[0];
|
||||||
|
self.widths = &self.widths[1..];
|
||||||
|
width
|
||||||
|
};
|
||||||
|
|
||||||
let width = self.widths[0];
|
|
||||||
self.widths = &self.widths[1..];
|
|
||||||
let width = CellSize::Absolute(width);
|
let width = CellSize::Absolute(width);
|
||||||
let height = CellSize::Absolute(self.height);
|
let height = CellSize::Absolute(self.height);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue