# AuroraView

> A lightweight WebView framework for DCC (Digital Content Creation) software, built with Rust and Python bindings. Perfect for Maya, 3ds Max, Houdini, Blender, and more.

## Overview

AuroraView provides a modern web-based UI solution for professional DCC applications like Maya, 3ds Max, Houdini, Blender, Photoshop, and Unreal Engine. Built on Rust's Wry library with PyO3 bindings, it offers native performance with minimal overhead.

- **Package**: `pip install auroraview`
- **Python**: 3.7+
- **Platforms**: Windows, macOS, Linux
- **License**: MIT

## Quick Start

### Installation

```bash
# Windows/macOS
pip install auroraview

# With Qt support (for Maya, Houdini, Nuke)
pip install auroraview[qt]
```

### Basic Usage

```python
from auroraview import WebView

# Create and show WebView
webview = WebView.create("My App", url="http://localhost:3000")
webview.show()
```

## Core API Reference

### WebView Class

The main class for creating WebView windows.

#### Constructor Parameters

```python
WebView(
    title: str = "AuroraView",      # Window title
    width: int = 800,               # Window width
    height: int = 600,              # Window height
    url: str = None,                # URL to load
    html: str = None,               # HTML content to load
    debug: bool = True,             # Enable DevTools (F12)
    context_menu: bool = True,      # Enable right-click menu
    resizable: bool = True,         # Allow window resize
    frame: bool = True,             # Show window frame
    parent: int = None,             # Parent HWND for embedding
    mode: str = None,               # "owner" or "child" mode
    asset_root: str = None,         # Root for auroraview:// protocol
    allow_file_protocol: bool = False,  # Enable file:// URLs
    always_on_top: bool = False,    # Keep window on top
    transparent: bool = False,      # Transparent window
)
```

#### Factory Methods

```python
# Recommended way to create WebView
webview = WebView.create(
    title="My App",
    url="http://localhost:3000",
    width=800,
    height=600,
    parent=hwnd,           # Optional: parent window handle
    mode="auto",           # "auto", "owner", "child"
    debug=True,            # Enable DevTools
    singleton="my_tool",   # Optional: singleton key
)

# For embedded mode with HWND (DCC or any host application)
webview = WebView.create_embedded(
    parent_hwnd=hwnd,
    title="Embedded Tool",
    width=800,
    height=600,
    url="http://localhost:3000",
)

# Convenience helper for embedded mode
webview = WebView.run_embedded(
    title="Tool",
    url="http://localhost:3000",
    parent=hwnd,
)
```

#### Content Loading

```python
# Load URL
webview.load_url("http://localhost:3000")

# Load HTML
webview.load_html("<h1>Hello World</h1>")

# Load local file
webview.load_file("path/to/index.html")
```

#### JavaScript Execution

```python
# Execute JavaScript
webview.eval_js("console.log('Hello from Python')")

# Execute with callback
webview.eval_js_async(
    "document.title",
    callback=lambda result: print(f"Title: {result}"),
    timeout_ms=5000
)
```

#### Window Control

```python
webview.show()              # Show window
webview.hide()              # Hide window
webview.close()             # Close window
webview.resize(1024, 768)   # Resize window
webview.move(100, 100)      # Move window
webview.minimize()          # Minimize
webview.maximize()          # Maximize
webview.toggle_fullscreen() # Toggle fullscreen
webview.focus()             # Focus window
```

#### Properties

```python
webview.title               # Get/set window title
webview.width               # Window width (read-only)
webview.height              # Window height (read-only)
webview.x                   # Window x position
webview.y                   # Window y position
```

### Python → JavaScript Communication

```python
# Emit event to JavaScript
webview.emit("update_data", {"frame": 120, "objects": ["cube", "sphere"]})
```

```javascript
// JavaScript: Listen for Python events
window.auroraview.on('update_data', (data) => {
    console.log('Frame:', data.frame);
});
```

### JavaScript → Python Communication

#### Method 1: Events (fire-and-forget)

