- Removed unnecessary build flags for Yaze minimal builds in release.yml. - Updated version number to 0.3.2 in yaze.h to reflect recent changes. - Enhanced OverworldEditor with new method to scroll the blockset canvas to the currently selected tile, improving navigation. - Implemented logic to handle area size checks for ZSCustomOverworld v3, ensuring compatibility with different ROM versions. - Refactored tile selection and drawing logic in Tile16Editor for improved accuracy and user interaction. - Updated context menu in OverworldEditor to include a refresh option for map changes, enhancing usability.
1444 lines
51 KiB
C++
1444 lines
51 KiB
C++
#include "canvas.h"
|
|
|
|
#include <cmath>
|
|
#include <string>
|
|
|
|
#include "app/core/window.h"
|
|
#include "app/gfx/atlas_renderer.h"
|
|
#include "app/gfx/bitmap.h"
|
|
#include "app/gfx/performance_profiler.h"
|
|
#include "app/gui/color.h"
|
|
#include "app/gui/style.h"
|
|
#include "app/gui/canvas_utils.h"
|
|
#include "util/log.h"
|
|
#include "imgui/imgui.h"
|
|
#include "imgui_memory_editor.h"
|
|
|
|
namespace yaze::gui {
|
|
|
|
using core::Renderer;
|
|
|
|
using ImGui::BeginMenu;
|
|
using ImGui::EndMenu;
|
|
using ImGui::GetContentRegionAvail;
|
|
using ImGui::GetCursorScreenPos;
|
|
using ImGui::GetIO;
|
|
using ImGui::GetMouseDragDelta;
|
|
using ImGui::GetWindowDrawList;
|
|
using ImGui::IsItemActive;
|
|
using ImGui::IsItemHovered;
|
|
using ImGui::IsMouseClicked;
|
|
using ImGui::IsMouseDragging;
|
|
using ImGui::MenuItem;
|
|
using ImGui::OpenPopupOnItemClick;
|
|
using ImGui::Selectable;
|
|
using ImGui::Text;
|
|
|
|
constexpr uint32_t kBlackColor = IM_COL32(0, 0, 0, 255);
|
|
constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
|
|
constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
|
|
constexpr uint32_t kOutlineRect = IM_COL32(255, 255, 255, 200);
|
|
|
|
constexpr ImGuiButtonFlags kMouseFlags =
|
|
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
|
|
|
|
namespace {
|
|
ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
|
|
return ImVec2(std::floor(pos.x / scale) * scale,
|
|
std::floor(pos.y / scale) * scale);
|
|
}
|
|
} // namespace
|
|
|
|
// Canvas class implementation begins here
|
|
|
|
void Canvas::InitializeDefaults() {
|
|
// Initialize configuration with sensible defaults
|
|
config_.enable_grid = true;
|
|
config_.enable_hex_labels = false;
|
|
config_.enable_custom_labels = false;
|
|
config_.enable_context_menu = true;
|
|
config_.is_draggable = false;
|
|
config_.grid_step = 32.0f;
|
|
config_.global_scale = 1.0f;
|
|
config_.canvas_size = ImVec2(0, 0);
|
|
config_.custom_canvas_size = false;
|
|
|
|
// Initialize selection state
|
|
selection_.Clear();
|
|
|
|
// Initialize palette editor
|
|
palette_editor_ = std::make_unique<EnhancedPaletteEditor>();
|
|
|
|
// Initialize legacy compatibility variables to match config
|
|
enable_grid_ = config_.enable_grid;
|
|
enable_hex_tile_labels_ = config_.enable_hex_labels;
|
|
enable_custom_labels_ = config_.enable_custom_labels;
|
|
enable_context_menu_ = config_.enable_context_menu;
|
|
draggable_ = config_.is_draggable;
|
|
custom_step_ = config_.grid_step;
|
|
global_scale_ = config_.global_scale;
|
|
custom_canvas_size_ = config_.custom_canvas_size;
|
|
select_rect_active_ = selection_.select_rect_active;
|
|
selected_tile_pos_ = selection_.selected_tile_pos;
|
|
}
|
|
|
|
void Canvas::Cleanup() {
|
|
palette_editor_.reset();
|
|
selection_.Clear();
|
|
}
|
|
|
|
void Canvas::InitializePaletteEditor(Rom* rom) {
|
|
rom_ = rom;
|
|
if (palette_editor_) {
|
|
palette_editor_->Initialize(rom);
|
|
}
|
|
}
|
|
|
|
void Canvas::ShowPaletteEditor() {
|
|
if (palette_editor_ && bitmap_) {
|
|
auto mutable_palette = bitmap_->mutable_palette();
|
|
palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor");
|
|
}
|
|
}
|
|
|
|
void Canvas::ShowColorAnalysis() {
|
|
if (palette_editor_ && bitmap_) {
|
|
palette_editor_->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
|
|
}
|
|
}
|
|
|
|
bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
|
|
if (palette_editor_ && bitmap_) {
|
|
return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Size reporting methods for table integration
|
|
ImVec2 Canvas::GetMinimumSize() const {
|
|
return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale);
|
|
}
|
|
|
|
ImVec2 Canvas::GetPreferredSize() const {
|
|
return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale);
|
|
}
|
|
|
|
void Canvas::ReserveTableSpace(const std::string& label) {
|
|
ImVec2 size = config_.auto_resize ? GetPreferredSize() : config_.canvas_size;
|
|
CanvasUtils::ReserveCanvasSpace(size, label);
|
|
}
|
|
|
|
bool Canvas::BeginTableCanvas(const std::string& label) {
|
|
if (config_.auto_resize) {
|
|
ImVec2 preferred_size = GetPreferredSize();
|
|
CanvasUtils::SetNextCanvasSize(preferred_size, true);
|
|
}
|
|
|
|
// Begin child window that properly reports size to tables
|
|
std::string child_id = canvas_id_ + "_TableChild";
|
|
ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
|
|
|
|
bool result = ImGui::BeginChild(child_id.c_str(), child_size,
|
|
true, // Always show border for table integration
|
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
|
|
if (!label.empty()) {
|
|
ImGui::Text("%s", label.c_str());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Canvas::EndTableCanvas() {
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
// Improved interaction detection methods
|
|
bool Canvas::HasValidSelection() const {
|
|
return !points_.empty() && points_.size() >= 2;
|
|
}
|
|
|
|
bool Canvas::WasClicked(ImGuiMouseButton button) const {
|
|
return ImGui::IsItemClicked(button) && HasValidSelection();
|
|
}
|
|
|
|
bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
|
|
return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection();
|
|
}
|
|
|
|
ImVec2 Canvas::GetLastClickPosition() const {
|
|
if (HasValidSelection()) {
|
|
return points_[0]; // Return the first point of the selection
|
|
}
|
|
return ImVec2(-1, -1); // Invalid position
|
|
}
|
|
|
|
void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
|
|
const std::function<void()> &event,
|
|
int tile_size, float scale) {
|
|
config_.global_scale = scale;
|
|
global_scale_ = scale; // Legacy compatibility
|
|
DrawBackground();
|
|
DrawContextMenu();
|
|
DrawBitmap(bitmap, 2, scale);
|
|
if (DrawSolidTilePainter(color, tile_size)) {
|
|
event();
|
|
}
|
|
DrawGrid();
|
|
DrawOverlay();
|
|
}
|
|
|
|
void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
|
|
config_.enable_custom_labels = true;
|
|
enable_custom_labels_ = true; // Legacy compatibility
|
|
DrawBackground(bg_size);
|
|
DrawInfoGrid(grid_size, 8, label_id);
|
|
DrawOverlay();
|
|
}
|
|
|
|
void Canvas::DrawBackground(ImVec2 canvas_size) {
|
|
draw_list_ = GetWindowDrawList();
|
|
canvas_p0_ = GetCursorScreenPos();
|
|
|
|
// Calculate canvas size using utility function
|
|
ImVec2 content_region = GetContentRegionAvail();
|
|
canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size);
|
|
|
|
if (canvas_size.x != 0) {
|
|
canvas_sz_ = canvas_size;
|
|
config_.canvas_size = canvas_size;
|
|
}
|
|
|
|
// Calculate scaled canvas bounds
|
|
ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale);
|
|
|
|
// CRITICAL FIX: Ensure minimum size to prevent ImGui assertions
|
|
if (scaled_size.x <= 0.0f) scaled_size.x = 1.0f;
|
|
if (scaled_size.y <= 0.0f) scaled_size.y = 1.0f;
|
|
|
|
canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y);
|
|
|
|
// Draw border and background color
|
|
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
|
|
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
|
|
|
|
ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags);
|
|
|
|
if (config_.is_draggable && IsItemHovered()) {
|
|
const ImGuiIO &io = GetIO();
|
|
const bool is_active = IsItemActive(); // Held
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
|
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
|
|
// Pan (we use a zero mouse threshold when there's no context menu)
|
|
if (const float mouse_threshold_for_pan =
|
|
enable_context_menu_ ? -1.0f : 0.0f;
|
|
is_active &&
|
|
IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
|
|
scrolling_.x += io.MouseDelta.x;
|
|
scrolling_.y += io.MouseDelta.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::DrawContextMenu() {
|
|
const ImGuiIO &io = GetIO();
|
|
const ImVec2 scaled_sz(canvas_sz_.x * global_scale_,
|
|
canvas_sz_.y * global_scale_);
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
|
|
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
|
|
static bool show_bitmap_data = false;
|
|
if (show_bitmap_data && bitmap_ != nullptr) {
|
|
static MemoryEditor mem_edit;
|
|
mem_edit.DrawWindow("Bitmap Data", (void *)bitmap_->data(), bitmap_->size(),
|
|
0);
|
|
}
|
|
|
|
// Context menu (under default mouse threshold)
|
|
if (ImVec2 drag_delta = GetMouseDragDelta(ImGuiMouseButton_Right);
|
|
enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
|
|
OpenPopupOnItemClick(context_id_.c_str(), ImGuiPopupFlags_MouseButtonRight);
|
|
|
|
// Contents of the Context Menu
|
|
if (ImGui::BeginPopup(context_id_.c_str())) {
|
|
// Draw custom context menu items first
|
|
for (const auto& item : context_menu_items_) {
|
|
DrawContextMenuItem(item);
|
|
}
|
|
|
|
// Add separator if there are custom items
|
|
if (!context_menu_items_.empty()) {
|
|
ImGui::Separator();
|
|
}
|
|
|
|
// Default canvas menu items
|
|
if (MenuItem("Reset View", nullptr, false)) {
|
|
ResetView();
|
|
}
|
|
if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) {
|
|
SetZoomToFit(*bitmap_);
|
|
}
|
|
if (MenuItem("Advanced Properties", nullptr, false)) {
|
|
ImGui::OpenPopup("Advanced Canvas Properties");
|
|
}
|
|
ImGui::Separator();
|
|
MenuItem("Show Grid", nullptr, &enable_grid_);
|
|
Selectable("Show Position Labels", &enable_hex_tile_labels_);
|
|
if (MenuItem("Edit Palette", nullptr, false) && bitmap_) {
|
|
ShowPaletteEditor();
|
|
}
|
|
if (MenuItem("Color Analysis", nullptr, false) && bitmap_) {
|
|
ShowColorAnalysis();
|
|
}
|
|
if (MenuItem("Scaling Controls", nullptr, false)) {
|
|
ImGui::OpenPopup("Scaling Controls");
|
|
}
|
|
if (BeginMenu("Canvas Properties")) {
|
|
Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
|
|
Text("Global Scale: %.1f", global_scale_);
|
|
Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y);
|
|
EndMenu();
|
|
}
|
|
if (bitmap_ != nullptr) {
|
|
if (BeginMenu("Bitmap Properties")) {
|
|
Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y);
|
|
Text("Pitch: %d", bitmap_->surface()->pitch);
|
|
Text("BitsPerPixel: %d", bitmap_->surface()->format->BitsPerPixel);
|
|
Text("BytesPerPixel: %d", bitmap_->surface()->format->BytesPerPixel);
|
|
MenuItem("Data", nullptr, &show_bitmap_data);
|
|
if (BeginMenu("Format")) {
|
|
if (MenuItem("Indexed")) {
|
|
bitmap_->Reformat(gfx::BitmapFormat::kIndexed);
|
|
Renderer::Get().UpdateBitmap(bitmap_);
|
|
}
|
|
if (MenuItem("4BPP")) {
|
|
bitmap_->Reformat(gfx::BitmapFormat::k4bpp);
|
|
Renderer::Get().UpdateBitmap(bitmap_);
|
|
}
|
|
if (MenuItem("8BPP")) {
|
|
bitmap_->Reformat(gfx::BitmapFormat::k8bpp);
|
|
Renderer::Get().UpdateBitmap(bitmap_);
|
|
}
|
|
|
|
EndMenu();
|
|
}
|
|
if (BeginMenu("ROM Palette Selection") && rom_) {
|
|
Text("Select ROM Palette Group:");
|
|
|
|
// Enhanced ROM palette group selection
|
|
if (palette_editor_) {
|
|
// Use our enhanced palette editor's ROM selection
|
|
if (MenuItem("Open Enhanced Palette Manager")) {
|
|
palette_editor_->ShowROMPaletteManager();
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
// Quick palette group selection
|
|
const char* palette_groups[] = {
|
|
"Overworld Main", "Overworld Aux", "Overworld Animated",
|
|
"Dungeon Main", "Global Sprites", "Armor", "Swords"
|
|
};
|
|
|
|
if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_,
|
|
palette_groups, IM_ARRAYSIZE(palette_groups))) {
|
|
// Group selection changed
|
|
}
|
|
|
|
ImGui::SetNextItemWidth(100.f);
|
|
if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) {
|
|
// Palette index changed
|
|
}
|
|
|
|
// Apply button with enhanced functionality
|
|
if (ImGui::Button("Apply to Canvas") && bitmap_) {
|
|
if (palette_editor_->ApplyROMPalette(bitmap_,
|
|
edit_palette_group_name_index_,
|
|
edit_palette_index_)) {
|
|
util::logf("Applied ROM palette group %d, index %d via context menu",
|
|
edit_palette_group_name_index_, edit_palette_index_);
|
|
}
|
|
}
|
|
|
|
// Direct palette editing with SelectablePalettePipeline
|
|
if (ImGui::TreeNode("Interactive Palette Editor")) {
|
|
if (rom_ && bitmap_) {
|
|
ImGui::Text("Interactive ROM Palette Editing");
|
|
ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]);
|
|
|
|
// Get the enhanced palette editor's ROM palette if available
|
|
if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) {
|
|
auto editable_palette = const_cast<gfx::SnesPalette&>(*rom_palette);
|
|
|
|
if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) {
|
|
// Use the existing SelectablePalettePipeline for interactive editing
|
|
gui::SelectablePalettePipeline(edit_palette_sub_index_,
|
|
refresh_graphics_, editable_palette);
|
|
|
|
if (refresh_graphics_) {
|
|
bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_);
|
|
Renderer::Get().UpdateBitmap(bitmap_);
|
|
refresh_graphics_ = false;
|
|
util::logf("Applied interactive palette changes to canvas");
|
|
}
|
|
}
|
|
ImGui::EndChild();
|
|
} else {
|
|
ImGui::Text("Load ROM palettes first using Enhanced Palette Manager");
|
|
}
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
}
|
|
EndMenu();
|
|
}
|
|
if (BeginMenu("View Palette")) {
|
|
(void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8);
|
|
EndMenu();
|
|
}
|
|
EndMenu();
|
|
}
|
|
}
|
|
ImGui::Separator();
|
|
if (BeginMenu("Grid Tile Size")) {
|
|
if (MenuItem("8x8", nullptr, custom_step_ == 8.0f)) {
|
|
custom_step_ = 8.0f;
|
|
}
|
|
if (MenuItem("16x16", nullptr, custom_step_ == 16.0f)) {
|
|
custom_step_ = 16.0f;
|
|
}
|
|
if (MenuItem("32x32", nullptr, custom_step_ == 32.0f)) {
|
|
custom_step_ = 32.0f;
|
|
}
|
|
if (MenuItem("64x64", nullptr, custom_step_ == 64.0f)) {
|
|
custom_step_ = 64.0f;
|
|
}
|
|
EndMenu();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// Draw enhanced property dialogs
|
|
ShowAdvancedCanvasProperties();
|
|
ShowScalingControls();
|
|
}
|
|
|
|
void Canvas::DrawContextMenuItem(const ContextMenuItem& item) {
|
|
if (!item.enabled_condition()) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
|
|
if (item.subitems.empty()) {
|
|
// Simple menu item
|
|
if (ImGui::MenuItem(item.label.c_str(), item.shortcut.empty() ? nullptr : item.shortcut.c_str())) {
|
|
item.callback();
|
|
}
|
|
} else {
|
|
// Menu with subitems
|
|
if (ImGui::BeginMenu(item.label.c_str())) {
|
|
for (const auto& subitem : item.subitems) {
|
|
DrawContextMenuItem(subitem);
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
if (!item.enabled_condition()) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
}
|
|
|
|
void Canvas::AddContextMenuItem(const ContextMenuItem& item) {
|
|
context_menu_items_.push_back(item);
|
|
}
|
|
|
|
void Canvas::ClearContextMenuItems() {
|
|
context_menu_items_.clear();
|
|
}
|
|
|
|
// Old ShowPaletteEditor method removed - now handled by EnhancedPaletteEditor
|
|
|
|
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
|
|
if (!bitmap.is_active()) return;
|
|
|
|
ImVec2 available = ImGui::GetContentRegionAvail();
|
|
float scale_x = available.x / bitmap.width();
|
|
float scale_y = available.y / bitmap.height();
|
|
config_.global_scale = std::min(scale_x, scale_y);
|
|
|
|
// Ensure minimum readable scale
|
|
if (config_.global_scale < 0.25f) config_.global_scale = 0.25f;
|
|
|
|
global_scale_ = config_.global_scale; // Legacy compatibility
|
|
|
|
// Center the view
|
|
scrolling_ = ImVec2(0, 0);
|
|
}
|
|
|
|
void Canvas::ResetView() {
|
|
config_.global_scale = 1.0f;
|
|
global_scale_ = 1.0f; // Legacy compatibility
|
|
scrolling_ = ImVec2(0, 0);
|
|
}
|
|
|
|
bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) {
|
|
const ImGuiIO &io = GetIO();
|
|
const bool is_hovered = IsItemHovered();
|
|
is_hovered_ = is_hovered;
|
|
// Lock scrolled origin
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
const auto scaled_size = size * scale;
|
|
|
|
// Erase the hover when the mouse is not in the canvas window.
|
|
if (!is_hovered) {
|
|
points_.clear();
|
|
return false;
|
|
}
|
|
|
|
// Reset the previous tile hover
|
|
if (!points_.empty()) {
|
|
points_.clear();
|
|
}
|
|
|
|
// Calculate the coordinates of the mouse
|
|
ImVec2 paint_pos = AlignPosToGrid(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);
|
|
points_.push_back(paint_pos);
|
|
points_.push_back(paint_pos_end);
|
|
|
|
if (bitmap.is_active()) {
|
|
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));
|
|
}
|
|
|
|
if (IsMouseClicked(ImGuiMouseButton_Left) &&
|
|
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
// Draw the currently selected tile on the overworld here
|
|
// Save the coordinates of the selected tile.
|
|
drawn_tile_pos_ = paint_pos;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) {
|
|
const ImGuiIO &io = GetIO();
|
|
const bool is_hovered = IsItemHovered();
|
|
is_hovered_ = is_hovered;
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
|
|
// Safety check: ensure tilemap is properly initialized
|
|
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) {
|
|
points_.clear();
|
|
return false;
|
|
}
|
|
|
|
if (!points_.empty()) {
|
|
points_.clear();
|
|
}
|
|
|
|
ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
|
|
mouse_pos_in_canvas_ = paint_pos;
|
|
|
|
points_.push_back(paint_pos);
|
|
points_.push_back(
|
|
ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
|
|
|
|
// CRITICAL FIX: Disable tile cache system to prevent crashes
|
|
// Just draw a simple preview tile using the atlas directly
|
|
if (tilemap.atlas.is_active() && tilemap.atlas.texture()) {
|
|
// Draw the tile directly from the atlas without caching
|
|
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;
|
|
|
|
// Simple bounds check
|
|
if (tile_x >= 0 && tile_x < tilemap.atlas.width() &&
|
|
tile_y >= 0 && tile_y < tilemap.atlas.height()) {
|
|
|
|
// Draw directly from atlas texture
|
|
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 (IsMouseClicked(ImGuiMouseButton_Left) ||
|
|
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
drawn_tile_pos_ = paint_pos;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) {
|
|
const ImGuiIO &io = GetIO();
|
|
const bool is_hovered = IsItemHovered();
|
|
is_hovered_ = is_hovered;
|
|
// Lock scrolled origin
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
auto scaled_tile_size = tile_size * global_scale_;
|
|
static bool is_dragging = false;
|
|
static ImVec2 start_drag_pos;
|
|
|
|
// Erase the hover when the mouse is not in the canvas window.
|
|
if (!is_hovered) {
|
|
points_.clear();
|
|
return false;
|
|
}
|
|
|
|
// Reset the previous tile hover
|
|
if (!points_.empty()) {
|
|
points_.clear();
|
|
}
|
|
|
|
// Calculate the coordinates of the mouse
|
|
ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
|
|
mouse_pos_in_canvas_ = paint_pos;
|
|
|
|
// Clamp the size to a grid
|
|
paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_);
|
|
paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_);
|
|
|
|
points_.push_back(paint_pos);
|
|
points_.push_back(
|
|
ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
|
|
|
|
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 (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;
|
|
}
|
|
|
|
void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap,
|
|
ImVec4 color) {
|
|
const ImVec2 position = drawn_tile_pos_;
|
|
int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
|
|
int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
|
|
|
|
ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
|
|
|
|
// Update the bitmap's pixel data based on the start_position and color
|
|
for (int y = 0; y < tile_size; ++y) {
|
|
for (int x = 0; x < tile_size; ++x) {
|
|
// Calculate the actual pixel index in the bitmap
|
|
int pixel_index =
|
|
(start_position.y + y) * bitmap->width() + (start_position.x + x);
|
|
|
|
// Write the color to the pixel
|
|
bitmap->WriteColor(pixel_index, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Canvas::DrawTileSelector(int size, int size_y) {
|
|
const ImGuiIO &io = GetIO();
|
|
const bool is_hovered = IsItemHovered();
|
|
is_hovered_ = is_hovered;
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
if (size_y == 0) {
|
|
size_y = size;
|
|
}
|
|
|
|
if (is_hovered && IsMouseClicked(ImGuiMouseButton_Left)) {
|
|
if (!points_.empty()) {
|
|
points_.clear();
|
|
}
|
|
ImVec2 painter_pos = AlignPosToGrid(mouse_pos, size);
|
|
|
|
points_.push_back(painter_pos);
|
|
points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
|
|
mouse_pos_in_canvas_ = painter_pos;
|
|
}
|
|
|
|
if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
|
|
gfx::ScopedTimer timer("canvas_select_rect");
|
|
|
|
const ImGuiIO &io = GetIO();
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
static ImVec2 drag_start_pos;
|
|
const float scaled_size = tile_size * scale;
|
|
static bool dragging = false;
|
|
constexpr int small_map_size = 0x200;
|
|
|
|
// Only handle mouse events if the canvas is hovered
|
|
const bool is_hovered = IsItemHovered();
|
|
if (!is_hovered) {
|
|
return;
|
|
}
|
|
|
|
// Calculate superX and superY accounting for world offset
|
|
int superY, superX;
|
|
if (current_map < 0x40) {
|
|
// Light World
|
|
superY = current_map / 8;
|
|
superX = current_map % 8;
|
|
} else if (current_map < 0x80) {
|
|
// Dark World
|
|
superY = (current_map - 0x40) / 8;
|
|
superX = (current_map - 0x40) % 8;
|
|
} else {
|
|
// Special World
|
|
superY = (current_map - 0x80) / 8;
|
|
superX = (current_map - 0x80) % 8;
|
|
}
|
|
|
|
// Handle right click for single tile selection
|
|
if (IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
ImVec2 painter_pos = AlignPosToGrid(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 = superX * 0x20 + tile16_x;
|
|
int index_y = superY * 0x20 + tile16_y;
|
|
selected_tile_pos_ = ImVec2(index_x, index_y);
|
|
selected_points_.clear();
|
|
select_rect_active_ = false;
|
|
|
|
// Start drag position for rectangle selection
|
|
drag_start_pos = AlignPosToGrid(mouse_pos, scaled_size);
|
|
}
|
|
|
|
// Calculate the rectangle's top-left and bottom-right corners
|
|
ImVec2 drag_end_pos = AlignPosToGrid(mouse_pos, scaled_size);
|
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
|
|
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, kWhiteColor);
|
|
dragging = true;
|
|
}
|
|
|
|
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
|
// Release dragging mode
|
|
dragging = false;
|
|
|
|
// Calculate the bounds of the rectangle in terms of 16x16 tile indices
|
|
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;
|
|
|
|
// Swap the start and end positions if they are in the wrong order
|
|
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));
|
|
|
|
// Number of tiles per local map (since each tile is 16x16)
|
|
constexpr int tiles_per_local_map = small_map_size / 16;
|
|
|
|
// Loop through the tiles in the rectangle and store their positions
|
|
for (int y = start_y; y <= end_y; y += tile16_size) {
|
|
for (int x = start_x; x <= end_x; x += tile16_size) {
|
|
// Determine which local map (512x512) the tile is in
|
|
int local_map_x = x / small_map_size;
|
|
int local_map_y = y / small_map_size;
|
|
|
|
// Calculate the tile's position within its local map
|
|
int tile16_x = (x % small_map_size) / tile16_size;
|
|
int tile16_y = (y % small_map_size) / tile16_size;
|
|
|
|
// Calculate the index within the overall map structure
|
|
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);
|
|
}
|
|
}
|
|
// Clear and add the calculated rectangle points
|
|
selected_points_.clear();
|
|
selected_points_.push_back(drag_start_pos);
|
|
selected_points_.push_back(drag_end_pos);
|
|
select_rect_active_ = true;
|
|
}
|
|
}
|
|
|
|
void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) {
|
|
if (!bitmap.is_active()) {
|
|
return;
|
|
}
|
|
bitmap_ = &bitmap;
|
|
|
|
// Update content size for table integration
|
|
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
|
|
|
|
draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
|
|
ImVec2(canvas_p0_.x, canvas_p0_.y),
|
|
ImVec2(canvas_p0_.x + (bitmap.width() * scale),
|
|
canvas_p0_.y + (bitmap.height() * scale)));
|
|
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
|
|
}
|
|
|
|
void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale,
|
|
int alpha) {
|
|
if (!bitmap.is_active()) {
|
|
return;
|
|
}
|
|
bitmap_ = &bitmap;
|
|
|
|
// Update content size for table integration
|
|
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
|
|
|
|
// Calculate the actual rendered size including scale and offsets
|
|
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
|
|
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
|
|
|
|
draw_list_->AddImage(
|
|
(ImTextureID)(intptr_t)bitmap.texture(),
|
|
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
|
|
canvas_p0_.y + y_offset + scrolling_.y),
|
|
ImVec2(
|
|
canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x,
|
|
canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y),
|
|
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
|
|
|
|
// Note: Content size for child windows should be set before BeginChild, not here
|
|
}
|
|
|
|
void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
|
|
ImVec2 src_pos, ImVec2 src_size) {
|
|
if (!bitmap.is_active()) {
|
|
return;
|
|
}
|
|
bitmap_ = &bitmap;
|
|
|
|
// Update content size for table integration
|
|
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
|
|
|
|
draw_list_->AddImage(
|
|
(ImTextureID)(intptr_t)bitmap.texture(),
|
|
ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
|
|
ImVec2(canvas_p0_.x + dest_pos.x + dest_size.x,
|
|
canvas_p0_.y + dest_pos.y + dest_size.y),
|
|
ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
|
|
ImVec2((src_pos.x + src_size.x) / bitmap.width(),
|
|
(src_pos.y + src_size.y) / bitmap.height()));
|
|
}
|
|
|
|
// TODO: Add parameters for sizing and positioning
|
|
void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) {
|
|
for (const auto &[key, value] : gfx_bin) {
|
|
int offset = 0x40 * (key + 1);
|
|
int top_left_y = canvas_p0_.y + 2;
|
|
if (key >= 1) {
|
|
top_left_y = canvas_p0_.y + 0x40 * key;
|
|
}
|
|
draw_list_->AddImage((ImTextureID)(intptr_t)value.texture(),
|
|
ImVec2(canvas_p0_.x + 2, top_left_y),
|
|
ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset));
|
|
}
|
|
}
|
|
|
|
void Canvas::DrawOutline(int x, int y, int w, int h) {
|
|
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200));
|
|
}
|
|
|
|
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
|
|
CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
|
|
}
|
|
|
|
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
|
|
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
|
|
}
|
|
|
|
void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
|
|
int tile_size, float scale) {
|
|
if (selected_points_.size() != 2) {
|
|
// points_ should contain exactly two points
|
|
return;
|
|
}
|
|
if (group.empty()) {
|
|
// group should not be empty
|
|
return;
|
|
}
|
|
|
|
// OPTIMIZATION: Use optimized rendering for large groups to improve performance
|
|
bool use_optimized_rendering = group.size() > 16; // Optimize for large selections
|
|
|
|
// Pre-calculate common values to avoid repeated computation
|
|
const float tile_scale = tile_size * scale;
|
|
const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
|
|
|
|
// Top-left and bottom-right corners of the rectangle
|
|
ImVec2 rect_top_left = selected_points_[0];
|
|
ImVec2 rect_bottom_right = selected_points_[1];
|
|
|
|
// Calculate the start and end tiles in the grid
|
|
int start_tile_x =
|
|
static_cast<int>(std::floor(rect_top_left.x / (tile_size * scale)));
|
|
int start_tile_y =
|
|
static_cast<int>(std::floor(rect_top_left.y / (tile_size * scale)));
|
|
int end_tile_x =
|
|
static_cast<int>(std::floor(rect_bottom_right.x / (tile_size * scale)));
|
|
int end_tile_y =
|
|
static_cast<int>(std::floor(rect_bottom_right.y / (tile_size * scale)));
|
|
|
|
if (start_tile_x > end_tile_x) std::swap(start_tile_x, end_tile_x);
|
|
if (start_tile_y > end_tile_y) std::swap(start_tile_y, end_tile_y);
|
|
|
|
// Calculate the size of the rectangle in 16x16 grid form
|
|
int rect_width = (end_tile_x - start_tile_x) * tile_size;
|
|
int rect_height = (end_tile_y - start_tile_y) * tile_size;
|
|
|
|
int tiles_per_row = rect_width / tile_size;
|
|
int tiles_per_col = rect_height / tile_size;
|
|
|
|
int i = 0;
|
|
for (int y = 0; y < tiles_per_col + 1; ++y) {
|
|
for (int x = 0; x < tiles_per_row + 1; ++x) {
|
|
// Check bounds to prevent access violations
|
|
if (i >= static_cast<int>(group.size())) {
|
|
break;
|
|
}
|
|
|
|
int tile_id = group[i];
|
|
|
|
// Check if tile_id is within the range of tile16_individual_
|
|
auto tilemap_size =
|
|
tilemap.atlas.width() * tilemap.atlas.height() / tilemap.map_size.x;
|
|
if (tile_id >= 0 && tile_id < tilemap_size) {
|
|
// Calculate the position of the tile within the rectangle
|
|
int tile_pos_x = (x + start_tile_x) * tile_size * scale;
|
|
int tile_pos_y = (y + start_tile_y) * tile_size * scale;
|
|
|
|
// OPTIMIZATION: Use pre-calculated values for better performance with large selections
|
|
if (tilemap.atlas.is_active() && tilemap.atlas.texture() && atlas_tiles_per_row > 0) {
|
|
int atlas_tile_x = (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
|
|
int atlas_tile_y = (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
|
|
|
|
// Simple bounds check
|
|
if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
|
|
atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
|
|
|
|
// Calculate UV coordinates once for efficiency
|
|
const float atlas_width = static_cast<float>(tilemap.atlas.width());
|
|
const float atlas_height = static_cast<float>(tilemap.atlas.height());
|
|
ImVec2 uv0 = ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
|
|
ImVec2 uv1 = ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
|
|
(atlas_tile_y + tilemap.tile_size.y) / atlas_height);
|
|
|
|
// Calculate screen positions
|
|
float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x;
|
|
float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y;
|
|
float screen_w = tilemap.tile_size.x * scale;
|
|
float screen_h = tilemap.tile_size.y * scale;
|
|
|
|
// Use higher alpha for large selections to make them more visible
|
|
uint32_t alpha_color = use_optimized_rendering ?
|
|
IM_COL32(255, 255, 255, 200) : IM_COL32(255, 255, 255, 150);
|
|
|
|
// Draw from atlas texture with optimized parameters
|
|
draw_list_->AddImage(
|
|
(ImTextureID)(intptr_t)tilemap.atlas.texture(),
|
|
ImVec2(screen_x, screen_y),
|
|
ImVec2(screen_x + screen_w, screen_y + screen_h),
|
|
uv0, uv1, alpha_color);
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
// Break outer loop if we've run out of tiles
|
|
if (i >= static_cast<int>(group.size())) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Performance optimization completed - tiles are now rendered with pre-calculated values
|
|
|
|
const ImGuiIO &io = GetIO();
|
|
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
|
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
|
auto new_start_pos = AlignPosToGrid(mouse_pos, tile_size * scale);
|
|
selected_points_.clear();
|
|
selected_points_.push_back(new_start_pos);
|
|
selected_points_.push_back(
|
|
ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height));
|
|
select_rect_active_ = true;
|
|
}
|
|
|
|
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
|
|
CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale);
|
|
}
|
|
|
|
void Canvas::DrawText(std::string text, int x, int y) {
|
|
CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale);
|
|
}
|
|
|
|
void Canvas::DrawGridLines(float grid_step) {
|
|
CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale);
|
|
}
|
|
|
|
void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
|
|
// Draw grid + all lines in the canvas
|
|
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
|
|
if (enable_grid_) {
|
|
if (custom_step_ != 0.f) grid_step = custom_step_;
|
|
grid_step *= global_scale_; // Apply global scale to grid step
|
|
|
|
DrawGridLines(grid_step);
|
|
DrawCustomHighlight(grid_step);
|
|
|
|
if (!enable_custom_labels_) {
|
|
return;
|
|
}
|
|
|
|
// Draw the contents of labels on the grid
|
|
for (float x = fmodf(scrolling_.x, grid_step);
|
|
x < canvas_sz_.x * global_scale_; x += grid_step) {
|
|
for (float y = fmodf(scrolling_.y, grid_step);
|
|
y < canvas_sz_.y * global_scale_; y += grid_step) {
|
|
int tile_x = (x - scrolling_.x) / grid_step;
|
|
int tile_y = (y - scrolling_.y) / grid_step;
|
|
int tile_id = tile_x + (tile_y * tile_id_offset);
|
|
|
|
if (tile_id >= labels_[label_id].size()) {
|
|
break;
|
|
}
|
|
std::string label = labels_[label_id][tile_id];
|
|
draw_list_->AddText(
|
|
ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
|
|
canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
|
|
kWhiteColor, label.data());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::DrawCustomHighlight(float grid_step) {
|
|
CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step);
|
|
}
|
|
|
|
void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
|
|
if (config_.grid_step != 0.f) grid_step = config_.grid_step;
|
|
|
|
// Create render context for utilities
|
|
CanvasUtils::CanvasRenderContext ctx = {
|
|
.draw_list = draw_list_,
|
|
.canvas_p0 = canvas_p0_,
|
|
.canvas_p1 = canvas_p1_,
|
|
.scrolling = scrolling_,
|
|
.global_scale = config_.global_scale,
|
|
.enable_grid = config_.enable_grid,
|
|
.enable_hex_labels = config_.enable_hex_labels,
|
|
.grid_step = grid_step
|
|
};
|
|
|
|
// Use high-level utility function
|
|
CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
|
|
|
|
// Draw custom labels if enabled
|
|
if (config_.enable_custom_labels) {
|
|
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
|
|
CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset);
|
|
draw_list_->PopClipRect();
|
|
}
|
|
}
|
|
|
|
void Canvas::DrawOverlay() {
|
|
// Create render context for utilities
|
|
CanvasUtils::CanvasRenderContext ctx = {
|
|
.draw_list = draw_list_,
|
|
.canvas_p0 = canvas_p0_,
|
|
.canvas_p1 = canvas_p1_,
|
|
.scrolling = scrolling_,
|
|
.global_scale = config_.global_scale,
|
|
.enable_grid = config_.enable_grid,
|
|
.enable_hex_labels = config_.enable_hex_labels,
|
|
.grid_step = config_.grid_step
|
|
};
|
|
|
|
// Use high-level utility function
|
|
CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
|
|
}
|
|
|
|
void Canvas::DrawLayeredElements() {
|
|
// Based on ImGui demo, should be adapted to use for OAM
|
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
|
{
|
|
Text("Blue shape is drawn first: appears in back");
|
|
Text("Red shape is drawn after: appears in front");
|
|
ImVec2 p0 = ImGui::GetCursorScreenPos();
|
|
draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
|
|
IM_COL32(0, 0, 255, 255)); // Blue
|
|
draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
|
|
ImVec2(p0.x + 75, p0.y + 75),
|
|
IM_COL32(255, 0, 0, 255)); // Red
|
|
ImGui::Dummy(ImVec2(75, 75));
|
|
}
|
|
ImGui::Separator();
|
|
{
|
|
Text("Blue shape is drawn first, into channel 1: appears in front");
|
|
Text("Red shape is drawn after, into channel 0: appears in back");
|
|
ImVec2 p1 = ImGui::GetCursorScreenPos();
|
|
|
|
// Create 2 channels and draw a Blue shape THEN a Red shape.
|
|
// You can create any number of channels. Tables API use 1 channel per
|
|
// column in order to better batch draw calls.
|
|
draw_list->ChannelsSplit(2);
|
|
draw_list->ChannelsSetCurrent(1);
|
|
draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
|
|
IM_COL32(0, 0, 255, 255)); // Blue
|
|
draw_list->ChannelsSetCurrent(0);
|
|
draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
|
|
ImVec2(p1.x + 75, p1.y + 75),
|
|
IM_COL32(255, 0, 0, 255)); // Red
|
|
|
|
// Flatten/reorder channels. Red shape is in channel 0 and it appears
|
|
// below the Blue shape in channel 1. This works by copying draw indices
|
|
// only (vertices are not copied).
|
|
draw_list->ChannelsMerge();
|
|
ImGui::Dummy(ImVec2(75, 75));
|
|
Text("After reordering, contents of channel 0 appears below channel 1.");
|
|
}
|
|
}
|
|
|
|
void BeginCanvas(Canvas &canvas, ImVec2 child_size) {
|
|
gui::BeginPadding(1);
|
|
|
|
// Use improved canvas sizing for table integration
|
|
ImVec2 effective_size = child_size;
|
|
if (child_size.x == 0 && child_size.y == 0) {
|
|
// Auto-size based on canvas configuration
|
|
if (canvas.IsAutoResize()) {
|
|
effective_size = canvas.GetPreferredSize();
|
|
} else {
|
|
effective_size = canvas.GetCurrentSize();
|
|
}
|
|
}
|
|
|
|
ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
|
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
canvas.DrawBackground();
|
|
gui::EndPadding();
|
|
canvas.DrawContextMenu();
|
|
}
|
|
|
|
void EndCanvas(Canvas &canvas) {
|
|
canvas.DrawGrid();
|
|
canvas.DrawOverlay();
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
|
|
int num_sheets_to_load, int canvas_id,
|
|
bool is_loaded, gfx::BitmapTable &graphics_bin) {
|
|
gui::Canvas canvas;
|
|
if (ImGuiID child_id =
|
|
ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
|
|
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
|
canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
|
|
canvas.DrawContextMenu();
|
|
if (is_loaded) {
|
|
for (const auto &[key, value] : graphics_bin) {
|
|
int offset = height * (key + 1);
|
|
int top_left_y = canvas.zero_point().y + 2;
|
|
if (key >= 1) {
|
|
top_left_y = canvas.zero_point().y + height * key;
|
|
}
|
|
canvas.draw_list()->AddImage(
|
|
(ImTextureID)(intptr_t)value.texture(),
|
|
ImVec2(canvas.zero_point().x + 2, top_left_y),
|
|
ImVec2(canvas.zero_point().x + 0x100,
|
|
canvas.zero_point().y + offset));
|
|
}
|
|
}
|
|
canvas.DrawTileSelector(tile_size);
|
|
canvas.DrawGrid(tile_size);
|
|
canvas.DrawOverlay();
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
|
|
int height, int tile_size, bool is_loaded,
|
|
bool scrollbar, int canvas_id) {
|
|
auto draw_canvas = [&](gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
|
|
int height, int tile_size, bool is_loaded) {
|
|
canvas.DrawBackground(ImVec2(width + 1, height + 1));
|
|
canvas.DrawContextMenu();
|
|
canvas.DrawBitmap(bitmap, 2, is_loaded);
|
|
canvas.DrawTileSelector(tile_size);
|
|
canvas.DrawGrid(tile_size);
|
|
canvas.DrawOverlay();
|
|
};
|
|
|
|
if (scrollbar) {
|
|
if (ImGuiID child_id =
|
|
ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
|
|
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
|
draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
|
|
}
|
|
ImGui::EndChild();
|
|
} else {
|
|
draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
|
|
}
|
|
}
|
|
|
|
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
|
|
const std::string& label, bool auto_resize) {
|
|
// Configure canvas for table integration
|
|
canvas.SetAutoResize(auto_resize);
|
|
|
|
if (auto_resize && bitmap.is_active()) {
|
|
// Auto-calculate size based on bitmap content
|
|
ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height());
|
|
ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale());
|
|
canvas.SetCanvasSize(preferred_size);
|
|
}
|
|
|
|
// Begin table-aware canvas
|
|
if (canvas.BeginTableCanvas(label)) {
|
|
// Draw the canvas content
|
|
canvas.DrawBackground();
|
|
canvas.DrawContextMenu();
|
|
|
|
if (bitmap.is_active()) {
|
|
canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale());
|
|
}
|
|
|
|
canvas.DrawGrid();
|
|
canvas.DrawOverlay();
|
|
}
|
|
canvas.EndTableCanvas();
|
|
}
|
|
|
|
void Canvas::ShowAdvancedCanvasProperties() {
|
|
if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
ImGui::Text("Advanced Canvas Configuration");
|
|
ImGui::Separator();
|
|
|
|
// Canvas properties (read-only info)
|
|
ImGui::Text("Canvas Properties");
|
|
ImGui::Text("ID: %s", canvas_id_.c_str());
|
|
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
|
|
ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y);
|
|
ImGui::Text("Global Scale: %.3f", config_.global_scale);
|
|
ImGui::Text("Grid Step: %.1f", config_.grid_step);
|
|
|
|
if (config_.content_size.x > 0 && config_.content_size.y > 0) {
|
|
ImVec2 min_size = GetMinimumSize();
|
|
ImVec2 preferred_size = GetPreferredSize();
|
|
ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
|
|
ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y);
|
|
}
|
|
|
|
// Editable properties using new config system
|
|
ImGui::Separator();
|
|
ImGui::Text("View Settings");
|
|
if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) {
|
|
enable_grid_ = config_.enable_grid; // Legacy sync
|
|
}
|
|
if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) {
|
|
enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync
|
|
}
|
|
if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) {
|
|
enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync
|
|
}
|
|
if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) {
|
|
enable_context_menu_ = config_.enable_context_menu; // Legacy sync
|
|
}
|
|
if (ImGui::Checkbox("Draggable", &config_.is_draggable)) {
|
|
draggable_ = config_.is_draggable; // Legacy sync
|
|
}
|
|
if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) {
|
|
// Auto resize setting changed
|
|
}
|
|
|
|
// Grid controls
|
|
ImGui::Separator();
|
|
ImGui::Text("Grid Configuration");
|
|
if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
|
|
custom_step_ = config_.grid_step; // Legacy sync
|
|
}
|
|
|
|
// Scale controls
|
|
ImGui::Separator();
|
|
ImGui::Text("Scale Configuration");
|
|
if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
|
|
global_scale_ = config_.global_scale; // Legacy sync
|
|
}
|
|
|
|
// Scrolling controls
|
|
ImGui::Separator();
|
|
ImGui::Text("Scrolling Configuration");
|
|
ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y);
|
|
if (ImGui::Button("Reset Scroll")) {
|
|
scrolling_ = ImVec2(0, 0);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Center View")) {
|
|
if (bitmap_) {
|
|
scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f,
|
|
-(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f);
|
|
}
|
|
}
|
|
|
|
if (ImGui::Button("Close")) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
// Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor
|
|
|
|
void Canvas::ShowScalingControls() {
|
|
if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
ImGui::Text("Canvas Scaling and Display Controls");
|
|
ImGui::Separator();
|
|
|
|
// Global scale with new config system
|
|
ImGui::Text("Global Scale: %.3f", config_.global_scale);
|
|
if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
|
|
global_scale_ = config_.global_scale; // Legacy sync
|
|
}
|
|
|
|
// Preset scale buttons
|
|
ImGui::Text("Preset Scales:");
|
|
if (ImGui::Button("0.25x")) {
|
|
config_.global_scale = 0.25f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("0.5x")) {
|
|
config_.global_scale = 0.5f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("1x")) {
|
|
config_.global_scale = 1.0f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("2x")) {
|
|
config_.global_scale = 2.0f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("4x")) {
|
|
config_.global_scale = 4.0f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("8x")) {
|
|
config_.global_scale = 8.0f;
|
|
global_scale_ = config_.global_scale;
|
|
}
|
|
|
|
// Grid configuration
|
|
ImGui::Separator();
|
|
ImGui::Text("Grid Configuration");
|
|
ImGui::Text("Grid Step: %.1f", config_.grid_step);
|
|
if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
|
|
custom_step_ = config_.grid_step; // Legacy sync
|
|
}
|
|
|
|
// Grid size presets
|
|
ImGui::Text("Grid Presets:");
|
|
if (ImGui::Button("8x8")) {
|
|
config_.grid_step = 8.0f;
|
|
custom_step_ = config_.grid_step;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("16x16")) {
|
|
config_.grid_step = 16.0f;
|
|
custom_step_ = config_.grid_step;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("32x32")) {
|
|
config_.grid_step = 32.0f;
|
|
custom_step_ = config_.grid_step;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("64x64")) {
|
|
config_.grid_step = 64.0f;
|
|
custom_step_ = config_.grid_step;
|
|
}
|
|
|
|
// Canvas size info
|
|
ImGui::Separator();
|
|
ImGui::Text("Canvas Information");
|
|
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
|
|
ImGui::Text("Scaled Size: %.0f x %.0f",
|
|
config_.canvas_size.x * config_.global_scale,
|
|
config_.canvas_size.y * config_.global_scale);
|
|
if (bitmap_) {
|
|
ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height());
|
|
ImGui::Text("Effective Scale: %.3f x %.3f",
|
|
(config_.canvas_size.x * config_.global_scale) / bitmap_->width(),
|
|
(config_.canvas_size.y * config_.global_scale) / bitmap_->height());
|
|
}
|
|
|
|
if (ImGui::Button("Close")) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
// Old ROM palette management methods removed - now handled by EnhancedPaletteEditor
|
|
|
|
} // namespace yaze::gui
|