Files
yaze/src/app/gui/canvas/canvas_interaction_handler.cc
scawful 991366113e Update canvas system with enhanced interaction and performance tracking features
- Introduced a new CanvasInteractionHandler for managing tile interactions, including painting and selection modes.
- Added CanvasContextMenu for improved user interaction with context-specific options.
- Implemented CanvasPerformanceIntegration to monitor and report performance metrics during canvas operations.
- Developed CanvasUsageTracker to track user interactions and usage patterns within the canvas.
- Refactored existing canvas utilities and integrated new modal systems for enhanced user experience.
- Updated CMake configuration to include new canvas components and ensure proper linking with existing libraries.
2025-09-30 13:10:32 -04:00

370 lines
12 KiB
C++

#include "canvas_interaction_handler.h"
#include <algorithm>
#include <cmath>
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
namespace canvas {
namespace {
// Helper function to align position to grid
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);
}
} // namespace
void CanvasInteractionHandler::Initialize(const std::string& canvas_id) {
canvas_id_ = canvas_id;
ClearState();
}
void CanvasInteractionHandler::ClearState() {
hover_points_.clear();
selected_points_.clear();
selected_tiles_.clear();
drawn_tile_pos_ = ImVec2(-1, -1);
mouse_pos_in_canvas_ = ImVec2(0, 0);
selected_tile_pos_ = ImVec2(-1, -1);
rect_select_active_ = false;
}
TileInteractionResult CanvasInteractionHandler::Update(
ImVec2 canvas_p0, ImVec2 scrolling, float /*global_scale*/, float /*tile_size*/,
ImVec2 /*canvas_size*/, bool is_hovered) {
TileInteractionResult result;
if (!is_hovered) {
hover_points_.clear();
return result;
}
const ImGuiIO& imgui_io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
mouse_pos_in_canvas_ = ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
// Update based on current mode - each mode is handled by its specific Draw method
// This method exists for future state updates if needed
(void)current_mode_; // Suppress unused warning
return result;
}
bool CanvasInteractionHandler::DrawTilePainter(
const gfx::Bitmap& bitmap, ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, 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 auto scaled_size = tile_size * global_scale;
// Clear hover when not hovering
if (!is_hovered) {
hover_points_.clear();
return false;
}
// Reset previous hover
hover_points_.clear();
// Calculate grid-aligned paint position
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
mouse_pos_in_canvas_ = paint_pos;
auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
hover_points_.push_back(paint_pos);
hover_points_.push_back(paint_pos_end);
// Draw preview of tile at hover position
if (bitmap.is_active() && draw_list) {
draw_list->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
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));
}
// Check for paint action
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
drawn_tile_pos_ = paint_pos;
return true;
}
return false;
}
bool CanvasInteractionHandler::DrawTilemapPainter(
gfx::Tilemap& tilemap, int current_tile, ImDrawList* draw_list,
ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, 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);
// Safety check
if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) {
return false;
}
const auto scaled_size = tilemap.tile_size.x * global_scale;
if (!is_hovered) {
hover_points_.clear();
return false;
}
hover_points_.clear();
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
mouse_pos_in_canvas_ = paint_pos;
hover_points_.push_back(paint_pos);
hover_points_.push_back(ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
// Draw tile preview from atlas
if (tilemap.atlas.is_active() && tilemap.atlas.texture() && draw_list) {
int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
if (tiles_per_row > 0) {
int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x;
int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y;
if (tile_x >= 0 && tile_x < tilemap.atlas.width() &&
tile_y >= 0 && tile_y < tilemap.atlas.height()) {
ImVec2 uv0 = ImVec2(static_cast<float>(tile_x) / tilemap.atlas.width(),
static_cast<float>(tile_y) / tilemap.atlas.height());
ImVec2 uv1 = ImVec2(static_cast<float>(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(),
static_cast<float>(tile_y + tilemap.tile_size.y) / tilemap.atlas.height());
draw_list->AddImage(
(ImTextureID)(intptr_t)tilemap.atlas.texture(),
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),
uv0, uv1);
}
}
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
drawn_tile_pos_ = paint_pos;
return true;
}
return false;
}
bool CanvasInteractionHandler::DrawSolidTilePainter(
const ImVec4& color, ImDrawList* draw_list, ImVec2 canvas_p0,
ImVec2 scrolling, 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);
auto scaled_tile_size = tile_size * global_scale;
static bool is_dragging = false;
static ImVec2 start_drag_pos;
if (!is_hovered) {
hover_points_.clear();
return false;
}
hover_points_.clear();
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_tile_size);
mouse_pos_in_canvas_ = paint_pos;
// Clamp to canvas bounds (assuming canvas_size from Update)
// For now, skip clamping as we don't have canvas_size here
hover_points_.push_back(paint_pos);
hover_points_.push_back(ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
if (draw_list) {
draw_list->AddRectFilled(
ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
ImVec2(origin.x + paint_pos.x + scaled_tile_size,
origin.y + paint_pos.y + scaled_tile_size),
IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
is_dragging = true;
start_drag_pos = paint_pos;
}
if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
is_dragging = false;
drawn_tile_pos_ = start_drag_pos;
return true;
}
return false;
}
bool CanvasInteractionHandler::DrawTileSelector(
ImDrawList* /*draw_list*/, ImVec2 canvas_p0, ImVec2 scrolling, 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);
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
hover_points_.clear();
ImVec2 painter_pos = AlignToGrid(mouse_pos, tile_size);
hover_points_.push_back(painter_pos);
hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size));
mouse_pos_in_canvas_ = painter_pos;
}
if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
return true;
}
return false;
}
bool CanvasInteractionHandler::DrawSelectRect(
int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
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) {
return false;
}
// Calculate superX and superY accounting for world offset
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;
}
// 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();
rect_select_active_ = false;
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;
return true;
}
return false;
}
// Helper methods - these are thin wrappers that could be static but kept as instance
// methods for potential future state access
ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) {
return AlignToGrid(pos, grid_step);
}
ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) {
const ImGuiIO& imgui_io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
}
bool CanvasInteractionHandler::IsMouseClicked(ImGuiMouseButton button) {
return ImGui::IsMouseClicked(button);
}
bool CanvasInteractionHandler::IsMouseDoubleClicked(ImGuiMouseButton button) {
return ImGui::IsMouseDoubleClicked(button);
}
bool CanvasInteractionHandler::IsMouseDragging(ImGuiMouseButton button) {
return ImGui::IsMouseDragging(button);
}
bool CanvasInteractionHandler::IsMouseReleased(ImGuiMouseButton button) {
return ImGui::IsMouseReleased(button);
}
} // namespace canvas
} // namespace gui
} // namespace yaze