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

14 KiB

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:

// Before (scattered state)
std::vector<size_t> 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

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

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

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

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
// 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

// Tile → Pixel (unscaled)
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) {
  return {room_x * 8, room_y * 8};
}

// Pixel → Tile (unscaled)
std::pair<int, int> 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:

// 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:

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<size_t> 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:

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:

// Setup
ObjectSelection selection;
std::vector<RoomObject> 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

// Replace selection with object 5
selection_.SelectObject(5, ObjectSelection::SelectionMode::Single);

Building Multi-Selection

// 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

// 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

// 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

// 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

// 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