13#include "imgui/imgui.h"
27 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
32 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
41 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
51 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
66 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
72 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
81 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
91 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
129 extensions_ = std::make_unique<CanvasExtensions>();
134using ImGui::GetContentRegionAvail;
135using ImGui::GetCursorScreenPos;
137using ImGui::GetWindowDrawList;
138using ImGui::IsItemActive;
139using ImGui::IsItemHovered;
140using ImGui::IsMouseClicked;
141using ImGui::IsMouseDragging;
148 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
152 return ImVec2(std::floor(pos.x / scale) * scale,
153 std::floor(pos.y / scale) * scale);
268 ImGui::OpenPopup(
"Canvas Usage Report");
269 if (ImGui::BeginPopupModal(
"Canvas Usage Report",
nullptr,
270 ImGuiWindowFlags_AlwaysAutoResize)) {
271 ImGui::Text(
"Canvas Usage Report");
273 ImGui::TextWrapped(
"%s", report.c_str());
275 if (ImGui::Button(
"Close")) {
276 ImGui::CloseCurrentPopup();
286 ext.InitializePaletteEditor();
287 if (ext.palette_editor) {
288 ext.palette_editor->Initialize(
rom);
302 ext.InitializePaletteEditor();
303 if (ext.palette_editor) {
305 ext.palette_editor->ShowPaletteEditor(*mutable_palette,
306 "Canvas Palette Editor");
314 ext.InitializePaletteEditor();
315 if (ext.palette_editor) {
316 ext.palette_editor->ShowColorAnalysis(*
bitmap_,
"Canvas Color Analysis");
352 std::string child_id =
canvas_id_ +
"_TableChild";
357 ImGui::BeginChild(child_id.c_str(), child_size,
359 ImGuiWindowFlags_NoScrollbar);
361 if (!label.empty()) {
362 ImGui::Text(
"%s", label.c_str());
376 if (child_size.x <= 0 || child_size.y <= 0) {
386 std::string child_id =
canvas_id_ +
"_TableChild";
387 ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoScrollbar;
389 child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
391 ImGui::BeginChild(child_id.c_str(), child_size,
true, child_flags);
393 if (!label.empty()) {
394 ImGui::Text(
"%s", label.c_str());
439 return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) &&
447 return ImVec2(-1, -1);
476 if (effective_size.x == 0 && effective_size.y == 0) {
484 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
486 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
488 ImGui::BeginChild(
canvas_id().c_str(), effective_size,
true, child_flags);
524 const std::function<
void()>& event,
525 int tile_size,
float scale) {
574 if (IsItemHovered()) {
575 const ImGuiIO& io = GetIO();
587 const ImGuiIO& io = GetIO();
588 const bool is_active = IsItemActive();
591 if (
const float mouse_threshold_for_pan =
594 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
603 const ImGuiIO& io = GetIO();
608 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
691 ext.InitializeModals();
702 ext.modals->ShowAdvancedProperties(
canvas_id_, modal_config,
710 ext.InitializeModals();
745 auto popup_callback = [
this](
const std::string& id,
746 std::function<void()> callback) {
772 last_section.items.push_back(item);
781 std::function<
void()> render_callback) {
800 ImVec2 available = ImGui::GetContentRegionAvail();
801 float scale_x = available.x / bitmap.
width();
802 float scale_y = available.y / bitmap.
height();
853 const ImGuiIO& io = GetIO();
854 const bool is_hovered = IsItemHovered();
858 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
859 const auto scaled_size = size * scale;
873 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
876 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
878 points_.push_back(paint_pos_end);
882 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
883 ImVec2(origin.x + paint_pos.x + scaled_size,
884 origin.y + paint_pos.y + scaled_size));
887 if (IsMouseClicked(ImGuiMouseButton_Left) &&
888 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
915 const ImGuiIO& io = GetIO();
918 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
920 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
926 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
937 const ImGuiIO& io = GetIO();
938 const bool is_hovered = IsItemHovered();
942 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
944 static bool is_dragging =
false;
945 static ImVec2 start_drag_pos;
959 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
968 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
971 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
972 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
973 origin.y + paint_pos.y + scaled_tile_size),
974 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
976 if (IsMouseClicked(ImGuiMouseButton_Left)) {
978 start_drag_pos = paint_pos;
981 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
993 int tile_index_x =
static_cast<int>(position.x /
global_scale_) / tile_size;
994 int tile_index_y =
static_cast<int>(position.y /
global_scale_) / tile_size;
996 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
999 for (
int y = 0; y < tile_size; ++y) {
1000 for (
int x = 0; x < tile_size; ++x) {
1003 (start_position.y + y) * bitmap->
width() + (start_position.x + x);
1021 ImVec2 selected_pos;
1025 if (
is_hovered_ && IsMouseClicked(ImGuiMouseButton_Left)) {
1026 const ImGuiIO& io = GetIO();
1029 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1030 ImVec2 painter_pos = AlignPosToGrid(mouse_pos,
static_cast<float>(size));
1033 points_.push_back(painter_pos);
1034 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
1038 return double_clicked;
1107 ImVec2 src_pos, ImVec2 src_size) {
1123 for (
const auto& [key, value] : gfx_bin) {
1125 if (!value || !value->is_active() || !value->texture()) {
1128 int offset = 0x40 * (key + 1);
1133 draw_list_->AddImage((ImTextureID)(intptr_t)value->texture(),
1141 IM_COL32(255, 255, 255, 200));
1155 int tile_size,
float ,
int local_map_size,
1156 ImVec2 total_map_size) {
1161 if (group.empty()) {
1173 bool use_optimized_rendering =
1177 const int small_map = local_map_size;
1178 const float large_map_width = total_map_size.x;
1179 const float large_map_height = total_map_size.y;
1182 const float tile_scale = tile_size * effective_scale;
1192 static_cast<int>(std::floor(rect_top_left.x / tile_size));
1194 static_cast<int>(std::floor(rect_top_left.y / tile_size));
1196 static_cast<int>(std::floor(rect_bottom_right.x / tile_size));
1198 static_cast<int>(std::floor(rect_bottom_right.y / tile_size));
1200 if (start_tile_x > end_tile_x)
1201 std::swap(start_tile_x, end_tile_x);
1202 if (start_tile_y > end_tile_y)
1203 std::swap(start_tile_y, end_tile_y);
1206 int rect_width = (end_tile_x - start_tile_x) * tile_size;
1207 int rect_height = (end_tile_y - start_tile_y) * tile_size;
1209 int tiles_per_row = rect_width / tile_size;
1210 int tiles_per_col = rect_height / tile_size;
1213 for (
int y = 0; y < tiles_per_col + 1; ++y) {
1214 for (
int x = 0; x < tiles_per_row + 1; ++x) {
1216 if (i >=
static_cast<int>(group.size())) {
1220 int tile_id = group[i];
1224 if (tile_id >= 0 && tile_id < tilemap_size) {
1226 int tile_pos_x = (x + start_tile_x) * tile_size * effective_scale;
1227 int tile_pos_y = (y + start_tile_y) * tile_size * effective_scale;
1232 atlas_tiles_per_row > 0) {
1234 (tile_id % atlas_tiles_per_row) * tilemap.
tile_size.
x;
1236 (tile_id / atlas_tiles_per_row) * tilemap.
tile_size.
y;
1239 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.
atlas.
width() &&
1240 atlas_tile_y >= 0 && atlas_tile_y < tilemap.
atlas.
height()) {
1242 const float atlas_width =
static_cast<float>(tilemap.
atlas.
width());
1243 const float atlas_height =
1246 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
1248 ImVec2((atlas_tile_x + tilemap.
tile_size.
x) / atlas_width,
1249 (atlas_tile_y + tilemap.
tile_size.
y) / atlas_height);
1254 float screen_w = tilemap.
tile_size.
x * effective_scale;
1255 float screen_h = tilemap.
tile_size.
y * effective_scale;
1258 uint32_t alpha_color = use_optimized_rendering
1259 ? IM_COL32(255, 255, 255, 200)
1260 : IM_COL32(255, 255, 255, 150);
1265 ImVec2(screen_x, screen_y),
1266 ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1,
1274 if (i >=
static_cast<int>(group.size())) {
1284 const ImGuiIO& io = GetIO();
1286 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1291 ImVec2 clamped_mouse_pos = mouse_pos;
1295 int mouse_local_map_x =
static_cast<int>(mouse_pos.x) / small_map;
1296 int mouse_local_map_y =
static_cast<int>(mouse_pos.y) / small_map;
1300 float potential_end_x = mouse_pos.x + rect_width;
1301 float potential_end_y = mouse_pos.y + rect_height;
1304 int potential_end_map_x =
static_cast<int>(potential_end_x) / small_map;
1305 int potential_end_map_y =
static_cast<int>(potential_end_y) / small_map;
1308 if (potential_end_map_x != mouse_local_map_x) {
1310 float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width;
1311 clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x);
1314 if (potential_end_map_y != mouse_local_map_y) {
1316 float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height;
1317 clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y);
1322 auto new_start_pos_screen = AlignPosToGrid(clamped_mouse_pos, tile_size * effective_scale);
1325 ImVec2 new_start_pos_world(new_start_pos_screen.x / effective_scale,
1326 new_start_pos_screen.y / effective_scale);
1329 new_start_pos_world.x =
1330 std::clamp(new_start_pos_world.x, 0.0f, large_map_width - rect_width);
1331 new_start_pos_world.y =
1332 std::clamp(new_start_pos_world.y, 0.0f, large_map_height - rect_height);
1337 ImVec2(new_start_pos_world.x + rect_width, new_start_pos_world.y + rect_height));
1372 for (
float x = fmodf(
scrolling_.x, grid_step);
1374 for (
float y = fmodf(
scrolling_.y, grid_step);
1378 int tile_id = tile_x + (tile_y * tile_id_offset);
1380 if (tile_id >=
labels_[label_id].size()) {
1383 std::string label =
labels_[label_id][tile_id];
1385 ImVec2(
canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
1386 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
1411 .grid_step = grid_step};
1447 ImDrawList*
draw_list = ImGui::GetWindowDrawList();
1449 Text(
"Blue shape is drawn first: appears in back");
1450 Text(
"Red shape is drawn after: appears in front");
1451 ImVec2 p0 = ImGui::GetCursorScreenPos();
1452 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
1453 IM_COL32(0, 0, 255, 255));
1454 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
1455 ImVec2(p0.x + 75, p0.y + 75),
1456 IM_COL32(255, 0, 0, 255));
1457 ImGui::Dummy(ImVec2(75, 75));
1461 Text(
"Blue shape is drawn first, into channel 1: appears in front");
1462 Text(
"Red shape is drawn after, into channel 0: appears in back");
1463 ImVec2 p1 = ImGui::GetCursorScreenPos();
1470 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
1471 IM_COL32(0, 0, 255, 255));
1473 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
1474 ImVec2(p1.x + 75, p1.y + 75),
1475 IM_COL32(255, 0, 0, 255));
1481 ImGui::Dummy(ImVec2(75, 75));
1482 Text(
"After reordering, contents of channel 0 appears below channel 1.");
1490 ImVec2 effective_size = child_size;
1491 if (child_size.x == 0 && child_size.y == 0) {
1502 ImGui::BeginChild(canvas.
canvas_id().c_str(), effective_size,
true,
1503 ImGuiWindowFlags_NoScrollbar);
1523 if (effective_size.x == 0 && effective_size.y == 0) {
1531 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
1533 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
1535 ImGui::BeginChild(canvas.
canvas_id().c_str(), effective_size,
true,
1589 result.
scale = 1.0f;
1590 result.
scroll = ImVec2(0, 0);
1592 if (content_px.x <= 0 || content_px.y <= 0) {
1597 float available_x = canvas_px.x - padding_px * 2;
1598 float available_y = canvas_px.y - padding_px * 2;
1600 if (available_x <= 0 || available_y <= 0) {
1605 float scale_x = available_x / content_px.x;
1606 float scale_y = available_y / content_px.y;
1607 result.
scale = std::min(scale_x, scale_y);
1610 float scaled_w = content_px.x * result.
scale;
1611 float scaled_h = content_px.y * result.
scale;
1612 result.
scroll.x = (canvas_px.x - scaled_w) / 2.0f;
1613 result.
scroll.y = (canvas_px.y - scaled_h) / 2.0f;
1618ImVec2
ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px) {
1621 float max_scroll_x = std::max(0.0f, content_px.x - canvas_px.x);
1622 float max_scroll_y = std::max(0.0f, content_px.y - canvas_px.y);
1628 std::clamp(scroll.x, -max_scroll_x, 0.0f),
1629 std::clamp(scroll.y, -max_scroll_y, 0.0f));
1633 int num_sheets_to_load,
int canvas_id,
1636 if (ImGuiID child_id =
1637 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1638 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(),
true,
1639 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1640 canvas.
DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
1643 for (
const auto& [key, value] : graphics_bin) {
1645 if (!value || !value->texture()) {
1648 int offset = height * (key + 1);
1651 top_left_y = canvas.
zero_point().y + height * key;
1654 (ImTextureID)(intptr_t)value->texture(),
1655 ImVec2(canvas.
zero_point().x + 2, top_left_y),
1670 int height,
int tile_size,
bool is_loaded,
1671 bool scrollbar,
int canvas_id) {
1673 int height,
int tile_size,
bool is_loaded) {
1685 if (ImGuiID child_id =
1686 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1687 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(),
true,
1688 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1689 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1693 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1698 const std::string& label,
bool auto_resize) {
1702 if (auto_resize && bitmap.
is_active()) {
1704 ImVec2 content_size = ImVec2(bitmap.
width(), bitmap.
height());
1731 ext.InitializeModals();
1762 if (ImGui::BeginPopupModal(
"Advanced Canvas Properties",
nullptr,
1763 ImGuiWindowFlags_AlwaysAutoResize)) {
1764 ImGui::Text(
"Advanced Canvas Configuration");
1768 ImGui::Text(
"Canvas Properties");
1780 ImGui::Text(
"Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
1781 ImGui::Text(
"Preferred Size: %.0f x %.0f", preferred_size.x,
1787 ImGui::Text(
"View Settings");
1794 if (ImGui::Checkbox(
"Enable Custom Labels",
1810 ImGui::Text(
"Grid Configuration");
1818 ImGui::Text(
"Scale Configuration");
1826 ImGui::Text(
"Scrolling Configuration");
1828 if (ImGui::Button(
"Reset Scroll")) {
1832 if (ImGui::Button(
"Center View")) {
1843 if (ImGui::Button(
"Close")) {
1844 ImGui::CloseCurrentPopup();
1855 ext.InitializeModals();
1884 ext.modals->ShowScalingControls(
canvas_id_, modal_config);
1889 if (ImGui::BeginPopupModal(
"Scaling Controls",
nullptr,
1890 ImGuiWindowFlags_AlwaysAutoResize)) {
1891 ImGui::Text(
"Canvas Scaling and Display Controls");
1902 ImGui::Text(
"Preset Scales:");
1903 if (ImGui::Button(
"0.25x")) {
1908 if (ImGui::Button(
"0.5x")) {
1913 if (ImGui::Button(
"1x")) {
1918 if (ImGui::Button(
"2x")) {
1923 if (ImGui::Button(
"4x")) {
1928 if (ImGui::Button(
"8x")) {
1935 ImGui::Text(
"Grid Configuration");
1943 ImGui::Text(
"Grid Presets:");
1944 if (ImGui::Button(
"8x8")) {
1949 if (ImGui::Button(
"16x16")) {
1954 if (ImGui::Button(
"32x32")) {
1959 if (ImGui::Button(
"64x64")) {
1966 ImGui::Text(
"Canvas Information");
1969 ImGui::Text(
"Scaled Size: %.0f x %.0f",
1975 "Effective Scale: %.3f x %.3f",
1980 if (ImGui::Button(
"Close")) {
1981 ImGui::CloseCurrentPopup();
1992 if (
bitmap_ && ext.bpp_format_ui) {
1993 ext.bpp_format_ui->RenderFormatSelector(
2003 if (
bitmap_ && ext.bpp_format_ui) {
2010 if (!ext.bpp_conversion_dialog) {
2011 ext.bpp_conversion_dialog = std::make_unique<gui::BppConversionDialog>(
2015 if (
bitmap_ && ext.bpp_conversion_dialog) {
2016 ext.bpp_conversion_dialog->Show(
2019 ConvertBitmapFormat(format);
2023 if (ext.bpp_conversion_dialog) {
2024 ext.bpp_conversion_dialog->Render();
2033 if (current_format == target_format) {
2050 }
catch (
const std::exception& e) {
2051 SDL_Log(
"Failed to convert bitmap format: %s", e.what());
2067 ext.InitializeAutomation(
this);
2068 return ext.automation_api.get();
2095 int y_offset,
float scale,
int alpha) {
2103 ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) {
2123 if (src_size.x <= 0 || src_size.y <= 0) {
2124 src_size = ImVec2(
static_cast<float>(bitmap.
width()),
2125 static_cast<float>(bitmap.
height()));
2143 ImVec2 src_size = options.
src_size;
2144 if (src_size.x <= 0 || src_size.y <= 0) {
2145 src_size = ImVec2(bitmap.
width(), bitmap.
height());
2169 ImVec2(
static_cast<float>(bmp.
width()),
2170 static_cast<float>(bmp.
height())));
2173 static_cast<int>(opts.
dest_pos.y), 1.0f, 255);
2186 h, color, rt.
scale);
2208 return ImVec2(std::floor(pos.x / scale) * scale,
2209 std::floor(pos.y / scale) * scale);
2214 int current_tile, ImVec2* out_drawn_pos) {
2217 const ImGuiIO& io = ImGui::GetIO();
2220 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2233 ImVec2 paint_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2238 if (tiles_per_row > 0) {
2239 int tile_x = (current_tile % tiles_per_row) * tilemap.
tile_size.
x;
2240 int tile_y = (current_tile / tiles_per_row) * tilemap.
tile_size.
y;
2243 if (tile_x >= 0 && tile_x < tilemap.
atlas.
width() && tile_y >= 0 &&
2246 ImVec2(
static_cast<float>(tile_x) / tilemap.
atlas.
width(),
2247 static_cast<float>(tile_y) / tilemap.
atlas.
height());
2248 ImVec2 uv1 = ImVec2(
static_cast<float>(tile_x + tilemap.
tile_size.
x) /
2250 static_cast<float>(tile_y + tilemap.
tile_size.
y) /
2255 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
2256 ImVec2(origin.x + paint_pos.x + scaled_size,
2257 origin.y + paint_pos.y + scaled_size),
2263 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
2264 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
2265 if (out_drawn_pos) *out_drawn_pos = paint_pos;
2273 ImVec2* out_selected_pos) {
2274 const ImGuiIO& io = ImGui::GetIO();
2277 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2283 if (rt.
hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
2284 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos,
static_cast<float>(size));
2285 if (out_selected_pos) *out_selected_pos = painter_pos;
2289 if (rt.
hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
2300 const ImGuiIO& io = ImGui::GetIO();
2303 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2304 static ImVec2 drag_start_pos;
2305 const float scaled_size = tile_size * scale;
2306 static bool dragging =
false;
2307 constexpr int small_map_size = 0x200;
2308 constexpr uint32_t kWhite = IM_COL32(255, 255, 255, 255);
2316 if (current_map < 0x40) {
2317 superY = current_map / 8;
2318 superX = current_map % 8;
2319 }
else if (current_map < 0x80) {
2320 superY = (current_map - 0x40) / 8;
2321 superX = (current_map - 0x40) % 8;
2323 superY = (current_map - 0x80) / 8;
2324 superX = (current_map - 0x80) % 8;
2328 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
2329 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2331 int world_x =
static_cast<int>(painter_pos.x / scale);
2332 int world_y =
static_cast<int>(painter_pos.y / scale);
2334 auto tile16_x = (world_x % small_map_size) / (small_map_size / 0x20);
2335 auto tile16_y = (world_y % small_map_size) / (small_map_size / 0x20);
2337 int index_x = superX * 0x20 + tile16_x;
2338 int index_y = superY * 0x20 + tile16_y;
2340 static_cast<float>(index_y));
2344 drag_start_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2348 ImVec2 drag_end_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2349 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
2351 ImVec2(origin.x + drag_start_pos.x, origin.y + drag_start_pos.y);
2353 auto end = ImVec2(origin.x + drag_end_pos.x + scaled_size,
2354 origin.y + drag_end_pos.y + scaled_size);
2355 rt.
draw_list->AddRect(start, end, kWhite);
2359 if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
2362 constexpr int tile16_size = 16;
2364 int start_x =
static_cast<int>(std::floor(drag_start_pos.x / scaled_size)) * tile16_size;
2365 int start_y =
static_cast<int>(std::floor(drag_start_pos.y / scaled_size)) * tile16_size;
2366 int end_x =
static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) * tile16_size;
2367 int end_y =
static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) * tile16_size;
2369 if (start_x > end_x) std::swap(start_x, end_x);
2370 if (start_y > end_y) std::swap(start_y, end_y);
2374 static_cast<size_t>(((end_x - start_x) / tile16_size + 1) *
2375 ((end_y - start_y) / tile16_size + 1)));
2377 constexpr int tiles_per_local_map = small_map_size / 16;
2379 for (
int y = start_y; y <= end_y; y += tile16_size) {
2380 for (
int x = start_x; x <= end_x; x += tile16_size) {
2381 int local_map_x = (x / small_map_size) % 8;
2382 int local_map_y = (y / small_map_size) % 8;
2383 int tile16_x = (x % small_map_size) / tile16_size;
2384 int tile16_y = (y % small_map_size) / tile16_size;
2385 int index_x = local_map_x * tiles_per_local_map + tile16_x;
2386 int index_y = local_map_y * tiles_per_local_map + tile16_y;
2387 selection.
selected_tiles.emplace_back(
static_cast<float>(index_x),
2388 static_cast<float>(index_y));
2396 ImVec2(drag_start_pos.x / scale, drag_start_pos.y / scale));
2398 ImVec2(drag_end_pos.x / scale, drag_end_pos.y / scale));
2413 draw_list_->AddImage(texture, screen_pos, screen_end);
2422 draw_list_->AddRectFilled(screen_pos, screen_end, color);
2429 draw_list_->AddText(screen_pos, color, text.c_str());
2437 : canvas_(&canvas), options_(options), active_(true) {
2448 : canvas_(other.canvas_), options_(other.options_), active_(other.active_) {
2449 other.active_ =
false;
2453 if (
this != &other) {
2455 canvas_->
End(options_);
2457 canvas_ = other.canvas_;
2458 options_ = other.options_;
2459 active_ = other.active_;
2460 other.active_ =
false;
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const SnesPalette & palette() const
TextureHandle texture() const
const std::vector< uint8_t > & vector() const
SnesPalette * mutable_palette()
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
void set_data(const std::vector< uint8_t > &data)
SDL_Surface * surface() const
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Defines an abstract interface for all rendering operations.
RAII timer for automatic timing management.
Programmatic interface for controlling canvas operations.
Lightweight RAII guard for existing Canvas instances.
CanvasFrameOptions options_
CanvasFrame & operator=(const CanvasFrame &)=delete
CanvasFrame(Canvas &canvas, CanvasFrameOptions options=CanvasFrameOptions())
void Initialize(const std::string &canvas_id)
Initialize the interaction handler.
Modern, robust canvas for drawing and manipulating graphics.
ImVector< ImVec2 > points_
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
PopupRegistry popup_registry_
void ShowScalingControls()
bool WasDoubleClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
ImVec2 selected_tile_pos_
auto global_scale() const
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
void SetUsageMode(CanvasUsage usage)
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
bool BeginTableCanvas(const std::string &label="")
void InitializeEnhancedComponents()
CanvasRuntime BuildCurrentRuntime() const
void ShowBppConversionDialog()
CanvasAutomationAPI * GetAutomationAPI()
void ShowAdvancedCanvasProperties()
void ApplyScaleSnapshot(const CanvasConfig &snapshot)
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
ImVec2 mouse_pos_in_canvas_
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
bool DrawSolidTilePainter(const ImVec4 &color, int size)
bool enable_context_menu_
CanvasMenuDefinition editor_menu_
void ApplyConfigSnapshot(const CanvasConfig &snapshot)
void DrawLayeredElements()
void ReserveTableSpace(const std::string &label="")
bool enable_custom_labels_
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
ImVec2 GetMinimumSize() const
void AddRectFilledAt(ImVec2 local_top_left, ImVec2 size, uint32_t color)
bool DrawTileSelector(int size, int size_y=0)
bool ConvertBitmapFormat(gfx::BppFormat target_format)
void DrawGridLines(float grid_step)
void SetCustomGridStep(float step)
zelda3::GameData * game_data() const
void ClearContextMenuItems()
void AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size)
void SetGameData(zelda3::GameData *game_data)
void DrawRect(int x, int y, int w, int h, ImVec4 color)
bool HasValidSelection() const
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
ImVector< ImVec2 > selected_points_
ImVec2 GetCurrentSize() const
void SetCanvasSize(ImVec2 canvas_size)
void UpdateColorPainter(gfx::IRenderer *renderer, gfx::Bitmap &bitmap, const ImVec4 &color, const std::function< void()> &event, int tile_size, float scale=1.0f)
void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color)
void DrawCustomHighlight(float grid_step)
std::unique_ptr< CanvasExtensions > extensions_
CanvasGridSize grid_size() const
void AddContextMenuItem(const gui::CanvasMenuItem &item)
ImVec2 GetPreferredSize() const
float GetGridStep() const
CanvasInteractionHandler interaction_handler_
void InitializePaletteEditor(Rom *rom)
void Begin(ImVec2 canvas_size=ImVec2(0, 0))
Begin canvas rendering (ImGui-style)
void SetZoomToFit(const gfx::Bitmap &bitmap)
bool WasClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
ImVector< ImVector< std::string > > labels_
gfx::BppFormat GetCurrentBppFormat() const
void ClosePersistentPopup(const std::string &popup_id)
void ShowBppFormatSelector()
void RecordCanvasOperation(const std::string &operation_name, double time_ms)
void RenderPersistentPopups()
void SetGridSize(CanvasGridSize grid_size)
bool IsAutoResize() const
std::shared_ptr< CanvasUsageTracker > usage_tracker_
void End()
End canvas rendering (ImGui-style)
float GetGlobalScale() const
void EndInTable(CanvasRuntime &runtime, const CanvasFrameOptions &options)
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
bool IsMouseHovering() const
std::unique_ptr< CanvasContextMenu > context_menu_
ImVec2 GetLastClickPosition() const
zelda3::GameData * game_data_
void DrawOutline(int x, int y, int w, int h)
void DrawInfoGrid(float grid_step=64.0f, int tile_id_offset=8, int label_id=0)
CanvasSelection selection_
CanvasRuntime BeginInTable(const std::string &label, const CanvasFrameOptions &options)
Begin canvas in table cell with frame options (modern API) Returns CanvasRuntime for stateless helper...
bool enable_hex_tile_labels_
void OpenPersistentPopup(const std::string &popup_id, std::function< void()> render_callback)
void DrawBitmapTable(const BitmapTable &gfx_bin)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
void InitializeDefaults()
std::shared_ptr< CanvasPerformanceIntegration > performance_integration_
void Init(const CanvasConfig &config)
Initialize canvas with configuration (post-construction) Preferred over constructor parameters for ne...
void SetAutoResize(bool auto_resize)
void SetGlobalScale(float scale)
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
void DrawContextMenuItem(const gui::CanvasMenuItem &item)
void DrawText(const std::string &text, int x, int y)
CanvasExtensions & EnsureExtensions()
std::vector< ImVec2 > selected_tiles_
bool ApplyROMPalette(int group_index, int palette_index)
std::unordered_map< int, std::unique_ptr< gfx::Bitmap > > BitmapTable
BppFormat
BPP format enumeration for SNES graphics.
@ kBpp8
8 bits per pixel (256 colors)
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string &label)
void SetNextCanvasSize(ImVec2 size, bool auto_resize)
void DrawCanvasRect(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color, float global_scale)
void DrawCanvasLabels(const CanvasRenderContext &ctx, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
void DrawCanvasOverlay(const CanvasRenderContext &ctx, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding)
void DrawCanvasOutline(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, uint32_t color)
void DrawCanvasOutlineWithColor(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale)
void DrawCanvasText(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, const std::string &text, int x, int y, float global_scale)
void DrawCustomHighlight(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int highlight_tile_id, float grid_step)
void DrawCanvasGridLines(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1, ImVec2 scrolling, float grid_step, float global_scale)
ImVec2 AlignPosToGridHelper(ImVec2 pos, float scale)
CanvasGeometry GetGeometryFromRuntime(const CanvasRuntime &rt)
ImVec2 AlignPosToGrid(ImVec2 pos, float scale)
Graphical User Interface (GUI) components for the application.
constexpr uint32_t kWhiteColor
constexpr uint32_t kRectangleColor
void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
CanvasUsage
Canvas usage patterns and tracking.
void EndCanvas(Canvas &canvas)
void DrawBitmapPreview(const CanvasRuntime &rt, gfx::Bitmap &bitmap, const BitmapPreviewOptions &options)
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
bool DrawTilemapPainter(const CanvasRuntime &rt, gfx::Tilemap &tilemap, int current_tile, ImVec2 *out_drawn_pos)
void DrawRect(const CanvasRuntime &rt, int x, int y, int w, int h, ImVec4 color)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void GraphicsBinCanvasPipeline(int width, int height, int tile_size, int num_sheets_to_load, int canvas_id, bool is_loaded, gfx::BitmapTable &graphics_bin)
void ApplyScrollDelta(CanvasGeometry &geometry, ImVec2 delta)
Apply scroll delta to geometry.
ImVec2 ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px)
ImVec2 CalculateMouseInCanvas(const CanvasGeometry &geometry, ImVec2 mouse_screen_pos)
Calculate mouse position in canvas space.
void RenderCanvasBackground(ImDrawList *draw_list, const CanvasGeometry &geometry)
Render canvas background and border.
CanvasGeometry CalculateCanvasGeometry(const CanvasConfig &config, ImVec2 requested_size, ImVec2 cursor_screen_pos, ImVec2 content_region_avail)
Calculate canvas geometry from configuration and ImGui context.
void DrawText(const CanvasRuntime &rt, const std::string &text, int x, int y)
ZoomToFitResult ComputeZoomToFit(ImVec2 content_px, ImVec2 canvas_px, float padding_px)
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
void DrawOutline(const CanvasRuntime &rt, int x, int y, int w, int h, ImU32 color)
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, const std::string &label, bool auto_resize)
constexpr ImGuiButtonFlags kMouseFlags
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
void DrawSelectRect(const CanvasRuntime &rt, int current_map, int tile_size, float scale, CanvasSelection &selection)
bool RenderPreviewPanel(const CanvasRuntime &rt, gfx::Bitmap &bmp, const PreviewPanelOpts &opts)
int y
Y coordinate or height.
int x
X coordinate or width.
Tilemap structure for SNES tile-based graphics management.
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Pair map_size
Size of tilemap in tiles.
Bitmap atlas
Master bitmap containing all tiles.
Unified configuration for canvas display and interaction.
std::function< void(const CanvasConfig &) on_config_changed)
bool clamp_rect_to_local_maps
std::function< void(const CanvasConfig &) on_scale_changed)
bool enable_custom_labels
Optional extension modules for Canvas.
std::optional< float > grid_step
Canvas geometry calculated per-frame.
Selection state for canvas interactions.
std::vector< ImVec2 > selected_tiles
std::vector< ImVec2 > selected_points
ImVec2 mouse_pos_in_canvas