From 2f4a3a127304716609f33683f7005f7813db3133 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 May 2020 17:53:02 +0200 Subject: [PATCH] [window] auto-position windows in a column layout --- egui/README.md | 1 + egui/src/containers/area.rs | 63 ++++++++++++++++++++++++++++++++--- egui/src/context.rs | 4 +-- egui/src/memory.rs | 22 ++++++++++-- egui/src/widgets/text_edit.rs | 2 +- 5 files changed, 82 insertions(+), 10 deletions(-) diff --git a/egui/README.md b/egui/README.md index 6ff29fa7..86affd9b 100644 --- a/egui/README.md +++ b/egui/README.md @@ -17,6 +17,7 @@ This is the core library crate Egui. It is fully platform independent without an * Then we could open the example app inside a window in the example app, recursively. * [x] Resize any side and corner on windows * [x] Fix autoshrink + * [x] Automatic positioning of new windows * [ ] Scroll areas * [x] Vertical scrolling * [ ] Horizontal scrolling diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index f7ab2f76..698d58ba 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -116,12 +116,11 @@ impl Area { fixed_pos, } = self; - let default_pos = default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO - let id = ctx.register_unique_id(id, "Area", default_pos); let layer = Layer { order, id }; - let mut state = ctx.memory().areas.get(id).unwrap_or_else(|| State { - pos: default_pos, + let state = ctx.memory().areas.get(id).cloned(); + let mut state = state.unwrap_or_else(|| State { + pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), size: Vec2::zero(), interactable, vel: Vec2::zero(), @@ -233,3 +232,59 @@ fn mouse_pressed_on_area(ctx: &Context, layer: Layer) -> bool { false } } + +fn automatic_area_position(ctx: &Context) -> Pos2 { + let mut existing: Vec = ctx + .memory() + .areas + .visible_windows() + .into_iter() + .map(State::rect) + .collect(); + existing.sort_by_key(|r| r.left().round() as i32); + + let left = 16.0; + let top = 32.0; // allow existence of menu bar. TODO: get from ui.available() + let spacing = 32.0; + + if existing.is_empty() { + return pos2(left, top); + } + + // Separate existing rectangles into columns: + let mut column_bbs = vec![existing[0]]; + + for &rect in &existing { + let current_column_bb = column_bbs.last_mut().unwrap(); + if rect.left() < current_column_bb.right() { + // same column + *current_column_bb = current_column_bb.union(rect); + } else { + // new column + column_bbs.push(rect); + } + } + + // Find first column with some available space at the bottom of it: + for col_bb in &column_bbs { + if col_bb.bottom() < ctx.input().screen_size.y * 0.5 { + return pos2(col_bb.left(), col_bb.bottom() + spacing); + } + } + + // Maybe we can fit a new column? + let rightmost = column_bbs.last().unwrap().right(); + if rightmost < ctx.input().screen_size.x - 200.0 { + return pos2(rightmost + spacing, top); + } + + // Ok, just put us in the column with the most space at the bottom: + let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing); + for col_bb in &column_bbs { + let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing); + if col_pos.y < best_pos.y { + best_pos = col_pos; + } + } + best_pos +} diff --git a/egui/src/context.rs b/egui/src/context.rs index 4dd93df8..8508c51c 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -399,8 +399,8 @@ impl Context { ); } - pub fn debug_rect(&self, rect: Rect, text: impl Into) { - let text = text.into(); + pub fn debug_rect(&self, rect: Rect, name: impl Into) { + let text = format!("{} {:?}", name.into(), rect); let layer = Layer::debug(); self.add_paint_cmd( layer, diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 82fde6d5..9e8796b8 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -98,7 +98,7 @@ impl Memory { if window_interaction.is_pure_move() { // Throw windows because it is fun: let area_layer = window_interaction.area_layer; - let area_state = self.areas.get(area_layer.id); + let area_state = self.areas.get(area_layer.id).cloned(); if let Some(mut area_state) = area_state { area_state.vel = prev_input.mouse.velocity; self.areas.set_state(area_layer, area_state); @@ -123,8 +123,8 @@ impl Areas { self.areas.len() } - pub(crate) fn get(&mut self, id: Id) -> Option { - self.areas.get(&id).cloned() + pub(crate) fn get(&self, id: Id) -> Option<&area::State> { + self.areas.get(&id) } pub(crate) fn order(&self) -> &[Layer] { @@ -164,6 +164,22 @@ impl Areas { self.visible_last_frame.contains(layer) || self.visible_current_frame.contains(layer) } + pub fn visible_layers(&self) -> HashSet { + self.visible_last_frame + .iter() + .cloned() + .chain(self.visible_current_frame.iter().cloned()) + .collect() + } + + pub(crate) fn visible_windows(&self) -> Vec<&area::State> { + self.visible_layers() + .iter() + .filter(|layer| layer.order == crate::layers::Order::Middle) + .filter_map(|layer| self.get(layer.id)) + .collect() + } + pub fn move_to_top(&mut self, layer: Layer) { self.visible_current_frame.insert(layer); self.wants_to_be_on_top.insert(layer); diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 51bb72a4..7064cde8 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -151,7 +151,7 @@ impl<'t> Widget for TextEdit<'t> { } fn insert_text(cursor: &mut usize, text: &mut String, text_to_insert: &str) { - eprintln!("insert_text {:?}", text_to_insert); + // eprintln!("insert_text {:?}", text_to_insert); let mut char_it = text.chars(); let mut new_text = String::with_capacity(text.capacity());