Skip to content

kyjohnso/egui-arbor

Repository files navigation

egui-arbor

egui-arbor example

A flexible tree/outliner widget for egui - hierarchical data visualization and editing inspired by Blender's outliner.

Crates.io Documentation License

Version Compatibility

egui-arbor egui bevy bevy_egui
0.2.0 0.31 0.16 0.34

Note: The bevy and bevy_egui versions are only required if you're using the Bevy integration example.

Features

  • 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

Quick Start

Add to your Cargo.toml:

[dependencies]
egui-arbor = "0.2"

Basic Example

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);
    }
}

Examples

Basic Example

Basic example showing tree structure with drag & drop

Run the basic example to see all core features in action:

cargo run --example basic

Features 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

Bevy 3D Outliner Example

Bevy 3D outliner with synchronized scene hierarchy

Run the Bevy integration example to see egui-arbor working with a 3D scene:

cargo run --example bevy_3d_outliner

Features 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.

Core Concepts

OutlinerNode Trait

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![] }
}

OutlinerActions Trait

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);
}

Action Icons

Built-in action icons:

  • Visibility (πŸ‘/🚫): Toggle node visibility
  • Lock (πŸ”’/πŸ”“): Prevent modifications
  • Selection (β˜‘/☐): Quick selection toggle
  • Custom: Define your own with custom icons and tooltips

Drag & Drop

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).

Customization

Custom Styling

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);

Custom Icons

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()),
            },
        ]
    }
}

Architecture

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.

Optional Features

  • serde: Enable serialization support for state persistence
[dependencies]
egui-arbor = { version = "0.2", features = ["serde"] }

Minimum Supported Rust Version (MSRV)

Rust 1.76 or later (edition 2024).

License

Licensed under either of:

at your option.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Acknowledgments

Inspired by Blender's outliner and designed to integrate seamlessly with the egui ecosystem.

About

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published

Languages