Files
yaze/docs/internal/architecture/object_selection_flow.md

24 KiB
Raw Permalink Blame History

Object Selection System - Interaction Flow

Visual Flow Diagrams

1. Single Object Selection (Left Click)

┌─────────────────────────────────────────────────────┐
│  User Input: Left Click on Object                   │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  DungeonObjectInteraction::HandleCanvasMouseInput()  │
│  - Detect left click                                 │
│  - Get mouse position                                │
│  - Convert to room coordinates                       │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  TrySelectObjectAtCursor(x, y, mode)                 │
│  - Iterate objects in reverse order                  │
│  - Check if cursor within object bounds              │
│  - Find topmost object at cursor                     │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  ObjectSelection::SelectObject(index, Single)        │
│  - Clear previous selection                          │
│  - Add object to selection (set.insert)              │
│  - Trigger selection changed callback                │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Visual Feedback                                     │
│  - Draw pulsing border (yellow-gold, 0.85f alpha)   │
│  - Draw corner handles (cyan-white, 0.85f alpha)    │
│  - Animate pulse at 4 Hz                            │
└─────────────────────────────────────────────────────┘

2. Multi-Selection (Shift+Click)

┌─────────────────────────────────────────────────────┐
│  User Input: Shift + Left Click                     │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  HandleCanvasMouseInput()                            │
│  - Detect Shift key down                             │
│  - Set mode = SelectionMode::Add                     │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  ObjectSelection::SelectObject(index, Add)           │
│  - Keep existing selection                           │
│  - Add new object (set.insert)                       │
│  - Trigger callback                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Visual Feedback                                     │
│  - Highlight ALL selected objects                    │
│  - Each with pulsing border + handles                │
└─────────────────────────────────────────────────────┘

3. Toggle Selection (Ctrl+Click)

┌─────────────────────────────────────────────────────┐
│  User Input: Ctrl + Left Click                      │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  HandleCanvasMouseInput()                            │
│  - Detect Ctrl key down                              │
│  - Set mode = SelectionMode::Toggle                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  ObjectSelection::SelectObject(index, Toggle)        │
│  - If selected: Remove (set.erase)                   │
│  - If not selected: Add (set.insert)                 │
│  - Trigger callback                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Visual Feedback                                     │
│  - Update highlights for current selection           │
│  - Removed objects no longer highlighted             │
└─────────────────────────────────────────────────────┘

4. Rectangle Selection (Right Click + Drag)

┌─────────────────────────────────────────────────────┐
│  User Input: Right Click + Drag                     │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Phase 1: Mouse Down (Right Button)                 │
│  ObjectSelection::BeginRectangleSelection(x, y)      │
│  - Store start position                              │
│  - Set rectangle_selection_active = true             │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Phase 2: Mouse Drag                                 │
│  ObjectSelection::UpdateRectangleSelection(x, y)     │
│  - Update end position                               │
│  - Draw rectangle preview                            │
│    • Border: accent_color @ 0.85f alpha             │
│    • Fill: accent_color @ 0.15f alpha               │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Phase 3: Mouse Release                              │
│  ObjectSelection::EndRectangleSelection(objects)     │
│  - Convert canvas coords to room coords              │
│  - For each object:                                  │
│    • Get object bounds                               │
│    • Check AABB intersection with rectangle          │
│    • If intersects: Add to selection                 │
│  - Set rectangle_selection_active = false            │
│  - Trigger callback                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Visual Feedback                                     │
│  - Highlight all selected objects                    │
│  - Remove rectangle preview                          │
└─────────────────────────────────────────────────────┘

5. Select All (Ctrl+A)

┌─────────────────────────────────────────────────────┐
│  User Input: Ctrl + A                                │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  HandleCanvasMouseInput()                            │
│  - Detect Ctrl + A key combination                   │
│  - Get current room object count                     │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  ObjectSelection::SelectAll(object_count)            │
│  - Clear previous selection                          │
│  - Add all object indices (0..count-1)               │
│  - Trigger callback                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Visual Feedback                                     │
│  - Highlight ALL objects in room                     │
│  - May cause performance impact if many objects      │
└─────────────────────────────────────────────────────┘

State Transitions

┌────────────┐
│ No         │◄─────────────────────┐
│ Selection  │                      │
└──────┬─────┘                      │
       │                            │
       │ Left Click                 │ Esc or Clear
       ▼                            │
┌────────────┐                      │
│ Single     │◄─────────┐           │
│ Selection  │          │           │
└──────┬─────┘          │           │
       │                │           │
       │ Shift+Click    │ Ctrl+Click│
       │ Right+Drag     │ (deselect)│
       ▼                │           │
┌────────────┐          │           │
│ Multi      │──────────┘           │
│ Selection  │──────────────────────┘
└────────────┘

Rendering Pipeline