```javascript
// JavaScript: Send event to Python
window.auroraview.send_event('export_scene', {
    path: '/path/to/export.fbx',
    format: 'fbx'
});
```

```python
# Python: Register event handler
@webview.on("export_scene")
def handle_export(data):
    print(f"Exporting to: {data['path']}")
```

#### Method 2: RPC with Return Value

```javascript
// JavaScript: Call Python method and get result
const result = await auroraview.api.get_hierarchy({ root: 'scene' });
console.log(result);  // { children: [...], count: 2 }
```

```python
# Python: Bind callable method
@webview.bind_call("api.get_hierarchy")
def get_hierarchy(root=None):
    return {"children": ["group1", "mesh_cube"], "count": 2}
```

### API Binding

```python
# Bind single method
@webview.bind_call("api.echo")
def echo(message: str = "") -> dict:
    return {"message": message}

# Bind all methods of an object
class API:
    def get_data(self) -> dict:
        return {"items": [1, 2, 3]}

    def save_item(self, name: str = "", value: int = 0) -> dict:
        return {"ok": True, "name": name}

webview.bind_api(API(), namespace="api")
```

### Custom Protocol Handler

```python
# Enable built-in auroraview:// protocol
webview = WebView(
    title="My App",
    asset_root="./assets"  # Files accessible via auroraview://
)

# Register custom protocol
def handle_maya_protocol(uri: str) -> dict:
    path = uri.replace("maya://", "")
    try:
        with open(f"C:/maya_projects/{path}", "rb") as f:
            return {
                "data": f.read(),
                "mime_type": "image/png",
                "status": 200
            }
    except FileNotFoundError:
        return {"data": b"Not Found", "mime_type": "text/plain", "status": 404}

webview.register_protocol("maya", handle_maya_protocol)
```

### Window Events

```python
@webview.on_shown
def on_shown(data):
    print("Window is now visible")

@webview.on_focused
def on_focused(data):
    print("Window gained focus")

@webview.on_resized
def on_resized(data):
    print(f"Window resized to {data.width}x{data.height}")

@webview.on_closing
def on_closing(data):
    return True  # Return True to allow close, False to cancel
```

### File Drop Events (Native Paths)

File drag-drop events are handled natively by Rust/wry, providing full file system paths
that browsers cannot access due to security restrictions.

```python
from auroraview.core.events import WindowEvent

# Handle file drop - receives full native file paths
@webview.on(WindowEvent.FILE_DROP)
def on_file_drop(data):
    # data['paths'] contains full file paths like 'C:\\Users\\...\\file.txt'
    for path in data['paths']:
        print(f"Dropped: {path}")
    print(f"Position: {data['position']['x']}, {data['position']['y']}")

# Handle file hover - also receives paths
@webview.on(WindowEvent.FILE_DROP_HOVER)
def on_file_hover(data):
    if data['hovering']:
        print(f"Dragging {len(data['paths'])} files over window")

# Handle file paste (clipboard - no full paths due to browser security)
@webview.on(WindowEvent.FILE_PASTE)
def on_file_paste(data):
    for file_info in data['files']:
        print(f"Pasted: {file_info['name']} ({file_info['size']} bytes)")
```

### Cancellable Events

```python
from auroraview.core.event_emitter import EventEmitter

emitter = EventEmitter()

# Handler can cancel event by returning False
@emitter.on("closing")
def on_closing(data):
    if has_unsaved_changes():
        return False  # Prevent closing
    return True  # Allow closing

# Use emit_cancellable for events that can be cancelled
if emitter.emit_cancellable("closing", {"reason": "user_request"}):
    # Event was not cancelled, proceed with close
    window.close()
else:
    # Event was cancelled by a handler
    print("Close prevented by handler")
```

### Signals (Qt-like Pattern)

```python
from auroraview import WebView, Signal

class MyTool(WebView):
    selection_changed = Signal(list)
    progress_updated = Signal(int, str)

    def __init__(self):
        super().__init__(title="My Tool")
        self.selection_changed.connect(self._on_selection)

    def _on_selection(self, items):
        print(f"Selection: {items}")

    def update_selection(self, items):
        self.selection_changed.emit(items)
```

