mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 14:50:03 -04:00
Resizing of horizontal layouts
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1331,6 +1331,7 @@ dependencies = [
|
|||||||
"egui",
|
"egui",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"image",
|
"image",
|
||||||
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"resvg",
|
"resvg",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ egui = { version = "0.21.0", path = "../egui", default-features = false }
|
|||||||
# For dock:
|
# For dock:
|
||||||
# required to make rand work on wasm, see https://github.com/rust-random/rand#wasm-support
|
# required to make rand work on wasm, see https://github.com/rust-random/rand#wasm-support
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
itertools = "0.10"
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
rand = { version = "0.8.5", features = ["getrandom", "small_rng"] }
|
rand = { version = "0.8.5", features = ["getrandom", "small_rng"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use egui::{pos2, vec2, NumExt, Rect};
|
use egui::{pos2, vec2, NumExt, Rect};
|
||||||
|
use itertools::Itertools as _;
|
||||||
|
|
||||||
use crate::dock::{
|
use crate::dock::{
|
||||||
is_being_dragged, Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes,
|
is_being_dragged, Behavior, DropContext, InsertionPoint, LayoutInsertion, NodeId, Nodes,
|
||||||
|
ResizeState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Shares;
|
use super::Shares;
|
||||||
@@ -161,6 +163,74 @@ impl Linear {
|
|||||||
|
|
||||||
prev_rect = Some(rect);
|
prev_rect = Some(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// resizing:
|
||||||
|
let parent_rect = nodes.rect(parent_id).unwrap();
|
||||||
|
for (i, (left, right)) in self.children.iter().tuple_windows().enumerate() {
|
||||||
|
let resize_id = egui::Id::new((parent_id, "resize", i));
|
||||||
|
|
||||||
|
let left_rect = nodes.rect(*left).unwrap();
|
||||||
|
let right_rect = nodes.rect(*right).unwrap();
|
||||||
|
let x = egui::lerp(left_rect.right()..=right_rect.left(), 0.5);
|
||||||
|
|
||||||
|
let mut resize_state = ResizeState::Idle;
|
||||||
|
if let Some(pointer) = ui.ctx().pointer_latest_pos() {
|
||||||
|
let we_are_on_top = ui
|
||||||
|
.ctx()
|
||||||
|
.layer_id_at(pointer)
|
||||||
|
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
|
||||||
|
|
||||||
|
let mouse_over_resize_line = we_are_on_top
|
||||||
|
&& parent_rect.y_range().contains(&pointer.y)
|
||||||
|
&& (x - pointer.x).abs() <= ui.style().interaction.resize_grab_radius_side;
|
||||||
|
|
||||||
|
if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down())
|
||||||
|
&& mouse_over_resize_line
|
||||||
|
{
|
||||||
|
ui.memory_mut(|mem| mem.set_dragged_id(resize_id));
|
||||||
|
}
|
||||||
|
if ui.memory(|mem| mem.is_being_dragged(resize_id)) {
|
||||||
|
resize_state = ResizeState::Dragging;
|
||||||
|
} else {
|
||||||
|
let dragging_something_else =
|
||||||
|
ui.input(|i| i.pointer.any_down() || i.pointer.any_pressed());
|
||||||
|
if mouse_over_resize_line && !dragging_something_else {
|
||||||
|
resize_state = ResizeState::Hovering;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resize_state == ResizeState::Dragging {
|
||||||
|
let dx = pointer.x - x;
|
||||||
|
if pointer.x < x {
|
||||||
|
// Expand right, shrink stuff to the left:
|
||||||
|
*self.shares.shares.entry(*right).or_insert(1.0) += shrink_shares(
|
||||||
|
behavior,
|
||||||
|
nodes,
|
||||||
|
&mut self.shares,
|
||||||
|
&self.children[0..=i].iter().copied().rev().collect_vec(),
|
||||||
|
dx.abs(),
|
||||||
|
);
|
||||||
|
} else if x < pointer.x {
|
||||||
|
// Expand the left, shrink stuff to the right:
|
||||||
|
*self.shares.shares.entry(*left).or_insert(1.0) += shrink_shares(
|
||||||
|
behavior,
|
||||||
|
nodes,
|
||||||
|
&mut self.shares,
|
||||||
|
&self.children[i + 1..],
|
||||||
|
dx.abs(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resize_state != ResizeState::Idle {
|
||||||
|
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeHorizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke = behavior.resize_stroke(ui.style(), resize_state);
|
||||||
|
ui.painter().vline(x, parent_rect.y_range(), stroke);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertical_ui<Leaf>(
|
fn vertical_ui<Leaf>(
|
||||||
@@ -175,5 +245,45 @@ impl Linear {
|
|||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
nodes.node_ui(behavior, drop_context, ui, *child);
|
nodes.node_ui(behavior, drop_context, ui, *child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: resizing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try shrink the children by a total of `target_in_points`,
|
||||||
|
/// making sure no child gets smaller than its minimum size.
|
||||||
|
fn shrink_shares<Leaf>(
|
||||||
|
behavior: &dyn Behavior<Leaf>,
|
||||||
|
nodes: &Nodes<Leaf>,
|
||||||
|
shares: &mut Shares,
|
||||||
|
children: &[NodeId],
|
||||||
|
target_in_points: f32,
|
||||||
|
) -> f32 {
|
||||||
|
assert!(!children.is_empty());
|
||||||
|
|
||||||
|
let mut total_shares = 0.0;
|
||||||
|
let mut total_points = 0.0;
|
||||||
|
for &child in children {
|
||||||
|
total_shares += shares.shares.get(&child).copied().unwrap_or(1.0);
|
||||||
|
total_points += nodes.rect(child).unwrap().width();
|
||||||
|
}
|
||||||
|
|
||||||
|
let shares_per_point = total_shares / total_points;
|
||||||
|
|
||||||
|
let min_size_in_points = shares_per_point * behavior.min_size();
|
||||||
|
|
||||||
|
let target_in_shares = shares_per_point * target_in_points;
|
||||||
|
let mut total_shares_lost = 0.0;
|
||||||
|
|
||||||
|
for &child in children {
|
||||||
|
let share = shares.shares.entry(child).or_insert(1.0);
|
||||||
|
let shrink_by = (target_in_shares - total_shares_lost)
|
||||||
|
.min(*share - min_size_in_points)
|
||||||
|
.max(0.0);
|
||||||
|
|
||||||
|
*share -= shrink_by;
|
||||||
|
total_shares_lost += shrink_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_shares_lost
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
// # TODO
|
||||||
|
// * A new ui for each node, nested
|
||||||
|
// * Better drag-and-drop around the "empty" drag source
|
||||||
|
// * Resizing of vertical layouts and grids
|
||||||
|
// * Styling
|
||||||
|
// * Handle rects without a lot of unwraps
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use egui::{Id, Key, NumExt, Pos2, Rect, Response, Sense, TextStyle, Ui, WidgetText};
|
use egui::{Id, Key, NumExt, Pos2, Rect, Response, Sense, TextStyle, Ui, WidgetText};
|
||||||
@@ -207,9 +214,29 @@ pub trait Behavior<Leaf> {
|
|||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No child should shrink below this size
|
||||||
|
fn min_size(&self) -> f32 {
|
||||||
|
32.0
|
||||||
|
}
|
||||||
|
|
||||||
fn simplification_options(&self) -> SimplificationOptions {
|
fn simplification_options(&self) -> SimplificationOptions {
|
||||||
SimplificationOptions::default()
|
SimplificationOptions::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> egui::Stroke {
|
||||||
|
match resize_state {
|
||||||
|
ResizeState::Idle => egui::Stroke::NONE, // Let the gap speak for itself
|
||||||
|
ResizeState::Hovering => style.visuals.widgets.hovered.fg_stroke,
|
||||||
|
ResizeState::Dragging => style.visuals.widgets.active.fg_stroke,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ResizeState {
|
||||||
|
Idle,
|
||||||
|
Hovering,
|
||||||
|
Dragging,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user