┌─────────────────────────────────────────────────────┐
│  DungeonCanvasViewer::DrawDungeonCanvas(room_id)    │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  1. Draw Room Background Layers (BG1, BG2)           │
│     - Load room graphics                             │
│     - Render to bitmaps                              │
│     - Draw bitmaps to canvas                         │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  2. Draw Sprites                                     │
│     - Render sprite markers (8x8 squares)            │
│     - Color-code by layer                            │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  3. Handle Object Interaction                        │
│     DungeonObjectInteraction::HandleCanvasMouseInput()│
│     - Process mouse/keyboard input                   │
│     - Update selection state                         │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  4. Draw Selection Visuals (TOP LAYER)               │
│     ObjectSelection::DrawSelectionHighlights()       │
│     - For each selected object:                      │
│       • Convert room coords to canvas coords         │
│       • Apply canvas scale                           │
│       • Draw pulsing border                          │
│       • Draw corner handles                          │
│     ObjectSelection::DrawRectangleSelectionBox()     │
│     - If active: Draw rectangle preview              │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  5. Draw Canvas Overlays                             │
│     - Grid lines                                     │
│     - Debug overlays (if enabled)                    │
└─────────────────────────────────────────────────────┘

Data Flow for Object Operations

Delete Selected Objects

┌─────────────────────────────────────────────────────┐
│  User Input: Delete Key                              │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  DungeonObjectInteraction::HandleDeleteSelected()    │
│  1. Get selected indices from ObjectSelection        │
│  2. Sort indices in descending order                 │
│  3. For each index (high to low):                    │
│     - Call room.RemoveTileObject(index)              │
│  4. Clear selection                                  │
│  5. Trigger cache invalidation (re-render)           │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  Room::RenderRoomGraphics()                          │
│  - Re-render room with deleted objects removed       │
└─────────────────────────────────────────────────────┘

Copy/Paste Selected Objects

Copy Flow:
┌─────────────────────────────────────────────────────┐
│  User Input: Ctrl+C                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  DungeonObjectInteraction::HandleCopySelected()      │
│  1. Get selected indices from ObjectSelection        │
│  2. Copy objects to clipboard_ vector                │
│  3. Set has_clipboard_data_ = true                   │
└─────────────────────────────────────────────────────┘

Paste Flow:
┌─────────────────────────────────────────────────────┐
│  User Input: Ctrl+V                                  │
└────────────────────┬────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────┐
│  DungeonObjectInteraction::HandlePasteObjects()      │
│  1. Get mouse position                               │
│  2. Calculate offset from first clipboard object     │
│  3. For each clipboard object:                       │
│     - Create copy with offset position               │
│     - Clamp to room bounds (0-63)                    │
│     - Add to room                                    │
│  4. Trigger re-render                                │
└─────────────────────────────────────────────────────┘

Performance Considerations

Selection State Storage

std::set<size_t> selected_indices_;

Advantages:
✓ O(log n) insert/delete/lookup
✓ Automatic sorting
✓ No duplicates
✓ Cache-friendly for small selections

Trade-offs:
✗ Slightly higher memory overhead
✗ Not as cache-friendly for iteration (vs vector)

Decision: Justified for correctness guarantees

Rendering Optimization

void DrawSelectionHighlights() {
  if (selected_indices_.empty()) {
    return;  // Early exit - O(1)
  }

  // Only render visible objects (canvas culling)
  for (size_t index : selected_indices_) {
    if (IsObjectVisible(index)) {
      DrawHighlight(index);  // O(k) where k = selected count
    }
  }
}

Complexity: O(k) where k = selected object count
Typical case: k < 20 objects selected
Worst case: k = 296 (all objects) - rare

Memory Layout

ObjectSelection Instance (~64 bytes)
├── selected_indices_ (std::set<size_t>)
│   └── Red-Black Tree
│       ├── Node overhead: ~32 bytes per node
│       └── Typical selection: 5 objects = ~160 bytes
├── rectangle_selection_active_ (bool) = 1 byte
├── rect_start_x_ (int) = 4 bytes
├── rect_start_y_ (int) = 4 bytes
├── rect_end_x_ (int) = 4 bytes
├── rect_end_y_ (int) = 4 bytes
└── selection_changed_callback_ (std::function) = 32 bytes

Total: ~64 bytes + (32 bytes × selected_count)

Example: 10 objects selected = ~384 bytes
Negligible compared to room graphics (~2MB)

Integration Checklist

When integrating ObjectSelection into DungeonObjectInteraction:

  • Add ObjectSelection selection_; member
  • Remove old selection state variables
  • Update HandleCanvasMouseInput() to use selection modes
  • Add TrySelectObjectAtCursor() helper
  • Update DrawSelectionHighlights() to delegate to ObjectSelection
  • Update DrawSelectBox() to delegate to ObjectSelection
  • Update HandleDeleteSelected() to use selection_.GetSelectedIndices()
  • Update HandleCopySelected() to use selection_.GetSelectedIndices()
  • Update clipboard operations
  • Add Ctrl+A handler for select all
  • Test single selection
  • Test multi-selection (Shift+click)
  • Test toggle selection (Ctrl+click)
  • Test rectangle selection
  • Test select all (Ctrl+A)
  • Test copy/paste/delete operations
  • Verify visual feedback (borders, handles)
  • Verify theme color usage
  • Run unit tests
  • Test performance with many objects

Diagram Format: ASCII art compatible with markdown viewers Last Updated: 2025-11-26