mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 14:50:03 -04:00
Code cleanup
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
use egui::{vec2, Color32, Id, Response, Rgba, Sense, Stroke, TextStyle, Ui, Visuals, WidgetText};
|
use egui::{
|
||||||
|
vec2, Color32, Id, Rect, Response, Rgba, Sense, Stroke, TextStyle, Ui, Visuals, WidgetText,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{Node, NodeId, Nodes, ResizeState, SimplificationOptions, UiResponse};
|
use super::{Node, NodeId, Nodes, ResizeState, SimplificationOptions, UiResponse};
|
||||||
|
|
||||||
@@ -7,14 +9,22 @@ pub trait Behavior<Leaf> {
|
|||||||
/// Show this leaf node in the given [`egui::Ui`].
|
/// Show this leaf node in the given [`egui::Ui`].
|
||||||
///
|
///
|
||||||
/// If this is an unknown node, return [`NodeAction::Remove`] and the node will be removed.
|
/// If this is an unknown node, return [`NodeAction::Remove`] and the node will be removed.
|
||||||
|
///
|
||||||
|
/// You can make the leaf draggable by returning [`UiResponse::DragStarted`]
|
||||||
|
/// when the user drags some handle.
|
||||||
fn leaf_ui(&mut self, _ui: &mut Ui, _node_id: NodeId, _leaf: &mut Leaf) -> UiResponse;
|
fn leaf_ui(&mut self, _ui: &mut Ui, _node_id: NodeId, _leaf: &mut Leaf) -> UiResponse;
|
||||||
|
|
||||||
fn tab_text_for_leaf(&mut self, leaf: &Leaf) -> WidgetText;
|
/// The title of a leaf tab.
|
||||||
|
fn tab_title_for_leaf(&mut self, leaf: &Leaf) -> WidgetText;
|
||||||
|
|
||||||
fn tab_text_for_node(&mut self, nodes: &Nodes<Leaf>, node_id: NodeId) -> WidgetText {
|
/// The title of a general tab.
|
||||||
|
///
|
||||||
|
/// The default implementation uses the name of the layout for branches, and
|
||||||
|
/// calls [`Self::tab_text_for_leaf`] for leaves.
|
||||||
|
fn tab_title_for_node(&mut self, nodes: &Nodes<Leaf>, node_id: NodeId) -> WidgetText {
|
||||||
if let Some(node) = nodes.nodes.get(&node_id) {
|
if let Some(node) = nodes.nodes.get(&node_id) {
|
||||||
match node {
|
match node {
|
||||||
Node::Leaf(leaf) => self.tab_text_for_leaf(leaf),
|
Node::Leaf(leaf) => self.tab_title_for_leaf(leaf),
|
||||||
Node::Branch(branch) => format!("{:?}", branch.get_layout()).into(),
|
Node::Branch(branch) => format!("{:?}", branch.get_layout()).into(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -23,6 +33,8 @@ pub trait Behavior<Leaf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Show the title of a tab as a button.
|
/// Show the title of a tab as a button.
|
||||||
|
///
|
||||||
|
/// You can override the default implementation to add e.g. a close button.
|
||||||
fn tab_ui(
|
fn tab_ui(
|
||||||
&mut self,
|
&mut self,
|
||||||
nodes: &Nodes<Leaf>,
|
nodes: &Nodes<Leaf>,
|
||||||
@@ -32,7 +44,7 @@ pub trait Behavior<Leaf> {
|
|||||||
active: bool,
|
active: bool,
|
||||||
is_being_dragged: bool,
|
is_being_dragged: bool,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let text = self.tab_text_for_node(nodes, node_id);
|
let text = self.tab_title_for_node(nodes, node_id);
|
||||||
let font_id = TextStyle::Button.resolve(ui.style());
|
let font_id = TextStyle::Button.resolve(ui.style());
|
||||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
|
let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
|
||||||
|
|
||||||
@@ -45,8 +57,8 @@ pub trait Behavior<Leaf> {
|
|||||||
|
|
||||||
// Show a gap when dragged
|
// Show a gap when dragged
|
||||||
if ui.is_rect_visible(rect) && !is_being_dragged {
|
if ui.is_rect_visible(rect) && !is_being_dragged {
|
||||||
let bg_color = self.tab_bg_color(ui.visuals(), active);
|
let bg_color = self.tab_bg_color(ui.visuals(), node_id, active);
|
||||||
let stroke = self.tab_outline_stroke(ui.visuals(), active);
|
let stroke = self.tab_outline_stroke(ui.visuals(), node_id, active);
|
||||||
ui.painter().rect(rect.shrink(0.5), 0.0, bg_color, stroke);
|
ui.painter().rect(rect.shrink(0.5), 0.0, bg_color, stroke);
|
||||||
|
|
||||||
if active {
|
if active {
|
||||||
@@ -58,7 +70,7 @@ pub trait Behavior<Leaf> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_color = self.tab_text_color(ui.visuals(), active);
|
let text_color = self.tab_text_color(ui.visuals(), node_id, active);
|
||||||
ui.painter().galley_with_color(
|
ui.painter().galley_with_color(
|
||||||
egui::Align2::CENTER_CENTER
|
egui::Align2::CENTER_CENTER
|
||||||
.align_size_within_rect(galley.size(), rect)
|
.align_size_within_rect(galley.size(), rect)
|
||||||
@@ -76,7 +88,7 @@ pub trait Behavior<Leaf> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds some UI to the top right of the tab bar.
|
/// Adds some UI to the top right of each tab bar.
|
||||||
///
|
///
|
||||||
/// You can use this to, for instance, add a button for adding new tabs.
|
/// You can use this to, for instance, add a button for adding new tabs.
|
||||||
///
|
///
|
||||||
@@ -89,7 +101,7 @@ pub trait Behavior<Leaf> {
|
|||||||
// --------
|
// --------
|
||||||
// Settings:
|
// Settings:
|
||||||
|
|
||||||
/// The height of the bar holding tab names.
|
/// The height of the bar holding tab titles.
|
||||||
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
|
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
|
||||||
24.0
|
24.0
|
||||||
}
|
}
|
||||||
@@ -100,15 +112,17 @@ pub trait Behavior<Leaf> {
|
|||||||
1.0
|
1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// No child should shrink below this size
|
/// No child should shrink below this width nor height.
|
||||||
fn min_size(&self) -> f32 {
|
fn min_size(&self) -> f32 {
|
||||||
32.0
|
32.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What are the rules for simplifying the tree?
|
||||||
fn simplification_options(&self) -> SimplificationOptions {
|
fn simplification_options(&self) -> SimplificationOptions {
|
||||||
SimplificationOptions::default()
|
SimplificationOptions::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The stroke used for the lines in horizontal, vertical, and grid layouts.
|
||||||
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> egui::Stroke {
|
fn resize_stroke(&self, style: &egui::Style, resize_state: ResizeState) -> egui::Stroke {
|
||||||
match resize_state {
|
match resize_state {
|
||||||
ResizeState::Idle => egui::Stroke::NONE, // Let the gap speak for itself
|
ResizeState::Idle => egui::Stroke::NONE, // Let the gap speak for itself
|
||||||
@@ -122,7 +136,7 @@ pub trait Behavior<Leaf> {
|
|||||||
8.0
|
8.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The background color of the tab bar
|
/// The background color of the tab bar.
|
||||||
fn tab_bar_color(&self, visuals: &Visuals) -> Color32 {
|
fn tab_bar_color(&self, visuals: &Visuals) -> Color32 {
|
||||||
if visuals.dark_mode {
|
if visuals.dark_mode {
|
||||||
Color32::BLACK
|
Color32::BLACK
|
||||||
@@ -131,7 +145,8 @@ pub trait Behavior<Leaf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_bg_color(&self, visuals: &Visuals, active: bool) -> Color32 {
|
/// The background color of a tab.
|
||||||
|
fn tab_bg_color(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Color32 {
|
||||||
if active {
|
if active {
|
||||||
visuals.window_fill() // same as the tab contents
|
visuals.window_fill() // same as the tab contents
|
||||||
} else {
|
} else {
|
||||||
@@ -140,7 +155,7 @@ pub trait Behavior<Leaf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stroke of the outline around a tab title.
|
/// Stroke of the outline around a tab title.
|
||||||
fn tab_outline_stroke(&self, visuals: &Visuals, active: bool) -> Stroke {
|
fn tab_outline_stroke(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Stroke {
|
||||||
if active {
|
if active {
|
||||||
Stroke::new(1.0, visuals.widgets.active.bg_fill)
|
Stroke::new(1.0, visuals.widgets.active.bg_fill)
|
||||||
} else {
|
} else {
|
||||||
@@ -153,11 +168,42 @@ pub trait Behavior<Leaf> {
|
|||||||
Stroke::new(1.0, visuals.widgets.noninteractive.bg_stroke.color)
|
Stroke::new(1.0, visuals.widgets.noninteractive.bg_stroke.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_text_color(&self, visuals: &Visuals, active: bool) -> Color32 {
|
/// The color of the title text of the tab.
|
||||||
|
fn tab_text_color(&self, visuals: &Visuals, _node_id: NodeId, active: bool) -> Color32 {
|
||||||
if active {
|
if active {
|
||||||
visuals.widgets.active.text_color()
|
visuals.widgets.active.text_color()
|
||||||
} else {
|
} else {
|
||||||
visuals.widgets.noninteractive.text_color()
|
visuals.widgets.noninteractive.text_color()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When drag-and-dropping a node, how do we preview what is about to happen?
|
||||||
|
fn paint_drag_preview(
|
||||||
|
&self,
|
||||||
|
visuals: &Visuals,
|
||||||
|
painter: &egui::Painter,
|
||||||
|
parent_rect: Option<Rect>,
|
||||||
|
preview_rect: Rect,
|
||||||
|
) {
|
||||||
|
let preview_stroke = visuals.selection.stroke;
|
||||||
|
let preview_color = preview_stroke.color;
|
||||||
|
|
||||||
|
if let Some(parent_rect) = parent_rect {
|
||||||
|
// Show which parent we will be dropped into
|
||||||
|
painter.rect_stroke(parent_rect, 1.0, preview_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
painter.rect(
|
||||||
|
preview_rect,
|
||||||
|
1.0,
|
||||||
|
preview_color.gamma_multiply(0.5),
|
||||||
|
preview_stroke,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show we preview leaves that are being dragged,
|
||||||
|
/// i.e. show their ui in the region where they will end up?
|
||||||
|
fn preview_dragged_leaves(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ impl<Leaf: std::fmt::Debug> std::fmt::Debug for Dock<Leaf> {
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Construction
|
|
||||||
|
|
||||||
impl<Leaf> Dock<Leaf> {
|
impl<Leaf> Dock<Leaf> {
|
||||||
pub fn new(root: NodeId, nodes: Nodes<Leaf>) -> Self {
|
pub fn new(root: NodeId, nodes: Nodes<Leaf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -79,6 +77,10 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root(&self) -> NodeId {
|
||||||
|
self.root
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parent_of(&self, node_id: NodeId) -> Option<NodeId> {
|
pub fn parent_of(&self, node_id: NodeId) -> Option<NodeId> {
|
||||||
self.nodes
|
self.nodes
|
||||||
.nodes
|
.nodes
|
||||||
@@ -92,15 +94,10 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
})
|
})
|
||||||
.map(|(id, _)| *id)
|
.map(|(id, _)| *id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Usage
|
/// Show the dock in the given [`Ui`].
|
||||||
impl<Leaf> Dock<Leaf> {
|
///
|
||||||
pub fn root(&self) -> NodeId {
|
/// The dock will use upp all the avilable space - nothing more, nothing less.
|
||||||
self.root
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show all the leaves in the dock.
|
|
||||||
pub fn ui(&mut self, behavior: &mut dyn Behavior<Leaf>, ui: &mut Ui) {
|
pub fn ui(&mut self, behavior: &mut dyn Behavior<Leaf>, ui: &mut Ui) {
|
||||||
let options = behavior.simplification_options();
|
let options = behavior.simplification_options();
|
||||||
self.simplify(&options);
|
self.simplify(&options);
|
||||||
@@ -133,6 +130,15 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
self.nodes
|
self.nodes
|
||||||
.node_ui(behavior, &mut drop_context, ui, self.root);
|
.node_ui(behavior, &mut drop_context, ui, self.root);
|
||||||
|
|
||||||
|
self.preview_dragged_node(behavior, &drop_context, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preview_dragged_node(
|
||||||
|
&mut self,
|
||||||
|
behavior: &mut dyn Behavior<Leaf>,
|
||||||
|
drop_context: &DropContext,
|
||||||
|
ui: &mut Ui,
|
||||||
|
) {
|
||||||
if let (Some(mouse_pos), Some(dragged_node_id)) =
|
if let (Some(mouse_pos), Some(dragged_node_id)) =
|
||||||
(drop_context.mouse_pos, drop_context.dragged_node_id)
|
(drop_context.mouse_pos, drop_context.dragged_node_id)
|
||||||
{
|
{
|
||||||
@@ -147,8 +153,8 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
let mut frame = egui::Frame::popup(ui.style());
|
let mut frame = egui::Frame::popup(ui.style());
|
||||||
frame.fill = frame.fill.gamma_multiply(0.5); // Make see-through
|
frame.fill = frame.fill.gamma_multiply(0.5); // Make see-through
|
||||||
frame.show(ui, |ui| {
|
frame.show(ui, |ui| {
|
||||||
// TODO: preview contents
|
// TODO(emilk): preview contents?
|
||||||
let text = behavior.tab_text_for_node(&self.nodes, dragged_node_id);
|
let text = behavior.tab_title_for_node(&self.nodes, dragged_node_id);
|
||||||
ui.label(text);
|
ui.label(text);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -156,26 +162,14 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
if let Some(preview_rect) = drop_context.preview_rect {
|
if let Some(preview_rect) = drop_context.preview_rect {
|
||||||
let preview_rect = self.smooth_preview_rect(ui.ctx(), preview_rect);
|
let preview_rect = self.smooth_preview_rect(ui.ctx(), preview_rect);
|
||||||
|
|
||||||
let preview_stroke = ui.visuals().selection.stroke;
|
let parent_rect = drop_context
|
||||||
let preview_color = preview_stroke.color;
|
.best_insertion
|
||||||
|
.and_then(|insertion_point| self.nodes.try_rect(insertion_point.parent_id));
|
||||||
|
|
||||||
if let Some(insertion_point) = &drop_context.best_insertion {
|
behavior.paint_drag_preview(ui.visuals(), ui.painter(), parent_rect, preview_rect);
|
||||||
if let Some(parent_rect) = self.nodes.try_rect(insertion_point.parent_id) {
|
|
||||||
// Show which parent we will be dropped into
|
|
||||||
ui.painter().rect_stroke(parent_rect, 1.0, preview_stroke);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.painter().rect(
|
if behavior.preview_dragged_leaves() {
|
||||||
preview_rect,
|
// TODO(emilk): add support for previewing branches too.
|
||||||
1.0,
|
|
||||||
preview_color.gamma_multiply(0.5),
|
|
||||||
preview_stroke,
|
|
||||||
);
|
|
||||||
|
|
||||||
let preview_child = false;
|
|
||||||
if preview_child {
|
|
||||||
// Preview actual child?
|
|
||||||
if preview_rect.width() > 32.0 && preview_rect.height() > 32.0 {
|
if preview_rect.width() > 32.0 && preview_rect.height() > 32.0 {
|
||||||
if let Some(Node::Leaf(leaf)) = self.nodes.get_mut(dragged_node_id) {
|
if let Some(Node::Leaf(leaf)) = self.nodes.get_mut(dragged_node_id) {
|
||||||
let _ = behavior.leaf_ui(
|
let _ = behavior.leaf_ui(
|
||||||
@@ -200,6 +194,7 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the preview rectangle and smooth it over time.
|
||||||
fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect {
|
fn smooth_preview_rect(&mut self, ctx: &egui::Context, new_rect: Rect) -> Rect {
|
||||||
let dt = ctx.input(|input| input.stable_dt).at_most(0.1);
|
let dt = ctx.input(|input| input.stable_dt).at_most(0.1);
|
||||||
let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt);
|
let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt);
|
||||||
@@ -228,7 +223,8 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_node(&mut self, moved_node_id: NodeId, insertion_point: InsertionPoint) {
|
/// Move the given node to the given insertion point.
|
||||||
|
pub fn move_node(&mut self, moved_node_id: NodeId, insertion_point: InsertionPoint) {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Moving {moved_node_id:?} into {:?}",
|
"Moving {moved_node_id:?} into {:?}",
|
||||||
insertion_point.insertion
|
insertion_point.insertion
|
||||||
@@ -238,7 +234,7 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the currently dragged node, if any.
|
/// Find the currently dragged node, if any.
|
||||||
fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
|
pub fn dragged_id(&self, ctx: &egui::Context) -> Option<NodeId> {
|
||||||
if !is_possible_drag(ctx) {
|
if !is_possible_drag(ctx) {
|
||||||
// We're not sure we're dragging _at all_ yet.
|
// We're not sure we're dragging _at all_ yet.
|
||||||
return None;
|
return None;
|
||||||
@@ -246,7 +242,7 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
|
|
||||||
for &node_id in self.nodes.nodes.keys() {
|
for &node_id in self.nodes.nodes.keys() {
|
||||||
if node_id == self.root {
|
if node_id == self.root {
|
||||||
continue; // now allowed to drag root
|
continue; // not allowed to drag root
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = node_id.id();
|
let id = node_id.id();
|
||||||
@@ -264,9 +260,12 @@ impl<Leaf> Dock<Leaf> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs no simplifcations!
|
/// Performs no simplifcations, nor does it remove the actual [`Node`].
|
||||||
fn remove_node_id_from_parent(&mut self, dragged_node_id: NodeId) {
|
pub fn remove_node_id_from_parent(&mut self, remove_me: NodeId) {
|
||||||
self.nodes
|
for parent in self.nodes.nodes.values_mut() {
|
||||||
.remove_node_id_from_parent(self.root, dragged_node_id);
|
if let Node::Branch(branch) = parent {
|
||||||
|
branch.retain(|child| child != remove_me);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,36 @@
|
|||||||
//! The user needs to implement this in order to specify the `ui` of each `Leaf` and
|
//! The user needs to implement this in order to specify the `ui` of each `Leaf` and
|
||||||
//! the tab name of leaves (if there are tab nodes).
|
//! the tab name of leaves (if there are tab nodes).
|
||||||
//!
|
//!
|
||||||
//! ## Implementation notes
|
|
||||||
//! In many places we want to recursively visit all noted, while also mutating them.
|
|
||||||
//! In order to not get into trouble with the borrow checker a trick is used:
|
|
||||||
//! each [`Node`] is removed, mutated, recursed, and then re-added.
|
|
||||||
//! You'll see this pattern many times reading the following code.
|
|
||||||
//!
|
|
||||||
//! ## Shortcomings
|
//! ## Shortcomings
|
||||||
//! We use real recursion, so if your trees get too deep you will get a stack overflow.
|
//! We use real recursion, so if your trees get too deep you will get a stack overflow.
|
||||||
//!
|
//!
|
||||||
|
//!
|
||||||
//! ## Future improvements
|
//! ## Future improvements
|
||||||
//! * A new ui for each node, nested
|
//! * Easy per-tab close-buttons
|
||||||
//! * Per-tab close-buttons
|
|
||||||
//! * Scrolling of tab-bar
|
//! * Scrolling of tab-bar
|
||||||
//! * Vertical tab bar
|
//! * Vertical tab bar
|
||||||
//! * Auto-grid layouts (re-arange as parent is resized)
|
//! * Auto-join nested horizontal/vertical layouts in the simplify step
|
||||||
|
|
||||||
|
// ## Implementation notes
|
||||||
|
// In many places we want to recursively visit all noted, while also mutating them.
|
||||||
|
// In order to not get into trouble with the borrow checker a trick is used:
|
||||||
|
// each [`Node`] is removed, mutated, recursed, and then re-added.
|
||||||
|
// You'll see this pattern many times reading the following code.
|
||||||
|
//
|
||||||
|
// Each frame consists of two passes: layout, and ui.
|
||||||
|
// The layout pass figures out where each node should be placed.
|
||||||
|
// The ui pass does all the painting.
|
||||||
|
// These two passes could be combined into one pass if we wanted to,
|
||||||
|
// but having them split up makes the code slightly simpler, and
|
||||||
|
// leaves the door open for more complex layout (e.g. min/max sizes per node).
|
||||||
|
//
|
||||||
|
// Everything is quite dynamic, so we have a bunch of defensive coding that call `warn!` on failure.
|
||||||
|
// These situations should not happen in normal use, but could happen if the user messes with
|
||||||
|
// the internals of the tree, putting it in an invalid state.
|
||||||
|
//
|
||||||
|
// ## TODO before release:
|
||||||
|
// * Auto-grid layouts (re-arange as parent is resized)
|
||||||
|
// * Clip tab titles to not cover "add new tab" button
|
||||||
|
|
||||||
use egui::{Id, Pos2, Rect};
|
use egui::{Id, Pos2, Rect};
|
||||||
|
|
||||||
@@ -96,6 +111,7 @@ pub enum UiResponse {
|
|||||||
DragStarted,
|
DragStarted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What are the rules for simplifying the tree?
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct SimplificationOptions {
|
pub struct SimplificationOptions {
|
||||||
pub prune_empty_tabs: bool,
|
pub prune_empty_tabs: bool,
|
||||||
@@ -143,7 +159,7 @@ pub struct InsertionPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InsertionPoint {
|
impl InsertionPoint {
|
||||||
fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self {
|
pub fn new(parent_id: NodeId, insertion: LayoutInsertion) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parent_id,
|
parent_id,
|
||||||
insertion,
|
insertion,
|
||||||
@@ -185,7 +201,14 @@ struct DropContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DropContext {
|
impl DropContext {
|
||||||
fn on_node<Leaf>(&mut self, parent_id: NodeId, rect: Rect, node: &Node<Leaf>) {
|
fn on_node<Leaf>(
|
||||||
|
&mut self,
|
||||||
|
behavior: &mut dyn Behavior<Leaf>,
|
||||||
|
style: &egui::Style,
|
||||||
|
parent_id: NodeId,
|
||||||
|
rect: Rect,
|
||||||
|
node: &Node<Leaf>,
|
||||||
|
) {
|
||||||
if !self.enabled {
|
if !self.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -212,17 +235,18 @@ impl DropContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// self.suggest_rect(InsertionPoint::new(parent_id, LayoutType::Tabs, 1), rect);
|
self.suggest_rect(
|
||||||
|
InsertionPoint::new(parent_id, LayoutInsertion::Tabs(usize::MAX)),
|
||||||
|
rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(style))
|
||||||
|
.1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suggest_rect(&mut self, insertion: InsertionPoint, preview_rect: Rect) {
|
fn suggest_rect(&mut self, insertion: InsertionPoint, preview_rect: Rect) {
|
||||||
self.suggest_point(insertion, preview_rect.center(), preview_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn suggest_point(&mut self, insertion: InsertionPoint, target_point: Pos2, preview_rect: Rect) {
|
|
||||||
if !self.enabled {
|
if !self.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let target_point = preview_rect.center();
|
||||||
if let Some(mouse_pos) = self.mouse_pos {
|
if let Some(mouse_pos) = self.mouse_pos {
|
||||||
let dist_sq = mouse_pos.distance_sq(target_point);
|
let dist_sq = mouse_pos.distance_sq(target_point);
|
||||||
if dist_sq < self.best_dist_sq {
|
if dist_sq < self.best_dist_sq {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub struct Nodes<Leaf> {
|
|||||||
|
|
||||||
/// Filled in by the layout step at the start of each frame.
|
/// Filled in by the layout step at the start of each frame.
|
||||||
#[serde(default, skip)]
|
#[serde(default, skip)]
|
||||||
pub rects: HashMap<NodeId, Rect>,
|
pub(super) rects: HashMap<NodeId, Rect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Leaf> Default for Nodes<Leaf> {
|
impl<Leaf> Default for Nodes<Leaf> {
|
||||||
@@ -29,11 +29,11 @@ impl<Leaf> Default for Nodes<Leaf> {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
impl<Leaf> Nodes<Leaf> {
|
impl<Leaf> Nodes<Leaf> {
|
||||||
pub fn try_rect(&self, node_id: NodeId) -> Option<Rect> {
|
pub(super) fn try_rect(&self, node_id: NodeId) -> Option<Rect> {
|
||||||
self.rects.get(&node_id).copied()
|
self.rects.get(&node_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect(&self, node_id: NodeId) -> Rect {
|
pub(super) fn rect(&self, node_id: NodeId) -> Rect {
|
||||||
let rect = self.try_rect(node_id);
|
let rect = self.try_rect(node_id);
|
||||||
debug_assert!(rect.is_some(), "Failed to find rect for {node_id:?}");
|
debug_assert!(rect.is_some(), "Failed to find rect for {node_id:?}");
|
||||||
rect.unwrap_or(egui::Rect::from_min_max(Pos2::ZERO, Pos2::ZERO))
|
rect.unwrap_or(egui::Rect::from_min_max(Pos2::ZERO, Pos2::ZERO))
|
||||||
@@ -90,23 +90,6 @@ impl<Leaf> Nodes<Leaf> {
|
|||||||
self.insert_node(Node::Branch(Branch::new_grid(children)))
|
self.insert_node(Node::Branch(Branch::new_grid(children)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs no simplifcations!
|
|
||||||
/// // TODO: remove ?
|
|
||||||
pub(super) fn remove_node_id_from_parent(&mut self, it: NodeId, remove: NodeId) -> GcAction {
|
|
||||||
if it == remove {
|
|
||||||
return GcAction::Remove;
|
|
||||||
}
|
|
||||||
let Some(mut node) = self.nodes.remove(&it) else {
|
|
||||||
log::warn!("Unexpected missing node during removal");
|
|
||||||
return GcAction::Remove;
|
|
||||||
};
|
|
||||||
if let Node::Branch(branch) = &mut node {
|
|
||||||
branch.retain(|child| self.remove_node_id_from_parent(child, remove) == GcAction::Keep);
|
|
||||||
}
|
|
||||||
self.nodes.insert(it, node);
|
|
||||||
GcAction::Keep
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, insertion_point: InsertionPoint, child_id: NodeId) {
|
pub fn insert(&mut self, insertion_point: InsertionPoint, child_id: NodeId) {
|
||||||
let InsertionPoint {
|
let InsertionPoint {
|
||||||
parent_id,
|
parent_id,
|
||||||
@@ -257,7 +240,7 @@ impl<Leaf> Nodes<Leaf> {
|
|||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
) {
|
) {
|
||||||
// NOTE: important that we get thr rect and node in two steps,
|
// NOTE: important that we get the rect and node in two steps,
|
||||||
// otherwise we could loose the node when there is no rect.
|
// otherwise we could loose the node when there is no rect.
|
||||||
let Some(rect) = self.try_rect(node_id) else {
|
let Some(rect) = self.try_rect(node_id) else {
|
||||||
log::warn!("Failed to find rect for node {node_id:?} during ui");
|
log::warn!("Failed to find rect for node {node_id:?} during ui");
|
||||||
@@ -273,14 +256,9 @@ impl<Leaf> Nodes<Leaf> {
|
|||||||
// Can't drag a node onto self or any children
|
// Can't drag a node onto self or any children
|
||||||
drop_context.enabled = false;
|
drop_context.enabled = false;
|
||||||
}
|
}
|
||||||
drop_context.on_node(node_id, rect, &node);
|
drop_context.on_node(behavior, ui.style(), node_id, rect, &node);
|
||||||
|
|
||||||
drop_context.suggest_rect(
|
|
||||||
InsertionPoint::new(node_id, LayoutInsertion::Tabs(usize::MAX)),
|
|
||||||
rect.split_top_bottom_at_y(rect.top() + behavior.tab_bar_height(ui.style()))
|
|
||||||
.1,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Each node gets its own `Ui`, nested inside each other, with proper clip rectangles.
|
||||||
let mut ui = egui::Ui::new(
|
let mut ui = egui::Ui::new(
|
||||||
ui.ctx().clone(),
|
ui.ctx().clone(),
|
||||||
ui.layer_id(),
|
ui.layer_id(),
|
||||||
@@ -314,7 +292,7 @@ impl<Leaf> Nodes<Leaf> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Node::Branch(branch) = &mut node {
|
if let Node::Branch(branch) = &mut node {
|
||||||
// TODO: join nested versions of the same horizontal/vertical layouts
|
// TODO(emilk): join nested versions of the same horizontal/vertical layouts
|
||||||
|
|
||||||
branch.simplify_children(|child| self.simplify(options, child));
|
branch.simplify_children(|child| self.simplify(options, child));
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl dock::Behavior<View> for DockBehavior {
|
|||||||
view.ui(ui)
|
view.ui(ui)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_text_for_leaf(&mut self, view: &View) -> egui::WidgetText {
|
fn tab_title_for_leaf(&mut self, view: &View) -> egui::WidgetText {
|
||||||
format!("View {}", view.nr).into()
|
format!("View {}", view.nr).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ fn tree_ui(
|
|||||||
// Get the name BEFORE we remove the node below!
|
// Get the name BEFORE we remove the node below!
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"{} - {node_id:?}",
|
"{} - {node_id:?}",
|
||||||
behavior.tab_text_for_node(nodes, node_id).text()
|
behavior.tab_title_for_node(nodes, node_id).text()
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(mut node) = nodes.nodes.remove(&node_id) else {
|
let Some(mut node) = nodes.nodes.remove(&node_id) else {
|
||||||
|
|||||||
Reference in New Issue
Block a user