feat(canvas): implement event-driven interaction handling for canvas
- Introduced new files `canvas_interaction.cc` and `canvas_interaction.h` to encapsulate event-driven logic for tile painting, rectangle selection, and entity interactions. - Refactored `CanvasInteractionHandler` to utilize the new event-based functions, improving code organization and maintainability. - Added `canvas_events.h` to define event payload structures for various canvas interactions, enhancing clarity and usability. Benefits: - Streamlines interaction handling by separating concerns and reducing stateful dependencies. - Improves testability and readability of the canvas interaction logic, facilitating future enhancements.
This commit is contained in:
177
src/app/gui/canvas/canvas_events.h
Normal file
177
src/app/gui/canvas/canvas_events.h
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_EVENTS_H
|
||||||
|
#define YAZE_APP_GUI_CANVAS_CANVAS_EVENTS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event payload for tile painting operations
|
||||||
|
*
|
||||||
|
* Represents a single tile paint action, either from a click or drag operation.
|
||||||
|
* Canvas-space coordinates are provided for positioning.
|
||||||
|
*/
|
||||||
|
struct TilePaintEvent {
|
||||||
|
ImVec2 position; ///< Canvas-space pixel coordinates
|
||||||
|
ImVec2 grid_position; ///< Grid-aligned tile position
|
||||||
|
int tile_id = -1; ///< Tile ID being painted (-1 if none)
|
||||||
|
bool is_drag = false; ///< True for continuous drag painting
|
||||||
|
bool is_complete = false; ///< True when paint action finishes
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
position = ImVec2(-1, -1);
|
||||||
|
grid_position = ImVec2(-1, -1);
|
||||||
|
tile_id = -1;
|
||||||
|
is_drag = false;
|
||||||
|
is_complete = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event payload for rectangle selection operations
|
||||||
|
*
|
||||||
|
* Represents a multi-tile rectangular selection, typically from right-click drag.
|
||||||
|
* Provides both the rectangle bounds and the individual selected tile positions.
|
||||||
|
*/
|
||||||
|
struct RectSelectionEvent {
|
||||||
|
std::vector<ImVec2> selected_tiles; ///< Individual tile positions (grid coords)
|
||||||
|
ImVec2 start_pos; ///< Rectangle start (canvas coords)
|
||||||
|
ImVec2 end_pos; ///< Rectangle end (canvas coords)
|
||||||
|
int current_map = -1; ///< Map ID for coordinate calculation
|
||||||
|
bool is_complete = false; ///< True when selection finishes
|
||||||
|
bool is_active = false; ///< True while dragging
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
selected_tiles.clear();
|
||||||
|
start_pos = ImVec2(-1, -1);
|
||||||
|
end_pos = ImVec2(-1, -1);
|
||||||
|
current_map = -1;
|
||||||
|
is_complete = false;
|
||||||
|
is_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Get number of selected tiles */
|
||||||
|
size_t Count() const { return selected_tiles.size(); }
|
||||||
|
|
||||||
|
/** @brief Check if selection is empty */
|
||||||
|
bool IsEmpty() const { return selected_tiles.empty(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event payload for single tile selection
|
||||||
|
*
|
||||||
|
* Represents selecting a single tile, typically from a right-click.
|
||||||
|
*/
|
||||||
|
struct TileSelectionEvent {
|
||||||
|
ImVec2 tile_position; ///< Selected tile position (grid coords)
|
||||||
|
int tile_id = -1; ///< Selected tile ID
|
||||||
|
bool is_valid = false; ///< True if selection is valid
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
tile_position = ImVec2(-1, -1);
|
||||||
|
tile_id = -1;
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event payload for entity interactions
|
||||||
|
*
|
||||||
|
* Represents various entity interaction events (hover, click, drag).
|
||||||
|
* Used for exits, entrances, sprites, items, etc.
|
||||||
|
*/
|
||||||
|
struct EntityInteractionEvent {
|
||||||
|
enum class Type {
|
||||||
|
kNone, ///< No interaction
|
||||||
|
kHover, ///< Mouse hovering over entity
|
||||||
|
kClick, ///< Single click on entity
|
||||||
|
kDoubleClick, ///< Double click on entity
|
||||||
|
kDragStart, ///< Started dragging entity
|
||||||
|
kDragMove, ///< Dragging entity (continuous)
|
||||||
|
kDragEnd ///< Finished dragging entity
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = Type::kNone; ///< Type of interaction
|
||||||
|
int entity_id = -1; ///< Entity being interacted with
|
||||||
|
ImVec2 position; ///< Current entity position (canvas coords)
|
||||||
|
ImVec2 delta; ///< Movement delta (for drag events)
|
||||||
|
ImVec2 grid_position; ///< Grid-aligned position
|
||||||
|
bool is_valid = false; ///< True if event is valid
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
type = Type::kNone;
|
||||||
|
entity_id = -1;
|
||||||
|
position = ImVec2(-1, -1);
|
||||||
|
delta = ImVec2(0, 0);
|
||||||
|
grid_position = ImVec2(-1, -1);
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Check if this is a drag event */
|
||||||
|
bool IsDragEvent() const {
|
||||||
|
return type == Type::kDragStart || type == Type::kDragMove ||
|
||||||
|
type == Type::kDragEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Check if this is a click event */
|
||||||
|
bool IsClickEvent() const {
|
||||||
|
return type == Type::kClick || type == Type::kDoubleClick;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Event payload for hover preview
|
||||||
|
*
|
||||||
|
* Represents hover state for overlay rendering.
|
||||||
|
*/
|
||||||
|
struct HoverEvent {
|
||||||
|
ImVec2 position; ///< Canvas-space hover position
|
||||||
|
ImVec2 grid_position; ///< Grid-aligned hover position
|
||||||
|
bool is_valid = false; ///< True if hovering over canvas
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
position = ImVec2(-1, -1);
|
||||||
|
grid_position = ImVec2(-1, -1);
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Combined interaction result for a frame
|
||||||
|
*
|
||||||
|
* Aggregates all possible interaction events for a single frame update.
|
||||||
|
* Handlers populate relevant events, consumers check which events occurred.
|
||||||
|
*/
|
||||||
|
struct CanvasInteractionEvents {
|
||||||
|
TilePaintEvent tile_paint;
|
||||||
|
RectSelectionEvent rect_selection;
|
||||||
|
TileSelectionEvent tile_selection;
|
||||||
|
EntityInteractionEvent entity_interaction;
|
||||||
|
HoverEvent hover;
|
||||||
|
|
||||||
|
/** @brief Reset all events */
|
||||||
|
void Reset() {
|
||||||
|
tile_paint.Reset();
|
||||||
|
rect_selection.Reset();
|
||||||
|
tile_selection.Reset();
|
||||||
|
entity_interaction.Reset();
|
||||||
|
hover.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Check if any event occurred */
|
||||||
|
bool HasAnyEvent() const {
|
||||||
|
return tile_paint.is_complete ||
|
||||||
|
rect_selection.is_complete ||
|
||||||
|
tile_selection.is_valid ||
|
||||||
|
entity_interaction.is_valid ||
|
||||||
|
hover.is_valid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_CANVAS_CANVAS_EVENTS_H
|
||||||
|
|
||||||
403
src/app/gui/canvas/canvas_interaction.cc
Normal file
403
src/app/gui/canvas/canvas_interaction.cc
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
#include "canvas_interaction.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Static state for rectangle selection (temporary until we have proper state management)
|
||||||
|
struct SelectRectState {
|
||||||
|
ImVec2 drag_start_pos = ImVec2(-1, -1);
|
||||||
|
bool is_dragging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per-canvas state (keyed by canvas geometry pointer for simplicity)
|
||||||
|
// TODO(scawful): Replace with proper state management in Phase 2.5
|
||||||
|
thread_local SelectRectState g_select_rect_state;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
|
||||||
|
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
|
||||||
|
std::floor(pos.y / grid_step) * grid_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry) {
|
||||||
|
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||||
|
const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
|
||||||
|
geometry.canvas_p0.y + geometry.scrolling.y);
|
||||||
|
return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsMouseInCanvas(const CanvasGeometry& geometry) {
|
||||||
|
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||||
|
return imgui_io.MousePos.x >= geometry.canvas_p0.x &&
|
||||||
|
imgui_io.MousePos.x <= geometry.canvas_p1.x &&
|
||||||
|
imgui_io.MousePos.y >= geometry.canvas_p0.y &&
|
||||||
|
imgui_io.MousePos.y <= geometry.canvas_p1.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale) {
|
||||||
|
const float scaled_size = tile_size * global_scale;
|
||||||
|
return ImVec2(std::floor(canvas_pos.x / scaled_size),
|
||||||
|
std::floor(canvas_pos.y / scaled_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Rectangle Selection Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
RectSelectionEvent HandleRectangleSelection(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int current_map,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button) {
|
||||||
|
|
||||||
|
RectSelectionEvent event;
|
||||||
|
event.current_map = current_map;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
constexpr int kSmallMapSize = 0x200; // 512 pixels
|
||||||
|
|
||||||
|
// Calculate super X/Y accounting for world offset
|
||||||
|
int super_y = 0;
|
||||||
|
int super_x = 0;
|
||||||
|
if (current_map < 0x40) {
|
||||||
|
// Light World (0x00-0x3F)
|
||||||
|
super_y = current_map / 8;
|
||||||
|
super_x = current_map % 8;
|
||||||
|
} else if (current_map < 0x80) {
|
||||||
|
// Dark World (0x40-0x7F)
|
||||||
|
super_y = (current_map - 0x40) / 8;
|
||||||
|
super_x = (current_map - 0x40) % 8;
|
||||||
|
} else {
|
||||||
|
// Special World (0x80+)
|
||||||
|
super_y = (current_map - 0x80) / 8;
|
||||||
|
super_x = (current_map - 0x80) % 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle mouse button press - start selection
|
||||||
|
if (ImGui::IsMouseClicked(mouse_button)) {
|
||||||
|
g_select_rect_state.drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
g_select_rect_state.is_dragging = false;
|
||||||
|
|
||||||
|
// Single tile selection on click
|
||||||
|
ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
int painter_x = static_cast<int>(painter_pos.x);
|
||||||
|
int painter_y = static_cast<int>(painter_pos.y);
|
||||||
|
|
||||||
|
auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
|
||||||
|
auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
|
||||||
|
|
||||||
|
int index_x = super_x * 0x20 + tile16_x;
|
||||||
|
int index_y = super_y * 0x20 + tile16_y;
|
||||||
|
|
||||||
|
event.start_pos = painter_pos;
|
||||||
|
event.selected_tiles.push_back(ImVec2(static_cast<float>(index_x),
|
||||||
|
static_cast<float>(index_y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle dragging - draw preview rectangle
|
||||||
|
ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
if (ImGui::IsMouseDragging(mouse_button) && draw_list) {
|
||||||
|
g_select_rect_state.is_dragging = true;
|
||||||
|
event.is_active = true;
|
||||||
|
event.start_pos = g_select_rect_state.drag_start_pos;
|
||||||
|
event.end_pos = drag_end_pos;
|
||||||
|
|
||||||
|
// Draw preview rectangle
|
||||||
|
auto start = ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x,
|
||||||
|
geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y);
|
||||||
|
auto end = ImVec2(geometry.canvas_p0.x + drag_end_pos.x + tile_size,
|
||||||
|
geometry.canvas_p0.y + drag_end_pos.y + tile_size);
|
||||||
|
draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle mouse release - complete selection
|
||||||
|
if (g_select_rect_state.is_dragging && !ImGui::IsMouseDown(mouse_button)) {
|
||||||
|
g_select_rect_state.is_dragging = false;
|
||||||
|
event.is_complete = true;
|
||||||
|
event.is_active = false;
|
||||||
|
event.start_pos = g_select_rect_state.drag_start_pos;
|
||||||
|
event.end_pos = drag_end_pos;
|
||||||
|
|
||||||
|
// Calculate selected tiles
|
||||||
|
constexpr int kTile16Size = 16;
|
||||||
|
int start_x = static_cast<int>(std::floor(g_select_rect_state.drag_start_pos.x / scaled_size)) * kTile16Size;
|
||||||
|
int start_y = static_cast<int>(std::floor(g_select_rect_state.drag_start_pos.y / scaled_size)) * kTile16Size;
|
||||||
|
int end_x = static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) * kTile16Size;
|
||||||
|
int end_y = static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) * kTile16Size;
|
||||||
|
|
||||||
|
if (start_x > end_x) std::swap(start_x, end_x);
|
||||||
|
if (start_y > end_y) std::swap(start_y, end_y);
|
||||||
|
|
||||||
|
constexpr int kTilesPerLocalMap = kSmallMapSize / 16;
|
||||||
|
|
||||||
|
for (int tile_y = start_y; tile_y <= end_y; tile_y += kTile16Size) {
|
||||||
|
for (int tile_x = start_x; tile_x <= end_x; tile_x += kTile16Size) {
|
||||||
|
int local_map_x = tile_x / kSmallMapSize;
|
||||||
|
int local_map_y = tile_y / kSmallMapSize;
|
||||||
|
|
||||||
|
int tile16_x = (tile_x % kSmallMapSize) / kTile16Size;
|
||||||
|
int tile16_y = (tile_y % kSmallMapSize) / kTile16Size;
|
||||||
|
|
||||||
|
int index_x = local_map_x * kTilesPerLocalMap + tile16_x;
|
||||||
|
int index_y = local_map_y * kTilesPerLocalMap + tile16_y;
|
||||||
|
|
||||||
|
event.selected_tiles.emplace_back(static_cast<float>(index_x),
|
||||||
|
static_cast<float>(index_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSelectionEvent HandleTileSelection(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int current_map,
|
||||||
|
float tile_size,
|
||||||
|
ImGuiMouseButton mouse_button) {
|
||||||
|
|
||||||
|
TileSelectionEvent event;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry) || !ImGui::IsMouseClicked(mouse_button)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
constexpr int kSmallMapSize = 0x200;
|
||||||
|
|
||||||
|
// Calculate super X/Y
|
||||||
|
int super_y = 0;
|
||||||
|
int super_x = 0;
|
||||||
|
if (current_map < 0x40) {
|
||||||
|
super_y = current_map / 8;
|
||||||
|
super_x = current_map % 8;
|
||||||
|
} else if (current_map < 0x80) {
|
||||||
|
super_y = (current_map - 0x40) / 8;
|
||||||
|
super_x = (current_map - 0x40) % 8;
|
||||||
|
} else {
|
||||||
|
super_y = (current_map - 0x80) / 8;
|
||||||
|
super_x = (current_map - 0x80) % 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
int painter_x = static_cast<int>(painter_pos.x);
|
||||||
|
int painter_y = static_cast<int>(painter_pos.y);
|
||||||
|
|
||||||
|
auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
|
||||||
|
auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
|
||||||
|
|
||||||
|
int index_x = super_x * 0x20 + tile16_x;
|
||||||
|
int index_y = super_y * 0x20 + tile16_y;
|
||||||
|
|
||||||
|
event.tile_position = ImVec2(static_cast<float>(index_x),
|
||||||
|
static_cast<float>(index_y));
|
||||||
|
event.is_valid = true;
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tile Painting Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
TilePaintEvent HandleTilePaint(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int tile_id,
|
||||||
|
float tile_size,
|
||||||
|
ImGuiMouseButton mouse_button) {
|
||||||
|
|
||||||
|
TilePaintEvent event;
|
||||||
|
event.tile_id = tile_id;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
|
||||||
|
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
event.position = mouse_pos;
|
||||||
|
event.grid_position = paint_pos;
|
||||||
|
|
||||||
|
// Check for paint action
|
||||||
|
if (ImGui::IsMouseClicked(mouse_button)) {
|
||||||
|
event.is_complete = true;
|
||||||
|
event.is_drag = false;
|
||||||
|
} else if (ImGui::IsMouseDragging(mouse_button)) {
|
||||||
|
event.is_complete = true;
|
||||||
|
event.is_drag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
TilePaintEvent HandleTilePaintWithPreview(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const gfx::Bitmap& bitmap,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button) {
|
||||||
|
|
||||||
|
TilePaintEvent event;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
|
||||||
|
// Calculate grid-aligned paint position
|
||||||
|
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
event.position = mouse_pos;
|
||||||
|
event.grid_position = paint_pos;
|
||||||
|
|
||||||
|
auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
|
||||||
|
|
||||||
|
// Draw preview of tile at hover position
|
||||||
|
if (bitmap.is_active() && draw_list) {
|
||||||
|
const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
|
||||||
|
geometry.canvas_p0.y + geometry.scrolling.y);
|
||||||
|
draw_list->AddImage(
|
||||||
|
reinterpret_cast<ImTextureID>(bitmap.texture()),
|
||||||
|
ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
|
||||||
|
ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for paint action
|
||||||
|
if (ImGui::IsMouseClicked(mouse_button) &&
|
||||||
|
ImGui::IsMouseDragging(mouse_button)) {
|
||||||
|
event.is_complete = true;
|
||||||
|
event.is_drag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
TilePaintEvent HandleTilemapPaint(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const gfx::Tilemap& tilemap,
|
||||||
|
int current_tile,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button) {
|
||||||
|
|
||||||
|
TilePaintEvent event;
|
||||||
|
event.tile_id = current_tile;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
|
||||||
|
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
event.position = mouse_pos;
|
||||||
|
event.grid_position = paint_pos;
|
||||||
|
|
||||||
|
// Draw preview if tilemap has texture
|
||||||
|
if (tilemap.atlas.is_active() && draw_list) {
|
||||||
|
const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
|
||||||
|
geometry.canvas_p0.y + geometry.scrolling.y);
|
||||||
|
// TODO(scawful): Render tilemap preview
|
||||||
|
(void)origin; // Suppress unused warning
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for paint action
|
||||||
|
if (ImGui::IsMouseDown(mouse_button)) {
|
||||||
|
event.is_complete = true;
|
||||||
|
event.is_drag = ImGui::IsMouseDragging(mouse_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Hover and Preview Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size) {
|
||||||
|
HoverEvent event;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
|
||||||
|
event.position = mouse_pos;
|
||||||
|
event.grid_position = AlignToGrid(mouse_pos, scaled_size);
|
||||||
|
event.is_valid = true;
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderHoverPreview(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const HoverEvent& hover,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImU32 color) {
|
||||||
|
|
||||||
|
if (!hover.is_valid || !draw_list) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
|
||||||
|
const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
|
||||||
|
geometry.canvas_p0.y + geometry.scrolling.y);
|
||||||
|
|
||||||
|
ImVec2 preview_start = ImVec2(origin.x + hover.grid_position.x,
|
||||||
|
origin.y + hover.grid_position.y);
|
||||||
|
ImVec2 preview_end = ImVec2(preview_start.x + scaled_size,
|
||||||
|
preview_start.y + scaled_size);
|
||||||
|
|
||||||
|
draw_list->AddRectFilled(preview_start, preview_end, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Entity Interaction Implementation (Stub for Phase 2.4)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
EntityInteractionEvent HandleEntityInteraction(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int entity_id,
|
||||||
|
ImVec2 entity_position) {
|
||||||
|
|
||||||
|
EntityInteractionEvent event;
|
||||||
|
event.entity_id = entity_id;
|
||||||
|
event.position = entity_position;
|
||||||
|
|
||||||
|
if (!IsMouseInCanvas(geometry)) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(scawful): Implement entity interaction logic in Phase 2.4
|
||||||
|
// For now, just return empty event
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
228
src/app/gui/canvas/canvas_interaction.h
Normal file
228
src/app/gui/canvas/canvas_interaction.h
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H
|
||||||
|
#define YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H
|
||||||
|
|
||||||
|
#include "app/gui/canvas/canvas_events.h"
|
||||||
|
#include "app/gui/canvas/canvas_state.h"
|
||||||
|
#include "app/gfx/core/bitmap.h"
|
||||||
|
#include "app/gfx/render/tilemap.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file canvas_interaction.h
|
||||||
|
* @brief Free functions for canvas interaction handling
|
||||||
|
*
|
||||||
|
* Phase 2 of Canvas refactoring: Extract interaction logic into event-driven
|
||||||
|
* free functions. These functions replace the stateful CanvasInteractionHandler
|
||||||
|
* methods with pure functions that return event payloads.
|
||||||
|
*
|
||||||
|
* Design Pattern:
|
||||||
|
* - Input: Canvas geometry, mouse state, interaction parameters
|
||||||
|
* - Output: Event payload struct (TilePaintEvent, RectSelectionEvent, etc.)
|
||||||
|
* - No hidden state, fully testable
|
||||||
|
* - Editors consume events and respond accordingly
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Rectangle Selection (Phase 2.1)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle rectangle selection interaction
|
||||||
|
*
|
||||||
|
* Processes right-click drag to select multiple tiles in a rectangular region.
|
||||||
|
* Returns event when selection completes.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry (position, size, scale)
|
||||||
|
* @param current_map Current map ID for coordinate calculation
|
||||||
|
* @param tile_size Logical tile size (before scaling)
|
||||||
|
* @param draw_list ImGui draw list for preview rendering
|
||||||
|
* @param mouse_button Mouse button for selection (default: right)
|
||||||
|
* @return RectSelectionEvent with selection results
|
||||||
|
*/
|
||||||
|
RectSelectionEvent HandleRectangleSelection(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int current_map,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Right);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle single tile selection (right-click)
|
||||||
|
*
|
||||||
|
* Processes single right-click to select one tile.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param current_map Current map ID
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @param mouse_button Mouse button for selection
|
||||||
|
* @return TileSelectionEvent with selected tile
|
||||||
|
*/
|
||||||
|
TileSelectionEvent HandleTileSelection(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int current_map,
|
||||||
|
float tile_size,
|
||||||
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Right);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tile Painting (Phase 2.2)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle tile painting interaction
|
||||||
|
*
|
||||||
|
* Processes left-click/drag to paint tiles on tilemap.
|
||||||
|
* Returns event when paint action occurs.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param tile_id Current tile ID to paint
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @param mouse_button Mouse button for painting
|
||||||
|
* @return TilePaintEvent with paint results
|
||||||
|
*/
|
||||||
|
TilePaintEvent HandleTilePaint(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int tile_id,
|
||||||
|
float tile_size,
|
||||||
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle tile painter with bitmap preview
|
||||||
|
*
|
||||||
|
* Renders preview of tile at hover position and handles paint interaction.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param bitmap Tile bitmap to paint
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @param draw_list ImGui draw list for preview
|
||||||
|
* @param mouse_button Mouse button for painting
|
||||||
|
* @return TilePaintEvent with paint results
|
||||||
|
*/
|
||||||
|
TilePaintEvent HandleTilePaintWithPreview(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const gfx::Bitmap& bitmap,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle tilemap painting interaction
|
||||||
|
*
|
||||||
|
* Processes painting with tilemap data (multiple tiles).
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param tilemap Tilemap containing tile data
|
||||||
|
* @param current_tile Current tile index in tilemap
|
||||||
|
* @param draw_list ImGui draw list for preview
|
||||||
|
* @param mouse_button Mouse button for painting
|
||||||
|
* @return TilePaintEvent with paint results
|
||||||
|
*/
|
||||||
|
TilePaintEvent HandleTilemapPaint(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const gfx::Tilemap& tilemap,
|
||||||
|
int current_tile,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Hover and Preview (Phase 2.3)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update hover state for canvas
|
||||||
|
*
|
||||||
|
* Calculates hover position and grid-aligned preview position.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @return HoverEvent with hover state
|
||||||
|
*/
|
||||||
|
HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render hover preview overlay
|
||||||
|
*
|
||||||
|
* Draws preview rectangle at hover position.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param hover Hover event from HandleHover
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @param draw_list ImGui draw list
|
||||||
|
* @param color Preview color (default: white with alpha)
|
||||||
|
*/
|
||||||
|
void RenderHoverPreview(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
const HoverEvent& hover,
|
||||||
|
float tile_size,
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
ImU32 color = IM_COL32(255, 255, 255, 80));
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Entity Interaction (Phase 2.4 - Future)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle entity interaction (hover, click, drag)
|
||||||
|
*
|
||||||
|
* Processes entity manipulation events.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @param entity_id Entity being interacted with
|
||||||
|
* @param entity_position Current entity position
|
||||||
|
* @return EntityInteractionEvent with interaction results
|
||||||
|
*/
|
||||||
|
EntityInteractionEvent HandleEntityInteraction(
|
||||||
|
const CanvasGeometry& geometry,
|
||||||
|
int entity_id,
|
||||||
|
ImVec2 entity_position);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Align position to grid
|
||||||
|
*
|
||||||
|
* Snaps canvas position to nearest grid cell.
|
||||||
|
*
|
||||||
|
* @param pos Canvas position
|
||||||
|
* @param grid_step Grid cell size
|
||||||
|
* @return Grid-aligned position
|
||||||
|
*/
|
||||||
|
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get mouse position in canvas space
|
||||||
|
*
|
||||||
|
* Converts screen-space mouse position to canvas-space coordinates.
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry (includes origin)
|
||||||
|
* @return Mouse position in canvas space
|
||||||
|
*/
|
||||||
|
ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if mouse is in canvas bounds
|
||||||
|
*
|
||||||
|
* @param geometry Canvas geometry
|
||||||
|
* @return True if mouse is within canvas
|
||||||
|
*/
|
||||||
|
bool IsMouseInCanvas(const CanvasGeometry& geometry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate tile grid indices from canvas position
|
||||||
|
*
|
||||||
|
* @param canvas_pos Canvas-space position
|
||||||
|
* @param tile_size Logical tile size
|
||||||
|
* @param global_scale Canvas scale factor
|
||||||
|
* @return Tile grid position (x, y)
|
||||||
|
*/
|
||||||
|
ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale);
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_H
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "canvas_interaction_handler.h"
|
#include "canvas_interaction_handler.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include "app/gui/canvas/canvas_interaction.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
@@ -9,8 +9,8 @@ namespace gui {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Helper function to align position to grid
|
// Helper function to align position to grid (local version)
|
||||||
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
|
ImVec2 AlignToGridLocal(ImVec2 pos, float grid_step) {
|
||||||
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
|
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
|
||||||
std::floor(pos.y / grid_step) * grid_step);
|
std::floor(pos.y / grid_step) * grid_step);
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ bool CanvasInteractionHandler::DrawTilePainter(
|
|||||||
hover_points_.clear();
|
hover_points_.clear();
|
||||||
|
|
||||||
// Calculate grid-aligned paint position
|
// Calculate grid-aligned paint position
|
||||||
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
|
ImVec2 paint_pos = AlignToGridLocal(mouse_pos, scaled_size);
|
||||||
mouse_pos_in_canvas_ = paint_pos;
|
mouse_pos_in_canvas_ = paint_pos;
|
||||||
auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
|
auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ bool CanvasInteractionHandler::DrawTilePainter(
|
|||||||
// Draw preview of tile at hover position
|
// Draw preview of tile at hover position
|
||||||
if (bitmap.is_active() && draw_list) {
|
if (bitmap.is_active() && draw_list) {
|
||||||
draw_list->AddImage(
|
draw_list->AddImage(
|
||||||
(ImTextureID)(intptr_t)bitmap.texture(),
|
reinterpret_cast<ImTextureID>(bitmap.texture()),
|
||||||
ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
|
ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
|
||||||
ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size));
|
ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size));
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ bool CanvasInteractionHandler::DrawTilemapPainter(
|
|||||||
static_cast<float>(tile_y + tilemap.tile_size.y) / tilemap.atlas.height());
|
static_cast<float>(tile_y + tilemap.tile_size.y) / tilemap.atlas.height());
|
||||||
|
|
||||||
draw_list->AddImage(
|
draw_list->AddImage(
|
||||||
(ImTextureID)(intptr_t)tilemap.atlas.texture(),
|
reinterpret_cast<ImTextureID>(tilemap.atlas.texture()),
|
||||||
ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
|
ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
|
||||||
ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size),
|
ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size),
|
||||||
uv0, uv1);
|
uv0, uv1);
|
||||||
@@ -218,7 +218,7 @@ bool CanvasInteractionHandler::DrawTileSelector(
|
|||||||
|
|
||||||
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||||
hover_points_.clear();
|
hover_points_.clear();
|
||||||
ImVec2 painter_pos = AlignToGrid(mouse_pos, tile_size);
|
ImVec2 painter_pos = AlignToGridLocal(mouse_pos, tile_size);
|
||||||
|
|
||||||
hover_points_.push_back(painter_pos);
|
hover_points_.push_back(painter_pos);
|
||||||
hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size));
|
hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size));
|
||||||
@@ -236,109 +236,47 @@ bool CanvasInteractionHandler::DrawSelectRect(
|
|||||||
int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||||
float global_scale, float tile_size, bool is_hovered) {
|
float global_scale, float tile_size, bool is_hovered) {
|
||||||
|
|
||||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
|
||||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
|
||||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
|
||||||
const float scaled_size = tile_size * global_scale;
|
|
||||||
static ImVec2 drag_start_pos;
|
|
||||||
static bool dragging = false;
|
|
||||||
constexpr int small_map_size = 0x200;
|
|
||||||
|
|
||||||
if (!is_hovered) {
|
if (!is_hovered) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate superX and superY accounting for world offset
|
// Create CanvasGeometry from parameters
|
||||||
int super_y = 0;
|
CanvasGeometry geometry;
|
||||||
int super_x = 0;
|
geometry.canvas_p0 = canvas_p0;
|
||||||
if (current_map < 0x40) {
|
geometry.scrolling = scrolling;
|
||||||
super_y = current_map / 8;
|
geometry.scaled_size = ImVec2(tile_size * global_scale, tile_size * global_scale);
|
||||||
super_x = current_map % 8;
|
geometry.canvas_sz = ImVec2(tile_size, tile_size); // Will be updated if needed
|
||||||
} else if (current_map < 0x80) {
|
|
||||||
super_y = (current_map - 0x40) / 8;
|
// Call new event-based function
|
||||||
super_x = (current_map - 0x40) % 8;
|
RectSelectionEvent event = HandleRectangleSelection(
|
||||||
} else {
|
geometry, current_map, tile_size, draw_list, ImGuiMouseButton_Right);
|
||||||
super_y = (current_map - 0x80) / 8;
|
|
||||||
super_x = (current_map - 0x80) % 8;
|
// Update internal state for backward compatibility
|
||||||
}
|
if (event.is_complete) {
|
||||||
|
selected_tiles_ = event.selected_tiles;
|
||||||
// Handle right click for single tile selection
|
|
||||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
|
|
||||||
int painter_x = painter_pos.x;
|
|
||||||
int painter_y = painter_pos.y;
|
|
||||||
|
|
||||||
auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20);
|
|
||||||
auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20);
|
|
||||||
|
|
||||||
int index_x = super_x * 0x20 + tile16_x;
|
|
||||||
int index_y = super_y * 0x20 + tile16_y;
|
|
||||||
selected_tile_pos_ = ImVec2(index_x, index_y);
|
|
||||||
selected_points_.clear();
|
selected_points_.clear();
|
||||||
rect_select_active_ = false;
|
selected_points_.push_back(event.start_pos);
|
||||||
|
selected_points_.push_back(event.end_pos);
|
||||||
drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw rectangle while dragging
|
|
||||||
ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
|
|
||||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && draw_list) {
|
|
||||||
auto start = ImVec2(canvas_p0.x + drag_start_pos.x,
|
|
||||||
canvas_p0.y + drag_start_pos.y);
|
|
||||||
auto end = ImVec2(canvas_p0.x + drag_end_pos.x + tile_size,
|
|
||||||
canvas_p0.y + drag_end_pos.y + tile_size);
|
|
||||||
draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
|
|
||||||
dragging = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete selection on release
|
|
||||||
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
|
||||||
dragging = false;
|
|
||||||
|
|
||||||
constexpr int tile16_size = 16;
|
|
||||||
int start_x = std::floor(drag_start_pos.x / scaled_size) * tile16_size;
|
|
||||||
int start_y = std::floor(drag_start_pos.y / scaled_size) * tile16_size;
|
|
||||||
int end_x = std::floor(drag_end_pos.x / scaled_size) * tile16_size;
|
|
||||||
int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size;
|
|
||||||
|
|
||||||
if (start_x > end_x) std::swap(start_x, end_x);
|
|
||||||
if (start_y > end_y) std::swap(start_y, end_y);
|
|
||||||
|
|
||||||
selected_tiles_.clear();
|
|
||||||
selected_tiles_.reserve(((end_x - start_x) / tile16_size + 1) *
|
|
||||||
((end_y - start_y) / tile16_size + 1));
|
|
||||||
|
|
||||||
constexpr int tiles_per_local_map = small_map_size / 16;
|
|
||||||
|
|
||||||
for (int tile_y = start_y; tile_y <= end_y; tile_y += tile16_size) {
|
|
||||||
for (int tile_x = start_x; tile_x <= end_x; tile_x += tile16_size) {
|
|
||||||
int local_map_x = tile_x / small_map_size;
|
|
||||||
int local_map_y = tile_y / small_map_size;
|
|
||||||
|
|
||||||
int tile16_x = (tile_x % small_map_size) / tile16_size;
|
|
||||||
int tile16_y = (tile_y % small_map_size) / tile16_size;
|
|
||||||
|
|
||||||
int index_x = local_map_x * tiles_per_local_map + tile16_x;
|
|
||||||
int index_y = local_map_y * tiles_per_local_map + tile16_y;
|
|
||||||
|
|
||||||
selected_tiles_.emplace_back(index_x, index_y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selected_points_.clear();
|
|
||||||
selected_points_.push_back(drag_start_pos);
|
|
||||||
selected_points_.push_back(drag_end_pos);
|
|
||||||
rect_select_active_ = true;
|
rect_select_active_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!event.selected_tiles.empty() && !event.is_complete) {
|
||||||
|
// Single tile selection
|
||||||
|
selected_tile_pos_ = event.selected_tiles[0];
|
||||||
|
selected_points_.clear();
|
||||||
|
rect_select_active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect_select_active_ = event.is_active;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods - these are thin wrappers that could be static but kept as instance
|
// Helper methods - these are thin wrappers that could be static but kept as instance
|
||||||
// methods for potential future state access
|
// methods for potential future state access
|
||||||
ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) {
|
ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) {
|
||||||
return AlignToGrid(pos, grid_step);
|
return AlignToGridLocal(pos, grid_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) {
|
ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ set(CANVAS_SRC
|
|||||||
app/gui/canvas/canvas_automation_api.cc
|
app/gui/canvas/canvas_automation_api.cc
|
||||||
app/gui/canvas/canvas_context_menu.cc
|
app/gui/canvas/canvas_context_menu.cc
|
||||||
app/gui/canvas/canvas_geometry.cc
|
app/gui/canvas/canvas_geometry.cc
|
||||||
|
app/gui/canvas/canvas_interaction.cc
|
||||||
app/gui/canvas/canvas_interaction_handler.cc
|
app/gui/canvas/canvas_interaction_handler.cc
|
||||||
app/gui/canvas/canvas_modals.cc
|
app/gui/canvas/canvas_modals.cc
|
||||||
app/gui/canvas/canvas_performance_integration.cc
|
app/gui/canvas/canvas_performance_integration.cc
|
||||||
|
|||||||
Reference in New Issue
Block a user