egui_tiles/
tree.rs

1use egui::{NumExt as _, Rect, Ui};
2
3use crate::behavior::EditAction;
4use crate::{ContainerInsertion, ContainerKind, UiResponse};
5
6use super::{
7    Behavior, Container, DropContext, InsertionPoint, SimplificationOptions, SimplifyAction, Tile,
8    TileId, Tiles,
9};
10
11/// The top level type. Contains all persistent state, including layouts and sizes.
12///
13/// You'll usually construct this once and then store it, calling [`Tree::ui`] each frame.
14///
15/// See [the crate-level documentation](crate) for a complete example.
16///
17/// ## How to construct a [`Tree`]
18/// ```
19/// use egui_tiles::{Tiles, TileId, Tree};
20///
21/// struct Pane { } // put some state here
22///
23/// let mut tiles = Tiles::default();
24/// let tabs: Vec<TileId> = vec![tiles.insert_pane(Pane { }), tiles.insert_pane(Pane { })];
25/// let root: TileId = tiles.insert_tab_tile(tabs);
26///
27/// let tree = Tree::new("my_tree", root, tiles);
28/// ```
29#[derive(Clone, PartialEq)]
30#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
31pub struct Tree<Pane> {
32    /// The constant, globally unique id of this tree.
33    pub(crate) id: egui::Id,
34
35    /// None = empty tree
36    pub root: Option<TileId>,
37
38    /// All the tiles in the tree.
39    pub tiles: Tiles<Pane>,
40
41    /// When finite, this values contains the exact height of this tree
42    #[cfg_attr(
43        feature = "serde",
44        serde(serialize_with = "serialize_f32_infinity_as_null"),
45        serde(deserialize_with = "deserialize_f32_null_as_infinity")
46    )]
47    height: f32,
48
49    /// When finite, this values contains the exact width of this tree
50    #[cfg_attr(
51        feature = "serde",
52        serde(serialize_with = "serialize_f32_infinity_as_null"),
53        serde(deserialize_with = "deserialize_f32_null_as_infinity")
54    )]
55    width: f32,
56}
57
58// Workaround for JSON which doesn't support infinity, because JSON is stupid.
59#[cfg(feature = "serde")]
60fn serialize_f32_infinity_as_null<S: serde::Serializer>(
61    t: &f32,
62    serializer: S,
63) -> Result<S::Ok, S::Error> {
64    if t.is_infinite() {
65        serializer.serialize_none()
66    } else {
67        serializer.serialize_some(t)
68    }
69}
70
71#[cfg(feature = "serde")]
72fn deserialize_f32_null_as_infinity<'de, D: serde::Deserializer<'de>>(
73    des: D,
74) -> Result<f32, D::Error> {
75    use serde::Deserialize as _;
76    Ok(Option::<f32>::deserialize(des)?.unwrap_or(f32::INFINITY))
77}
78
79impl<Pane: std::fmt::Debug> std::fmt::Debug for Tree<Pane> {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        // Print a hierarchical view of the tree:
82        fn format_tile<Pane: std::fmt::Debug>(
83            f: &mut std::fmt::Formatter<'_>,
84            tiles: &Tiles<Pane>,
85            indent: usize,
86            tile_id: TileId,
87        ) -> std::fmt::Result {
88            write!(f, "{} {tile_id:?}: ", "  ".repeat(indent))?;
89            if let Some(tile) = tiles.get(tile_id) {
90                match tile {
91                    Tile::Pane(pane) => writeln!(f, "Pane {pane:?}"),
92                    Tile::Container(container) => {
93                        writeln!(
94                            f,
95                            "{}",
96                            match container {
97                                Container::Tabs(_) => "Tabs",
98                                Container::Linear(_) => "Linear",
99                                Container::Grid(_) => "Grid",
100                            }
101                        )?;
102                        for &child in container.children() {
103                            format_tile(f, tiles, indent + 1, child)?;
104                        }
105                        Ok(())
106                    }
107                }
108            } else {
109                writeln!(f, "DANGLING")
110            }
111        }
112
113        let Self {
114            id,
115            root,
116            tiles,
117            width,
118            height,
119        } = self;
120
121        if let Some(root) = root {
122            writeln!(f, "Tree {{")?;
123            writeln!(f, "    id: {id:?}")?;
124            writeln!(f, "    width: {width:?}")?;
125            writeln!(f, "    height: {height:?}")?;
126            format_tile(f, tiles, 1, *root)?;
127            write!(f, "}}")
128        } else {
129            writeln!(f, "Tree {{ }}")
130        }
131    }
132}
133
134// ----------------------------------------------------------------------------
135
136impl<Pane> Tree<Pane> {
137    /// Construct an empty tree.
138    ///
139    /// The `id` must be _globally_ unique (!).
140    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
141    pub fn empty(id: impl Into<egui::Id>) -> Self {
142        Self {
143            id: id.into(),
144            root: None,
145            tiles: Default::default(),
146            width: f32::INFINITY,
147            height: f32::INFINITY,
148        }
149    }
150
151    /// The most flexible constructor, allowing you to set up the tiles
152    /// however you want.
153    ///
154    /// The `id` must be _globally_ unique (!).
155    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
156    pub fn new(id: impl Into<egui::Id>, root: TileId, tiles: Tiles<Pane>) -> Self {
157        Self {
158            id: id.into(),
159            root: Some(root),
160            tiles,
161            width: f32::INFINITY,
162            height: f32::INFINITY,
163        }
164    }
165
166    /// Create a top-level [`crate::Tabs`] container with the given panes.
167    ///
168    /// The `id` must be _globally_ unique (!).
169    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
170    pub fn new_tabs(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
171        Self::new_container(id, ContainerKind::Tabs, panes)
172    }
173
174    /// Create a top-level horizontal [`crate::Linear`] container with the given panes.
175    ///
176    /// The `id` must be _globally_ unique (!).
177    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
178    pub fn new_horizontal(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
179        Self::new_container(id, ContainerKind::Horizontal, panes)
180    }
181
182    /// Create a top-level vertical [`crate::Linear`] container with the given panes.
183    ///
184    /// The `id` must be _globally_ unique (!).
185    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
186    pub fn new_vertical(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
187        Self::new_container(id, ContainerKind::Vertical, panes)
188    }
189
190    /// Create a top-level [`crate::Grid`] container with the given panes.
191    ///
192    /// The `id` must be _globally_ unique (!).
193    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
194    pub fn new_grid(id: impl Into<egui::Id>, panes: Vec<Pane>) -> Self {
195        Self::new_container(id, ContainerKind::Grid, panes)
196    }
197
198    /// Create a top-level container with the given panes.
199    ///
200    /// The `id` must be _globally_ unique (!).
201    /// This is so that the same tree can be added to different [`egui::Ui`]s (if you want).
202    pub fn new_container(id: impl Into<egui::Id>, kind: ContainerKind, panes: Vec<Pane>) -> Self {
203        let mut tiles = Tiles::default();
204        let tile_ids = panes
205            .into_iter()
206            .map(|pane| tiles.insert_pane(pane))
207            .collect();
208        let root = tiles.insert_new(Tile::Container(Container::new(kind, tile_ids)));
209        Self::new(id, root, tiles)
210    }
211
212    /// Remove the given tile and all child tiles, recursively.
213    ///
214    /// This also removes the tile id from the parent's list of children.
215    ///
216    /// All removed tiles are returned in unspecified order.
217    pub fn remove_recursively(&mut self, id: TileId) -> Vec<Tile<Pane>> {
218        // Remove the top-most tile_id from its parent
219        self.remove_tile_id_from_parent(id);
220
221        let mut removed_tiles = vec![];
222        self.remove_recursively_impl(id, &mut removed_tiles);
223        removed_tiles
224    }
225
226    fn remove_recursively_impl(&mut self, id: TileId, removed_tiles: &mut Vec<Tile<Pane>>) {
227        // We can safely use the raw `tiles.remove` API here because either the parent was cleaned
228        // up explicitly from `remove_recursively` or the parent is also being removed so there's
229        // no reason to clean it up.
230        if let Some(tile) = self.tiles.remove(id) {
231            if let Tile::Container(container) = &tile {
232                for &child_id in container.children() {
233                    self.remove_recursively_impl(child_id, removed_tiles);
234                }
235            }
236            removed_tiles.push(tile);
237        }
238    }
239
240    /// The globally unique id used by this `Tree`.
241    #[inline]
242    pub fn id(&self) -> egui::Id {
243        self.id
244    }
245
246    /// Check if [`Self::root`] is [`None`].
247    #[inline]
248    pub fn is_empty(&self) -> bool {
249        self.root.is_none()
250    }
251
252    #[inline]
253    pub fn root(&self) -> Option<TileId> {
254        self.root
255    }
256
257    #[inline]
258    pub fn is_root(&self, tile: TileId) -> bool {
259        self.root == Some(tile)
260    }
261
262    /// Tiles are visible by default.
263    ///
264    /// Invisible tiles still retain their place in the tile hierarchy.
265    pub fn is_visible(&self, tile_id: TileId) -> bool {
266        self.tiles.is_visible(tile_id)
267    }
268
269    /// Tiles are visible by default.
270    ///
271    /// Invisible tiles still retain their place in the tile hierarchy.
272    pub fn set_visible(&mut self, tile_id: TileId, visible: bool) {
273        self.tiles.set_visible(tile_id, visible);
274    }
275
276    /// All visible tiles.
277    ///
278    /// This excludes all tiles that are invisible or are inactive tabs, recursively.
279    ///
280    /// The order of the returned tiles is arbitrary.
281    pub fn active_tiles(&self) -> Vec<TileId> {
282        let mut tiles = vec![];
283        if let Some(root) = self.root {
284            if self.is_visible(root) {
285                self.tiles.collect_active_tiles(root, &mut tiles);
286            }
287        }
288        tiles
289    }
290
291    /// All non-visible tiles.
292    ///
293    /// This includes all tiles that are invisible or are inactive tabs. Uses `active_tiles`.
294    ///
295    /// The order of the returned tiles is arbitrary.
296    pub fn inactive_tiles(&self) -> Vec<TileId> {
297        let active_tiles = self.active_tiles();
298        self.tiles
299            .tile_ids()
300            .filter(|id| !active_tiles.contains(id))
301            .collect()
302    }
303
304    /// Show the tree in the given [`Ui`].
305    ///
306    /// The tree will use upp all the available space - nothing more, nothing less.
307    pub fn ui(&mut self, behavior: &mut dyn Behavior<Pane>, ui: &mut Ui) {
308        self.simplify(&behavior.simplification_options());
309
310        self.gc(behavior);
311
312        self.tiles.rects.clear();
313
314        // Check if anything is being dragged:
315        let mut drop_context = DropContext {
316            enabled: true,
317            dragged_tile_id: self.dragged_id(ui.ctx()),
318            mouse_pos: ui.input(|i| i.pointer.interact_pos()),
319            best_dist_sq: f32::INFINITY,
320            best_insertion: None,
321            preview_rect: None,
322        };
323
324        let mut rect = ui.available_rect_before_wrap();
325        if self.height.is_finite() {
326            rect.set_height(self.height);
327        }
328        if self.width.is_finite() {
329            rect.set_width(self.width);
330        }
331        if let Some(root) = self.root {
332            self.tiles.layout_tile(ui.style(), behavior, rect, root);
333
334            self.tile_ui(behavior, &mut drop_context, ui, root);
335        }
336
337        self.preview_dragged_tile(behavior, &drop_context, ui);
338        ui.advance_cursor_after_rect(rect);
339    }
340
341    /// Sets the exact height that can be used by the tree.
342    ///
343    /// Determines the height that will be used by the tree component.
344    /// By default, the tree occupies all the available space in the parent container.
345    pub fn set_height(&mut self, height: f32) {
346        if height.is_sign_positive() && height.is_finite() {
347            self.height = height;
348        } else {
349            self.height = f32::INFINITY;
350        }
351    }
352
353    /// Sets the exact width that can be used by the tree.
354    ///
355    /// Determines the width that will be used by the tree component.
356    /// By default, the tree occupies all the available space in the parent container.
357    pub fn set_width(&mut self, width: f32) {
358        if width.is_sign_positive() && width.is_finite() {
359            self.width = width;
360        } else {
361            self.width = f32::INFINITY;
362        }
363    }
364
365    pub(super) fn tile_ui(
366        &mut self,
367        behavior: &mut dyn Behavior<Pane>,
368        drop_context: &mut DropContext,
369        ui: &Ui,
370        tile_id: TileId,
371    ) {
372        if !self.is_visible(tile_id) {
373            return;
374        }
375        // NOTE: important that we get the rect and tile in two steps,
376        // otherwise we could loose the tile when there is no rect.
377        let Some(rect) = self.tiles.rect(tile_id) else {
378            log::debug!("Failed to find rect for tile {tile_id:?} during ui");
379            return;
380        };
381        let Some(mut tile) = self.tiles.remove(tile_id) else {
382            log::debug!("Failed to find tile {tile_id:?} during ui");
383            return;
384        };
385
386        let drop_context_was_enabled = drop_context.enabled;
387        if Some(tile_id) == drop_context.dragged_tile_id {
388            // Can't drag a tile onto self or any children
389            drop_context.enabled = false;
390        }
391        drop_context.on_tile(behavior, ui.style(), tile_id, rect, &tile);
392
393        // Each tile gets its own `Ui`, nested inside each other, with proper clip rectangles.
394        let enabled = ui.is_enabled();
395        let mut ui = egui::Ui::new(
396            ui.ctx().clone(),
397            ui.id().with(tile_id),
398            egui::UiBuilder::new()
399                .layer_id(ui.layer_id())
400                .max_rect(rect),
401        );
402
403        ui.add_enabled_ui(enabled, |ui| {
404            match &mut tile {
405                Tile::Pane(pane) => {
406                    if behavior.pane_ui(ui, tile_id, pane) == UiResponse::DragStarted {
407                        ui.ctx().set_dragged_id(tile_id.egui_id(self.id));
408                    }
409                }
410                Tile::Container(container) => {
411                    container.ui(self, behavior, drop_context, ui, rect, tile_id);
412                }
413            };
414
415            behavior.paint_on_top_of_tile(ui.painter(), ui.style(), tile_id, rect);
416
417            self.tiles.insert(tile_id, tile);
418            drop_context.enabled = drop_context_was_enabled;
419        });
420    }
421
422    /// Recursively "activate" the ancestors of the tiles that matches the given predicate.
423    ///
424    /// This means making the matching tiles and its ancestors the active tab in any tab layout.
425    ///
426    /// Returns `true` if a tab was made active.
427    pub fn make_active(
428        &mut self,
429        mut should_activate: impl FnMut(TileId, &Tile<Pane>) -> bool,
430    ) -> bool {
431        if let Some(root) = self.root {
432            self.tiles.make_active(root, &mut should_activate)
433        } else {
434            false
435        }
436    }
437
438    fn preview_dragged_tile(
439        &mut self,
440        behavior: &mut dyn Behavior<Pane>,
441        drop_context: &DropContext,
442        ui: &mut Ui,
443    ) {
444        let (Some(mouse_pos), Some(dragged_tile_id)) =
445            (drop_context.mouse_pos, drop_context.dragged_tile_id)
446        else {
447            return;
448        };
449
450        ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Grabbing);
451
452        // Preview what is being dragged:
453        egui::Area::new(ui.id().with((dragged_tile_id, "preview")))
454            .pivot(egui::Align2::CENTER_CENTER)
455            .current_pos(mouse_pos)
456            .interactable(false)
457            .show(ui.ctx(), |ui| {
458                behavior.drag_ui(&self.tiles, ui, dragged_tile_id);
459            });
460
461        if let Some(preview_rect) = drop_context.preview_rect {
462            let preview_rect = smooth_preview_rect(ui.ctx(), dragged_tile_id, preview_rect);
463
464            let parent_rect = drop_context
465                .best_insertion
466                .and_then(|insertion_point| self.tiles.rect(insertion_point.parent_id));
467
468            behavior.paint_drag_preview(ui.visuals(), ui.painter(), parent_rect, preview_rect);
469
470            if behavior.preview_dragged_panes() {
471                // TODO(emilk): add support for previewing containers too.
472                if preview_rect.width() > 32.0 && preview_rect.height() > 32.0 {
473                    if let Some(Tile::Pane(pane)) = self.tiles.get_mut(dragged_tile_id) {
474                        // Intentionally ignore the response, since the user cannot possibly
475                        // begin a drag on the preview pane.
476                        let _ignored: UiResponse = behavior.pane_ui(
477                            &mut ui.new_child(egui::UiBuilder::new().max_rect(preview_rect)),
478                            dragged_tile_id,
479                            pane,
480                        );
481                    }
482                }
483            }
484        }
485
486        if ui.input(|i| i.pointer.any_released()) {
487            if let Some(insertion_point) = drop_context.best_insertion {
488                behavior.on_edit(EditAction::TileDropped);
489                self.move_tile(dragged_tile_id, insertion_point, false);
490            }
491            clear_smooth_preview_rect(ui.ctx(), dragged_tile_id);
492        }
493    }
494
495    /// Simplify and normalize the tree using the given options.
496    ///
497    /// This is also called at the start of [`Self::ui`].
498    pub fn simplify(&mut self, options: &SimplificationOptions) {
499        if let Some(root) = self.root {
500            match self.tiles.simplify(options, root, None) {
501                SimplifyAction::Keep => {}
502                SimplifyAction::Remove => {
503                    self.root = None;
504                }
505                SimplifyAction::Replace(new_root) => {
506                    self.root = Some(new_root);
507                }
508            }
509
510            if options.all_panes_must_have_tabs {
511                if let Some(tile_id) = self.root {
512                    self.tiles.make_all_panes_children_of_tabs(false, tile_id);
513                }
514            }
515        }
516    }
517
518    /// Simplify all of the children of the given container tile recursively.
519    pub fn simplify_children_of_tile(&mut self, tile_id: TileId, options: &SimplificationOptions) {
520        if let Some(Tile::Container(mut container)) = self.tiles.remove(tile_id) {
521            let kind = container.kind();
522            container.simplify_children(|child| self.tiles.simplify(options, child, Some(kind)));
523            self.tiles.insert(tile_id, Tile::Container(container));
524        }
525    }
526
527    /// Garbage-collect tiles that are no longer reachable from the root tile.
528    ///
529    /// This is also called by [`Self::ui`], so usually you don't need to call this yourself.
530    pub fn gc(&mut self, behavior: &mut dyn Behavior<Pane>) {
531        self.tiles.gc_root(behavior, self.root);
532    }
533
534    /// Move a tile to a new container, at the specified insertion index.
535    ///
536    /// If the insertion index is greater than the current number of children, the tile is appended at the end.
537    ///
538    /// The grid layout needs a special treatment because it can have holes. When dragging a tile away from a grid, it
539    /// leaves behind it a hole. As a result, if the tile is the dropped in the same grid, it there is no need to account
540    /// for an insertion index shift (the hole can still occupy the original place of the dragged tile). However, if the
541    /// tiles are reordered in a separate, linear representation of the grid (such as the Rerun blueprint tree), the
542    /// expectation is that the grid is properly reordered and thus the insertion index must be shifted in case the tile
543    /// is moved inside the same grid. The `reflow_grid` parameter controls this behavior.
544    ///
545    /// TL;DR:
546    /// - when drag-and-dropping from a 2D representation of the grid, set `reflow_grid = false`
547    /// - when drag-and-dropping from a 1D representation of the grid, set `reflow_grid = true`
548    pub fn move_tile_to_container(
549        &mut self,
550        moved_tile_id: TileId,
551        destination_container: TileId,
552        mut insertion_index: usize,
553        reflow_grid: bool,
554    ) {
555        // find target container
556        if let Some(Tile::Container(target_container)) = self.tiles.get(destination_container) {
557            let num_children = target_container.num_children();
558            if insertion_index > num_children {
559                insertion_index = num_children;
560            }
561
562            let container_insertion = match target_container.kind() {
563                ContainerKind::Tabs => ContainerInsertion::Tabs(insertion_index),
564                ContainerKind::Horizontal => ContainerInsertion::Horizontal(insertion_index),
565                ContainerKind::Vertical => ContainerInsertion::Vertical(insertion_index),
566                ContainerKind::Grid => ContainerInsertion::Grid(insertion_index),
567            };
568
569            self.move_tile(
570                moved_tile_id,
571                InsertionPoint {
572                    parent_id: destination_container,
573                    insertion: container_insertion,
574                },
575                reflow_grid,
576            );
577        } else {
578            log::warn!(
579                "Failed to find destination container {destination_container:?} during `move_tile_to_container()`"
580            );
581        }
582    }
583
584    /// Move the given tile to the given insertion point.
585    ///
586    /// See [`Self::move_tile_to_container()`] for details on `reflow_grid`.
587    pub(super) fn move_tile(
588        &mut self,
589        moved_tile_id: TileId,
590        insertion_point: InsertionPoint,
591        reflow_grid: bool,
592    ) {
593        log::trace!(
594            "Moving {moved_tile_id:?} into {:?}",
595            insertion_point.insertion
596        );
597
598        if let Some((prev_parent_id, source_index)) = self.remove_tile_id_from_parent(moved_tile_id)
599        {
600            // Check to see if we are moving a tile within the same container:
601
602            if prev_parent_id == insertion_point.parent_id {
603                let parent_tile = self.tiles.get_mut(prev_parent_id);
604
605                if let Some(Tile::Container(container)) = parent_tile {
606                    if container.kind() == insertion_point.insertion.kind() {
607                        let dest_index = insertion_point.insertion.index();
608                        log::trace!(
609                            "Moving within the same parent: {source_index} -> {dest_index}"
610                        );
611                        // lets swap the two indices
612
613                        let adjusted_index = if source_index < dest_index {
614                            // We removed an earlier element, so we need to adjust the index:
615                            dest_index - 1
616                        } else {
617                            dest_index
618                        };
619
620                        match container {
621                            Container::Tabs(tabs) => {
622                                let insertion_index = adjusted_index.min(tabs.children.len());
623                                tabs.children.insert(insertion_index, moved_tile_id);
624                                tabs.active = Some(moved_tile_id);
625                            }
626                            Container::Linear(linear) => {
627                                let insertion_index = adjusted_index.min(linear.children.len());
628                                linear.children.insert(insertion_index, moved_tile_id);
629                            }
630                            Container::Grid(grid) => {
631                                if reflow_grid {
632                                    self.tiles.insert_at(insertion_point, moved_tile_id);
633                                } else {
634                                    let dest_tile = grid.replace_at(dest_index, moved_tile_id);
635                                    if let Some(dest) = dest_tile {
636                                        grid.insert_at(source_index, dest);
637                                    }
638                                };
639                            }
640                        }
641                        return; // done
642                    }
643                }
644            }
645        }
646
647        // Moving to a new parent
648        self.tiles.insert_at(insertion_point, moved_tile_id);
649    }
650
651    /// Find the currently dragged tile, if any.
652    pub fn dragged_id(&self, ctx: &egui::Context) -> Option<TileId> {
653        for tile_id in self.tiles.tile_ids() {
654            if self.is_root(tile_id) {
655                continue; // not allowed to drag root
656            }
657
658            let is_tile_being_dragged = crate::is_being_dragged(ctx, self.id, tile_id);
659            if is_tile_being_dragged {
660                // Abort drags on escape:
661                if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
662                    ctx.stop_dragging();
663                    return None;
664                }
665
666                return Some(tile_id);
667            }
668        }
669        None
670    }
671
672    /// This removes the given tile from the parents list of children.
673    ///
674    /// The [`Tile`] itself is not removed from [`Self::tiles`].
675    ///
676    /// Performs no simplifications.
677    ///
678    /// If found, the parent tile and the child's index is returned.
679    pub(super) fn remove_tile_id_from_parent(
680        &mut self,
681        remove_me: TileId,
682    ) -> Option<(TileId, usize)> {
683        let mut result = None;
684
685        for (parent_id, parent) in self.tiles.iter_mut() {
686            if let Tile::Container(container) = parent {
687                if let Some(child_index) = container.remove_child(remove_me) {
688                    result = Some((*parent_id, child_index));
689                }
690            }
691        }
692
693        // Make sure that if we drag away the active some tabs,
694        // that the tab container gets assigned another active tab.
695        // If the tab is dragged to the same container, then it will become active again,
696        // since all tabs become active when dragged, wherever they end up.
697        if let Some((parent_id, _)) = result {
698            if let Some(mut tile) = self.tiles.remove(parent_id) {
699                if let Tile::Container(Container::Tabs(tabs)) = &mut tile {
700                    tabs.ensure_active(&self.tiles);
701                }
702                self.tiles.insert(parent_id, tile);
703            }
704        }
705
706        result
707    }
708}
709
710// ----------------------------------------------------------------------------
711
712/// We store the preview rect in egui temp storage so that it is not serialized,
713/// and so that a user could re-create the [`Tree`] each frame and still get smooth previews.
714fn smooth_preview_rect_id(dragged_tile_id: TileId) -> egui::Id {
715    egui::Id::new((dragged_tile_id, "smoothed_preview_rect"))
716}
717
718fn clear_smooth_preview_rect(ctx: &egui::Context, dragged_tile_id: TileId) {
719    let data_id = smooth_preview_rect_id(dragged_tile_id);
720    ctx.data_mut(|data| data.remove::<Rect>(data_id));
721}
722
723/// Take the preview rectangle and smooth it over time.
724fn smooth_preview_rect(ctx: &egui::Context, dragged_tile_id: TileId, new_rect: Rect) -> Rect {
725    let data_id = smooth_preview_rect_id(dragged_tile_id);
726
727    let dt = ctx.input(|input| input.stable_dt).at_most(0.1);
728
729    let mut requires_repaint = false;
730
731    let smoothed = ctx.data_mut(|data| {
732        let smoothed: &mut Rect = data.get_temp_mut_or(data_id, new_rect);
733
734        let t = egui::emath::exponential_smooth_factor(0.9, 0.05, dt);
735
736        *smoothed = smoothed.lerp_towards(&new_rect, t);
737
738        let diff = smoothed.min.distance(new_rect.min) + smoothed.max.distance(new_rect.max);
739        if diff < 0.5 {
740            *smoothed = new_rect;
741        } else {
742            requires_repaint = true;
743        }
744        *smoothed
745    });
746
747    if requires_repaint {
748        ctx.request_repaint();
749    }
750
751    smoothed
752}