1
0
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:
René Rössler
2021-12-17 15:33:29 +01:00
parent 5ec14867c8
commit 4e16d48dd6
16 changed files with 1552 additions and 10 deletions

View 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));
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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)
}
}

View 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::*;

View 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)
}
}

View 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()
}
}

View 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();
}
}