1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 15:13:12 -04:00

auto-grid

This commit is contained in:
Emil Ernerfeldt
2023-05-08 10:58:41 +02:00
parent e84eef7815
commit f5d2bc4e33
2 changed files with 75 additions and 11 deletions

View File

@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet};
use egui::{emath::Rangef, pos2, vec2, NumExt as _, Rect};
use itertools::Itertools as _;
@@ -28,15 +28,45 @@ pub struct GridLoc {
}
impl GridLoc {
#[inline]
pub fn from_col_row(col: usize, row: usize) -> Self {
Self { col, row }
}
}
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde::Serialize,
serde::Deserialize,
)]
pub enum GridLayout {
/// Place children in a grid, with a dynamic number of columns and rows.
/// Resizing the window may change the number of columns and rows.
#[default]
Auto,
/// Place children in a grid with this many columns,
/// and as many rows as needed.
Columns(usize),
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Grid {
pub children: Vec<NodeId>,
pub layout: GridLayout,
/// Where each child is located.
///
/// If the chils is missing from this set, it will be assgined a location during layout.
pub locations: HashMap<NodeId, GridLoc>,
/// Share of the available width assigned to each column.
@@ -72,22 +102,30 @@ impl Grid {
behavior: &mut dyn Behavior<Leaf>,
rect: Rect,
) {
let gap = behavior.gap_width(style);
let child_ids: HashSet<NodeId> = self.children.iter().copied().collect();
let mut num_cols = 2.min(self.children.len()); // les than 2 and it is not a grid
let num_cols = match self.layout {
GridLayout::Auto => num_columns_heuristic(self.children.len(), rect, gap),
GridLayout::Columns(num_columns) => num_columns.at_least(1),
};
let num_rows = (self.children.len() + num_cols - 1) / num_cols;
// Where to place each node?
let mut node_id_from_location: BTreeMap<GridLoc, NodeId> = Default::default();
self.locations.retain(|&child_id, &mut loc| {
if child_ids.contains(&child_id) {
match node_id_from_location.entry(loc) {
std::collections::btree_map::Entry::Occupied(_) => {
btree_map::Entry::Occupied(_) => {
false // two nodes assigned to the same position - forget this one for now
}
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(child_id);
num_cols = num_cols.max(loc.col + 1);
true
btree_map::Entry::Vacant(entry) => {
if num_cols <= loc.col || num_rows <= loc.row {
false // out of bounds
} else {
entry.insert(child_id);
true
}
}
}
} else {
@@ -98,8 +136,7 @@ impl Grid {
// Find location for nodes that don't have one yet
let mut next_pos = 0;
for &child_id in &self.children {
if let std::collections::hash_map::Entry::Vacant(entry) = self.locations.entry(child_id)
{
if let hash_map::Entry::Vacant(entry) = self.locations.entry(child_id) {
// find a position:
loop {
let loc = GridLoc::from_col_row(next_pos % num_cols, next_pos / num_cols);
@@ -121,7 +158,6 @@ impl Grid {
self.col_shares.resize(num_cols, 1.0);
self.row_shares.resize(num_rows, 1.0);
let gap = behavior.gap_width(style);
let col_widths = sizes_from_shares(&self.col_shares, rect.width(), gap);
let row_heights = sizes_from_shares(&self.row_shares, rect.height(), gap);
@@ -271,6 +307,34 @@ impl Grid {
}
}
/// How many columns should we use to fit `n` children in a grid?
fn num_columns_heuristic(n: usize, rect: Rect, gap: f32) -> usize {
let desired_aspect = 4.0 / 3.0;
let mut best_loss = f32::INFINITY;
let mut best_num_columns = 1;
for ncols in 1..=n {
let nrows = (n + ncols - 1) / ncols;
let cell_width = (rect.width() - gap * (ncols as f32 - 1.0)) / (ncols as f32);
let cell_height = (rect.height() - gap * (nrows as f32 - 1.0)) / (nrows as f32);
let cell_aspect = cell_width / cell_height;
let aspect_diff = (desired_aspect - cell_aspect).abs();
let num_empty_cells = ncols * nrows - n;
let loss = aspect_diff + 0.1 * num_empty_cells as f32; // TODO(emilk): weight differently?
if loss < best_loss {
best_loss = loss;
best_num_columns = ncols;
}
}
best_num_columns
}
fn resize_interaction<Leaf>(
behavior: &mut dyn Behavior<Leaf>,
ranges: &[Rangef],

View File

@@ -182,7 +182,7 @@ impl Default for MyApp {
nodes.insert_vertical_node(children)
});
tabs.push({
let cells = (0..12).map(|_| nodes.insert_leaf(gen_view())).collect();
let cells = (0..11).map(|_| nodes.insert_leaf(gen_view())).collect();
nodes.insert_grid_node(cells)
});
tabs.push(nodes.insert_leaf(gen_view()));