Files
game_engine/docs/superpowers/plans/2026-03-26-asset-browser.md
tolelom b65585b739 docs: add asset browser implementation plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:43:29 +09:00

7.2 KiB

Asset Browser Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a file system asset browser panel to the editor that displays files/folders in a flat list with navigation.

Architecture: AssetBrowser struct manages directory state (current path, entries cache). asset_browser_panel function renders the UI. Uses std::fs::read_dir for file listing. No external dependencies needed.

Tech Stack: Rust, std::fs, voltex_editor (UiContext, Rect, LayoutState, widgets)

Spec: docs/superpowers/specs/2026-03-26-asset-browser-design.md


Task 1: AssetBrowser struct + logic + tests

Files:

  • Create: crates/voltex_editor/src/asset_browser.rs

  • Modify: crates/voltex_editor/src/lib.rs

  • Step 1: Write tests and implementation

Create crates/voltex_editor/src/asset_browser.rs with the full AssetBrowser implementation and tests. The struct handles directory scanning, navigation, and path safety.

Key implementation details:

  • new(root) → canonicalize root, set current=root, refresh
  • refresh() → read_dir, collect into Vec, sort (dirs first, then by name)
  • navigate_to(dir_name) → join path, check starts_with(root) && is_dir, refresh
  • go_up() → parent path, check >= root, refresh
  • relative_path() → strip_prefix(root) display
  • format_size(bytes) → "B" / "KB" / "MB" formatting

Tests using std::fs temp directories:

  • test_new_scans_entries

  • test_navigate_to

  • test_go_up

  • test_go_up_at_root

  • test_root_guard

  • test_entries_sorted (dirs before files)

  • test_format_size

  • Step 2: Add module to lib.rs

pub mod asset_browser;
pub use asset_browser::{AssetBrowser, asset_browser_panel};
  • Step 3: Run tests

Run: cargo test -p voltex_editor --lib asset_browser -- --nocapture Expected: all tests PASS

  • Step 4: Commit
git add crates/voltex_editor/src/asset_browser.rs crates/voltex_editor/src/lib.rs
git commit -m "feat(editor): add AssetBrowser with directory navigation and file listing"

Task 2: asset_browser_panel UI function

Files:

  • Modify: crates/voltex_editor/src/asset_browser.rs

  • Step 1: Write test

#[test]
fn test_panel_draws_commands() {
    let dir = std::env::temp_dir().join("voltex_ab_ui_test");
    let _ = std::fs::create_dir_all(&dir);
    std::fs::write(dir.join("test.txt"), "hello").unwrap();

    let mut browser = AssetBrowser::new(dir.clone());
    let mut ui = UiContext::new(800.0, 600.0);
    let rect = Rect { x: 0.0, y: 0.0, w: 300.0, h: 400.0 };

    ui.begin_frame(0.0, 0.0, false);
    asset_browser_panel(&mut ui, &mut browser, &rect);
    ui.end_frame();

    assert!(ui.draw_list.commands.len() > 0);

    let _ = std::fs::remove_dir_all(&dir);
}
  • Step 2: Implement asset_browser_panel
pub fn asset_browser_panel(
    ui: &mut UiContext,
    browser: &mut AssetBrowser,
    rect: &Rect,
) {
    ui.layout = LayoutState::new(rect.x + PADDING, rect.y + PADDING);

    // Current path
    let path_text = format!("Path: /{}", browser.relative_path());
    ui.text(&path_text);

    // Go up button (not at root)
    if browser.current != browser.root {
        if ui.button("[..]") {
            browser.go_up();
            return; // entries changed, redraw next frame
        }
    }

    // Entry list
    let gw = ui.font.glyph_width as f32;
    let gh = ui.font.glyph_height as f32;
    let line_h = gh + PADDING;
    let mut clicked_dir: Option<String> = None;

    for entry in browser.entries() {
        let y = ui.layout.cursor_y;
        let x = rect.x + PADDING;

        // Highlight selected file
        if !entry.is_dir {
            if browser.selected_file.as_deref() == Some(&entry.name) {
                ui.draw_list.add_rect(rect.x, y, rect.w, line_h, COLOR_SELECTED);
            }
        }

        // Click detection
        if ui.mouse_clicked && ui.mouse_in_rect(rect.x, y, rect.w, line_h) {
            if entry.is_dir {
                clicked_dir = Some(entry.name.clone());
            } else {
                browser.selected_file = Some(entry.name.clone());
            }
        }

        // Draw label
        let label = if entry.is_dir {
            format!("[D] {}", entry.name)
        } else {
            format!("    {}", entry.name)
        };

        let text_y = y + (line_h - gh) * 0.5;
        let mut cx = x;
        for ch in label.chars() {
            let (u0, v0, u1, v1) = ui.font.glyph_uv(ch);
            ui.draw_list.add_rect_uv(cx, text_y, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
            cx += gw;
        }

        ui.layout.cursor_y += line_h;
    }

    // Navigate after iteration (avoids borrow conflict)
    if let Some(dir) = clicked_dir {
        browser.navigate_to(&dir);
    }

    // Selected file info
    if let Some(ref file_name) = browser.selected_file.clone() {
        ui.text("-- File Info --");
        ui.text(&format!("Name: {}", file_name));
        if let Some(entry) = browser.entries.iter().find(|e| e.name == *file_name) {
            ui.text(&format!("Size: {}", format_size(entry.size)));
            if let Some(ext) = file_name.rsplit('.').next() {
                if file_name.contains('.') {
                    ui.text(&format!("Type: .{}", ext));
                }
            }
        }
    }
}

Note: selected_file needs to be pub(crate) or accessed via a method. Also entries field needs to be accessible. Simplest: make selected_file and entries pub.

  • Step 3: Run tests

Run: cargo test -p voltex_editor --lib asset_browser -- --nocapture Expected: all tests PASS

  • Step 4: Commit
git add crates/voltex_editor/src/asset_browser.rs
git commit -m "feat(editor): add asset_browser_panel UI function"

Task 3: Integrate into editor_demo

Files:

  • Modify: examples/editor_demo/src/main.rs

  • Step 1: Replace Console panel with AssetBrowser

Add import:

use voltex_editor::{..., AssetBrowser, asset_browser_panel};

Add to AppState:

asset_browser: AssetBrowser,

In resumed, initialize:

let asset_browser = AssetBrowser::new(std::env::current_dir().unwrap_or_default());

In the panel loop, replace panel 3 (Console):

3 => {
    asset_browser_panel(&mut state.ui, &mut state.asset_browser, rect);
}
  • Step 2: Build and test

Run: cargo build -p editor_demo Run: cargo test -p voltex_editor

  • Step 3: Commit
git add examples/editor_demo/src/main.rs
git commit -m "feat(editor): integrate asset browser into editor_demo"

Task 4: Update docs

Files:

  • Modify: docs/STATUS.md

  • Modify: docs/DEFERRED.md

  • Step 1: Update STATUS.md

Add: - voltex_editor: AssetBrowser (file system browsing, directory navigation) Update test count.

  • Step 2: Update DEFERRED.md
- ~~**에셋 브라우저**~~ ✅ 파일 목록 + 디렉토리 탐색 완료. 에셋 로딩/프리뷰 미구현.
  • Step 3: Commit
git add docs/STATUS.md docs/DEFERRED.md
git commit -m "docs: update STATUS.md and DEFERRED.md with asset browser"