Skip to content

Add Screenshot Export Functionality #1

@rsthornton

Description

@rsthornton

Description

Overview

Add screenshot export functionality to BERT, allowing users to capture and save system diagrams. This feature enables users to export their visual models for documentation, presentations, and sharing. Perfect first issue for learning BERT's Bevy + Leptos + Tauri architecture.

User Value

  • Export visual diagrams for documentation and presentations
  • Share models with collaborators
  • Save snapshots of work in progress

Scope

  • Target: Desktop builds only (Tauri/Bevy). Web/wasm screenshot support will be a follow-up issue.
  • Screenshot capture should be gated behind #[cfg(not(target_arch = "wasm32"))].
  • Minimum viable implementation uses Bevy's screenshot API and saves to the current working directory with a timestamped filename.

Important Caveat (Past Implementation)

  • The older bert-old implementation frequently exported blank images. The root cause was not confirmed; suspected causes include capturing before the frame finished rendering or targeting the wrong render surface.
  • Contributors are encouraged to investigate alternative causes (e.g., stage timing/scheduling, incorrect window or camera target, MSAA/alpha/DPI issues) and propose the most reliable fix.
  • To avoid blank screenshots, ensure capture happens after rendering has completed for the current frame. Use Bevy’s Screenshot API correctly and, if necessary, defer capture to the next frame using events or a small state machine.

Prior Attempt (Reference Only)

A previous screenshot attempt exists in our older codebase. It was unreliable (often blank). Use it only to study patterns and API usage; do not assume correctness and do not copy directly.

Key Technical Patterns to Adapt

1. Bevy Screenshot Capture

// Pattern from previous implementation - adapt for current architecture
use bevy::{
    prelude::*,
    render::view::screenshot::{Screenshot, ScreenshotFinished},
    window::PrimaryWindow,
};

pub fn take_screenshot(
    main_window: Query<Entity, With<PrimaryWindow>>,
    mut commands: Commands,
) {
    // Called when UI button is clicked
    let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S");
    let path = PathBuf::from(format!("bert_screenshot_{}.png", timestamp));
    commands.insert_resource(ScreenshotFile(path));
    let screenshot_entity = commands.spawn(Screenshot::window(main_window.single())).id();
    info!("Taking screenshot, saving to entity {:?}", screenshot_entity);
}
}

2. File Save Pattern

// Handle screenshot completion and file saving
pub fn save_screenshot_to_disk(
    mut screenshot_events: EventReader<ScreenshotFinished>,
    mut commands: Commands,
    screenshot_file: Option<Res<ScreenshotFile>>,
) {
    if let Some(screenshot_file) = screenshot_file {
        for event in screenshot_events.read() {
            if let Some(image_data) = &event.image_data {
                let path = &screenshot_file.0;
                match std::fs::write(path, image_data) {
                    Ok(_) => info!("Screenshot saved to {}", path.display()),
                    Err(e) => error!("Failed to save screenshot: {}", e),
                }
                commands.remove_resource::<ScreenshotFile>();
            }
        }
    }
}

3. File Naming Convention

// Timestamp-based naming for unique files
let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S");
let filename = format!("bert_screenshot_{}.png", timestamp);

Implementation Tasks

