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

260 lines
7.2 KiB
Markdown

# 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<DirEntry>, 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**
```rust
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**
```bash
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**
```rust
#[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**
```rust
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**
```bash
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:
```rust
use voltex_editor::{..., AssetBrowser, asset_browser_panel};
```
Add to AppState:
```rust
asset_browser: AssetBrowser,
```
In `resumed`, initialize:
```rust
let asset_browser = AssetBrowser::new(std::env::current_dir().unwrap_or_default());
```
In the panel loop, replace panel 3 (Console):
```rust
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**
```bash
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**
```bash
git add docs/STATUS.md docs/DEFERRED.md
git commit -m "docs: update STATUS.md and DEFERRED.md with asset browser"
```