## Desktop Application APIs

AuroraView provides native desktop capabilities through JavaScript plugins:
- `auroraview.fs` - File system operations
- `auroraview.dialog` - Native file/folder dialogs
- `auroraview.shell` - Shell commands and script execution
- `auroraview.clipboard` - System clipboard access

### File Dialogs

```javascript
// Open single file with filters
const result = await auroraview.dialog.openFile({
    title: 'Select a File',
    defaultPath: '/home/user',
    filters: [
        { name: 'Images', extensions: ['png', 'jpg', 'gif'] },
        { name: 'All Files', extensions: ['*'] }
    ]
});
// result: { path: '/path/to/file.png', cancelled: false }

// Open multiple files
const files = await auroraview.dialog.openFiles({ title: 'Select Files' });
// files: { paths: ['/path/1.txt', '/path/2.txt'], cancelled: false }

// Open folder
const folder = await auroraview.dialog.openFolder({ title: 'Select Folder' });

// Save file dialog
const savePath = await auroraview.dialog.saveFile({
    title: 'Save As',
    defaultName: 'document.txt',
    filters: [{ name: 'Text', extensions: ['txt'] }]
});

// Message dialogs
await auroraview.dialog.info('Operation completed!', 'Success');
await auroraview.dialog.warning('This action cannot be undone.', 'Warning');
await auroraview.dialog.error('File not found.', 'Error');

// Confirmation dialog
const confirmed = await auroraview.dialog.ask('Save changes?', 'Confirm');
// confirmed: true or false
```

### File System Operations

```javascript
// Read file as text
const content = await auroraview.fs.readFile('/path/to/file.txt');

// Read file as binary (base64)
const base64 = await auroraview.fs.readFileBinary('/path/to/image.png');

// Read file as ArrayBuffer
const buffer = await auroraview.fs.readFileBuffer('/path/to/file.bin');

// Write file
await auroraview.fs.writeFile('/path/to/file.txt', 'Hello World');

// Append to file
await auroraview.fs.writeFile('/path/to/file.txt', 'More content', true);

// Write binary data
await auroraview.fs.writeFileBinary('/path/to/file.bin', uint8Array);

// Check if path exists
const exists = await auroraview.fs.exists('/path/to/file.txt');

// Get file/directory info
const stat = await auroraview.fs.stat('/path/to/file.txt');
// stat: { isFile: true, isDir: false, size: 1024, modified: 1699000000 }

// List directory contents
const entries = await auroraview.fs.readDir('/path/to/dir');
// entries: [{ name: 'file.txt', isFile: true, isDir: false, size: 100 }, ...]

// List directory recursively
const allFiles = await auroraview.fs.readDir('/path/to/dir', true);

// Create directory
await auroraview.fs.createDir('/path/to/new/dir');

// Remove file or directory
await auroraview.fs.remove('/path/to/file.txt');
await auroraview.fs.remove('/path/to/dir', true);  // recursive

// Copy file
await auroraview.fs.copy('/source/file.txt', '/dest/file.txt');

// Rename/move file
await auroraview.fs.rename('/old/path.txt', '/new/path.txt');
```

### Shell Commands & Script Execution

```javascript
// Execute command and wait for result
const result = await auroraview.shell.execute('python', ['-c', 'print("Hello")']);
// result: { code: 0, stdout: 'Hello\n', stderr: '' }

// Execute with options
const gitResult = await auroraview.shell.execute('git', ['status'], {
    cwd: '/path/to/repo',
    env: { GIT_AUTHOR_NAME: 'User' }
});

// Spawn detached process (fire and forget)
const { pid } = await auroraview.shell.spawn('notepad', ['file.txt']);

// Find executable path
const pythonPath = await auroraview.shell.which('python');
// pythonPath: 'C:\\Python39\\python.exe' or null

// Get environment variable
const home = await auroraview.shell.getEnv('HOME');
const path = await auroraview.shell.getEnv('PATH');

// Get all environment variables
const env = await auroraview.shell.getEnvAll();
// env: { HOME: '/home/user', PATH: '...', ... }

// Open URL in default browser
await auroraview.shell.open('https://github.com');

// Open file with default application
await auroraview.shell.openPath('/path/to/document.pdf');

// Reveal file in file manager (Explorer/Finder)
await auroraview.shell.showInFolder('/path/to/file.txt');
```