Core Functionality (Bevy)

  • Create src/bevy_app/systems/screenshot.rs with a take_screenshot and save_screenshot_to_disk system (desktop only via #[cfg(not(target_arch = "wasm32"))]).
  • Add a ScreenshotFile resource for path handling and cleanup after save.
  • Handle ScreenshotFinished events for saving to disk.
  • Export the module in src/bevy_app/systems/mod.rs.
  • Register the systems in src/bevy_app/mod.rs under Update (co-locate near other input-driven systems).
  • Emit a SaveSuccessEvent with a brief message on success to drive the existing toast UI.
  • Ensure capture occurs after a rendered frame: if you see blank images, gate take_screenshot to schedule a capture on the next frame (e.g., via a separate RequestScreenshot event and a follow-up system running in PostUpdate).

User Interface (Leptos)

  • Optional (nice to have for a second PR): Add a Screenshot button in the toolbar (src/leptos_app/mod.rs) and/or in src/leptos_app/components/controls_menu.rs.
  • Optional: Wire the button to trigger the screenshot system (e.g., via a new Bevy event like ScreenshotRequest).
  • Optional: Add a "Save As" dialog using Tauri (desktop only).
  • Optional: Show user feedback via the existing Toast component.

Dependencies

  • Add chrono to Cargo.toml for timestamped filenames (desktop only path).
  • Confirm Bevy 0.15 screenshot functionality on desktop and gate code for non-wasm.

Architecture Overview

BERT uses three main frameworks:

  • Bevy: Game engine handling rendering and systems (add screenshot logic here)
  • Leptos: Web framework for UI components (add screenshot button here)
  • Tauri: Desktop app wrapper (handles file dialogs)

Key Files

  • Systems: /src/bevy_app/systems/ - Add screenshot.rs here
  • UI Components: /src/leptos_app/components/ - Add controls here
  • System Registration: /src/bevy_app/mod.rs - Register new systems
  • Notifications: /src/bevy_app/systems/toast_handler.rs - Available for future feedback

Acceptance Criteria

Must Have

  • Keyboard shortcut to take a screenshot (e.g., Ctrl/Cmd+Shift+S) on desktop builds
  • Save to current directory with a timestamp filename
  • Console logging and a SaveSuccessEventToast for success/failure feedback
  • No blank images: validated by testing procedure below

Nice to Have

  • Screenshot button in toolbar and/or Controls menu
  • "Save As" dialog for custom location/filename
  • Multiple format support (PNG, JPG)
  • Copy to clipboard option
  • Web/wasm screenshot via canvas capture (separate issue)

Getting Started

1. Explore Current Architecture

# Look at existing systems for patterns
ls src/bevy_app/systems/
cat src/bevy_app/systems/mod.rs

# Check existing UI components
ls src/leptos_app/components/
cat src/leptos_app/components/controls_menu.rs

2. Study Reference Implementation

  • Review the linked files for overall approach
  • Note the patterns but adapt for current architecture
  • Pay attention to system registration and event handling

3. Start Small

  • Begin with desktop-only keyboard shortcut + file save (timestamped PNG)
  • Gate the implementation with #[cfg(not(target_arch = "wasm32"))]
  • Add UI controls (button and Save As dialog) in a follow-up PR

Technical Notes

Key Differences from Reference

  • Current version has evolved significantly since reference implementation
  • Use current Leptos component patterns, not old ones
  • Integrate with existing toast system rather than creating new notifications (SaveSuccessEvent)
  • Register systems in src/bevy_app/mod.rs (.add_systems(Update, ...)) and export from src/bevy_app/systems/mod.rs

Testing

  • Test keyboard shortcuts on different platforms (Mac/Windows/Linux)
  • Verify file naming doesn't conflict with existing files
  • Test error cases (permission issues, disk full, etc.)
  • Confirm screenshots capture the full system diagram correctly on desktop
  • Validate non-blank capture: create a model with distinct colors and labels, then compare a few known pixel samples or visually confirm contents. If blank, defer the capture by one frame (PostUpdate) or after TransformPropagate and retry.

Questions?

Feel free to ask questions in this issue or reach out in our Discord! This is designed to be a learning-friendly first contribution.


Additional Context for Implementation

Current File Structure Reference

src/bevy_app/systems/
├── mod.rs                 # System registration patterns here
├── setup.rs              # App initialization patterns  
├── camera.rs             # Camera/view handling
├── toast_handler.rs      # Notification system (use this!)
└── ui/                   # UI-related systems
    ├── mod.rs
    ├── color.rs
    ├── drag.rs
    └── zoom.rs

Leptos Component Integration

src/leptos_app/components/
├── mod.rs
├── controls_menu.rs      # Likely place for screenshot button
├── button.rs            # Button component patterns
└── toast.rs             # Toast notification patterns

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions