Skip to content

Commit

Permalink
Fix Id clash when using multiple Trees (#32)
Browse files Browse the repository at this point in the history
* Closes #11 

* (this is a fork of #12)

---

Each `Tree` now has a globally unique id, meaning the same `Tree` can be
rendered in different `Ui`:s and still work, and you can have multiple
trees. However, it is up to the user to pick a globally unique id.

---------

Co-authored-by: tosti007 <git@brianjanssen.nl>
  • Loading branch information
emilk and tosti007 authored Nov 21, 2023
1 parent 90922d3 commit 7a3991d
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 40 deletions.
2 changes: 1 addition & 1 deletion examples/advanced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ impl Default for MyApp {

let root = tiles.insert_tab_tile(tabs);

let tree = egui_tiles::Tree::new(root, tiles);
let tree = egui_tiles::Tree::new("my_tree", root, tiles);

Self {
tree,
Expand Down
2 changes: 1 addition & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ fn create_tree() -> egui_tiles::Tree<Pane> {

let root = tiles.insert_tab_tile(tabs);

egui_tiles::Tree::new(root, tiles)
egui_tiles::Tree::new("my_tree", root, tiles)
}
2 changes: 1 addition & 1 deletion src/container/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ mod tests {
let mut tiles = Tiles::default();
let panes: Vec<TileId> = vec![tiles.insert_pane(Pane {}), tiles.insert_pane(Pane {})];
let root: TileId = tiles.insert_grid_tile(panes);
Tree::new(root, tiles)
Tree::new("test_tree", root, tiles)
};

let style = egui::Style::default();
Expand Down
2 changes: 1 addition & 1 deletion src/container/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ fn linear_drop_zones<Pane>(
let preview_thickness = 12.0;
let dragged_index = children
.iter()
.position(|&child| is_being_dragged(egui_ctx, child));
.position(|&child| is_being_dragged(egui_ctx, tree.id, child));

let after_rect = |rect: Rect| match dir {
LinearDir::Horizontal => Rect::from_min_max(
Expand Down
6 changes: 3 additions & 3 deletions src/container/tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ impl Tabs {
.drag_started()
{
behavior.on_edit();
ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id()));
ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id(tree.id)));
}
}

Expand All @@ -281,10 +281,10 @@ impl Tabs {
continue;
}

let is_being_dragged = is_being_dragged(ui.ctx(), child_id);
let is_being_dragged = is_being_dragged(ui.ctx(), tree.id, child_id);

let selected = self.is_active(child_id);
let id = child_id.egui_id();
let id = child_id.egui_id(tree.id);

let response = behavior.tab_ui(
&tree.tiles,
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ fn is_possible_drag(ctx: &egui::Context) -> bool {
ctx.input(|input| input.pointer.is_decidedly_dragging())
}

fn is_being_dragged(ctx: &egui::Context, tile_id: TileId) -> bool {
ctx.memory(|mem| mem.is_being_dragged(tile_id.egui_id())) && is_possible_drag(ctx)
fn is_being_dragged(ctx: &egui::Context, tree_id: egui::Id, tile_id: TileId) -> bool {
ctx.memory(|mem| mem.is_being_dragged(tile_id.egui_id(tree_id))) && is_possible_drag(ctx)
}

/// If this tile is currently being dragged, cover it with a semi-transparent overlay ([`Behavior::dragged_overlay_color`]).
Expand All @@ -278,7 +278,7 @@ fn cover_tile_if_dragged<Pane>(
ui: &mut egui::Ui,
tile_id: TileId,
) {
if is_being_dragged(ui.ctx(), tile_id) {
if is_being_dragged(ui.ctx(), tree.id, tile_id) {
if let Some(child_rect) = tree.tiles.try_rect(tile_id) {
let overlay_color = behavior.dragged_overlay_color(ui.visuals());
ui.painter().rect_filled(child_rect, 0.0, overlay_color);
Expand Down
8 changes: 5 additions & 3 deletions src/tile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::{Container, ContainerKind};

/// An identifier for a [`Tile`] in the tree, be it a [`Container`] or a pane.
///
/// This id is unique within the tree, but not across trees.
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct TileId(u64);
Expand All @@ -10,9 +12,9 @@ impl TileId {
Self(n)
}

/// Corresponding [`egui::Id`], used for dragging.
pub fn egui_id(&self) -> egui::Id {
egui::Id::new(("egui_tile", self))
/// Corresponding [`egui::Id`], used for tracking dragging of tiles.
pub fn egui_id(&self, tree_id: egui::Id) -> egui::Id {
tree_id.with(("tile", self))
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/tiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use super::{
/// let tabs: Vec<TileId> = vec![tiles.insert_pane(Pane { }), tiles.insert_pane(Pane { })];
/// let root: TileId = tiles.insert_tab_tile(tabs);
///
/// let tree = Tree::new(root, tiles);
/// let tree = Tree::new("my_tree", root, tiles);
/// ```
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down
79 changes: 53 additions & 26 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,21 @@ use super::{
/// let tabs: Vec<TileId> = vec![tiles.insert_pane(Pane { }), tiles.insert_pane(Pane { })];
/// let root: TileId = tiles.insert_tab_tile(tabs);
///
/// let tree = Tree::new(root, tiles);
/// let tree = Tree::new("my_tree", root, tiles);
/// ```
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Tree<Pane> {
/// The constant, globally unique id of this tree.
pub(crate) id: egui::Id,

/// None = empty tree
pub root: Option<TileId>,

/// All the tiles in the tree.
pub tiles: Tiles<Pane>,
}

impl<Pane> Default for Tree<Pane> {
// An empty tree
fn default() -> Self {
Self {
root: None,
tiles: Default::default(),
}
}
}

impl<Pane: std::fmt::Debug> std::fmt::Debug for Tree<Pane> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Print a hierarchical view of the tree:
Expand Down Expand Up @@ -92,48 +85,81 @@ impl<Pane: std::fmt::Debug> std::fmt::Debug for Tree<Pane> {
// ----------------------------------------------------------------------------

impl<Pane> Tree<Pane> {
pub fn empty() -> Self {
Self::default()
/// Construct an empty tree.
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn empty(id: impl Into<egui::Id>) -> Self {
Self {
id: id.into(),
root: None,
tiles: Default::default(),
}
}

/// The most flexible constructor, allowing you to set up the tiles
/// however you want.
pub fn new(root: TileId, tiles: Tiles<Pane>) -> Self {
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new(id: impl Into<egui::Id>, root: TileId, tiles: Tiles<Pane>) -> Self {
Self {
id: id.into(),
root: Some(root),
tiles,
}
}

/// Create a top-level [`crate::Tabs`] container with the given panes.
pub fn new_tabs(panes: Vec<Pane>) -> Self {
Self::new_container(ContainerKind::Tabs, panes)
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new_tabs(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
Self::new_container(id, ContainerKind::Tabs, panes)
}

/// Create a top-level horizontal [`crate::Linear`] container with the given panes.
pub fn new_horizontal(panes: Vec<Pane>) -> Self {
Self::new_container(ContainerKind::Horizontal, panes)
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new_horizontal(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
Self::new_container(id, ContainerKind::Horizontal, panes)
}

/// Create a top-level vertical [`crate::Linear`] container with the given panes.
pub fn new_vertical(panes: Vec<Pane>) -> Self {
Self::new_container(ContainerKind::Vertical, panes)
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new_vertical(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
Self::new_container(id, ContainerKind::Vertical, panes)
}

/// Create a top-level [`crate::Grid`] container with the given panes.
pub fn new_grid(panes: Vec<Pane>) -> Self {
Self::new_container(ContainerKind::Grid, panes)
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new_grid(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
Self::new_container(id, ContainerKind::Grid, panes)
}

/// Create a top-level container with the given panes.
pub fn new_container(kind: ContainerKind, panes: Vec<Pane>) -> Self {
///
/// The `id` must be _globally_ unique (!).
/// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
pub fn new_container(id: impl Into<egui::Id>, kind: ContainerKind, panes: Vec<Pane>) -> Self {
let mut tiles = Tiles::default();
let tile_ids = panes
.into_iter()
.map(|pane| tiles.insert_pane(pane))
.collect();
let root = tiles.insert_new(Tile::Container(Container::new(kind, tile_ids)));
Self::new(root, tiles)
Self::new(id, root, tiles)
}

/// The globally unique id used by this `Tree`.
#[inline]
pub fn id(&self) -> egui::Id {
self.id
}

/// Check if [`Self::root`] is [`None`].
Expand All @@ -147,6 +173,7 @@ impl<Pane> Tree<Pane> {
self.root
}

#[inline]
pub fn is_root(&self, tile: TileId) -> bool {
self.root == Some(tile)
}
Expand Down Expand Up @@ -234,7 +261,7 @@ impl<Pane> Tree<Pane> {
match &mut tile {
Tile::Pane(pane) => {
if behavior.pane_ui(&mut ui, tile_id, pane) == UiResponse::DragStarted {
ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id()));
ui.memory_mut(|mem| mem.set_dragged_id(tile_id.egui_id(self.id)));
}
}
Tile::Container(container) => {
Expand Down Expand Up @@ -411,7 +438,7 @@ impl<Pane> Tree<Pane> {
continue; // not allowed to drag root
}

let id = tile_id.egui_id();
let id = tile_id.egui_id(self.id);
let is_tile_being_dragged = ctx.memory(|mem| mem.is_being_dragged(id));
if is_tile_being_dragged {
// Abort drags on escape:
Expand Down

0 comments on commit 7a3991d

Please sign in to comment.