### Python API for Desktop Features

```python
from auroraview import WebView

# Create webview with plugins enabled
webview = WebView(
    title="Desktop App",
    width=1000,
    height=800,
    html=my_html,
    debug=True,
    plugins=["fs", "dialog", "shell", "clipboard"]
)

# Python-side file dialogs (alternative to JS)
result = webview.open_file_dialog(
    title="Select File",
    filters=[("Python Files", ["py"]), ("All Files", ["*"])]
)
if result:
    print(f"Selected: {result}")

# Save dialog
save_path = webview.save_file_dialog(
    title="Save As",
    default_name="script.py"
)

# Folder selection
folder = webview.select_folder_dialog(title="Select Folder")

# Message dialogs
webview.alert_dialog("Success", "Operation completed!")
if webview.confirm_dialog("Confirm", "Delete this file?"):
    # User clicked Yes
    pass
```

## Qt Integration (QtWebView)

For Qt-based DCC applications (Maya, Houdini, Nuke, 3ds Max).

```python
from auroraview import QtWebView
from qtpy.QtWidgets import QDialog, QVBoxLayout

# Create dialog
dialog = QDialog(maya_main_window())
layout = QVBoxLayout(dialog)

# Create WebView as Qt widget
webview = QtWebView(
    parent=dialog,
    width=800,
    height=600
)
layout.addWidget(webview)

# Load content
webview.load_url("http://localhost:3000")

# Show
dialog.show()
webview.show()
```

### WebView2 Pre-warming

```python
from auroraview.integration.qt import WebViewPool

# Pre-warm at DCC startup (optional - happens automatically)
WebViewPool.prewarm()

# Check status
if WebViewPool.has_prewarmed():
    print(f"Pre-warm took {WebViewPool.get_prewarm_time():.2f}s")
```

## HWND Integration (AuroraView)

For Unreal Engine and non-Qt applications.

```python
from auroraview import AuroraView

# Create standalone WebView
webview = AuroraView(url="http://localhost:3000")
webview.show()

# Get HWND for external embedding
hwnd = webview.get_hwnd()
if hwnd:
    # Unreal Engine integration
    import unreal
    unreal.parent_external_window_to_slate(hwnd)
```

## Standalone Mode

```python
from auroraview import run_standalone

# One-liner for standalone apps
run_standalone(
    title="My App",
    url="https://example.com",
    width=1024,
    height=768
)
```

## System Tray Support

Create desktop applications with system tray integration:

```python
from auroraview import run_desktop

# Launch app with system tray
run_desktop(
    title="Background App",
    html=my_html,
    width=400,
    height=300,
    system_tray=True,  # Enable system tray icon
    hide_on_close=True,  # Minimize to tray instead of closing
)
```

## Floating Tool Windows

Create floating panels for AI assistants or tool palettes:

```python
from auroraview import WebView

# Create floating tool window
webview = WebView.create(
    title="AI Assistant",
    html=panel_html,
    width=320,
    height=400,
    frame=False,  # Frameless window
    transparent=True,  # Transparent background
    always_on_top=True,  # Keep on top
    tool_window=True,  # Hide from taskbar/Alt+Tab (WS_EX_TOOLWINDOW)
    parent=parent_hwnd,  # Optional: follow parent window
    mode="owner",  # Window follows parent minimize/restore
)
webview.show()
```

## Gallery Application

Interactive showcase of all AuroraView features:

```bash
# Run Gallery
just gallery

# Build standalone Gallery executable
just gallery-pack
```

Features:
- Example browser with categories
- Live example runner with stdout/stderr streaming
- Settings panel for runtime configuration
- Search across all examples

## Command Line Interface

