# 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 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) │ └── 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