18#include "imgui/imgui.h"
32 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
37 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
46 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
56 : renderer_(nullptr), canvas_id_(id), context_id_(id +
"Context") {
71 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
77 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
86 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
96 : renderer_(renderer), canvas_id_(id), context_id_(id +
"Context") {
134 extensions_ = std::make_unique<CanvasExtensions>();
139using ImGui::GetContentRegionAvail;
140using ImGui::GetCursorScreenPos;
142using ImGui::GetWindowDrawList;
143using ImGui::IsItemActive;
144using ImGui::IsItemHovered;
145using ImGui::IsMouseClicked;
146using ImGui::IsMouseDragging;
153 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
157 return ImVec2(std::floor(pos.x / scale) * scale,
158 std::floor(pos.y / scale) * scale);
231 if (old_scale != scale) {
311 ImGui::OpenPopup(
"Canvas Usage Report");
312 if (ImGui::BeginPopupModal(
"Canvas Usage Report",
nullptr,
313 ImGuiWindowFlags_AlwaysAutoResize)) {
314 ImGui::Text(
"Canvas Usage Report");
316 ImGui::TextWrapped(
"%s", report.c_str());
318 if (ImGui::Button(
"Close")) {
319 ImGui::CloseCurrentPopup();
329 ext.InitializePaletteEditor();
330 if (ext.palette_editor) {
331 ext.palette_editor->Initialize(
rom);
345 ext.InitializePaletteEditor();
346 if (ext.palette_editor) {
348 ext.palette_editor->ShowPaletteEditor(*mutable_palette,
349 "Canvas Palette Editor");
357 ext.InitializePaletteEditor();
358 if (ext.palette_editor) {
359 ext.palette_editor->ShowColorAnalysis(*
bitmap_,
"Canvas Color Analysis");
395 std::string child_id =
canvas_id_ +
"_TableChild";
400 ImGui::BeginChild(child_id.c_str(), child_size,
402 ImGuiWindowFlags_NoScrollbar);
404 if (!label.empty()) {
405 ImGui::Text(
"%s", label.c_str());
419 if (child_size.x <= 0 || child_size.y <= 0) {
429 std::string child_id =
canvas_id_ +
"_TableChild";
430 ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoScrollbar;
432 child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
434 ImGui::BeginChild(child_id.c_str(), child_size,
true, child_flags);
436 if (!label.empty()) {
437 ImGui::Text(
"%s", label.c_str());
482 return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) &&
490 return ImVec2(-1, -1);
519 if (effective_size.x == 0 && effective_size.y == 0) {
527 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
529 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
531 ImGui::BeginChild(
canvas_id().c_str(), effective_size,
true, child_flags);
567 const std::function<
void()>& event,
568 int tile_size,
float scale) {
617 if (IsItemHovered()) {
618 const ImGuiIO& io = GetIO();
631 ImGuiIO& io = GetIO();
632 const float wheel_x = io.MouseWheelH;
633 const float wheel_y = io.MouseWheel;
635 if (wheel_x != 0.0f || wheel_y != 0.0f) {
637 io.MouseWheelH = 0.0f;
638 io.MouseWheel = 0.0f;
640 if (io.KeyCtrl && wheel_y != 0.0f) {
642 constexpr float kMinScale = 0.25f;
643 constexpr float kMaxScale = 8.0f;
645 const float new_scale = std::clamp(unclamped, kMinScale, kMaxScale);
657 constexpr float kTouchWheelToPixels = 10.0f;
660 ImVec2(wheel_x * kTouchWheelToPixels, wheel_y * kTouchWheelToPixels));
669 const ImGuiIO& io = GetIO();
670 const bool is_active = IsItemActive();
673 if (
const float mouse_threshold_for_pan =
676 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
685 const ImGuiIO& io = GetIO();
690 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
773 ext.InitializeModals();
784 ext.modals->ShowAdvancedProperties(
canvas_id_, modal_config,
792 ext.InitializeModals();
827 auto popup_callback = [
this](
const std::string& id,
828 std::function<void()> callback) {
854 last_section.items.push_back(item);
863 std::function<
void()> render_callback) {
882 ImVec2 available = ImGui::GetContentRegionAvail();
883 float scale_x = available.x / bitmap.
width();
884 float scale_y = available.y / bitmap.
height();
935 const ImGuiIO& io = GetIO();
936 const bool is_hovered = IsItemHovered();
940 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
941 const auto scaled_size = size * scale;
955 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
958 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
960 points_.push_back(paint_pos_end);
964 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
965 ImVec2(origin.x + paint_pos.x + scaled_size,
966 origin.y + paint_pos.y + scaled_size));
969 if (IsMouseClicked(ImGuiMouseButton_Left) &&
970 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
997 const ImGuiIO& io = GetIO();
1000 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1002 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
1008 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
1019 const ImGuiIO& io = GetIO();
1020 const bool is_hovered = IsItemHovered();
1024 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1026 static bool is_dragging =
false;
1027 static ImVec2 start_drag_pos;
1041 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
1050 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
1053 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
1054 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
1055 origin.y + paint_pos.y + scaled_tile_size),
1056 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
1058 if (IsMouseClicked(ImGuiMouseButton_Left)) {
1060 start_drag_pos = paint_pos;
1063 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
1064 is_dragging =
false;
1075 int tile_index_x =
static_cast<int>(position.x /
global_scale_) / tile_size;
1076 int tile_index_y =
static_cast<int>(position.y /
global_scale_) / tile_size;
1078 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
1081 for (
int y = 0; y < tile_size; ++y) {
1082 for (
int x = 0; x < tile_size; ++x) {
1085 (start_position.y + y) * bitmap->
width() + (start_position.x + x);
1103 ImVec2 selected_pos;
1107 if (
is_hovered_ && IsMouseClicked(ImGuiMouseButton_Left)) {
1108 const ImGuiIO& io = GetIO();
1111 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1112 ImVec2 painter_pos = AlignPosToGrid(mouse_pos,
static_cast<float>(size));
1115 points_.push_back(painter_pos);
1116 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
1120 return double_clicked;
1189 ImVec2 src_pos, ImVec2 src_size) {
1205 for (
const auto& [key, value] : gfx_bin) {
1207 if (!value || !value->is_active() || !value->texture()) {
1210 int offset = 0x40 * (key + 1);
1215 draw_list_->AddImage((ImTextureID)(intptr_t)value->texture(),
1223 IM_COL32(255, 255, 255, 200));
1237 int tile_size,
float ,
int local_map_size,
1238 ImVec2 total_map_size) {
1243 if (group.empty()) {
1255 bool use_optimized_rendering =
1259 const int small_map = local_map_size;
1260 const float large_map_width = total_map_size.x;
1261 const float large_map_height = total_map_size.y;
1264 const float tile_scale = tile_size * effective_scale;
1274 static_cast<int>(std::floor(rect_top_left.x / tile_size));
1276 static_cast<int>(std::floor(rect_top_left.y / tile_size));
1278 static_cast<int>(std::floor(rect_bottom_right.x / tile_size));
1280 static_cast<int>(std::floor(rect_bottom_right.y / tile_size));
1282 if (start_tile_x > end_tile_x)
1283 std::swap(start_tile_x, end_tile_x);
1284 if (start_tile_y > end_tile_y)
1285 std::swap(start_tile_y, end_tile_y);
1288 int rect_width = (end_tile_x - start_tile_x) * tile_size;
1289 int rect_height = (end_tile_y - start_tile_y) * tile_size;
1291 int tiles_per_row = rect_width / tile_size;
1292 int tiles_per_col = rect_height / tile_size;
1295 for (
int y = 0; y < tiles_per_col + 1; ++y) {
1296 for (
int x = 0; x < tiles_per_row + 1; ++x) {
1298 if (i >=
static_cast<int>(group.size())) {
1302 int tile_id = group[i];
1306 if (tile_id >= 0 && tile_id < tilemap_size) {
1308 int tile_pos_x = (x + start_tile_x) * tile_size * effective_scale;
1309 int tile_pos_y = (y + start_tile_y) * tile_size * effective_scale;
1314 atlas_tiles_per_row > 0) {
1316 (tile_id % atlas_tiles_per_row) * tilemap.
tile_size.
x;
1318 (tile_id / atlas_tiles_per_row) * tilemap.
tile_size.
y;
1321 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.
atlas.
width() &&
1322 atlas_tile_y >= 0 && atlas_tile_y < tilemap.
atlas.
height()) {
1324 const float atlas_width =
static_cast<float>(tilemap.
atlas.
width());
1325 const float atlas_height =
1328 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
1330 ImVec2((atlas_tile_x + tilemap.
tile_size.
x) / atlas_width,
1331 (atlas_tile_y + tilemap.
tile_size.
y) / atlas_height);
1336 float screen_w = tilemap.
tile_size.
x * effective_scale;
1337 float screen_h = tilemap.
tile_size.
y * effective_scale;
1340 uint32_t alpha_color = use_optimized_rendering
1341 ? IM_COL32(255, 255, 255, 200)
1342 : IM_COL32(255, 255, 255, 150);
1347 ImVec2(screen_x, screen_y),
1348 ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1,
1356 if (i >=
static_cast<int>(group.size())) {
1366 const ImGuiIO& io = GetIO();
1368 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1373 ImVec2 clamped_mouse_pos = mouse_pos;
1377 int mouse_local_map_x =
static_cast<int>(mouse_pos.x) / small_map;
1378 int mouse_local_map_y =
static_cast<int>(mouse_pos.y) / small_map;
1382 float potential_end_x = mouse_pos.x + rect_width;
1383 float potential_end_y = mouse_pos.y + rect_height;
1386 int potential_end_map_x =
static_cast<int>(potential_end_x) / small_map;
1387 int potential_end_map_y =
static_cast<int>(potential_end_y) / small_map;
1390 if (potential_end_map_x != mouse_local_map_x) {
1392 float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width;
1393 clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x);
1396 if (potential_end_map_y != mouse_local_map_y) {
1398 float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height;
1399 clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y);
1404 auto new_start_pos_screen = AlignPosToGrid(clamped_mouse_pos, tile_size * effective_scale);
1407 ImVec2 new_start_pos_world(new_start_pos_screen.x / effective_scale,
1408 new_start_pos_screen.y / effective_scale);
1411 new_start_pos_world.x =
1412 std::clamp(new_start_pos_world.x, 0.0f, large_map_width - rect_width);
1413 new_start_pos_world.y =
1414 std::clamp(new_start_pos_world.y, 0.0f, large_map_height - rect_height);
1419 ImVec2(new_start_pos_world.x + rect_width, new_start_pos_world.y + rect_height));
1454 for (
float x = fmodf(
scrolling_.x, grid_step);
1456 for (
float y = fmodf(
scrolling_.y, grid_step);
1460 int tile_id = tile_x + (tile_y * tile_id_offset);
1462 if (tile_id >=
labels_[label_id].size()) {
1465 std::string label =
labels_[label_id][tile_id];
1467 ImVec2(
canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
1468 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
1493 .grid_step = grid_step};
1529 ImDrawList*
draw_list = ImGui::GetWindowDrawList();
1531 Text(
"Blue shape is drawn first: appears in back");
1532 Text(
"Red shape is drawn after: appears in front");
1533 ImVec2 p0 = ImGui::GetCursorScreenPos();
1534 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
1535 IM_COL32(0, 0, 255, 255));
1536 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
1537 ImVec2(p0.x + 75, p0.y + 75),
1538 IM_COL32(255, 0, 0, 255));
1539 ImGui::Dummy(ImVec2(75, 75));
1543 Text(
"Blue shape is drawn first, into channel 1: appears in front");
1544 Text(
"Red shape is drawn after, into channel 0: appears in back");
1545 ImVec2 p1 = ImGui::GetCursorScreenPos();
1552 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
1553 IM_COL32(0, 0, 255, 255));
1555 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
1556 ImVec2(p1.x + 75, p1.y + 75),
1557 IM_COL32(255, 0, 0, 255));
1563 ImGui::Dummy(ImVec2(75, 75));
1564 Text(
"After reordering, contents of channel 0 appears below channel 1.");
1572 ImVec2 effective_size = child_size;
1573 if (child_size.x == 0 && child_size.y == 0) {
1584 ImGui::BeginChild(canvas.
canvas_id().c_str(), effective_size,
true,
1585 ImGuiWindowFlags_NoScrollbar);
1605 if (effective_size.x == 0 && effective_size.y == 0) {
1613 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
1615 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
1617 ImGui::BeginChild(canvas.
canvas_id().c_str(), effective_size,
true,
1671 result.
scale = 1.0f;
1672 result.
scroll = ImVec2(0, 0);
1674 if (content_px.x <= 0 || content_px.y <= 0) {
1679 float available_x = canvas_px.x - padding_px * 2;
1680 float available_y = canvas_px.y - padding_px * 2;
1682 if (available_x <= 0 || available_y <= 0) {
1687 float scale_x = available_x / content_px.x;
1688 float scale_y = available_y / content_px.y;
1689 result.
scale = std::min(scale_x, scale_y);
1692 float scaled_w = content_px.x * result.
scale;
1693 float scaled_h = content_px.y * result.
scale;
1694 result.
scroll.x = (canvas_px.x - scaled_w) / 2.0f;
1695 result.
scroll.y = (canvas_px.y - scaled_h) / 2.0f;
1700ImVec2
ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px) {
1703 float max_scroll_x = std::max(0.0f, content_px.x - canvas_px.x);
1704 float max_scroll_y = std::max(0.0f, content_px.y - canvas_px.y);
1710 std::clamp(scroll.x, -max_scroll_x, 0.0f),
1711 std::clamp(scroll.y, -max_scroll_y, 0.0f));
1715 int num_sheets_to_load,
int canvas_id,
1718 if (ImGuiID child_id =
1719 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1720 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(),
true,
1721 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1722 canvas.
DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
1725 for (
const auto& [key, value] : graphics_bin) {
1727 if (!value || !value->texture()) {
1730 int offset = height * (key + 1);
1733 top_left_y = canvas.
zero_point().y + height * key;
1736 (ImTextureID)(intptr_t)value->texture(),
1737 ImVec2(canvas.
zero_point().x + 2, top_left_y),
1752 int height,
int tile_size,
bool is_loaded,
1753 bool scrollbar,
int canvas_id) {
1755 int height,
int tile_size,
bool is_loaded) {
1767 if (ImGuiID child_id =
1768 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1769 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(),
true,
1770 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1771 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1775 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1780 const std::string& label,
bool auto_resize) {
1784 if (auto_resize && bitmap.
is_active()) {
1786 ImVec2 content_size = ImVec2(bitmap.
width(), bitmap.
height());
1813 ext.InitializeModals();
1844 if (ImGui::BeginPopupModal(
"Advanced Canvas Properties",
nullptr,
1845 ImGuiWindowFlags_AlwaysAutoResize)) {
1846 ImGui::Text(
"Advanced Canvas Configuration");
1850 ImGui::Text(
"Canvas Properties");
1862 ImGui::Text(
"Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
1863 ImGui::Text(
"Preferred Size: %.0f x %.0f", preferred_size.x,
1869 ImGui::Text(
"View Settings");
1876 if (ImGui::Checkbox(
"Enable Custom Labels",
1892 ImGui::Text(
"Grid Configuration");
1900 ImGui::Text(
"Scale Configuration");
1908 ImGui::Text(
"Scrolling Configuration");
1910 if (ImGui::Button(
"Reset Scroll")) {
1914 if (ImGui::Button(
"Center View")) {
1925 if (ImGui::Button(
"Close")) {
1926 ImGui::CloseCurrentPopup();
1937 ext.InitializeModals();
1966 ext.modals->ShowScalingControls(
canvas_id_, modal_config);
1971 if (ImGui::BeginPopupModal(
"Scaling Controls",
nullptr,
1972 ImGuiWindowFlags_AlwaysAutoResize)) {
1973 ImGui::Text(
"Canvas Scaling and Display Controls");
1984 ImGui::Text(
"Preset Scales:");
1985 if (ImGui::Button(
"0.25x")) {
1990 if (ImGui::Button(
"0.5x")) {
1995 if (ImGui::Button(
"1x")) {
2000 if (ImGui::Button(
"2x")) {
2005 if (ImGui::Button(
"4x")) {
2010 if (ImGui::Button(
"8x")) {
2017 ImGui::Text(
"Grid Configuration");
2025 ImGui::Text(
"Grid Presets:");
2026 if (ImGui::Button(
"8x8")) {
2031 if (ImGui::Button(
"16x16")) {
2036 if (ImGui::Button(
"32x32")) {
2041 if (ImGui::Button(
"64x64")) {
2048 ImGui::Text(
"Canvas Information");
2051 ImGui::Text(
"Scaled Size: %.0f x %.0f",
2057 "Effective Scale: %.3f x %.3f",
2062 if (ImGui::Button(
"Close")) {
2063 ImGui::CloseCurrentPopup();
2074 if (
bitmap_ && ext.bpp_format_ui) {
2075 ext.bpp_format_ui->RenderFormatSelector(
2085 if (
bitmap_ && ext.bpp_format_ui) {
2092 if (!ext.bpp_conversion_dialog) {
2093 ext.bpp_conversion_dialog = std::make_unique<gui::BppConversionDialog>(
2097 if (
bitmap_ && ext.bpp_conversion_dialog) {
2098 ext.bpp_conversion_dialog->Show(
2101 ConvertBitmapFormat(format);
2105 if (ext.bpp_conversion_dialog) {
2106 ext.bpp_conversion_dialog->Render();
2115 if (current_format == target_format) {
2132 }
catch (
const std::exception& e) {
2133 SDL_Log(
"Failed to convert bitmap format: %s", e.what());
2149 ext.InitializeAutomation(
this);
2150 return ext.automation_api.get();
2177 int y_offset,
float scale,
int alpha) {
2185 ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) {
2205 if (src_size.x <= 0 || src_size.y <= 0) {
2206 src_size = ImVec2(
static_cast<float>(bitmap.
width()),
2207 static_cast<float>(bitmap.
height()));
2225 ImVec2 src_size = options.
src_size;
2226 if (src_size.x <= 0 || src_size.y <= 0) {
2227 src_size = ImVec2(bitmap.
width(), bitmap.
height());
2251 ImVec2(
static_cast<float>(bmp.
width()),
2252 static_cast<float>(bmp.
height())));
2255 static_cast<int>(opts.
dest_pos.y), 1.0f, 255);
2268 h, color, rt.
scale);
2290 return ImVec2(std::floor(pos.x / scale) * scale,
2291 std::floor(pos.y / scale) * scale);
2296 int current_tile, ImVec2* out_drawn_pos) {
2299 const ImGuiIO& io = ImGui::GetIO();
2302 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2315 ImVec2 paint_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2320 if (tiles_per_row > 0) {
2321 int tile_x = (current_tile % tiles_per_row) * tilemap.
tile_size.
x;
2322 int tile_y = (current_tile / tiles_per_row) * tilemap.
tile_size.
y;
2325 if (tile_x >= 0 && tile_x < tilemap.
atlas.
width() && tile_y >= 0 &&
2328 ImVec2(
static_cast<float>(tile_x) / tilemap.
atlas.
width(),
2329 static_cast<float>(tile_y) / tilemap.
atlas.
height());
2330 ImVec2 uv1 = ImVec2(
static_cast<float>(tile_x + tilemap.
tile_size.
x) /
2332 static_cast<float>(tile_y + tilemap.
tile_size.
y) /
2337 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
2338 ImVec2(origin.x + paint_pos.x + scaled_size,
2339 origin.y + paint_pos.y + scaled_size),
2345 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
2346 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
2347 if (out_drawn_pos) *out_drawn_pos = paint_pos;
2355 ImVec2* out_selected_pos) {
2356 const ImGuiIO& io = ImGui::GetIO();
2359 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2365 if (rt.
hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
2366 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos,
static_cast<float>(size));
2367 if (out_selected_pos) *out_selected_pos = painter_pos;
2371 if (rt.
hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
2382 const ImGuiIO& io = ImGui::GetIO();
2385 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2386 static ImVec2 drag_start_pos;
2387 const float scaled_size = tile_size * scale;
2388 static bool dragging =
false;
2389 constexpr int small_map_size = 0x200;
2390 constexpr uint32_t kWhite = IM_COL32(255, 255, 255, 255);
2398 if (current_map < 0x40) {
2399 superY = current_map / 8;
2400 superX = current_map % 8;
2401 }
else if (current_map < 0x80) {
2402 superY = (current_map - 0x40) / 8;
2403 superX = (current_map - 0x40) % 8;
2405 superY = (current_map - 0x80) / 8;
2406 superX = (current_map - 0x80) % 8;
2410 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
2411 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2413 int world_x =
static_cast<int>(painter_pos.x / scale);
2414 int world_y =
static_cast<int>(painter_pos.y / scale);
2416 auto tile16_x = (world_x % small_map_size) / (small_map_size / 0x20);
2417 auto tile16_y = (world_y % small_map_size) / (small_map_size / 0x20);
2419 int index_x = superX * 0x20 + tile16_x;
2420 int index_y = superY * 0x20 + tile16_y;
2422 static_cast<float>(index_y));
2426 drag_start_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2430 ImVec2 drag_end_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2431 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
2433 ImVec2(origin.x + drag_start_pos.x, origin.y + drag_start_pos.y);
2435 auto end = ImVec2(origin.x + drag_end_pos.x + scaled_size,
2436 origin.y + drag_end_pos.y + scaled_size);
2437 rt.
draw_list->AddRect(start, end, kWhite);
2441 if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
2444 constexpr int tile16_size = 16;
2446 int start_x =
static_cast<int>(std::floor(drag_start_pos.x / scaled_size)) * tile16_size;
2447 int start_y =
static_cast<int>(std::floor(drag_start_pos.y / scaled_size)) * tile16_size;
2448 int end_x =
static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) * tile16_size;
2449 int end_y =
static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) * tile16_size;
2451 if (start_x > end_x) std::swap(start_x, end_x);
2452 if (start_y > end_y) std::swap(start_y, end_y);
2456 static_cast<size_t>(((end_x - start_x) / tile16_size + 1) *
2457 ((end_y - start_y) / tile16_size + 1)));
2459 constexpr int tiles_per_local_map = small_map_size / 16;
2461 for (
int y = start_y; y <= end_y; y += tile16_size) {
2462 for (
int x = start_x; x <= end_x; x += tile16_size) {
2463 int local_map_x = (x / small_map_size) % 8;
2464 int local_map_y = (y / small_map_size) % 8;
2465 int tile16_x = (x % small_map_size) / tile16_size;
2466 int tile16_y = (y % small_map_size) / tile16_size;
2467 int index_x = local_map_x * tiles_per_local_map + tile16_x;
2468 int index_y = local_map_y * tiles_per_local_map + tile16_y;
2469 selection.
selected_tiles.emplace_back(
static_cast<float>(index_x),
2470 static_cast<float>(index_y));
2478 ImVec2(drag_start_pos.x / scale, drag_start_pos.y / scale));
2480 ImVec2(drag_end_pos.x / scale, drag_end_pos.y / scale));
2495 draw_list_->AddImage(texture, screen_pos, screen_end);
2504 draw_list_->AddRectFilled(screen_pos, screen_end, color);
2511 draw_list_->AddText(screen_pos, color, text.c_str());
2519 : canvas_(&canvas), options_(options), active_(true) {
2530 : canvas_(other.canvas_), options_(other.options_), active_(other.active_) {
2531 other.active_ =
false;
2535 if (
this != &other) {
2537 canvas_->
End(options_);
2539 canvas_ = other.canvas_;
2540 options_ = other.options_;
2541 active_ = other.active_;
2542 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 set_global_scale(float scale)
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)
static bool IsTouchDevice()
::yaze::EventBus * event_bus()
Get the current EventBus instance.
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 ComputeScrollForZoomAtScreenPos(const CanvasGeometry &geometry, float old_scale, float new_scale, ImVec2 mouse_screen_pos)
Compute new scroll offset to keep a canvas point locked under the mouse.
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)
static ZoomChangedEvent Create(const std::string &src, float old_z, float new_z, size_t session=0)
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