Browse Source

auto-grid

emilk/dock
Emil Ernerfeldt 2 years ago
parent
commit
f5d2bc4e33
  1. 84
      crates/egui_extras/src/dock/branch/grid.rs
  2. 2
      examples/dock/src/main.rs

84
crates/egui_extras/src/dock/branch/grid.rs

@ -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],

2
examples/dock/src/main.rs

@ -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()));

Loading…
Cancel
Save