```bash
# Load URL
auroraview --url https://example.com

# Load local HTML
auroraview --html /path/to/file.html

# Custom configuration
auroraview --url https://example.com --title "My App" --width 1024 --height 768

# Using uvx
uvx auroraview --url https://example.com
```

## Testing Framework

### HeadlessWebView

```python
from auroraview.testing import HeadlessWebView

# Auto-detect best backend
with HeadlessWebView.auto() as webview:
    webview.goto("https://example.com")
    webview.click("#button")
    assert webview.text("#result") == "Success"

# Explicitly use Playwright
with HeadlessWebView.playwright() as webview:
    webview.load_html("<h1>Test</h1>")
    assert webview.text("h1") == "Test"
    webview.screenshot("test.png")
```

### Available Backends

| Backend | Method | Platform | Use Case |
|---------|--------|----------|----------|
| Playwright | `HeadlessWebView.playwright()` | All | CI/CD |
| Xvfb | `HeadlessWebView.virtual_display()` | Linux | Real WebView |
| WebView2 CDP | `HeadlessWebView.webview2_cdp(url)` | Windows | Real WebView2 |

## DCC Software Support

| Software | Status | Python | Notes |
|----------|--------|--------|-------|
| Maya | ✅ | 3.7+ | Qt Native (QtWebView) |
| 3ds Max | ✅ | 3.7+ | Qt Native (QtWebView) |
| Houdini | ✅ | 3.7+ | Qt Native (QtWebView) |
| Blender | ✅ | 3.7+ | Standalone mode |
| Nuke | ✅ | 3.7+ | Qt Native (QtWebView) |
| Unreal Engine | ✅ | 3.7+ | HWND mode (AuroraView) |
| Photoshop | 🚧 | 3.7+ | Planned |

## JavaScript API Reference

### window.auroraview

```javascript
// Call Python method (returns Promise)
const result = await auroraview.call('api.method_name', { param: value });

// Call via api proxy (shorthand)
const result = await auroraview.api.method_name({ param: value });

// Invoke plugin command (returns Promise)
const content = await auroraview.invoke('plugin:fs|read_file', { path: '/path/to/file.txt' });
const file = await auroraview.invoke('plugin:dialog|open_file', { title: 'Select File' });

// Send event to Python (fire-and-forget)
auroraview.send_event('event_name', { data: value });

// Listen for Python events
const unsubscribe = auroraview.on('event_name', (data) => {
    console.log('Received:', data);
});

// Unsubscribe
auroraview.off('event_name', handler);
// or
unsubscribe();

// Local JS event (does NOT reach Python)
auroraview.trigger('local_event', data);
```

### Event Utilities (Debounce/Throttle)

```javascript
// Debounce - delays execution until after wait ms have elapsed
const debouncedResize = auroraview.utils.debounce(function() {
    console.log('Resized!');
}, 250);
window.addEventListener('resize', debouncedResize);

// Throttle - limits execution to at most once per wait ms
const throttledScroll = auroraview.utils.throttle(function() {
    console.log('Scrolled!');
}, 100);
window.addEventListener('scroll', throttledScroll);

// Once - restricts function to single invocation
const initialize = auroraview.utils.once(function() {
    console.log('Initialized!');
    return { ready: true };
});

// Convenience wrappers for event handlers
auroraview.onDebounced('resize', handler, 250);
auroraview.onThrottled('scroll', handler, 100);
```

### Ready Event

```javascript
window.addEventListener('auroraviewready', () => {
    // Safe to use auroraview API
    auroraview.api.init();
});
```

## Key Imports

```python
from auroraview import (
    # Core
    WebView,
    Signal,

    # Integration
    AuroraView,      # HWND mode
    QtWebView,       # Qt mode
    run_standalone,  # Standalone mode

    # Utilities
    EventTimer,
    path_to_file_url,
)
```

## Links

- **Repository**: https://github.com/loonghao/auroraview
- **PyPI**: https://pypi.org/project/auroraview/
- **Documentation**: https://github.com/loonghao/auroraview/tree/main/docs
- **Examples**: https://github.com/loonghao/auroraview/tree/main/examples
