use egui::Rangef;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ColumnFlags: u8 {
const RESIZABLE = 1 << 0;
const AUTO_SIZE_THIS_FRAME = 1 << 1;
const AUTO_FIT = 1 << 2;
const REMAINDER = 1 << 3;
}
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub struct Column {
pub current: f32,
pub range: Rangef,
pub id: Option<egui::Id>,
pub flags: ColumnFlags,
}
impl Default for Column {
fn default() -> Self {
Self {
current: 100.0,
range: Rangef::new(4.0, f32::INFINITY),
id: None,
flags: ColumnFlags::RESIZABLE,
}
}
}
impl Column {
#[must_use]
#[inline]
pub fn new(current: f32) -> Self {
Self {
current,
..Default::default()
}
}
#[must_use]
#[inline]
pub fn range(mut self, range: impl Into<Rangef>) -> Self {
self.range = range.into();
self
}
#[must_use]
#[inline]
pub const fn id(mut self, id: egui::Id) -> Self {
self.id = Some(id);
self
}
#[must_use]
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
self.flags.set(ColumnFlags::RESIZABLE, resizable);
self
}
#[must_use]
#[inline]
pub fn auto_size_this_frame(mut self, auto_size_this_frame: bool) -> Self {
self.flags
.set(ColumnFlags::AUTO_SIZE_THIS_FRAME, auto_size_this_frame);
self
}
#[must_use]
#[inline]
pub fn auto_fit(mut self, auto_fit: bool) -> Self {
self.flags.set(ColumnFlags::AUTO_FIT, auto_fit);
self
}
#[must_use]
#[inline]
pub fn remainder(mut self, remainder: bool) -> Self {
self.flags.set(ColumnFlags::REMAINDER, remainder);
self
}
#[inline]
#[must_use]
pub const fn is_resizable(&self) -> bool {
self.flags.contains(ColumnFlags::RESIZABLE)
}
#[inline]
#[must_use]
pub const fn is_auto_size_this_frame(&self) -> bool {
self.flags.contains(ColumnFlags::AUTO_SIZE_THIS_FRAME)
}
#[inline]
#[must_use]
pub const fn is_auto_fit(&self) -> bool {
self.flags.contains(ColumnFlags::AUTO_FIT)
}
#[inline]
#[must_use]
pub const fn is_remainder(&self) -> bool {
self.flags.contains(ColumnFlags::REMAINDER)
}
#[must_use]
#[inline]
pub fn id_for(&self, col_idx: usize) -> egui::Id {
self.id.unwrap_or_else(|| egui::Id::new(col_idx))
}
pub fn auto_size(columns: &mut [Self], target_width: f32) {
if columns.is_empty() {
return;
}
let mut max_width = 0.0;
let mut current_width = 0.0;
for column in columns.iter_mut() {
column.current = column.range.clamp(column.current);
max_width += column.range.max;
current_width += column.current;
}
if current_width >= target_width {
return;
}
let sign = 1.0;
if max_width <= current_width {
return; }
let mut can_change: Vec<(f32, usize)> = columns
.iter()
.enumerate()
.filter_map(|(i, c)| {
if c.flags.contains(ColumnFlags::REMAINDER) && c.current < c.range.max {
let room = if c.range.max == f32::INFINITY {
10000.0 - c.current } else {
c.range.max - c.current
};
return Some((room, i));
}
None
})
.collect();
if can_change.is_empty() {
return; }
can_change.sort_by(|a, b| b.0.partial_cmp(&a.0).expect("NaN or Inf in column code"));
debug_assert!(
can_change[0].0 >= can_change.last().expect("Can't be empty").0,
"The sort is broken"
);
let mut remaining_abs = (target_width - current_width).abs();
while let Some((room_in_least, least_idx)) = can_change.pop() {
#[allow(clippy::cast_precision_loss)]
let evenly_distributed = remaining_abs / (can_change.len() as f32 + 1.0);
if evenly_distributed <= room_in_least {
columns[least_idx].current += sign * evenly_distributed;
for (_, i) in can_change {
columns[i].current += sign * evenly_distributed;
}
return;
}
columns[least_idx].current = columns[least_idx].range.max;
remaining_abs -= room_in_least;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn col(c: i32, range: std::ops::RangeInclusive<i32>) -> Column {
#[allow(clippy::cast_precision_loss)]
Column::new(c as f32).range(Rangef::new(*range.start() as f32, *range.end() as f32))
}
fn widths(columns: &[Column]) -> Vec<f32> {
columns.iter().map(|c| c.current).collect()
}
#[test]
fn test_single_column() {
let mut columns = [col(0, 100..=200).remainder(true)];
Column::auto_size(&mut columns, 50.0);
assert_eq!(widths(&columns), [100.0]);
Column::auto_size(&mut columns, 132.0);
assert_eq!(widths(&columns), [132.0]);
Column::auto_size(&mut columns, 500.0);
assert_eq!(widths(&columns), [200.0]);
}
#[test]
fn test_many_columns() {
let mut columns = [
col(15, 10..=20).remainder(true),
col(25, 10..=100).remainder(true),
col(150, 100..=200).remainder(true),
];
Column::auto_size(&mut columns, 190.0);
assert_eq!(
widths(&columns),
[15.0, 25.0, 150.0],
"They have no need to grow"
);
Column::auto_size(&mut columns, 207.0);
assert_eq!(
widths(&columns),
[20.0, 31.0, 156.0],
"They should grow equally"
);
}
}