yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_workbench.cc
Go to the documentation of this file.
1// Tile16 editor workbench UI (draw/layout). Domain logic: tile16/tile16_edit_session.
3#include "util/i18n/tr.h"
4
5#include <algorithm>
6#include <array>
7#include <memory>
8
9#include "absl/status/status.h"
10#include "absl/strings/str_format.h"
15#include "app/gfx/core/bitmap.h"
21#include "app/gui/core/input.h"
23#include "app/gui/core/style.h"
27#include "imgui/imgui.h"
28#include "rom/rom.h"
29#include "util/hex.h"
30#include "util/log.h"
31#include "util/macro.h"
32#include "zelda3/game_data.h"
38
39namespace yaze {
40namespace editor {
41
42namespace {
43
44const char* EditModeLabel(Tile16EditMode mode) {
45 switch (mode) {
47 return "Paint";
49 return "Pick";
51 return "Usage Probe";
52 default:
53 return "Paint";
54 }
55}
56
57gfx::TileInfo& TileInfoForQuadrant(gfx::Tile16* tile, int quadrant) {
58 return zelda3::MutableTile16QuadrantInfo(*tile, quadrant);
59}
60
61void SyncTilesInfoArray(gfx::Tile16* tile) {
63}
64
65} // namespace
66
67using namespace ImGui;
68
69constexpr float kTile8DisplayScale = 4.0f;
70
72 if (BeginPopup("##Tile16EditorContextMenu")) {
73 if (BeginMenu(tr("View"))) {
74 if (MenuItem(tr("Show Grid"), nullptr, session_.show_tile_grid())) {
76 }
77 if (MenuItem(tr("Show Collision IDs"), nullptr,
83 }
84 EndMenu();
85 }
86
87 if (BeginMenu(tr("Edit"))) {
88 if (MenuItem(tr("Copy Current Tile16"), "Ctrl+C")) {
90 }
91 if (MenuItem(tr("Paste to Current Tile16"), "Ctrl+V")) {
93 }
94 Separator();
95 if (MenuItem(tr("Flip Horizontal"), "H")) {
97 }
98 if (MenuItem(tr("Flip Vertical"), "V")) {
100 }
101 if (MenuItem(tr("Rotate"), "R")) {
103 }
104 if (MenuItem(tr("Clear"), "Delete")) {
106 }
107 EndMenu();
108 }
109
110 if (BeginMenu(tr("Scratch Space"))) {
111 for (int i = 0; i < 4; i++) {
112 std::string slot_name = "Slot " + std::to_string(i + 1);
114 if (MenuItem((slot_name + " (Load)").c_str())) {
116 }
117 if (MenuItem((slot_name + " (Save)").c_str())) {
119 }
120 if (MenuItem((slot_name + " (Clear)").c_str())) {
122 }
123 } else {
124 if (MenuItem((slot_name + " (Save)").c_str())) {
126 }
127 }
128 if (i < 3)
129 Separator();
130 }
131 EndMenu();
132 }
133
134 EndPopup();
135 }
136}
138 absl::Status draw_status;
139 static bool show_advanced_controls = false;
140 static bool show_debug_info = false;
141
142 gui::StyleVarGuard header_var_guard(
143 {{ImGuiStyleVar_FramePadding, ImVec2(8, 4)},
144 {ImGuiStyleVar_ItemSpacing, ImVec2(8, 4)}});
145
146 draw_status.Update(
147 DrawCompactActionStatusRow(&show_debug_info, &show_advanced_controls));
148
149 ImGui::Separator();
150
151 const int total_tiles =
153 const float layout_width = ImGui::GetContentRegionAvail().x;
154 const bool compact_layout = layout_width < 900.0f;
155 const int layout_columns = compact_layout ? 1 : 2;
156
157 if (ImGui::BeginTable(
158 "##Tile16EditLayout", layout_columns,
159 ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) {
160 if (compact_layout) {
161 ImGui::TableSetupColumn("Tile16 Workbench",
162 ImGuiTableColumnFlags_WidthStretch, 1.0f);
163 } else {
164 ImGui::TableSetupColumn("Tile8 Source",
165 ImGuiTableColumnFlags_WidthStretch, 0.58f);
166 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
167 0.42f);
168 }
169
170 if (compact_layout) {
171 ImGui::TableNextRow();
172 ImGui::TableNextColumn();
173 ImGui::BeginGroup();
174 gui::LayoutHelpers::SectionHeader(tr("Tile16 Editor"));
175 draw_status.Update(DrawTile16NavigationHeader(total_tiles));
176 draw_status.Update(DrawTile16EditorWorkbenchColumn(
177 show_debug_info, show_advanced_controls));
178 ImGui::EndGroup();
179
180 ImGui::TableNextRow();
181 ImGui::TableNextColumn();
182 ImGui::BeginGroup();
183 gui::LayoutHelpers::SectionHeader(tr("Tile8 Source"));
184 draw_status.Update(DrawTile8SourcePanel(320.0f));
185 ImGui::EndGroup();
186 } else {
187 ImGui::TableNextRow();
188 ImGui::TableNextColumn();
189 ImGui::BeginGroup();
190 gui::LayoutHelpers::SectionHeader(tr("Tile8 Source"));
191 draw_status.Update(DrawTile8SourcePanel(0.0f));
192 ImGui::EndGroup();
193
194 ImGui::TableNextColumn();
195 ImGui::BeginGroup();
196 gui::LayoutHelpers::SectionHeader(tr("Tile16 Editor"));
197 draw_status.Update(DrawTile16NavigationHeader(total_tiles));
198 draw_status.Update(DrawTile16EditorWorkbenchColumn(
199 show_debug_info, show_advanced_controls));
200 ImGui::EndGroup();
201 }
202
203 ImGui::EndTable();
204 }
205
207
214
215 return draw_status;
216}
217
218absl::Status Tile16Editor::DrawTile16NavigationHeader(int total_tiles) {
219 gui::StyleVarGuard nav_spacing_guard(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
220
221 ImGui::TextDisabled(tr("0x%03X / 0x%03X"), session_.current_tile16(),
222 std::max(0, total_tiles - 1));
223 ImGui::SameLine();
224
225 ImGui::SetNextItemWidth(80);
226 if (ImGui::InputInt("##JumpToTile", session_.mutable_jump_to_tile_id(), 0,
227 0)) {
229 std::clamp(session_.jump_to_tile_id(), 0, total_tiles - 1);
233 }
234 }
235 if (ImGui::IsItemHovered()) {
236 ImGui::SetTooltip(tr("Tile ID (0-%d) - navigates as you type"),
237 total_tiles - 1);
238 }
239
240 const int total_pages = (total_tiles + Tile16EditSession::kTilesPerPage - 1) /
244
245 ImGui::SameLine();
246 if (ImGui::Button("<<")) {
249 }
250 if (ImGui::IsItemHovered())
251 ImGui::SetTooltip(tr("First page"));
252
253 ImGui::SameLine();
254 if (ImGui::Button("<")) {
255 int new_tile = std::max(
257 RequestTileSwitch(new_tile);
259 }
260 if (ImGui::IsItemHovered())
261 ImGui::SetTooltip(tr("Previous page (PageUp)"));
262
263 ImGui::SameLine();
264 ImGui::TextDisabled(tr("Page %d/%d"), session_.current_page() + 1,
265 total_pages);
266
267 ImGui::SameLine();
268 if (ImGui::Button(">")) {
269 int new_tile =
270 std::min(total_tiles - 1,
272 RequestTileSwitch(new_tile);
274 }
275 if (ImGui::IsItemHovered())
276 ImGui::SetTooltip(tr("Next page (PageDown)"));
277
278 ImGui::SameLine();
279 if (ImGui::Button(">>")) {
280 RequestTileSwitch(total_tiles - 1);
282 }
283 if (ImGui::IsItemHovered())
284 ImGui::SetTooltip(tr("Last page"));
285
286 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
287 if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
288 int new_tile = std::max(
290 RequestTileSwitch(new_tile);
292 }
293 if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
294 int new_tile =
295 std::min(total_tiles - 1, session_.current_tile16() +
297 RequestTileSwitch(new_tile);
299 }
300 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
303 }
304 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
305 RequestTileSwitch(total_tiles - 1);
307 }
308
309 if (!ImGui::GetIO().KeyCtrl) {
310 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) &&
311 session_.current_tile16() > 0) {
314 }
315 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) &&
316 session_.current_tile16() < total_tiles - 1) {
319 }
320 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) &&
325 }
326 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) &&
328 total_tiles) {
332 }
333 }
334 }
335
336 return absl::OkStatus();
337}
338
340 bool show_debug_info, bool show_advanced_controls) {
341 absl::Status draw_status;
342 // The four-tile stamp needs room for its full 2x2 Tile16 footprint.
343 const float preview_size = session_.tile8_stamp_size() == 4 ? 128.0f : 64.0f;
344 if (ImGui::BeginChild(
345 "##Tile16FixedCanvas", ImVec2(preview_size + 26, preview_size + 26),
346 true,
347 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
348 // Configure canvas frame options for tile16 editor
349 gui::CanvasFrameOptions tile16_edit_frame_opts;
350 tile16_edit_frame_opts.canvas_size = ImVec2(preview_size, preview_size);
351 tile16_edit_frame_opts.draw_grid = session_.show_tile_grid();
352 tile16_edit_frame_opts.grid_step =
353 kTile8Size *
354 kTile8DisplayScale; // 8x8 source pixels at 4x preview scale.
355 tile16_edit_frame_opts.draw_context_menu = true;
356 tile16_edit_frame_opts.draw_overlay = true;
357 tile16_edit_frame_opts.render_popups = true;
358 tile16_edit_frame_opts.use_child_window = false;
359
360 auto tile16_edit_rt =
361 gui::BeginCanvas(tile16_edit_canvas_, tile16_edit_frame_opts);
362
363 // Draw current tile16 bitmap with dynamic zoom
367 }
368
369 // Handle tile8 painting with improved hover preview
370 if (session_.current_tile8() >= 0 &&
372 static_cast<int>(
374 // Create a display tile that shows the current palette selection
377 8, 8, 8, std::vector<uint8_t>(kTile8PixelCount, 0));
378 }
379
380 // Get the original pixel data (already has sheet offsets from
381 // ProcessGraphicsBuffer)
382 auto& preview_data =
384 std::copy(
386 .begin(),
388 .end(),
389 preview_data.begin());
390
391 // Apply the correct sheet-aware palette slice for the preview
392 const gfx::SnesPalette* display_palette = nullptr;
393 if (session_.overworld_palette().size() >= 256) {
394 display_palette = &session_.overworld_palette();
395 } else if (session_.palette().size() >= 256) {
396 display_palette = &session_.palette();
397 } else {
398 display_palette = session_.current_gfx_bmp()
400 : &session_.palette();
401 }
402
403 if (display_palette && !display_palette->empty()) {
405 CreateRemappedPaletteForViewing(*display_palette,
407 }
408
409 // Apply flips if needed
410 if (session_.x_flip() || session_.y_flip()) {
412
413 if (session_.x_flip()) {
414 for (int y = 0; y < 8; ++y) {
415 for (int x = 0; x < 4; ++x) {
416 std::swap(data[y * 8 + x], data[y * 8 + (7 - x)]);
417 }
418 }
419 }
420
421 if (session_.y_flip()) {
422 for (int y = 0; y < 4; ++y) {
423 for (int x = 0; x < 8; ++x) {
424 std::swap(data[y * 8 + x], data[(7 - y) * 8 + x]);
425 }
426 }
427 }
428 }
429
430 // Push pixel changes to the existing surface before queuing texture work
432
433 // Queue texture creation/update on the persistent preview bitmap to
434 // avoid dangling stack pointers in the arena queue
435 const auto preview_command =
440 preview_command, &session_.mutable_tile8_preview_bitmap());
441
442 // Preview the same mutations the click will publish. Pick/Usage modes
443 // must keep the actual tile visible rather than cover it with a brush.
444 if (tile16_edit_rt.hovered &&
446 const ImVec2 origin = tile16_edit_canvas_.zero_point();
447 const auto mouse = ImGui::GetIO().MousePos;
448 const auto preview_status = session_.BuildStampPreview(
449 {(mouse.x - origin.x) / kTile8DisplayScale,
450 (mouse.y - origin.y) / kTile8DisplayScale},
452 draw_status.Update(preview_status);
453 if (preview_status.ok()) {
461 }
462 }
463
464 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
465 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
466
467 if (left_clicked || right_clicked) {
468 const ImGuiIO& io = ImGui::GetIO();
469 ImVec2 canvas_pos = tile16_edit_canvas_.zero_point();
470 ImVec2 mouse_pos =
471 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
472
473 // The hover tile painter and bitmap share a 0,0 origin, matching
474 // ZScream/HMagic's direct preview hit-test behavior.
475 const ImVec2 tile_position =
477 int tile_x = static_cast<int>(tile_position.x);
478 int tile_y = static_cast<int>(tile_position.y);
479
480 // Clamp to valid range (0-15 for 16x16 tile)
481 tile_x = std::max(0, std::min(15, tile_x));
482 tile_y = std::max(0, std::min(15, tile_y));
483
485 "Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d), mode=%s",
486 mouse_pos.x, mouse_pos.y, tile_x, tile_y,
487 EditModeLabel(session_.edit_mode()));
488
489 draw_status.Update(HandleTile16CanvasClick(
490 ImVec2(tile_x, tile_y), left_clicked, right_clicked));
491 }
492 }
493
494 gui::EndCanvas(tile16_edit_canvas_, tile16_edit_rt, tile16_edit_frame_opts);
495 }
496 ImGui::EndChild();
497
498 Separator();
499
500 // === Compact Controls Section ===
501
502 // Tile8 info and preview
503 if (session_.current_tile8() >= 0 &&
505 static_cast<int>(session_.mutable_current_gfx_individual().size())) {
506 Text(tr("Tile8: %02X"), session_.current_tile8());
507 SameLine();
508 auto* tile8_texture = session_.mutable_tile8_preview_bitmap().texture();
509 if (tile8_texture) {
510 ImGui::Image((ImTextureID)(intptr_t)tile8_texture, ImVec2(24, 24));
511 }
512
513 const int sheet_idx = GetSheetIndexForTile8(session_.current_tile8());
514 ImGui::SameLine();
515 ImGui::TextDisabled(tr("G%d"), sheet_idx);
516 if (ImGui::IsItemHovered()) {
517 ImGui::BeginTooltip();
518 ImGui::Text(tr("Graphics chunk: %d"), sheet_idx);
519 ImGui::EndTooltip();
520 }
521 }
522
523 // Tile8 transform options in compact form
524 Checkbox(tr("X Flip"), session_.mutable_x_flip());
525 SameLine();
526 Checkbox(tr("Y Flip"), session_.mutable_y_flip());
527 SameLine();
528 Checkbox(tr("Priority"), session_.mutable_priority_tile());
529
530 Text(tr("Stamp:"));
531 SameLine();
532 if (ImGui::RadioButton(tr("1x"), session_.tile8_stamp_size() == 1)) {
534 }
535 SameLine();
536 if (ImGui::RadioButton(tr("2x"), session_.tile8_stamp_size() == 2)) {
538 }
539 SameLine();
540 if (ImGui::RadioButton(tr("4x"), session_.tile8_stamp_size() == 4)) {
542 }
544 "1x: paint one quadrant\n2x: fill current tile16 from a 2x2 tile8 "
545 "block\n4x: stamp a 2x2 tile16 patch from a 4x4 tile8 block");
546
547 Text(tr("Edit Mode:"));
548 if (ImGui::RadioButton(tr("Paint (P)"),
551 }
552 SameLine();
553 if (ImGui::RadioButton(tr("Pick (I)"),
556 }
557 SameLine();
558 if (ImGui::RadioButton(tr("Usage (U)"),
562 }
564 "Paint: left-click places Tile8 into Tile16.\n"
565 "Pick: left-click samples Tile8 from Tile16.\n"
566 "Usage: keeps usage overlay visible and samples on click.\n"
567 "Right-click on Tile16 preview always samples.");
568
571 if (ImGui::CollapsingHeader(tr("Usage Navigator"),
572 ImGuiTreeNodeFlags_DefaultOpen)) {
573 const float nav_height = 220.0f;
574 if (BeginChild("##UsageBlocksetNav", ImVec2(0, nav_height), true,
575 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
579 }
582 opts.grid_step = 32.0f;
583 opts.draw_overlay = true;
584 opts.use_child_window = false;
585 auto rt = gui::BeginCanvas(blockset_canvas_, opts);
586 if (session_.tile16_blockset_bmp() != nullptr) {
587 auto result = blockset_selector_.Render(
590 if (result.selection_changed) {
591 RequestTileSwitch(result.selected_tile);
592 }
593 }
596 }
597 EndChild();
598 }
599 }
600
601 Separator();
602
603 draw_status.Update(DrawBrushAndTilePaletteControls(show_debug_info));
604
605 Separator();
606
607 draw_status.Update(DrawPrimaryActionControls());
608
609 // Advanced controls (collapsible)
610 if (show_advanced_controls) {
611 Separator();
612 Text(tr("Advanced:"));
613
614 if (Button(tr("Palette Settings"), ImVec2(-1, 0))) {
617 }
618
619 if (Button(tr("Analyze Data"), ImVec2(-1, 0))) {
621 }
622 HOVER_HINT("Analyze tile8 source data format and palette state");
623
624 if (Button(tr("Manual Edit"), ImVec2(-1, 0))) {
625 ImGui::OpenPopup("ManualTile8Editor");
626 }
627
628 if (Button(tr("Refresh Blockset"), ImVec2(-1, 0))) {
629 draw_status.Update(RefreshTile16Blockset());
630 }
631
632 // Scratch space in compact form
633 Text(tr("Scratch:"));
635
636 // Manual tile8 editor popup
638 }
639
640 // Compact debug information panel
641 if (show_debug_info) {
642 Separator();
643 Text(tr("Debug:"));
644 ImGui::TextDisabled(tr("T16:%02X T8:%d Pal:%d"), session_.current_tile16(),
646
647 if (session_.current_tile8() >= 0) {
648 int sheet_index = GetSheetIndexForTile8(session_.current_tile8());
649 int actual_slot =
651 ImGui::TextDisabled(tr("Sheet:%d Slot:%d"), sheet_index, actual_slot);
652 }
653
654 // Compact palette mapping table
655 if (ImGui::CollapsingHeader(tr("Palette Map"),
656 ImGuiTreeNodeFlags_DefaultOpen)) {
657 ImGui::BeginChild("##PaletteMappingScroll", ImVec2(0, 120), true);
658 if (ImGui::BeginTable("##PalMap", 2,
659 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
660 ImGuiTableFlags_SizingFixedFit)) {
661 ImGui::TableSetupColumn("Btn", ImGuiTableColumnFlags_WidthFixed, 30);
662 ImGui::TableSetupColumn("CGRAM", ImGuiTableColumnFlags_WidthFixed, 70);
663 ImGui::TableHeadersRow();
664
665 for (int i = 0; i < 8; ++i) {
666 ImGui::TableNextRow();
667 ImGui::TableNextColumn();
668 ImGui::Text("%d", i);
669 ImGui::TableNextColumn();
670 ImGui::Text(tr("0x%02X"), GetActualPaletteSlot(i, 0));
671 }
672 ImGui::EndTable();
673 }
674 ImGui::EndChild();
675 }
676
677 // Color preview - compact
678 if (ImGui::CollapsingHeader(tr("Colors"))) {
679 if (session_.overworld_palette().size() >= 256) {
680 int actual_slot = GetActualPaletteSlotForCurrentTile16();
681 ImGui::Text(tr("Slot %d:"), actual_slot);
682
683 for (int i = 0;
684 i < 8 && (actual_slot + i) <
685 static_cast<int>(session_.overworld_palette().size());
686 ++i) {
687 int color_index = actual_slot + i;
688 auto color = session_.overworld_palette()[color_index];
689 ImVec4 display_color = color.rgb();
690
691 ImGui::ColorButton(absl::StrFormat("##c%d", i).c_str(), display_color,
692 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
693 if (ImGui::IsItemHovered()) {
694 ImGui::SetTooltip(tr("%d:0x%04X"), color_index, color.snes());
695 }
696
697 if ((i + 1) % 4 != 0)
698 ImGui::SameLine();
699 }
700 }
701 }
702 }
703
704 return draw_status;
705}
706
708 bool* show_debug_info, bool* show_advanced_controls) {
709 const float available_width = ImGui::GetContentRegionAvail().x;
710 const int action_count = 4;
711 const int columns = ComputeTile16CompactActionColumnCount(available_width);
712 const int rows = ComputeTile16ActionRowCount(action_count, columns);
713 const float row_height = ImGui::GetTextLineHeightWithSpacing() +
714 rows * ImGui::GetFrameHeightWithSpacing() +
715 ImGui::GetStyle().FramePadding.y * 2.0f;
716 absl::Status action_status = absl::OkStatus();
717
718 if (ImGui::BeginChild(
719 "##Tile16CompactStatus", ImVec2(0, row_height), false,
720 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
721 ImGui::Text(tr("Tile16 0x%03X"), session_.current_tile16());
722 ImGui::SameLine();
723 ImGui::TextDisabled("| %s | Live edits",
724 EditModeLabel(session_.edit_mode()));
725 if (ImGui::IsItemHovered()) {
726 ImGui::SetTooltip(
727 "Edits update every use of this Tile16 in the document.\n"
728 "Use Undo/Redo to revise edits and Save to write the ROM.");
729 }
730
731 if (ImGui::BeginTable("##Tile16CompactActions", columns,
732 ImGuiTableFlags_SizingStretchProp)) {
733 int action_index = 0;
734 auto next_action_cell = [&]() {
735 if (action_index % columns == 0) {
736 ImGui::TableNextRow();
737 }
738 ImGui::TableNextColumn();
739 ++action_index;
740 };
741
742 next_action_cell();
743 if (!session_.CanUndo()) {
744 ImGui::BeginDisabled();
745 }
746 if (ImGui::Button(tr("Undo"),
747 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
748 action_status = Undo();
749 }
750 if (!session_.CanUndo()) {
751 ImGui::EndDisabled();
752 }
753
754 next_action_cell();
755 if (!session_.CanRedo()) {
756 ImGui::BeginDisabled();
757 }
758 if (ImGui::Button(tr("Redo"),
759 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
760 action_status = Redo();
761 }
762 if (!session_.CanRedo()) {
763 ImGui::EndDisabled();
764 }
765
766 next_action_cell();
767 const char* advanced_label = *show_advanced_controls
768 ? "Advanced*##Tile16AdvancedToggle"
769 : "Advanced##Tile16AdvancedToggle";
770 if (ImGui::Button(advanced_label,
771 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
772 *show_advanced_controls = !*show_advanced_controls;
773 }
774
775 next_action_cell();
776 const char* debug_label = *show_debug_info ? "Debug*##Tile16DebugToggle"
777 : "Debug##Tile16DebugToggle";
778 if (ImGui::Button(debug_label,
779 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
780 *show_debug_info = !*show_debug_info;
781 }
782
783 ImGui::EndTable();
784 }
785 }
786 ImGui::EndChild();
787 return action_status;
788}
789
791 bool show_debug_info) {
792 absl::Status draw_status;
793 // Palette selector - this is the paint brush palette for new placements.
794 Text(tr("Brush Palette:"));
795 if (show_debug_info) {
796 SameLine();
797 int actual_slot = GetActualPaletteSlotForCurrentTile16();
798 ImGui::TextDisabled(tr("(Slot %d)"), actual_slot);
799 }
800 ImGui::TextDisabled(tr("Used for new tile8 placements"));
801
802 // Compact palette grid
803 ImGui::BeginGroup();
804 float available_width = ImGui::GetContentRegionAvail().x;
805 float button_size = ComputeTile16PaletteButtonSize(available_width);
806 const gui::ButtonColorSet default_button_colors{
807 gui::GetThemeColor(ImGuiCol_Button),
808 gui::GetThemeColor(ImGuiCol_ButtonHovered),
809 gui::GetThemeColor(ImGuiCol_ButtonActive),
810 };
811 const gui::ButtonColorSet selected_button_colors =
813
814 for (int row = 0; row < 2; ++row) {
815 for (int col = 0; col < 4; ++col) {
816 if (col > 0)
817 ImGui::SameLine();
818
819 int i = row * 4 + col;
820 bool is_current = (session_.current_palette() == i);
821
822 // Modern button styling with better visual hierarchy
823 ImGui::PushID(i);
824
825 const gui::ButtonColorSet& palette_button_colors =
826 is_current ? selected_button_colors : default_button_colors;
827 const ImVec4 palette_button_border =
828 is_current ? gui::GetSuccessColor()
829 : gui::GetThemeColor(ImGuiCol_Border);
830 gui::StyleColorGuard palette_btn_colors(
831 {{ImGuiCol_Button, palette_button_colors.button},
832 {ImGuiCol_ButtonHovered, palette_button_colors.hovered},
833 {ImGuiCol_ButtonActive, palette_button_colors.active},
834 {ImGuiCol_Border, palette_button_border}});
835 gui::StyleVarGuard palette_btn_border(ImGuiStyleVar_FrameBorderSize,
836 1.0f);
837
838 if (ImGui::Button(absl::StrFormat("%d", i).c_str(),
839 ImVec2(button_size, button_size))) {
840 if (session_.current_palette() != i) {
842 auto status = RefreshAllPalettes();
843 if (!status.ok()) {
844 util::logf("Failed to refresh palettes: %s",
845 status.message().data());
846 } else {
847 util::logf("Palette successfully changed to %d",
849 }
850 }
851 }
852
853 ImGui::PopID();
854
855 // Right-click menu. Applying to all quadrants rewrites the tile's
856 // per-quadrant palettes, so it confirms instead of firing on RMB.
857 gui::ItemContextMenu(nullptr, [&, i]() {
858 std::vector<gui::MenuItemSpec> items;
859 items.emplace_back("Use Palette", ICON_MD_BRUSH, [this, i]() {
860 if (session_.current_palette() == i) {
861 return;
862 }
864 auto status = RefreshAllPalettes();
865 if (!status.ok()) {
866 util::logf("Failed to refresh palettes: %s",
867 status.message().data());
868 }
869 });
870 items.push_back(gui::MenuItemSpec::Destructive(
871 "Apply to All Quadrants...", ICON_MD_FORMAT_COLOR_FILL,
872 [this, i, &draw_status]() {
873 session_.set_current_palette(static_cast<uint8_t>(i));
874 draw_status.Update(ApplyPaletteToAll(session_.current_palette()));
875 }));
876 items.back().tooltip = "Sets all four quadrants to this palette.";
877 items.back().separator_after = true;
878
879 const auto& ow_palette = session_.overworld_palette();
880 const int slot = GetActualPaletteSlot(i, 0);
881 std::string colors;
882 for (int c = slot;
883 c < slot + 16 && c < static_cast<int>(ow_palette.size()); ++c) {
884 colors += absl::StrFormat("%s$%04X", colors.empty() ? "" : ",",
885 ow_palette[c].snes());
886 }
887 auto copy_item =
888 gui::CopyToClipboardItem("Copy Palette Colors", colors);
889 copy_item.enabled_condition = [has = !colors.empty()]() {
890 return has;
891 };
892 items.push_back(std::move(copy_item));
893 return items;
894 });
895
896 // Tooltip with palette info
897 if (ImGui::IsItemHovered()) {
898 ImGui::BeginTooltip();
899 if (show_debug_info) {
900 ImGui::Text(tr("Palette %d -> CGRAM 0x%02X"), i,
902 } else {
903 ImGui::Text(tr("Brush Palette %d"), i);
904 ImGui::TextDisabled(tr("Applied to new tile8 placements"));
905 ImGui::TextDisabled(tr("Right-click: palette actions"));
906 ImGui::TextDisabled(tr("Quadrant metadata is shown in strip below"));
907 ImGui::TextDisabled(
908 tr("Hotkeys: Ctrl+1..8 palette, 1..4 quadrant focus"));
909 if (is_current) {
911 }
912 }
913 ImGui::EndTooltip();
914 }
915 }
916 }
917 ImGui::EndGroup();
918
919 if (auto* tile_data = GetCurrentTile16Data(); tile_data != nullptr) {
921 Text(tr("Quadrant Focus:"));
922 SameLine();
923 ImGui::TextDisabled("1-4");
924 static constexpr std::array<const char*, 4> kQuadrantLabels = {"TL", "TR",
925 "BL", "BR"};
926 const float quadrant_button_width = std::max(58.0f, button_size + 24.0f);
927 const gui::ButtonColorSet active_quadrant_colors =
929 for (int q = 0; q < 4; ++q) {
930 if (q > 0) {
931 SameLine();
932 }
933
934 const gfx::TileInfo& info = zelda3::Tile16QuadrantInfo(*tile_data, q);
935 const uint8_t quadrant_palette = info.palette_;
936 const bool is_active_quadrant = (session_.active_quadrant() == q);
937 const bool matches_brush =
938 (quadrant_palette == session_.current_palette());
939
940 ImGui::PushID(100 + q);
941 const gui::ButtonColorSet& quadrant_button_colors =
942 is_active_quadrant ? active_quadrant_colors
943 : (matches_brush ? selected_button_colors
944 : default_button_colors);
945 const ImVec4 quadrant_border =
946 is_active_quadrant
948 : (matches_brush ? gui::GetSuccessColor()
949 : gui::GetThemeColor(ImGuiCol_Border));
950 gui::StyleColorGuard quadrant_btn_colors(
951 {{ImGuiCol_Button, quadrant_button_colors.button},
952 {ImGuiCol_ButtonHovered, quadrant_button_colors.hovered},
953 {ImGuiCol_ButtonActive, quadrant_button_colors.active},
954 {ImGuiCol_Border, quadrant_border}});
955 gui::StyleVarGuard quadrant_btn_border(ImGuiStyleVar_FrameBorderSize,
956 is_active_quadrant ? 2.0f : 1.0f);
957
958 if (ImGui::Button(absl::StrFormat("%d %s:%d", q + 1, kQuadrantLabels[q],
959 quadrant_palette)
960 .c_str(),
961 ImVec2(quadrant_button_width, 0))) {
963 if (session_.current_palette() != quadrant_palette) {
964 session_.set_current_palette(quadrant_palette);
965 auto status = RefreshAllPalettes();
966 if (!status.ok()) {
967 util::logf("Failed to refresh palettes: %s",
968 status.message().data());
969 }
970 }
971 }
972
973 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
975 draw_status.Update(
977 }
978
979 if (ImGui::IsItemHovered()) {
980 ImGui::BeginTooltip();
981 ImGui::Text(tr("Quadrant %s metadata"), kQuadrantLabels[q]);
982 ImGui::Separator();
983 ImGui::Text(tr("Tile8: %02X"), info.id_);
984 ImGui::Text(tr("Palette: %d"), quadrant_palette);
985 ImGui::Text(tr("Flip: H:%s V:%s"), info.horizontal_mirror_ ? "Y" : "N",
986 info.vertical_mirror_ ? "Y" : "N");
987 ImGui::Text(tr("Priority: %s"), info.over_ ? "Y" : "N");
988 ImGui::TextDisabled(tr("LMB: set brush palette from this quadrant"));
989 ImGui::TextDisabled(
990 tr("RMB: apply current brush palette to this quadrant"));
991 ImGui::TextDisabled(tr("Keys 1..4: focus TL/TR/BL/BR"));
992 ImGui::EndTooltip();
993 }
994
995 ImGui::PopID();
996 }
997
998 const gfx::TileInfo& active_info =
1000 ImGui::TextDisabled(tr("Active %s: Tile8 %02X | P%d | H:%s V:%s | Pri:%s"),
1001 kQuadrantLabels[session_.active_quadrant()],
1002 active_info.id_, active_info.palette_,
1003 active_info.horizontal_mirror_ ? "Y" : "N",
1004 active_info.vertical_mirror_ ? "Y" : "N",
1005 active_info.over_ ? "Y" : "N");
1006
1007 if (Button(tr("Apply Brush to Active Quadrant"), ImVec2(-1, 0))) {
1008 draw_status.Update(ApplyPaletteToQuadrant(session_.active_quadrant(),
1010 }
1011 HOVER_HINT(
1012 "Copy the Brush Palette into the selected quadrant metadata.\n"
1013 "Use keys 1..4 to change active quadrant quickly.");
1014 }
1015
1016 // Copy the current brush palette into all stored quadrant palette fields.
1017 if (Button(tr("Apply Brush to All Quadrants"), ImVec2(-1, 0))) {
1018 draw_status.Update(ApplyPaletteToAll(session_.current_palette()));
1019 }
1020 HOVER_HINT(
1021 "Copy the Brush Palette into Tile Palette metadata for all 4 "
1022 "quadrants.\n"
1023 "Tip: right-click any brush palette button above for a one-step apply.");
1024 return draw_status;
1025}
1026
1027absl::Status Tile16Editor::DrawTile8SourcePanel(float preferred_height) {
1028 absl::Status draw_status;
1031 ImGui::Text(tr("Tile8 Source"));
1032 ImGui::SameLine();
1033 ImGui::Checkbox(tr("Grid##Tile16GridToggle"),
1035 ImGui::SameLine();
1036 if (ImGui::Checkbox(tr("IDs##Tile16CollisionIds"),
1040 }
1041
1042 int source_bitmap_height = 0;
1046 if (session_.current_gfx_bmp() != nullptr &&
1049 ComputeTile8SourceDisplayScale(ImGui::GetContentRegionAvail().x,
1051 source_bitmap_height = session_.current_gfx_bmp()->height();
1053 ImVec2(session_.current_gfx_bmp()->width() *
1057 ImGui::SameLine();
1058 ImGui::TextDisabled(tr("%.1fx"), session_.tile8_source_display_scale());
1059 }
1060
1061 const float panel_height = ComputeTile8SourcePanelHeight(
1062 ImGui::GetContentRegionAvail().y, source_bitmap_height,
1063 session_.tile8_source_display_scale(), preferred_height);
1064
1065 if (BeginChild("##Tile8SourceScrollable", ImVec2(0, panel_height), true,
1066 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1067 gui::CanvasFrameOptions tile8_frame_opts;
1068 tile8_frame_opts.draw_grid = session_.show_tile_grid();
1069 tile8_frame_opts.grid_step =
1070 8.0f * session_.tile8_source_display_scale(); // Tile8 grid
1071 tile8_frame_opts.draw_context_menu = true;
1072 tile8_frame_opts.draw_overlay = true;
1073 tile8_frame_opts.render_popups = true;
1074 tile8_frame_opts.use_child_window = false;
1075
1076 auto tile8_rt = gui::BeginCanvas(tile8_source_canvas_, tile8_frame_opts);
1077
1080
1081 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
1082 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
1083
1084 // ZScream parity: hold right-click on tile8 source to show usage overlay.
1085 const bool temporary_usage = ComputeTile8UsageHighlight(
1086 ImGui::IsItemHovered(), ImGui::IsMouseDown(ImGuiMouseButton_Right));
1089 temporary_usage;
1090
1091 if (left_clicked || right_clicked) {
1092 draw_status.Update(HandleTile8SourceSelection(
1093 right_clicked, session_.tile8_source_display_scale()));
1094 }
1095
1096 if (session_.current_gfx_bmp() != nullptr) {
1099 }
1100
1101 gui::EndCanvas(tile8_source_canvas_, tile8_rt, tile8_frame_opts);
1102 }
1103 EndChild();
1104
1105 return draw_status;
1106}
1107
1108absl::Status Tile16Editor::HandleTile8SourceSelection(bool right_clicked,
1109 float display_scale) {
1110 const ImGuiIO& io = ImGui::GetIO();
1111 ImVec2 canvas_pos = tile8_source_canvas_.zero_point();
1112 ImVec2 mouse_pos =
1113 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1114
1115 const int new_tile8 = ComputeTile8IndexFromCanvasMouse(
1116 mouse_pos.x, mouse_pos.y, session_.current_gfx_bmp()->width(),
1117 static_cast<int>(session_.mutable_current_gfx_individual().size()),
1118 display_scale);
1119 if (new_tile8 < 0 || new_tile8 == session_.current_tile8()) {
1120 return absl::OkStatus();
1121 }
1122
1123 session_.set_current_tile8(new_tile8);
1125 if (right_clicked) {
1127 }
1128 util::logf("Selected Tile8: %d", session_.current_tile8());
1129
1130 return absl::OkStatus();
1131}
1132
1134 // Local tile-shaping actions stay in the right column.
1135 if (Button(tr("Clear"), ImVec2(-1, 0))) {
1137 }
1138
1139 if (Button(tr("Copy"), ImVec2(-1, 0))) {
1141 }
1142
1143 if (Button(tr("Paste"), ImVec2(-1, 0))) {
1145 }
1146
1147 return absl::OkStatus();
1148}
1149
1153 return;
1154 }
1155
1157 auto cache_status = session_.RebuildTile8UsageCache();
1158 if (!cache_status.ok()) {
1159 util::logf("Tile8 usage cache rebuild failed: %s",
1160 cache_status.message().data());
1161 return;
1162 }
1163 }
1164
1165 const auto& hits = session_.tile8_usage_cache()[session_.current_tile8()];
1166 if (hits.empty()) {
1167 return;
1168 }
1169
1170 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1171 const ImVec2 canvas_pos = blockset_canvas_.zero_point();
1172 const float scale = blockset_canvas_.GetGlobalScale();
1173 const float tile16_display = 32.0f * scale;
1174 const float quadrant_display = 16.0f * scale;
1175 const int tiles_per_row =
1176 std::max(1, session_.tile16_blockset_bmp()->width() / kTile16Size);
1177
1178 for (const auto& hit : hits) {
1179 const int tile_x = hit.tile16_id % tiles_per_row;
1180 const int tile_y = hit.tile16_id / tiles_per_row;
1181 const int quad_x = hit.quadrant % 2;
1182 const int quad_y = hit.quadrant / 2;
1183
1184 const ImVec2 min(
1185 canvas_pos.x + (tile_x * tile16_display) + (quad_x * quadrant_display),
1186 canvas_pos.y + (tile_y * tile16_display) + (quad_y * quadrant_display));
1187 const ImVec2 max(min.x + quadrant_display, min.y + quadrant_display);
1188
1189 // Mirrors ZScream's right-click usage tint (purple, transparent).
1190 draw_list->AddRectFilled(min, max, IM_COL32(150, 0, 210, 80));
1191 draw_list->AddRect(min, max, IM_COL32(215, 170, 255, 180));
1192 }
1193}
1194
1197 if (Begin("Advanced Palette Settings",
1199 Text(tr("Pixel Normalization & Color Correction:"));
1200
1201 int mask_value = static_cast<int>(session_.palette_normalization_mask());
1202 if (SliderInt(tr("Normalization Mask"), &mask_value, 1, 255, "0x%02X")) {
1204 static_cast<uint8_t>(mask_value);
1205 }
1206
1207 Checkbox(tr("Auto Normalize Pixels"),
1209
1210 if (Button(tr("Apply to All Graphics"))) {
1211 auto reload_result = LoadTile8();
1212 if (!reload_result.ok()) {
1213 Text(tr("Error: %s"), reload_result.message().data());
1214 }
1215 }
1216
1217 SameLine();
1218 if (Button(tr("Reset Defaults"))) {
1221 auto reload_result = LoadTile8();
1222 (void)reload_result; // Suppress warning
1223 }
1224
1225 Separator();
1226 Text(tr("Current State:"));
1227 static constexpr std::array<const char*, 7> palette_group_names = {
1228 "OW Main", "OW Aux", "OW Anim", "Dungeon",
1229 "Sprites", "Armor", "Sword"};
1230 Text(tr("Palette Group: %d (%s)"), session_.current_palette_group(),
1232 ? palette_group_names[session_.current_palette_group()]
1233 : "Unknown");
1234 Text(tr("Current Palette: %d"), session_.current_palette());
1235
1236 Separator();
1237 Text(tr("Sheet-Specific Fixes:"));
1238
1239 // Sheet-specific palette fixes
1240 static bool fix_sheet_0 = true;
1241 static bool fix_sprite_sheets = true;
1242 static bool use_transparent_for_terrain = false;
1243
1244 if (Checkbox(tr("Fix Sheet 0 (Trees)"), &fix_sheet_0)) {
1245 auto reload_result = LoadTile8();
1246 if (!reload_result.ok()) {
1247 Text(tr("Error reloading: %s"), reload_result.message().data());
1248 }
1249 }
1250 HOVER_HINT(
1251 "Use direct palette for sheet 0 instead of transparent palette");
1252
1253 if (Checkbox(tr("Fix Sprite Sheets"), &fix_sprite_sheets)) {
1254 auto reload_result = LoadTile8();
1255 if (!reload_result.ok()) {
1256 Text(tr("Error reloading: %s"), reload_result.message().data());
1257 }
1258 }
1259 HOVER_HINT("Use direct palette for sprite graphics sheets");
1260
1261 if (Checkbox(tr("Transparent for Terrain"),
1262 &use_transparent_for_terrain)) {
1263 auto reload_result = LoadTile8();
1264 if (!reload_result.ok()) {
1265 Text(tr("Error reloading: %s"), reload_result.message().data());
1266 }
1267 }
1268 HOVER_HINT("Force transparent palette for terrain graphics");
1269
1270 Separator();
1271 Text(tr("Color Analysis:"));
1272 if (session_.current_tile8() >= 0 &&
1274 static_cast<int>(
1276 Text(tr("Selected Tile8 Analysis:"));
1277 const auto& tile_data =
1279 std::map<uint8_t, int> pixel_counts;
1280 for (uint8_t pixel : tile_data) {
1281 pixel_counts[pixel & 0x0F]++; // Normalize to 4-bit
1282 }
1283
1284 Text(tr("Pixel Value Distribution:"));
1285 for (const auto& pair : pixel_counts) {
1286 int value = pair.first;
1287 int count = pair.second;
1288 Text(tr(" Value %d (0x%X): %d pixels"), value, value, count);
1289 }
1290
1291 Text(tr("Palette Colors Used:"));
1292 const gfx::SnesPalette* analysis_palette =
1294 if (analysis_palette == nullptr || analysis_palette->empty()) {
1295 analysis_palette = session_.current_gfx_bmp()
1297 : &session_.palette();
1298 }
1299 for (const auto& pair : pixel_counts) {
1300 int value = pair.first;
1301 int count = pair.second;
1302 if (value < static_cast<int>(analysis_palette->size())) {
1303 auto color = (*analysis_palette)[value];
1304 ImVec4 display_color = color.rgb();
1305 ImGui::ColorButton(("##analysis" + std::to_string(value)).c_str(),
1306 display_color, ImGuiColorEditFlags_NoTooltip,
1307 ImVec2(16, 16));
1308 if (ImGui::IsItemHovered()) {
1309 ImGui::SetTooltip(tr("Index %d: 0x%04X (%d pixels)"), value,
1310 color.snes(), count);
1311 }
1312 if (value % 8 != 7)
1313 ImGui::SameLine();
1314 }
1315 }
1316 }
1317
1318 // Enhanced ROM Palette Management Section
1319 Separator();
1320 if (CollapsingHeader(tr("ROM Palette Manager")) && session_.rom()) {
1321 Text(tr("Experimental ROM Palette Selection:"));
1322 HOVER_HINT(
1323 "Use ROM palettes to experiment with different color schemes");
1324
1325 if (Button(tr("Open Enhanced Palette Editor"))) {
1327 }
1328 SameLine();
1329 if (Button(tr("Show Color Analysis"))) {
1331 }
1332
1333 // Quick palette application
1334 static int quick_group = 0;
1335 static int quick_index = 0;
1336
1337 SliderInt(tr("ROM Group"), &quick_group, 0, 6);
1338 SliderInt(tr("Palette Index"), &quick_index, 0, 7);
1339
1340 if (Button(tr("Apply to Tile8 Source"))) {
1341 if (tile8_source_canvas_.ApplyROMPalette(quick_group, quick_index)) {
1342 util::logf("Applied ROM palette group %d, index %d to Tile8 source",
1343 quick_group, quick_index);
1344 }
1345 }
1346 SameLine();
1347 if (Button(tr("Apply to Tile16 Editor"))) {
1348 if (tile16_edit_canvas_.ApplyROMPalette(quick_group, quick_index)) {
1349 util::logf(
1350 "Applied ROM palette group %d, index %d to Tile16 editor",
1351 quick_group, quick_index);
1352 }
1353 }
1354 }
1355 }
1356 End();
1357 }
1358}
1359
1361 Text(tr("Layout Scratch:"));
1362 for (int i = 0; i < 4; ++i) {
1363 ImGui::PushID(i);
1364 std::string slot_name = "S" + std::to_string(i + 1);
1365
1366 if (Button((slot_name + " Save").c_str(), ImVec2(70, 20))) {
1368 }
1369 SameLine();
1370
1371 bool can_load = session_.layout_scratch(i).in_use;
1372 if (!can_load) {
1373 ImGui::BeginDisabled();
1374 }
1375 if (Button((slot_name + " Load").c_str(), ImVec2(70, 20)) && can_load) {
1377 }
1378 if (!can_load) {
1379 ImGui::EndDisabled();
1380 }
1381 SameLine();
1382 TextDisabled("%s", session_.layout_scratch(i).name.c_str());
1383 ImGui::PopID();
1384 }
1385}
1386
1388 if (ImGui::BeginPopupModal("ManualTile8Editor", nullptr,
1389 ImGuiWindowFlags_AlwaysAutoResize)) {
1390 ImGui::Text("Tile16 0x%03X — quadrant properties",
1392 auto edited = session_.current_tile16_data();
1393 bool changed = false;
1394 const char* names[] = {"Top left", "Top right", "Bottom left",
1395 "Bottom right"};
1396 for (int q = 0; q < 4; ++q) {
1397 ImGui::PushID(q);
1398 ImGui::SeparatorText(names[q]);
1399 auto& info = TileInfoForQuadrant(&edited, q);
1400 int id = info.id_;
1401 if (ImGui::InputInt("Tile8", &id)) {
1402 info.id_ = std::clamp(id, 0, 1023);
1403 changed = true;
1404 }
1405 int palette = info.palette_;
1406 if (ImGui::SliderInt("Palette", &palette, 0, 7)) {
1407 info.palette_ = palette;
1408 changed = true;
1409 }
1410 changed |= ImGui::Checkbox("X Flip", &info.horizontal_mirror_);
1411 ImGui::SameLine();
1412 changed |= ImGui::Checkbox("Y Flip", &info.vertical_mirror_);
1413 ImGui::SameLine();
1414 changed |= ImGui::Checkbox("Priority", &info.over_);
1415 ImGui::PopID();
1416 }
1417 if (changed)
1418 status_ = ReplaceCurrentTile(edited);
1419 if (ImGui::Button("Close"))
1420 ImGui::CloseCurrentPopup();
1421 ImGui::EndPopup();
1422 }
1423}
1424
1425} // namespace editor
1426} // namespace yaze
const gfx::SnesPalette & overworld_palette() const
Tile16ScratchData & scratch_slot(int i)
absl::Status BuildStampPreview(Tile16LocalPos pos, gfx::Bitmap *output) const
Tile16LayoutScratch & layout_scratch(int i)
std::vector< zelda3::Tile8PixelData > & mutable_current_gfx_individual()
const gfx::SnesPalette & palette() const
const gfx::SnesPalette * ResolveDisplayPalette() const
const zelda3::Tile8UsageIndex & tile8_usage_cache() const
void set_edit_mode(Tile16EditMode mode)
absl::Status SaveTile16ToScratchSpace(int slot)
absl::Status LoadLayoutFromScratch(int slot)
static ImVec2 Tile16PreviewDisplayPixelToTilePosition(const ImVec2 &display_position)
absl::Status DrawTile16EditorWorkbenchColumn(bool show_debug_info, bool show_advanced_controls)
absl::Status HandleTile16CanvasClick(const ImVec2 &tile_position, bool left_click, bool right_click)
absl::Status ReplaceCurrentTile(const gfx::Tile16 &data)
void AnalyzeTile8SourceData() const
absl::Status ApplyPaletteToAll(uint8_t palette_id)
int GetActualPaletteSlotForCurrentTile16() const
absl::Status HandleTile8SourceSelection(bool right_clicked, float display_scale)
absl::Status PasteTile16FromClipboard()
gui::TileSelectorWidget blockset_selector_
absl::Status SaveLayoutToScratch(int slot)
absl::Status LoadTile16FromScratchSpace(int slot)
gfx::SnesPalette CreateRemappedPaletteForViewing(const gfx::SnesPalette &source, int target_row) const
int GetActualPaletteSlot(int palette_button, int sheet_index) const
absl::Status FlipTile16Horizontal()
absl::Status DrawBrushAndTilePaletteControls(bool show_debug_info)
absl::Status DrawTile8SourcePanel(float preferred_height=0.0f)
absl::Status UpdateTile8Palette(int tile8_id)
absl::Status RefreshTile16Blockset()
absl::Status RefreshAllPalettes()
absl::Status ApplyPaletteToQuadrant(int quadrant, uint8_t palette_id)
absl::Status DrawTile16NavigationHeader(int total_tiles)
gfx::Tile16 * GetCurrentTile16Data()
absl::Status DrawCompactActionStatusRow(bool *show_debug_info, bool *show_advanced_controls)
void RequestTileSwitch(int target_tile_id)
absl::Status CopyTile16ToClipboard(int tile_id)
absl::Status ClearScratchSpace(int slot)
int GetSheetIndexForTile8(int tile8_id) const
absl::Status FlipTile16Vertical()
Tile16EditSession session_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:39
static Arena & Get()
Definition arena.cc:24
const SnesPalette & palette() const
Definition bitmap.h:393
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:198
TextureHandle texture() const
Definition bitmap.h:410
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
Definition bitmap.cc:373
bool is_active() const
Definition bitmap.h:414
int height() const
Definition bitmap.h:399
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:452
int width() const
Definition bitmap.h:398
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:408
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1293
void ShowScalingControls()
Definition canvas.cc:1834
void ShowAdvancedCanvasProperties()
Definition canvas.cc:1709
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1229
auto custom_labels_enabled()
Definition canvas.h:414
void SetCanvasSize(ImVec2 canvas_size)
Definition canvas.h:388
void set_draggable(bool draggable)
Definition canvas.h:376
float GetGlobalScale() const
Definition canvas.h:392
auto zero_point() const
Definition canvas.h:364
void ShowPaletteEditor()
Definition canvas.cc:449
bool ApplyROMPalette(int group_index, int palette_index)
Definition canvas.cc:475
void ShowColorAnalysis()
Definition canvas.cc:463
static void SectionHeader(const char *label)
RAII guard for ImGui style colors.
Definition style_guard.h:27
RAII guard for ImGui style vars.
Definition style_guard.h:68
RenderResult Render(gfx::Bitmap &atlas, bool atlas_ready)
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define HOVER_HINT(string)
Definition macro.h:24
Definition input.cc:30
float ComputeTile8SourcePanelHeight(float available_height_px, int source_bitmap_height_px, float display_scale, float preferred_height_px=0.0f, float min_height_px=220.0f, float max_height_px=520.0f)
constexpr int kTile8PixelCount
int ComputeTile16CompactActionColumnCount(float available_width_px)
constexpr float kTile8DisplayScale
float ComputeTile16PaletteButtonSize(float available_width_px)
bool ComputeTile8UsageHighlight(bool source_hovered, bool right_mouse_down)
int ComputeTile16ActionRowCount(int action_count, int column_count)
constexpr int kTile8Size
constexpr int kTile16Size
int ComputeTile8IndexFromCanvasMouse(float mouse_x, float mouse_y, int source_bitmap_width_px, int max_tile_count, float display_scale)
float ComputeTile8SourceDisplayScale(float available_width_px, int source_bitmap_width_px, float min_scale=1.0f, float max_scale=4.0f)
void EndCanvas(Canvas &canvas)
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:49
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
bool ItemContextMenu(const char *id, const std::function< std::vector< MenuItemSpec >()> &build, ImGuiPopupFlags flags)
Right-click menu for the last submitted ImGui item.
ButtonColorSet GetSuccessButtonColors()
MenuItemSpec CopyToClipboardItem(const std::string &label, const std::string &text)
Color GetThemeColor(const std::string &color_name)
void ThemedText(const char *text, SemanticColor color)
ImVec4 GetAccentColor()
Definition ui_helpers.cc:69
ButtonColorSet GetPrimaryButtonColors()
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:146
gfx::TileInfo & MutableTile16QuadrantInfo(gfx::Tile16 &tile, int quadrant)
void SyncTile16TilesInfo(gfx::Tile16 *tile)
int ComputeTile16Count(const gfx::Tilemap *tile16_blockset)
const gfx::TileInfo & Tile16QuadrantInfo(const gfx::Tile16 &tile, int quadrant)
constexpr int kMaxTile8UsageId
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::optional< float > grid_step
static CanvasMenuItem Destructive(const std::string &lbl, const std::string &ico, std::function< void()> cb, bool confirm=true, const std::string &sc="")