mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
integrate into egui
This commit is contained in:
26
egui_dynamic_grid/src/grid.rs
Normal file
26
egui_dynamic_grid/src/grid.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
mod horizontal;
|
||||
mod vertical;
|
||||
|
||||
use crate::Padding;
|
||||
use egui::Ui;
|
||||
pub use horizontal::*;
|
||||
pub use vertical::*;
|
||||
|
||||
pub struct GridBuilder<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
}
|
||||
|
||||
impl<'a> GridBuilder<'a> {
|
||||
pub fn new(ui: &'a mut Ui, padding: Padding) -> Self {
|
||||
Self { ui, padding }
|
||||
}
|
||||
|
||||
pub fn horizontal(self, horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder)) {
|
||||
horizontal_grid_builder(HorizontalGridBuilder::new(self.ui, self.padding));
|
||||
}
|
||||
|
||||
pub fn vertical(self, vertical_grid_builder: impl FnOnce(VerticalGridBuilder)) {
|
||||
vertical_grid_builder(VerticalGridBuilder::new(self.ui, self.padding));
|
||||
}
|
||||
}
|
||||
147
egui_dynamic_grid/src/grid/horizontal.rs
Normal file
147
egui_dynamic_grid/src/grid/horizontal.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use crate::{
|
||||
layout::{CellSize, LineDirection},
|
||||
sizing::Sizing,
|
||||
Layout, Padding, Size,
|
||||
};
|
||||
use egui::Ui;
|
||||
|
||||
use super::VerticalGridBuilder;
|
||||
|
||||
pub struct HorizontalGridBuilder<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
sizing: Sizing,
|
||||
}
|
||||
|
||||
impl<'a> HorizontalGridBuilder<'a> {
|
||||
pub(crate) fn new(ui: &'a mut Ui, padding: Padding) -> Self {
|
||||
let layouter = Sizing::new(
|
||||
ui.available_rect_before_wrap().width() - 2.0 * padding.outer,
|
||||
padding.inner,
|
||||
);
|
||||
|
||||
Self {
|
||||
ui,
|
||||
padding,
|
||||
sizing: layouter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn column(mut self, size: Size) -> Self {
|
||||
self.sizing.add_size(size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn columns(mut self, size: Size, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
self.sizing.add_size(size.clone());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build<F>(self, horizontal_grid: F)
|
||||
where
|
||||
F: for<'b> FnOnce(HorizontalGrid<'a, 'b>),
|
||||
{
|
||||
let widths = self.sizing.into_lengths();
|
||||
let mut layout = Layout::new(self.ui, self.padding.clone(), LineDirection::TopToBottom);
|
||||
let grid = HorizontalGrid {
|
||||
layout: &mut layout,
|
||||
padding: self.padding.clone(),
|
||||
widths,
|
||||
};
|
||||
horizontal_grid(grid);
|
||||
layout.done();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HorizontalGrid<'a, 'b> {
|
||||
layout: &'b mut Layout<'a>,
|
||||
padding: Padding,
|
||||
widths: Vec<f32>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> HorizontalGrid<'a, 'b> {
|
||||
pub fn empty(&mut self) {
|
||||
assert!(
|
||||
!self.widths.is_empty(),
|
||||
"Tried using more grid cells then available."
|
||||
);
|
||||
|
||||
self.layout.empty(
|
||||
CellSize::Absolute(self.widths.remove(0)),
|
||||
CellSize::Remainder,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn _cell(&mut self, clip: bool, add_contents: impl FnOnce(&mut Ui)) {
|
||||
assert!(
|
||||
!self.widths.is_empty(),
|
||||
"Tried using more grid cells then available."
|
||||
);
|
||||
|
||||
self.layout.add(
|
||||
CellSize::Absolute(self.widths.remove(0)),
|
||||
CellSize::Remainder,
|
||||
clip,
|
||||
add_contents,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||
self._cell(true, add_contents);
|
||||
}
|
||||
|
||||
pub fn cell_noclip(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||
self._cell(false, add_contents);
|
||||
}
|
||||
|
||||
pub fn _horizontal(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder),
|
||||
) {
|
||||
let padding = self.padding.clone();
|
||||
self._cell(clip, |ui| {
|
||||
horizontal_grid_builder(HorizontalGridBuilder::new(ui, padding));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn horizontal(&mut self, horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder)) {
|
||||
self._horizontal(true, horizontal_grid_builder)
|
||||
}
|
||||
|
||||
pub fn horizontal_noclip(
|
||||
&mut self,
|
||||
horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder),
|
||||
) {
|
||||
self._horizontal(false, horizontal_grid_builder)
|
||||
}
|
||||
|
||||
pub fn _vertical(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
vertical_grid_builder: impl FnOnce(VerticalGridBuilder),
|
||||
) {
|
||||
let padding = self.padding.clone();
|
||||
self._cell(clip, |ui| {
|
||||
vertical_grid_builder(VerticalGridBuilder::new(ui, padding));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn vertical(&mut self, vertical_grid_builder: impl FnOnce(VerticalGridBuilder)) {
|
||||
self._vertical(true, vertical_grid_builder);
|
||||
}
|
||||
|
||||
pub fn vertical_noclip(&mut self, vertical_grid_builder: impl FnOnce(VerticalGridBuilder)) {
|
||||
self._vertical(false, vertical_grid_builder);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Drop for HorizontalGrid<'a, 'b> {
|
||||
fn drop(&mut self) {
|
||||
while !self.widths.is_empty() {
|
||||
self.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
147
egui_dynamic_grid/src/grid/vertical.rs
Normal file
147
egui_dynamic_grid/src/grid/vertical.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use crate::{layout::CellSize, sizing::Sizing, Layout, Padding, Size};
|
||||
use egui::Ui;
|
||||
|
||||
use super::HorizontalGridBuilder;
|
||||
|
||||
pub struct VerticalGridBuilder<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
sizing: Sizing,
|
||||
}
|
||||
|
||||
impl<'a> VerticalGridBuilder<'a> {
|
||||
pub(crate) fn new(ui: &'a mut Ui, padding: Padding) -> Self {
|
||||
let layouter = Sizing::new(
|
||||
ui.available_rect_before_wrap().height() - 2.0 * padding.outer,
|
||||
padding.inner,
|
||||
);
|
||||
|
||||
Self {
|
||||
ui,
|
||||
padding,
|
||||
sizing: layouter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row(mut self, size: Size) -> Self {
|
||||
self.sizing.add_size(size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rows(mut self, size: Size, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
self.sizing.add_size(size.clone());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build<F>(self, vertical_grid: F)
|
||||
where
|
||||
F: for<'b> FnOnce(VerticalGrid<'a, 'b>),
|
||||
{
|
||||
let heights = self.sizing.into_lengths();
|
||||
let mut layout = Layout::new(
|
||||
self.ui,
|
||||
self.padding.clone(),
|
||||
crate::layout::LineDirection::LeftToRight,
|
||||
);
|
||||
let grid = VerticalGrid {
|
||||
layout: &mut layout,
|
||||
padding: self.padding.clone(),
|
||||
heights,
|
||||
};
|
||||
vertical_grid(grid);
|
||||
layout.done();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VerticalGrid<'a, 'b> {
|
||||
layout: &'b mut Layout<'a>,
|
||||
padding: Padding,
|
||||
heights: Vec<f32>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> VerticalGrid<'a, 'b> {
|
||||
pub fn empty(&mut self) {
|
||||
assert!(
|
||||
!self.heights.is_empty(),
|
||||
"Tried using more grid cells then available."
|
||||
);
|
||||
|
||||
self.layout.empty(
|
||||
CellSize::Remainder,
|
||||
CellSize::Absolute(self.heights.remove(0)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn _cell(&mut self, clip: bool, add_contents: impl FnOnce(&mut Ui)) {
|
||||
assert!(
|
||||
!self.heights.is_empty(),
|
||||
"Tried using more grid cells then available."
|
||||
);
|
||||
|
||||
self.layout.add(
|
||||
CellSize::Remainder,
|
||||
CellSize::Absolute(self.heights.remove(0)),
|
||||
clip,
|
||||
add_contents,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cell(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||
self._cell(true, add_contents);
|
||||
}
|
||||
|
||||
pub fn cell_noclip(&mut self, add_contents: impl FnOnce(&mut Ui)) {
|
||||
self._cell(false, add_contents);
|
||||
}
|
||||
|
||||
pub fn _horizontal(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder),
|
||||
) {
|
||||
let padding = self.padding.clone();
|
||||
self._cell(clip, |ui| {
|
||||
horizontal_grid_builder(HorizontalGridBuilder::new(ui, padding));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn horizontal(&mut self, horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder)) {
|
||||
self._horizontal(true, horizontal_grid_builder)
|
||||
}
|
||||
|
||||
pub fn horizontal_noclip(
|
||||
&mut self,
|
||||
horizontal_grid_builder: impl FnOnce(HorizontalGridBuilder),
|
||||
) {
|
||||
self._horizontal(false, horizontal_grid_builder)
|
||||
}
|
||||
|
||||
pub fn _vertical(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
vertical_grid_builder: impl FnOnce(VerticalGridBuilder),
|
||||
) {
|
||||
let padding = self.padding.clone();
|
||||
self._cell(clip, |ui| {
|
||||
vertical_grid_builder(VerticalGridBuilder::new(ui, padding));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn vertical(&mut self, vertical_grid_builder: impl FnOnce(VerticalGridBuilder)) {
|
||||
self._vertical(true, vertical_grid_builder);
|
||||
}
|
||||
|
||||
pub fn vertical_noclip(&mut self, vertical_grid_builder: impl FnOnce(VerticalGridBuilder)) {
|
||||
self._vertical(false, vertical_grid_builder);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Drop for VerticalGrid<'a, 'b> {
|
||||
fn drop(&mut self) {
|
||||
while !self.heights.is_empty() {
|
||||
self.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
163
egui_dynamic_grid/src/layout.rs
Normal file
163
egui_dynamic_grid/src/layout.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use crate::Padding;
|
||||
use egui::{Pos2, Rect, Response, Rgba, Sense, Ui, Vec2};
|
||||
|
||||
pub(crate) enum CellSize {
|
||||
Absolute(f32),
|
||||
Remainder,
|
||||
}
|
||||
|
||||
pub(crate) enum LineDirection {
|
||||
/// Cells go from top to bottom
|
||||
LeftToRight,
|
||||
/// Cells go from left to right
|
||||
TopToBottom,
|
||||
}
|
||||
|
||||
pub struct Layout<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
direction: LineDirection,
|
||||
rect: Rect,
|
||||
pos: Pos2,
|
||||
max: Pos2,
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub(crate) fn new(ui: &'a mut Ui, padding: Padding, direction: LineDirection) -> Self {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_left(rect.left() + padding.outer + padding.inner);
|
||||
rect.set_top(rect.top() + padding.outer + padding.inner);
|
||||
rect.set_width(rect.width() - 2.0 * padding.outer);
|
||||
rect.set_height(rect.height() - 2.0 * padding.outer);
|
||||
let pos = rect.left_top();
|
||||
|
||||
Self {
|
||||
ui,
|
||||
padding,
|
||||
rect,
|
||||
pos,
|
||||
max: pos,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_y(&self) -> f32 {
|
||||
self.rect.top()
|
||||
}
|
||||
|
||||
fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect {
|
||||
Rect {
|
||||
min: self.pos,
|
||||
max: Pos2 {
|
||||
x: match width {
|
||||
CellSize::Absolute(width) => self.pos.x + width,
|
||||
CellSize::Remainder => self.rect.right(),
|
||||
},
|
||||
y: match height {
|
||||
CellSize::Absolute(height) => self.pos.y + height,
|
||||
CellSize::Remainder => self.rect.bottom(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, rect: Rect) {
|
||||
match self.direction {
|
||||
LineDirection::LeftToRight => {
|
||||
self.pos.y = rect.bottom() + self.padding.inner;
|
||||
}
|
||||
LineDirection::TopToBottom => {
|
||||
self.pos.x = rect.right() + self.padding.inner;
|
||||
}
|
||||
}
|
||||
|
||||
self.max.x = self.max.x.max(rect.right() + self.padding.inner);
|
||||
self.max.y = self.max.y.max(rect.bottom() + self.padding.inner);
|
||||
}
|
||||
|
||||
pub(crate) fn empty(&mut self, width: CellSize, height: CellSize) {
|
||||
self.set_pos(self.cell_rect(&width, &height));
|
||||
}
|
||||
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
width: CellSize,
|
||||
height: CellSize,
|
||||
clip: bool,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Response {
|
||||
let rect = self.cell_rect(&width, &height);
|
||||
self.cell(rect, clip, add_contents);
|
||||
self.set_pos(rect);
|
||||
|
||||
self.ui.allocate_rect(rect, Sense::click())
|
||||
}
|
||||
|
||||
pub(crate) fn add_striped(
|
||||
&mut self,
|
||||
width: CellSize,
|
||||
height: CellSize,
|
||||
clip: bool,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Response {
|
||||
let mut rect = self.cell_rect(&width, &height);
|
||||
*rect.top_mut() -= self.padding.inner;
|
||||
*rect.left_mut() -= self.padding.inner;
|
||||
|
||||
let text_color: Rgba = self.ui.visuals().text_color().into();
|
||||
self.ui
|
||||
.painter()
|
||||
.rect_filled(rect, 0.0, text_color.multiply(0.2));
|
||||
|
||||
self.add(width, height, clip, add_contents)
|
||||
}
|
||||
|
||||
/// only needed for layouts with multiple lines, like Table
|
||||
pub fn end_line(&mut self) {
|
||||
match self.direction {
|
||||
LineDirection::LeftToRight => {
|
||||
self.pos.x = self.max.x;
|
||||
self.pos.y = self.rect.top();
|
||||
}
|
||||
LineDirection::TopToBottom => {
|
||||
self.pos.y = self.max.y;
|
||||
self.pos.x = self.rect.left();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_rect(&mut self) {
|
||||
let mut rect = self.rect;
|
||||
rect.set_right(self.max.x);
|
||||
rect.set_bottom(self.max.y);
|
||||
|
||||
self.ui
|
||||
.allocate_rect(rect, Sense::focusable_noninteractive());
|
||||
}
|
||||
|
||||
pub fn done(&mut self) {
|
||||
self.set_rect();
|
||||
}
|
||||
|
||||
pub fn done_ui(mut self) -> &'a mut Ui {
|
||||
self.set_rect();
|
||||
self.ui
|
||||
}
|
||||
|
||||
fn cell(&mut self, rect: Rect, clip: bool, add_contents: impl FnOnce(&mut Ui)) {
|
||||
let mut child_ui = self.ui.child_ui(rect, *self.ui.layout());
|
||||
|
||||
if clip {
|
||||
let mut clip_rect = child_ui.clip_rect();
|
||||
clip_rect.min = clip_rect
|
||||
.min
|
||||
.max(rect.min - Vec2::new(self.padding.inner, self.padding.inner));
|
||||
clip_rect.max = clip_rect
|
||||
.max
|
||||
.min(rect.max + Vec2::new(self.padding.inner, self.padding.inner));
|
||||
child_ui.set_clip_rect(clip_rect);
|
||||
}
|
||||
|
||||
add_contents(&mut child_ui)
|
||||
}
|
||||
}
|
||||
11
egui_dynamic_grid/src/lib.rs
Normal file
11
egui_dynamic_grid/src/lib.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod grid;
|
||||
mod layout;
|
||||
mod padding;
|
||||
mod sizing;
|
||||
mod table;
|
||||
|
||||
pub use grid::*;
|
||||
pub(crate) use layout::Layout;
|
||||
pub use padding::Padding;
|
||||
pub use sizing::Size;
|
||||
pub use table::*;
|
||||
27
egui_dynamic_grid/src/padding.rs
Normal file
27
egui_dynamic_grid/src/padding.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Padding {
|
||||
pub(crate) inner: f32,
|
||||
pub(crate) outer: f32,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn new(inner: f32, outer: f32) -> Self {
|
||||
Self { inner, outer }
|
||||
}
|
||||
|
||||
pub fn inner(mut self, inner: f32) -> Self {
|
||||
self.inner = inner;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn outer(mut self, outer: f32) -> Self {
|
||||
self.outer = outer;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Padding {
|
||||
fn default() -> Self {
|
||||
Self::new(5.0, 10.0)
|
||||
}
|
||||
}
|
||||
90
egui_dynamic_grid/src/sizing.rs
Normal file
90
egui_dynamic_grid/src/sizing.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Size {
|
||||
/// in points
|
||||
Absolute(f32),
|
||||
/// 0.0 to 1.0
|
||||
Relative(f32),
|
||||
RelativeMinimum {
|
||||
/// 0.0 to 1.0
|
||||
relative: f32,
|
||||
/// in points
|
||||
minimum: f32,
|
||||
},
|
||||
/// multiple remainders get each the same space
|
||||
Remainder,
|
||||
/// multiple remainders get each the same space, at least the minimum
|
||||
RemainderMinimum(f32),
|
||||
}
|
||||
|
||||
pub struct Sizing {
|
||||
length: f32,
|
||||
inner_padding: f32,
|
||||
sizes: Vec<Size>,
|
||||
}
|
||||
|
||||
impl Sizing {
|
||||
pub fn new(length: f32, inner_padding: f32) -> Self {
|
||||
Self {
|
||||
length,
|
||||
inner_padding,
|
||||
sizes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_size(&mut self, size: Size) {
|
||||
self.sizes.push(size);
|
||||
}
|
||||
|
||||
pub fn into_lengths(self) -> Vec<f32> {
|
||||
let mut remainders = 0;
|
||||
let length = self.length;
|
||||
let sum_non_remainder = self
|
||||
.sizes
|
||||
.iter()
|
||||
.map(|size| match size {
|
||||
Size::Absolute(absolute) => *absolute,
|
||||
Size::Relative(relative) => {
|
||||
assert!(*relative > 0.0, "Below 0.0 is not allowed.");
|
||||
assert!(*relative <= 1.0, "Above 1.0 is not allowed.");
|
||||
length * relative
|
||||
}
|
||||
Size::RelativeMinimum { relative, minimum } => {
|
||||
assert!(*relative > 0.0, "Below 0.0 is not allowed.");
|
||||
assert!(*relative <= 1.0, "Above 1.0 is not allowed.");
|
||||
minimum.max(length * relative)
|
||||
}
|
||||
Size::Remainder | Size::RemainderMinimum(..) => {
|
||||
remainders += 1;
|
||||
0.0
|
||||
}
|
||||
})
|
||||
.sum::<f32>()
|
||||
+ self.inner_padding * (self.sizes.len() + 1) as f32;
|
||||
|
||||
let avg_remainder_length = if remainders == 0 {
|
||||
0.0
|
||||
} else {
|
||||
let mut remainder_length = length - sum_non_remainder;
|
||||
let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32);
|
||||
self.sizes.iter().for_each(|size| {
|
||||
if let Size::RemainderMinimum(minimum) = size {
|
||||
if *minimum > avg_remainder_length {
|
||||
remainder_length -= minimum - avg_remainder_length;
|
||||
}
|
||||
}
|
||||
});
|
||||
0.0f32.max(remainder_length / remainders as f32)
|
||||
};
|
||||
|
||||
self.sizes
|
||||
.into_iter()
|
||||
.map(|size| match size {
|
||||
Size::Absolute(absolute) => absolute,
|
||||
Size::Relative(relative) => length * relative,
|
||||
Size::RelativeMinimum { relative, minimum } => minimum.max(length * relative),
|
||||
Size::Remainder => avg_remainder_length,
|
||||
Size::RemainderMinimum(minimum) => minimum.max(avg_remainder_length),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
270
egui_dynamic_grid/src/table.rs
Normal file
270
egui_dynamic_grid/src/table.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use crate::{
|
||||
layout::{CellSize, LineDirection},
|
||||
sizing::Sizing,
|
||||
Layout, Padding, Size,
|
||||
};
|
||||
|
||||
use egui::{Response, Ui};
|
||||
use std::cmp;
|
||||
|
||||
pub struct TableBuilder<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
sizing: Sizing,
|
||||
scroll: bool,
|
||||
striped: bool,
|
||||
}
|
||||
|
||||
impl<'a> TableBuilder<'a> {
|
||||
pub fn new(ui: &'a mut Ui, padding: Padding) -> Self {
|
||||
let sizing = Sizing::new(
|
||||
ui.available_rect_before_wrap().width() - 2.0 * padding.outer,
|
||||
padding.inner,
|
||||
);
|
||||
|
||||
Self {
|
||||
ui,
|
||||
padding,
|
||||
sizing,
|
||||
scroll: true,
|
||||
striped: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll(mut self, scroll: bool) -> Self {
|
||||
self.scroll = scroll;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn striped(mut self, striped: bool) -> Self {
|
||||
self.striped = striped;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn column(mut self, width: Size) -> Self {
|
||||
self.sizing.add_size(width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn columns(mut self, size: Size, count: usize) -> Self {
|
||||
for _ in 0..count {
|
||||
self.sizing.add_size(size.clone());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn header<F>(self, height: f32, header: F) -> Table<'a>
|
||||
where
|
||||
F: for<'b> FnOnce(TableRow<'a, 'b>),
|
||||
{
|
||||
let widths = self.sizing.into_lengths();
|
||||
let mut layout = Layout::new(self.ui, self.padding.clone(), LineDirection::TopToBottom);
|
||||
{
|
||||
let row = TableRow {
|
||||
layout: &mut layout,
|
||||
widths: widths.clone(),
|
||||
striped: false,
|
||||
height,
|
||||
clicked: false,
|
||||
};
|
||||
header(row);
|
||||
}
|
||||
let ui = layout.done_ui();
|
||||
|
||||
Table {
|
||||
ui,
|
||||
padding: self.padding,
|
||||
widths,
|
||||
scroll: self.scroll,
|
||||
striped: self.striped,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn body<F>(self, body: F)
|
||||
where
|
||||
F: for<'b> FnOnce(TableBody<'b>),
|
||||
{
|
||||
let widths = self.sizing.into_lengths();
|
||||
|
||||
Table {
|
||||
ui: self.ui,
|
||||
padding: self.padding,
|
||||
widths,
|
||||
scroll: self.scroll,
|
||||
striped: self.striped,
|
||||
}
|
||||
.body(body)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Table<'a> {
|
||||
ui: &'a mut Ui,
|
||||
padding: Padding,
|
||||
widths: Vec<f32>,
|
||||
scroll: bool,
|
||||
striped: bool,
|
||||
}
|
||||
|
||||
impl<'a> Table<'a> {
|
||||
pub fn body<F>(self, body: F)
|
||||
where
|
||||
F: for<'b> FnOnce(TableBody<'b>),
|
||||
{
|
||||
let padding = self.padding;
|
||||
let ui = self.ui;
|
||||
let widths = self.widths;
|
||||
let striped = self.striped;
|
||||
let start_y = ui.available_rect_before_wrap().top();
|
||||
let end_y = ui.available_rect_before_wrap().bottom();
|
||||
|
||||
egui::ScrollArea::new([false, self.scroll]).show(ui, move |ui| {
|
||||
let layout = Layout::new(ui, padding, LineDirection::TopToBottom);
|
||||
|
||||
body(TableBody {
|
||||
layout,
|
||||
widths,
|
||||
striped,
|
||||
odd: true,
|
||||
start_y,
|
||||
end_y,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableBody<'b> {
|
||||
layout: Layout<'b>,
|
||||
widths: Vec<f32>,
|
||||
striped: bool,
|
||||
odd: bool,
|
||||
start_y: f32,
|
||||
end_y: f32,
|
||||
}
|
||||
|
||||
impl<'a> TableBody<'a> {
|
||||
pub fn rows(&mut self, height: f32, rows: usize, mut row: impl FnMut(usize, TableRow)) {
|
||||
let delta = self.layout.current_y() - self.start_y;
|
||||
let mut start = 0;
|
||||
|
||||
if delta < 0.0 {
|
||||
start = (-delta / height).floor() as usize;
|
||||
|
||||
let skip_height = start as f32 * height;
|
||||
let mut row = TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: self.widths.clone(),
|
||||
striped: self.striped && self.odd,
|
||||
height: skip_height,
|
||||
clicked: false,
|
||||
};
|
||||
|
||||
row.col(|_| {});
|
||||
}
|
||||
|
||||
let max_height = self.end_y - self.start_y;
|
||||
let count = (max_height / height).ceil() as usize;
|
||||
let end = cmp::min(start + count, rows);
|
||||
|
||||
if start % 2 != 0 {
|
||||
self.odd = false;
|
||||
}
|
||||
|
||||
for idx in start..end {
|
||||
row(
|
||||
idx,
|
||||
TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: self.widths.clone(),
|
||||
striped: self.striped && self.odd,
|
||||
height,
|
||||
clicked: false,
|
||||
},
|
||||
);
|
||||
self.odd = !self.odd;
|
||||
}
|
||||
|
||||
if rows - end > 0 {
|
||||
let skip_height = (rows - end) as f32 * height;
|
||||
|
||||
let mut row = TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: self.widths.clone(),
|
||||
striped: self.striped && self.odd,
|
||||
height: skip_height,
|
||||
clicked: false,
|
||||
};
|
||||
|
||||
row.col(|_| {});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row<'b>(&'b mut self, height: f32, row: impl FnOnce(TableRow<'a, 'b>)) {
|
||||
row(TableRow {
|
||||
layout: &mut self.layout,
|
||||
widths: self.widths.clone(),
|
||||
striped: self.striped && self.odd,
|
||||
height,
|
||||
clicked: false,
|
||||
});
|
||||
|
||||
self.odd = !self.odd;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TableBody<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.layout.done();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableRow<'a, 'b> {
|
||||
layout: &'b mut Layout<'a>,
|
||||
widths: Vec<f32>,
|
||||
striped: bool,
|
||||
height: f32,
|
||||
clicked: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b> TableRow<'a, 'b> {
|
||||
pub fn clicked(&self) -> bool {
|
||||
self.clicked
|
||||
}
|
||||
|
||||
fn _col(&mut self, clip: bool, add_contents: impl FnOnce(&mut Ui)) -> Response {
|
||||
assert!(
|
||||
!self.widths.is_empty(),
|
||||
"Tried using more table columns then available."
|
||||
);
|
||||
|
||||
let width = CellSize::Absolute(self.widths.remove(0));
|
||||
let height = CellSize::Absolute(self.height);
|
||||
|
||||
let response;
|
||||
|
||||
if self.striped {
|
||||
response = self.layout.add_striped(width, height, clip, add_contents);
|
||||
} else {
|
||||
response = self.layout.add(width, height, clip, add_contents);
|
||||
}
|
||||
|
||||
if response.clicked() {
|
||||
self.clicked = true;
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
pub fn col(&mut self, add_contents: impl FnOnce(&mut Ui)) -> Response {
|
||||
self._col(true, add_contents)
|
||||
}
|
||||
|
||||
pub fn col_noclip(&mut self, add_contents: impl FnOnce(&mut Ui)) -> Response {
|
||||
self._col(false, add_contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Drop for TableRow<'a, 'b> {
|
||||
fn drop(&mut self) {
|
||||
self.layout.end_line();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user