mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 15:13:12 -04:00
auto-grid
This commit is contained in:
@@ -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],
|
||||
|
||||
@@ -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()));
|
||||
|
||||
Reference in New Issue
Block a user