A flexible tree/outliner widget for egui - hierarchical data visualization and editing inspired by Blender's outliner.
| egui-arbor | egui | bevy | bevy_egui |
|---|---|---|---|
| 0.2.0 | 0.31 | 0.16 | 0.34 |
Note: The
bevyandbevy_eguiversions are only required if you're using the Bevy integration example.
- Hierarchical Tree View: Display nested data structures with collections and entities
- Expand/Collapse: Navigate through tree hierarchy with visual expand/collapse arrows
- Drag & Drop: Reorder and reparent nodes with Before/After/Inside positioning
- Multi-Selection: Full multi-select support with keyboard modifiers (Ctrl/Cmd for toggle, Shift for range)
- Action Icons: Built-in visibility, lock, and selection toggles with custom icon support
- Blender-Style Visibility: Parent visibility changes cascade to all children
- Inline Editing: Double-click to rename nodes with keyboard shortcuts
- Customizable Styling: Configure indentation, colors, icons, and spacing
- Trait-Based Integration: Works with any data structure implementing
OutlinerNode - Bevy Integration: Full support for Bevy game engine with 3D scene synchronization
- egui Memory Integration: Automatic state persistence across frames
Add to your Cargo.toml:
[dependencies]
egui-arbor = "0.2"use egui_arbor::{Outliner, OutlinerNode, OutlinerActions, ActionIcon};
// 1. Define your data structure
#[derive(Clone)]
struct TreeNode {
id: u64,
name: String,
children: Vec<TreeNode>,
}
// 2. Implement OutlinerNode trait
impl OutlinerNode for TreeNode {
type Id = u64;
fn id(&self) -> Self::Id { self.id }
fn name(&self) -> &str { &self.name }
fn is_collection(&self) -> bool { !self.children.is_empty() }
fn children(&self) -> &[Self] { &self.children }
fn children_mut(&mut self) -> &mut Vec<Self> { &mut self.children }
fn action_icons(&self) -> Vec<ActionIcon> {
vec![ActionIcon::Visibility, ActionIcon::Lock, ActionIcon::Selection]
}
}
// 3. Implement OutlinerActions trait
struct MyActions {
selected: Option<u64>,
}
impl OutlinerActions<TreeNode> for MyActions {
fn on_select(&mut self, id: &u64, selected: bool) {
self.selected = if selected { Some(*id) } else { None };
}
fn is_selected(&self, id: &u64) -> bool {
self.selected == Some(*id)
}
// Implement other required methods...
fn on_rename(&mut self, id: &u64, new_name: String) { /* ... */ }
fn on_move(&mut self, id: &u64, target: &u64, position: DropPosition) { /* ... */ }
fn is_visible(&self, id: &u64) -> bool { true }
fn is_locked(&self, id: &u64) -> bool { false }
fn on_visibility_toggle(&mut self, id: &u64) { /* ... */ }
fn on_lock_toggle(&mut self, id: &u64) { /* ... */ }
fn on_selection_toggle(&mut self, id: &u64) { /* ... */ }
fn on_custom_action(&mut self, id: &u64, icon: &str) { /* ... */ }
}
// 4. Use in your egui code
fn show_tree(ui: &mut egui::Ui, nodes: &[TreeNode], actions: &mut MyActions) {
let response = Outliner::new("my_tree")
.show(ui, nodes, actions);
// Handle events
if let Some(id) = response.selected() {
println!("Selected node: {:?}", id);
}
if let Some((id, new_name)) = response.renamed() {
println!("Renamed node {} to {}", id, new_name);
}
if let Some(drop_event) = response.drop_event() {
println!("Moved node {} to {}", drop_event.source, drop_event.target);
}
}Run the basic example to see all core features in action:
cargo run --example basicFeatures demonstrated:
- Tree structure with collections and entities
- All action icons (visibility, lock, selection)
- Drag & drop with visual feedback
- Multi-selection with keyboard modifiers
- Inline renaming with double-click
- Event logging and statistics
- Custom styling options
Run the Bevy integration example to see egui-arbor working with a 3D scene:
cargo run --example bevy_3d_outlinerFeatures demonstrated:
- Integration with Bevy 0.16.1 game engine
- 3D scene with three collections (Red, Green, Blue)
- Tree outliner synchronized with 3D scene visibility
- Blender-style visibility behavior (parent changes cascade to children)
- Drag and drop to reorganize scene hierarchy
- Inline rename for scene objects
- Orbit camera controls (left mouse: orbit, right mouse: pan, scroll: zoom)
The example creates 9 objects total (3 shapes Γ 3 colors) arranged in a grid pattern, with a tree outliner on the left side that controls their visibility in real-time.
Implement this trait on your data structure to make it work with the outliner:
pub trait OutlinerNode {
type Id: Hash + Eq + Clone;
fn id(&self) -> Self::Id;
fn name(&self) -> &str;
fn is_collection(&self) -> bool;
fn children(&self) -> &[Self];
fn children_mut(&mut self) -> &mut Vec<Self>;
fn icon(&self) -> Option<IconType> { None }
fn action_icons(&self) -> Vec<ActionIcon> { vec![] }
}Handle user interactions by implementing this trait:
pub trait OutlinerActions<N: OutlinerNode> {
fn on_rename(&mut self, id: &N::Id, new_name: String);
fn on_move(&mut self, id: &N::Id, target: &N::Id, position: DropPosition);
fn on_select(&mut self, id: &N::Id, selected: bool);
fn is_selected(&self, id: &N::Id) -> bool;
fn is_visible(&self, id: &N::Id) -> bool;
fn is_locked(&self, id: &N::Id) -> bool;
fn on_visibility_toggle(&mut self, id: &N::Id);
fn on_lock_toggle(&mut self, id: &N::Id);
fn on_selection_toggle(&mut self, id: &N::Id);
fn on_custom_action(&mut self, id: &N::Id, icon: &str);
}Built-in action icons:
- Visibility (π/π«): Toggle node visibility
- Lock (π/π): Prevent modifications
- Selection (β/β): Quick selection toggle
- Custom: Define your own with custom icons and tooltips
Three drop positions supported:
- Before: Insert before the target node
- After: Insert after the target node
- Inside: Add as child of target (collections only)
Automatic validation prevents invalid operations (e.g., parent into child).
use egui_arbor::{Outliner, Style, ExpandIconStyle};
let style = Style::default()
.with_indent(20.0)
.with_row_height(24.0)
.with_selection_color(egui::Color32::from_rgb(100, 150, 255));
Outliner::new("styled_tree")
.with_style(style)
.show(ui, &nodes, &mut actions);impl OutlinerNode for MyNode {
fn icon(&self) -> Option<IconType> {
if self.is_folder {
Some(IconType::Collection)
} else {
Some(IconType::Entity)
}
}
fn action_icons(&self) -> Vec<ActionIcon> {
vec![
ActionIcon::Visibility,
ActionIcon::Custom {
icon: "β".to_string(),
tooltip: Some("Favorite".to_string()),
},
]
}
}egui-arbor follows egui ecosystem conventions:
- User-owned data: You own your data structures
- Trait-based integration: Flexible integration with any data type
- Immediate mode: Widget reconstructed each frame
- egui memory integration: Automatic state persistence
See ARCHITECTURE.md for detailed design documentation.
serde: Enable serialization support for state persistence
[dependencies]
egui-arbor = { version = "0.2", features = ["serde"] }Rust 1.76 or later (edition 2024).
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.
Inspired by Blender's outliner and designed to integrate seamlessly with the egui ecosystem.