# Object Selection System Architecture ## Overview The Object Selection System provides comprehensive selection functionality for the dungeon editor. It's designed following the Single Responsibility Principle, separating selection state management from input handling and rendering. **Version**: 1.0 **Date**: 2025-11-26 **Location**: `src/app/editor/dungeon/object_selection.{h,cc}` ## Architecture Diagram ``` ┌─────────────────────────────────────────────────────┐ │ DungeonEditorV2 (Main Editor) │ │ - Coordinates all components │ │ - Card-based UI system │ │ - Handles Save/Load/Undo/Redo │ └────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ DungeonCanvasViewer (Canvas Rendering) │ │ - Room graphics display │ │ - Layer management (BG1/BG2) │ │ - Sprite rendering │ └────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ DungeonObjectInteraction (Input Handling) │ │ - Mouse input processing │ │ - Keyboard shortcut handling │ │ - Coordinate conversion │ │ - Drag operations │ │ - Copy/Paste/Delete │ └────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ ObjectSelection (Selection State) │ │ - Selection state management │ │ - Multi-selection logic │ │ - Rectangle selection │ │ - Visual rendering │ │ - Bounding box calculations │ └─────────────────────────────────────────────────────┘ ``` ## Component Responsibilities ### ObjectSelection (New Component) **Purpose**: Manages selection state and provides selection operations **Key Responsibilities**: - Single object selection - Multi-selection (Shift, Ctrl modifiers) - Rectangle drag selection - Select all functionality - Visual feedback rendering - Coordinate conversion utilities **Design Principles**: - **Stateless Operations**: Pure functions where possible - **Composable**: Can be integrated into any editor - **Testable**: All operations have unit tests - **Type-Safe**: Uses enums instead of magic booleans ### DungeonObjectInteraction (Enhanced) **Purpose**: Handles user input and coordinates object manipulation **Integration Points**: ```cpp // Before (scattered state) std::vector selected_object_indices_; bool object_select_active_; ImVec2 object_select_start_; ImVec2 object_select_end_; // After (delegated to ObjectSelection) ObjectSelection selection_; ``` ## Selection Modes The system supports four distinct selection modes: ### 1. Single Selection (Default) **Trigger**: Left click on object **Behavior**: Replace current selection with clicked object **Use Case**: Basic object selection ```cpp selection_.SelectObject(index, ObjectSelection::SelectionMode::Single); ``` ### 2. Add Selection (Shift+Click) **Trigger**: Shift + Left click **Behavior**: Add object to existing selection **Use Case**: Building multi-object selections incrementally ```cpp selection_.SelectObject(index, ObjectSelection::SelectionMode::Add); ``` ### 3. Toggle Selection (Ctrl+Click) **Trigger**: Ctrl + Left click **Behavior**: Toggle object in/out of selection **Use Case**: Fine-tuning selections by removing specific objects ```cpp selection_.SelectObject(index, ObjectSelection::SelectionMode::Toggle); ``` ### 4. Rectangle Selection (Drag) **Trigger**: Right click + drag **Behavior**: Select all objects within rectangle **Use Case**: Bulk selection of objects ```cpp selection_.BeginRectangleSelection(x, y); selection_.UpdateRectangleSelection(x, y); selection_.EndRectangleSelection(objects, mode); ``` ## Keyboard Shortcuts | Shortcut | Action | Implementation | |----------|--------|----------------| | **Left Click** | Select single object | `SelectObject(index, Single)` | | **Shift + Click** | Add to selection | `SelectObject(index, Add)` | | **Ctrl + Click** | Toggle in selection | `SelectObject(index, Toggle)` | | **Right Drag** | Rectangle select | `Begin/Update/EndRectangleSelection()` | | **Ctrl + A** | Select all | `SelectAll(count)` | | **Delete** | Delete selected | `HandleDeleteSelected()` | | **Ctrl + C** | Copy selected | `HandleCopySelected()` | | **Ctrl + V** | Paste objects | `HandlePasteObjects()` | | **Esc** | Clear selection | `ClearSelection()` | ## Visual Feedback ### Selected Objects - **Border**: Pulsing animated outline (yellow-gold) - **Handles**: Four corner handles (cyan-white at 0.85f alpha) - **Animation**: Sinusoidal pulse at 4 Hz ```cpp // Animation formula float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 4.0f); ``` ### Rectangle Selection - **Border**: Accent color at 0.85f alpha (high visibility) - **Fill**: Accent color at 0.15f alpha (subtle background) - **Thickness**: 2.0f pixels ### Entity Visibility Standards All entity rendering follows yaze's visibility standards: - **High-contrast colors**: Bright yellow-gold, cyan-white - **Alpha value**: 0.85f for primary visibility - **Background alpha**: 0.15f for fills ## Coordinate Systems ### Room Coordinates (Tiles) - **Range**: 0-63 (64x64 tile rooms) - **Unit**: Tiles - **Origin**: Top-left corner (0, 0) ### Canvas Coordinates (Pixels) - **Range**: 0-511 (unscaled, 8 pixels per tile) - **Unit**: Pixels - **Origin**: Top-left corner (0, 0) - **Scale**: Subject to canvas zoom (global_scale) ### Conversion Functions ```cpp // Tile → Pixel (unscaled) std::pair RoomToCanvasCoordinates(int room_x, int room_y) { return {room_x * 8, room_y * 8}; } // Pixel → Tile (unscaled) std::pair CanvasToRoomCoordinates(int canvas_x, int canvas_y) { return {canvas_x / 8, canvas_y / 8}; } ``` ## Bounding Box Calculation Objects have variable sizes based on their `size_` field: ```cpp // Object size encoding uint8_t size_h = (object.size_ & 0x0F); // Horizontal size uint8_t size_v = (object.size_ >> 4) & 0x0F; // Vertical size // Dimensions (in tiles) int width = size_h + 1; int height = size_v + 1; ``` ### Examples: | `size_` | Width | Height | Total | |---------|-------|--------|-------| | `0x00` | 1 | 1 | 1x1 | | `0x11` | 2 | 2 | 2x2 | | `0x23` | 4 | 3 | 4x3 | | `0xFF` | 16 | 16 | 16x16 | ## Theme Integration All colors are sourced from `AgentUITheme`: ```cpp const auto& theme = AgentUI::GetTheme(); // Selection colors theme.dungeon_selection_primary // Yellow-gold (pulsing border) theme.dungeon_selection_secondary // Cyan (secondary elements) theme.dungeon_selection_handle // Cyan-white (corner handles) theme.accent_color // UI accent (rectangle selection) ``` **Critical Rule**: NEVER use hardcoded `ImVec4` colors. Always use theme system. ## Implementation Details ### Selection State Storage Uses `std::set` for selected indices: **Advantages**: - O(log n) insertion/deletion - O(log n) lookup - Automatic sorting - No duplicates **Trade-offs**: - Slightly higher memory overhead than vector - Justified by performance and correctness guarantees ### Rectangle Selection Algorithm **Intersection Test**: ```cpp bool IsObjectInRectangle(const RoomObject& object, int min_x, int min_y, int max_x, int max_y) { auto [obj_x, obj_y, obj_width, obj_height] = GetObjectBounds(object); int obj_min_x = obj_x; int obj_max_x = obj_x + obj_width - 1; int obj_min_y = obj_y; int obj_max_y = obj_y + obj_height - 1; bool x_overlap = (obj_min_x <= max_x) && (obj_max_x >= min_x); bool y_overlap = (obj_min_y <= max_y) && (obj_max_y >= min_y); return x_overlap && y_overlap; } ``` This uses standard axis-aligned bounding box (AABB) intersection. ## Testing Strategy ### Unit Tests Location: `test/unit/object_selection_test.cc` **Coverage**: - Single selection (replace existing) - Multi-selection (Shift+click add) - Toggle selection (Ctrl+click toggle) - Rectangle selection (all modes) - Select all - Coordinate conversion - Bounding box calculation - Callback invocation **Test Patterns**: ```cpp // Setup ObjectSelection selection; std::vector objects = CreateTestObjects(); // Action selection.SelectObject(0, ObjectSelection::SelectionMode::Single); // Verify EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_EQ(selection.GetSelectionCount(), 1); ``` ### Integration Points Test integration with: 1. `DungeonObjectInteraction` for input handling 2. `DungeonCanvasViewer` for rendering 3. `DungeonEditorV2` for undo/redo ## Performance Characteristics | Operation | Complexity | Notes | |-----------|------------|-------| | `SelectObject` | O(log n) | Set insertion | | `IsObjectSelected` | O(log n) | Set lookup | | `GetSelectedIndices` | O(n) | Convert set to vector | | `SelectObjectsInRect` | O(m * log n) | m objects checked | | `DrawSelectionHighlights` | O(k) | k selected objects | Where: - n = total objects in selection - m = total objects in room - k = selected object count ## API Examples ### Single Selection ```cpp // Replace selection with object 5 selection_.SelectObject(5, ObjectSelection::SelectionMode::Single); ``` ### Building Multi-Selection ```cpp // Start with object 0 selection_.SelectObject(0, ObjectSelection::SelectionMode::Single); // Add objects 2, 4, 6 selection_.SelectObject(2, ObjectSelection::SelectionMode::Add); selection_.SelectObject(4, ObjectSelection::SelectionMode::Add); selection_.SelectObject(6, ObjectSelection::SelectionMode::Add); // Toggle object 4 (remove it) selection_.SelectObject(4, ObjectSelection::SelectionMode::Toggle); // Result: Objects 0, 2, 6 selected ``` ### Rectangle Selection ```cpp // Begin selection at (10, 10) selection_.BeginRectangleSelection(10, 10); // Update to (50, 50) as user drags selection_.UpdateRectangleSelection(50, 50); // Complete selection (add mode) selection_.EndRectangleSelection(objects, ObjectSelection::SelectionMode::Add); ``` ### Working with Selected Objects ```cpp // Get all selected indices (sorted) auto indices = selection_.GetSelectedIndices(); // Get primary (first) selection if (auto primary = selection_.GetPrimarySelection()) { size_t index = primary.value(); // Use primary object... } // Check selection state if (selection_.HasSelection()) { size_t count = selection_.GetSelectionCount(); // Process selected objects... } ``` ## Integration Guide See `OBJECT_SELECTION_INTEGRATION.md` for step-by-step integration instructions. **Key Steps**: 1. Add `ObjectSelection` member to `DungeonObjectInteraction` 2. Update input handling to use selection modes 3. Replace manual selection state with `ObjectSelection` API 4. Implement click selection helper 5. Update rendering to use `ObjectSelection::Draw*()` methods ## Future Enhancements ### Planned Features - **Lasso Selection**: Free-form polygon selection - **Selection Filters**: Filter by object type, layer, size - **Selection History**: Undo/redo for selection changes - **Selection Groups**: Named selections (e.g., "All Chests") - **Smart Selection**: Select similar objects (by type/size) - **Marquee Zoom**: Zoom to fit selected objects ### API Extensions ```cpp // Future API ideas void SelectByType(int16_t object_id); void SelectByLayer(RoomObject::LayerType layer); void SelectSimilar(size_t reference_index); void SaveSelectionGroup(const std::string& name); void LoadSelectionGroup(const std::string& name); ``` ## Debugging ### Enable Debug Logging ```cpp // In object_selection.cc #define SELECTION_DEBUG_LOGGING // Logs will appear like: // [ObjectSelection] SelectObject: index=5, mode=Single // [ObjectSelection] Selection count: 3 ``` ### Visual Debugging Use the Debug Controls card in the dungeon editor: 1. Enable "Show Object Bounds" 2. Filter by object type/layer 3. Inspect selection state in real-time ### Common Issues **Issue**: Objects not selecting on click **Solution**: Check object bounds calculation, verify coordinate conversion **Issue**: Selection persists after clear **Solution**: Ensure `NotifySelectionChanged()` is called **Issue**: Visual artifacts during drag **Solution**: Verify canvas scale is applied correctly in rendering ## References - **ZScream**: Reference implementation for dungeon object selection - **ImGui Test Engine**: Automated UI testing framework - **yaze Canvas System**: `src/app/gui/canvas/canvas.h` - **Theme System**: `src/app/editor/agent/agent_ui_theme.h` ## Changelog ### Version 1.0 (2025-11-26) - Initial implementation - Single/multi/rectangle selection - Visual feedback with theme integration - Comprehensive unit test coverage - Integration with DungeonObjectInteraction --- **Maintainer**: yaze development team **Last Updated**: 2025-11-26