integrate into egui
This commit is contained in:
parent
5ec14867c8
commit
4e16d48dd6
16 changed files with 1552 additions and 10 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -890,6 +890,16 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_datepicker"
|
||||||
|
version = "0.15.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"egui",
|
||||||
|
"egui_dynamic_grid",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_demo_app"
|
name = "egui_demo_app"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
@ -905,6 +915,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"criterion",
|
"criterion",
|
||||||
"egui",
|
"egui",
|
||||||
|
"egui_datepicker",
|
||||||
"ehttp",
|
"ehttp",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
"epi",
|
"epi",
|
||||||
|
@ -914,6 +925,13 @@ dependencies = [
|
||||||
"unicode_names2",
|
"unicode_names2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui_dynamic_grid"
|
||||||
|
version = "0.15.0"
|
||||||
|
dependencies = [
|
||||||
|
"egui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glium"
|
name = "egui_glium"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
|
"egui_datepicker",
|
||||||
"egui_demo_app",
|
"egui_demo_app",
|
||||||
"egui_demo_lib",
|
"egui_demo_lib",
|
||||||
|
"egui_dynamic_grid",
|
||||||
"egui_glium",
|
"egui_glium",
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
"egui_web",
|
"egui_web",
|
||||||
|
|
10
egui_datepicker/Cargo.toml
Normal file
10
egui_datepicker/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "egui_datepicker"
|
||||||
|
version = "0.15.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "0.4"
|
||||||
|
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||||
|
egui_dynamic_grid = { version = "0.15.0", path = "../egui_dynamic_grid" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
569
egui_datepicker/src/lib.rs
Normal file
569
egui_datepicker/src/lib.rs
Normal file
|
@ -0,0 +1,569 @@
|
||||||
|
use chrono::{Date, Datelike, Duration, NaiveDate, Utc, Weekday};
|
||||||
|
use egui::{
|
||||||
|
Align, Area, Button, Color32, ComboBox, Direction, Frame, Id, Key, Label, Layout, Order,
|
||||||
|
RichText, Ui, Widget,
|
||||||
|
};
|
||||||
|
use egui_dynamic_grid::{GridBuilder, Padding, Size, TableBuilder};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Week {
|
||||||
|
number: u8,
|
||||||
|
days: Vec<Date<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn month_data(year: i32, month: u32) -> Vec<Week> {
|
||||||
|
let first = Date::from_utc(NaiveDate::from_ymd(year, month, 1), Utc);
|
||||||
|
let mut start = first;
|
||||||
|
while start.weekday() != Weekday::Mon {
|
||||||
|
start = start.checked_sub_signed(Duration::days(1)).unwrap();
|
||||||
|
}
|
||||||
|
let mut weeks = vec![];
|
||||||
|
let mut week = vec![];
|
||||||
|
while start < first || start.month() == first.month() || start.weekday() != Weekday::Mon {
|
||||||
|
week.push(start);
|
||||||
|
|
||||||
|
if start.weekday() == Weekday::Sun {
|
||||||
|
weeks.push(Week {
|
||||||
|
number: start.iso_week().week() as u8,
|
||||||
|
days: week.drain(..).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
start = start.checked_add_signed(Duration::days(1)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
weeks
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||||
|
struct DatePickerPopupState {
|
||||||
|
year: i32,
|
||||||
|
month: u32,
|
||||||
|
day: u32,
|
||||||
|
setup: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatePickerPopupState {
|
||||||
|
fn last_day_of_month(&self) -> u32 {
|
||||||
|
let date: Date<Utc> = Date::from_utc(NaiveDate::from_ymd(self.year, self.month, 1), Utc);
|
||||||
|
date.with_day(31)
|
||||||
|
.map(|_| 31)
|
||||||
|
.or_else(|| date.with_day(30).map(|_| 30))
|
||||||
|
.or_else(|| date.with_day(29).map(|_| 29))
|
||||||
|
.unwrap_or(28)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DatePickerPopup<'a> {
|
||||||
|
selection: &'a mut Date<Utc>,
|
||||||
|
button_id: Id,
|
||||||
|
combo_boxes: bool,
|
||||||
|
arrows: bool,
|
||||||
|
calendar: bool,
|
||||||
|
calendar_week: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DatePickerPopup<'a> {
|
||||||
|
fn draw(&mut self, ui: &mut Ui) {
|
||||||
|
let id = ui.make_persistent_id("date_picker");
|
||||||
|
let today = chrono::offset::Utc::now().date();
|
||||||
|
let mut popup_state = ui
|
||||||
|
.memory()
|
||||||
|
.data
|
||||||
|
.get_persisted::<DatePickerPopupState>(id)
|
||||||
|
.unwrap_or_default();
|
||||||
|
if !popup_state.setup {
|
||||||
|
popup_state.year = self.selection.year();
|
||||||
|
popup_state.month = self.selection.month();
|
||||||
|
popup_state.day = self.selection.day();
|
||||||
|
popup_state.setup = true;
|
||||||
|
ui.memory().data.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let weeks = month_data(popup_state.year, popup_state.month);
|
||||||
|
let mut close = false;
|
||||||
|
let height = 20.0;
|
||||||
|
GridBuilder::new(ui, Padding::new(2.0, 0.0)).vertical(|builder| {
|
||||||
|
builder
|
||||||
|
.rows(
|
||||||
|
Size::Absolute(height),
|
||||||
|
match (self.combo_boxes, self.arrows) {
|
||||||
|
(true, true) => 2,
|
||||||
|
(true, false) | (false, true) => 1,
|
||||||
|
(false, false) => 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.rows(
|
||||||
|
Size::Absolute(2.0 + (height + 2.0) * weeks.len() as f32),
|
||||||
|
if self.calendar { 1 } else { 0 },
|
||||||
|
)
|
||||||
|
.row(Size::Absolute(height))
|
||||||
|
.build(|mut grid| {
|
||||||
|
if self.combo_boxes {
|
||||||
|
grid.horizontal_noclip(|builder| {
|
||||||
|
builder.columns(Size::Remainder, 3).build(|mut grid| {
|
||||||
|
grid.cell_noclip(|ui| {
|
||||||
|
ComboBox::from_id_source("date_picker_year")
|
||||||
|
.selected_text(format!("{}", popup_state.year))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for year in today.year() - 5..today.year() + 10 {
|
||||||
|
if ui
|
||||||
|
.selectable_value(
|
||||||
|
&mut popup_state.year,
|
||||||
|
year,
|
||||||
|
format!("{}", year),
|
||||||
|
)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
grid.cell_noclip(|ui| {
|
||||||
|
ComboBox::from_id_source("date_picker_month")
|
||||||
|
.selected_text(format!("{}", popup_state.month))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for month in 1..=12 {
|
||||||
|
if ui
|
||||||
|
.selectable_value(
|
||||||
|
&mut popup_state.month,
|
||||||
|
month,
|
||||||
|
format!("{}", month),
|
||||||
|
)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
grid.cell_noclip(|ui| {
|
||||||
|
ComboBox::from_id_source("date_picker_day")
|
||||||
|
.selected_text(format!("{}", popup_state.day))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for day in 1..=popup_state.last_day_of_month() {
|
||||||
|
if ui
|
||||||
|
.selectable_value(
|
||||||
|
&mut popup_state.day,
|
||||||
|
day,
|
||||||
|
format!("{}", day),
|
||||||
|
)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.arrows {
|
||||||
|
grid.horizontal(|builder| {
|
||||||
|
builder.columns(Size::Remainder, 6).build(|mut grid| {
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui
|
||||||
|
.button("<<<")
|
||||||
|
.on_hover_text("substract one year")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
popup_state.year -= 1;
|
||||||
|
popup_state.day = popup_state
|
||||||
|
.day
|
||||||
|
.min(popup_state.last_day_of_month());
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui
|
||||||
|
.button("<<")
|
||||||
|
.on_hover_text("substract one month")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
popup_state.month -= 1;
|
||||||
|
if popup_state.month == 0 {
|
||||||
|
popup_state.month = 12;
|
||||||
|
popup_state.year -= 1;
|
||||||
|
}
|
||||||
|
popup_state.day = popup_state
|
||||||
|
.day
|
||||||
|
.min(popup_state.last_day_of_month());
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui
|
||||||
|
.button("<")
|
||||||
|
.on_hover_text("substract one day")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
popup_state.day -= 1;
|
||||||
|
if popup_state.day == 0 {
|
||||||
|
popup_state.month -= 1;
|
||||||
|
if popup_state.month == 0 {
|
||||||
|
popup_state.year -= 1;
|
||||||
|
popup_state.month = 12;
|
||||||
|
}
|
||||||
|
popup_state.day =
|
||||||
|
popup_state.last_day_of_month();
|
||||||
|
}
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui.button(">").on_hover_text("add one day").clicked()
|
||||||
|
{
|
||||||
|
popup_state.day += 1;
|
||||||
|
if popup_state.day > popup_state.last_day_of_month()
|
||||||
|
{
|
||||||
|
popup_state.day = 1;
|
||||||
|
popup_state.month += 1;
|
||||||
|
if popup_state.month > 12 {
|
||||||
|
popup_state.month = 1;
|
||||||
|
popup_state.year += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui
|
||||||
|
.button(">>")
|
||||||
|
.on_hover_text("add one month")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
popup_state.month += 1;
|
||||||
|
if popup_state.month > 12 {
|
||||||
|
popup_state.month = 1;
|
||||||
|
popup_state.year += 1;
|
||||||
|
}
|
||||||
|
popup_state.day = popup_state
|
||||||
|
.day
|
||||||
|
.min(popup_state.last_day_of_month());
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui
|
||||||
|
.button(">>>")
|
||||||
|
.on_hover_text("add one year")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
popup_state.year += 1;
|
||||||
|
popup_state.day = popup_state
|
||||||
|
.day
|
||||||
|
.min(popup_state.last_day_of_month());
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.insert_persisted(id, popup_state.clone());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.calendar {
|
||||||
|
grid.cell(|ui| {
|
||||||
|
TableBuilder::new(ui, Padding::new(2.0, 0.0))
|
||||||
|
.scroll(false)
|
||||||
|
.columns(Size::Remainder, if self.calendar_week { 8 } else { 7 })
|
||||||
|
.header(height, |mut header| {
|
||||||
|
if self.calendar_week {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::centered_and_justified(Direction::TopDown),
|
||||||
|
|ui| {
|
||||||
|
ui.add(Label::new("Week"));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for name in ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"] {
|
||||||
|
header.col(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::centered_and_justified(Direction::TopDown),
|
||||||
|
|ui| {
|
||||||
|
ui.add(Label::new(name));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.body(|mut body| {
|
||||||
|
for week in weeks {
|
||||||
|
body.row(height, |mut row| {
|
||||||
|
if self.calendar_week {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(Label::new(format!("{}", week.number)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for day in week.days {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::top_down_justified(Align::Center),
|
||||||
|
|ui| {
|
||||||
|
let fill_color = if popup_state.year
|
||||||
|
== day.year()
|
||||||
|
&& popup_state.month == day.month()
|
||||||
|
&& popup_state.day == day.day()
|
||||||
|
{
|
||||||
|
Color32::DARK_BLUE
|
||||||
|
} else if day.weekday() == Weekday::Sat
|
||||||
|
|| day.weekday() == Weekday::Sun
|
||||||
|
{
|
||||||
|
Color32::DARK_RED
|
||||||
|
} else {
|
||||||
|
Color32::BLACK
|
||||||
|
};
|
||||||
|
let text_color = if day == today {
|
||||||
|
Color32::RED
|
||||||
|
} else if day.month()
|
||||||
|
== popup_state.month
|
||||||
|
{
|
||||||
|
Color32::WHITE
|
||||||
|
} else {
|
||||||
|
Color32::from_gray(80)
|
||||||
|
};
|
||||||
|
|
||||||
|
let button = Button::new(
|
||||||
|
RichText::new(format!(
|
||||||
|
"{}",
|
||||||
|
day.day()
|
||||||
|
))
|
||||||
|
.color(text_color),
|
||||||
|
)
|
||||||
|
.fill(fill_color);
|
||||||
|
|
||||||
|
if ui.add(button).clicked() {
|
||||||
|
popup_state.year = day.year();
|
||||||
|
popup_state.month = day.month();
|
||||||
|
popup_state.day = day.day();
|
||||||
|
ui.memory().data.insert_persisted(
|
||||||
|
id,
|
||||||
|
popup_state.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.horizontal(|builder| {
|
||||||
|
builder.columns(Size::Remainder, 3).build(|mut grid| {
|
||||||
|
grid.empty();
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
|
||||||
|
if ui.button("Abbrechen").clicked() {
|
||||||
|
close = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
grid.cell(|ui| {
|
||||||
|
ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
|
||||||
|
if ui.button("Speichern").clicked() {
|
||||||
|
*self.selection = Date::from_utc(
|
||||||
|
NaiveDate::from_ymd(
|
||||||
|
popup_state.year,
|
||||||
|
popup_state.month,
|
||||||
|
popup_state.day,
|
||||||
|
),
|
||||||
|
Utc,
|
||||||
|
);
|
||||||
|
close = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if close {
|
||||||
|
popup_state.setup = false;
|
||||||
|
ui.memory().data.insert_persisted(id, popup_state);
|
||||||
|
|
||||||
|
ui.memory()
|
||||||
|
.data
|
||||||
|
.get_persisted_mut_or_default::<DatePickerButtonState>(self.button_id)
|
||||||
|
.picker_visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||||
|
struct DatePickerButtonState {
|
||||||
|
picker_visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DatePickerButton<'a> {
|
||||||
|
selection: &'a mut Date<Utc>,
|
||||||
|
id_source: Option<&'a str>,
|
||||||
|
combo_boxes: bool,
|
||||||
|
arrows: bool,
|
||||||
|
calendar: bool,
|
||||||
|
calendar_week: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DatePickerButton<'a> {
|
||||||
|
pub fn new(selection: &'a mut Date<Utc>) -> Self {
|
||||||
|
Self {
|
||||||
|
selection,
|
||||||
|
id_source: None,
|
||||||
|
combo_boxes: true,
|
||||||
|
arrows: true,
|
||||||
|
calendar: true,
|
||||||
|
calendar_week: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add id source.
|
||||||
|
/// Must be set if multiple date picker buttons are in the same Ui.
|
||||||
|
pub fn id_source(mut self, id_source: &'a str) -> Self {
|
||||||
|
self.id_source = Some(id_source);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show combo boxes in date picker popup. (Default: true)
|
||||||
|
pub fn combo_boxes(mut self, combo_boxes: bool) -> Self {
|
||||||
|
self.combo_boxes = combo_boxes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show arrows in date picker popup. (Default: true)
|
||||||
|
pub fn arrows(mut self, arrows: bool) -> Self {
|
||||||
|
self.arrows = arrows;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show calendar in date picker popup. (Default: true)
|
||||||
|
pub fn calendar(mut self, calendar: bool) -> Self {
|
||||||
|
self.calendar = calendar;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show calendar week in date picker popup. (Default: true)
|
||||||
|
pub fn calendar_week(mut self, week: bool) -> Self {
|
||||||
|
self.calendar_week = week;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for DatePickerButton<'a> {
|
||||||
|
fn ui(self, ui: &mut Ui) -> egui::Response {
|
||||||
|
let id = ui.make_persistent_id(&self.id_source);
|
||||||
|
let mut button_state = ui
|
||||||
|
.memory()
|
||||||
|
.data
|
||||||
|
.get_persisted::<DatePickerButtonState>(id)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut text = RichText::new(format!("{} 📆", self.selection.format("%d.%m.%Y")));
|
||||||
|
let visuals = ui.visuals().widgets.open;
|
||||||
|
if button_state.picker_visible {
|
||||||
|
text = text.color(visuals.text_color());
|
||||||
|
}
|
||||||
|
let mut button = Button::new(text);
|
||||||
|
if button_state.picker_visible {
|
||||||
|
button = button.fill(visuals.bg_fill).stroke(visuals.bg_stroke);
|
||||||
|
}
|
||||||
|
let button_response = ui.add(button);
|
||||||
|
if button_response.clicked() {
|
||||||
|
button_state.picker_visible = true;
|
||||||
|
ui.memory().data.insert_persisted(id, button_state.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if button_state.picker_visible {
|
||||||
|
let width = 333.0;
|
||||||
|
let mut pos = button_response.rect.left_bottom();
|
||||||
|
let width_with_padding =
|
||||||
|
width + ui.style().spacing.item_spacing.x + ui.style().spacing.window_padding.x;
|
||||||
|
if pos.x + width_with_padding > ui.clip_rect().right() {
|
||||||
|
pos.x = button_response.rect.right() - width_with_padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
let area_response = Area::new(ui.make_persistent_id(&self.id_source))
|
||||||
|
.order(Order::Foreground)
|
||||||
|
.fixed_pos(pos)
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
let frame = Frame::popup(ui.style());
|
||||||
|
frame.show(ui, |ui| {
|
||||||
|
ui.set_min_width(width);
|
||||||
|
ui.set_max_width(width);
|
||||||
|
|
||||||
|
DatePickerPopup {
|
||||||
|
selection: self.selection,
|
||||||
|
button_id: id,
|
||||||
|
combo_boxes: self.combo_boxes,
|
||||||
|
arrows: self.arrows,
|
||||||
|
calendar: self.calendar,
|
||||||
|
calendar_week: self.calendar_week,
|
||||||
|
}
|
||||||
|
.draw(ui)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.response;
|
||||||
|
|
||||||
|
if !button_response.clicked()
|
||||||
|
&& (ui.input().key_pressed(Key::Escape) || area_response.clicked_elsewhere())
|
||||||
|
{
|
||||||
|
button_state.picker_visible = false;
|
||||||
|
ui.memory().data.insert_persisted(id, button_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button_response
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,12 +10,7 @@ readme = "README.md"
|
||||||
repository = "https://github.com/emilk/egui/tree/master/egui_demo_lib"
|
repository = "https://github.com/emilk/egui/tree/master/egui_demo_lib"
|
||||||
categories = ["gui", "graphics"]
|
categories = ["gui", "graphics"]
|
||||||
keywords = ["glium", "egui", "gui", "gamedev"]
|
keywords = ["glium", "egui", "gui", "gamedev"]
|
||||||
include = [
|
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||||
"../LICENSE-APACHE",
|
|
||||||
"../LICENSE-MIT",
|
|
||||||
"**/*.rs",
|
|
||||||
"Cargo.toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
@ -25,6 +20,7 @@ all-features = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||||
epi = { version = "0.15.0", path = "../epi" }
|
epi = { version = "0.15.0", path = "../epi" }
|
||||||
|
egui_datepicker = { version = "0.15.0", path = "../egui_datepicker", optional = true }
|
||||||
|
|
||||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
|
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
|
||||||
enum-map = { version = "1", features = ["serde"] }
|
enum-map = { version = "1", features = ["serde"] }
|
||||||
|
@ -32,10 +28,15 @@ unicode_names2 = { version = "0.4.0", default-features = false }
|
||||||
|
|
||||||
# feature "http":
|
# feature "http":
|
||||||
ehttp = { version = "0.1.0", optional = true }
|
ehttp = { version = "0.1.0", optional = true }
|
||||||
image = { version = "0.23", default-features = false, features = ["jpeg", "png"], optional = true }
|
image = { version = "0.23", default-features = false, features = [
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
# feature "syntax_highlighting":
|
# feature "syntax_highlighting":
|
||||||
syntect = { version = "4", default-features = false, features = ["default-fancy"], optional = true }
|
syntect = { version = "4", default-features = false, features = [
|
||||||
|
"default-fancy",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
# feature "persistence":
|
# feature "persistence":
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
@ -44,7 +45,8 @@ serde = { version = "1", features = ["derive"], optional = true }
|
||||||
criterion = { version = "0.3", default-features = false }
|
criterion = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["chrono"]
|
default = ["datetime"]
|
||||||
|
datetime = ["egui_datepicker", "chrono"]
|
||||||
|
|
||||||
# Enable additional checks if debug assertions are enabled (debug builds).
|
# Enable additional checks if debug assertions are enabled (debug builds).
|
||||||
extra_debug_asserts = ["egui/extra_debug_asserts"]
|
extra_debug_asserts = ["egui/extra_debug_asserts"]
|
||||||
|
@ -53,7 +55,7 @@ extra_asserts = ["egui/extra_asserts"]
|
||||||
|
|
||||||
http = ["ehttp", "image"]
|
http = ["ehttp", "image"]
|
||||||
persistence = ["egui/persistence", "epi/persistence", "serde"]
|
persistence = ["egui/persistence", "epi/persistence", "serde"]
|
||||||
serialize = ["egui/serialize", "serde"]
|
serialize = ["egui/serialize", "serde"]
|
||||||
syntax_highlighting = ["syntect"]
|
syntax_highlighting = ["syntect"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
use egui_datepicker::DatePickerButton;
|
||||||
|
|
||||||
|
#[cfg(feature = "datetime")]
|
||||||
|
mod serde_date_format;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
enum Enum {
|
enum Enum {
|
||||||
|
@ -17,6 +22,9 @@ pub struct WidgetGallery {
|
||||||
string: String,
|
string: String,
|
||||||
color: egui::Color32,
|
color: egui::Color32,
|
||||||
animate_progress_bar: bool,
|
animate_progress_bar: bool,
|
||||||
|
#[cfg(feature = "datetime")]
|
||||||
|
#[serde(with = "serde_date_format")]
|
||||||
|
date: chrono::Date<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WidgetGallery {
|
impl Default for WidgetGallery {
|
||||||
|
@ -30,6 +38,8 @@ impl Default for WidgetGallery {
|
||||||
string: Default::default(),
|
string: Default::default(),
|
||||||
color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5),
|
color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5),
|
||||||
animate_progress_bar: false,
|
animate_progress_bar: false,
|
||||||
|
#[cfg(feature = "datetime")]
|
||||||
|
date: chrono::offset::Utc::now().date(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +109,7 @@ impl WidgetGallery {
|
||||||
string,
|
string,
|
||||||
color,
|
color,
|
||||||
animate_progress_bar,
|
animate_progress_bar,
|
||||||
|
date,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.add(doc_link_label("Label", "label,heading"));
|
ui.add(doc_link_label("Label", "label,heading"));
|
||||||
|
@ -195,6 +206,13 @@ impl WidgetGallery {
|
||||||
}
|
}
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
||||||
|
#[cfg(feature = "datetime")]
|
||||||
|
{
|
||||||
|
ui.add(doc_link_label("DatePickerButton", "DatePickerButton"));
|
||||||
|
ui.add(DatePickerButton::new(date));
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
|
||||||
ui.add(doc_link_label("Separator", "separator"));
|
ui.add(doc_link_label("Separator", "separator"));
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
use chrono::{Date, NaiveDate, Utc};
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
const FORMAT: &str = "%d.%m.%Y";
|
||||||
|
|
||||||
|
pub fn serialize<S>(date: &Date<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let s = format!("{}", date.format(FORMAT));
|
||||||
|
serializer.serialize_str(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Date<Utc>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
NaiveDate::parse_from_str(&s, FORMAT)
|
||||||
|
.map(|naive_date| Date::from_utc(naive_date, Utc))
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
19
egui_dynamic_grid/Cargo.toml
Normal file
19
egui_dynamic_grid/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "egui_dynamic_grid"
|
||||||
|
version = "0.15.0"
|
||||||
|
edition = "2018"
|
||||||
|
description = "Dynamic grid and table for egui"
|
||||||
|
authors = [
|
||||||
|
"René Rössler <rene@freshx.de>",
|
||||||
|
"Dominik Rössler <dominik@freshx.de>",
|
||||||
|
]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
homepage = "https://github.com/emilk/egui/tree/master/egui_dynamic_grid"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/emilk/egui/tree/master/egui_dynamic_grid"
|
||||||
|
categories = ["gui", "graphics"]
|
||||||
|
keywords = ["glium", "egui", "gui", "gamedev"]
|
||||||
|
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue