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 // These keys work only while the Tile16 panel has focus, so they are tooltips
73 // instead of menu hints that would read as global (and clash with Copy/Paste).
74 if (MenuItem(tr("Copy Current Tile16"))) {
76 }
77 SetItemTooltip("%s", tr("Ctrl+C in the Tile16 panel"));
78 if (MenuItem(tr("Paste to Current Tile16"))) {
80 }
81 SetItemTooltip("%s", tr("Ctrl+V in the Tile16 panel"));
82 Separator();
83 if (MenuItem(tr("Flip Tile16 Horizontal"))) {
85 }
86 SetItemTooltip("%s", tr("H in the Tile16 panel"));
87 if (MenuItem(tr("Flip Tile16 Vertical"))) {
89 }
90 SetItemTooltip("%s", tr("V in the Tile16 panel"));
91 if (MenuItem(tr("Rotate Tile16"))) {
93 }
94 SetItemTooltip("%s", tr("R in the Tile16 panel"));
95 if (MenuItem(tr("Clear Tile16"))) {
97 }
98 SetItemTooltip("%s", tr("Delete in the Tile16 panel"));
99}
100
102 if (MenuItem(tr("Show Grid"), nullptr, session_.show_tile_grid())) {
104 }
105 if (MenuItem(tr("Show Collision IDs"), nullptr,
111 }
112 Separator();
113 if (BeginMenu(tr("Scratch Slots"))) {
114 for (int i = 0; i < 4; i++) {
115 std::string slot_name = "Slot " + std::to_string(i + 1);
117 if (MenuItem((slot_name + " (Load)").c_str())) {
119 }
120 if (MenuItem((slot_name + " (Save)").c_str())) {
122 }
123 if (MenuItem((slot_name + " (Clear)").c_str())) {
125 }
126 } else {
127 if (MenuItem((slot_name + " (Save)").c_str())) {
129 }
130 }
131 if (i < 3)
132 Separator();
133 }
134 EndMenu();
135 }
136}
137
139 absl::Status draw_status;
140 static bool show_advanced_controls = false;
141 static bool show_debug_info = false;
142
143 gui::StyleVarGuard header_var_guard(
144 {{ImGuiStyleVar_FramePadding, ImVec2(8, 4)},
145 {ImGuiStyleVar_ItemSpacing, ImVec2(8, 4)}});
146
147 draw_status.Update(
148 DrawCompactActionStatusRow(&show_debug_info, &show_advanced_controls));
149
150 ImGui::Separator();
151
152 const int total_tiles =
154 const float layout_width = ImGui::GetContentRegionAvail().x;
155 const bool compact_layout = layout_width < 900.0f;
156 const int layout_columns = compact_layout ? 1 : 2;
157
158 if (ImGui::BeginTable(
159 "##Tile16EditLayout", layout_columns,
160 ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) {
161 if (compact_layout) {
162 ImGui::TableSetupColumn("Tile16 Workbench",
163 ImGuiTableColumnFlags_WidthStretch, 1.0f);
164 } else {
165 ImGui::TableSetupColumn("Tile8 Source",
166 ImGuiTableColumnFlags_WidthStretch, 0.58f);
167 ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
168 0.42f);
169 }
170
171 if (compact_layout) {
172 ImGui::TableNextRow();
173 ImGui::TableNextColumn();
174 ImGui::BeginGroup();
175 gui::LayoutHelpers::SectionHeader(tr("Tile16 Editor"));
176 draw_status.Update(DrawTile16NavigationHeader(total_tiles));
177 draw_status.Update(DrawTile16EditorWorkbenchColumn(
178 show_debug_info, show_advanced_controls));
179 ImGui::EndGroup();
180
181 ImGui::TableNextRow();
182 ImGui::TableNextColumn();
183 ImGui::BeginGroup();
184 gui::LayoutHelpers::SectionHeader(tr("Tile8 Source"));
185 draw_status.Update(DrawTile8SourcePanel(320.0f));
186 ImGui::EndGroup();
187 } else {
188 ImGui::TableNextRow();
189 ImGui::TableNextColumn();
190 ImGui::BeginGroup();
191 gui::LayoutHelpers::SectionHeader(tr("Tile8 Source"));
192 draw_status.Update(DrawTile8SourcePanel(0.0f));
193 ImGui::EndGroup();
194
195 ImGui::TableNextColumn();
196 ImGui::BeginGroup();
197 gui::LayoutHelpers::SectionHeader(tr("Tile16 Editor"));
198 draw_status.Update(DrawTile16NavigationHeader(total_tiles));
199 draw_status.Update(DrawTile16EditorWorkbenchColumn(
200 show_debug_info, show_advanced_controls));
201 ImGui::EndGroup();
202 }
203
204 ImGui::EndTable();
205 }
206
208
215
216 return draw_status;
217}
218
219absl::Status Tile16Editor::DrawTile16NavigationHeader(int total_tiles) {
220 gui::StyleVarGuard nav_spacing_guard(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
221
222 ImGui::TextDisabled(tr("0x%03X / 0x%03X"), session_.current_tile16(),
223 std::max(0, total_tiles - 1));
224 ImGui::SameLine();
225
226 ImGui::SetNextItemWidth(80);
227 if (ImGui::InputInt("##JumpToTile", session_.mutable_jump_to_tile_id(), 0,
228 0)) {
230 std::clamp(session_.jump_to_tile_id(), 0, total_tiles - 1);
234 }
235 }
236 if (ImGui::IsItemHovered()) {
237 ImGui::SetTooltip(tr("Tile ID (0-%d) - navigates as you type"),
238 total_tiles - 1);
239 }
240
241 const int total_pages = (total_tiles + Tile16EditSession::kTilesPerPage - 1) /
245
246 ImGui::SameLine();
247 if (ImGui::Button("<<")) {
250 }
251 if (ImGui::IsItemHovered())
252 ImGui::SetTooltip(tr("First page"));
253
254 ImGui::SameLine();
255 if (ImGui::Button("<")) {
256 int new_tile = std::max(
258 RequestTileSwitch(new_tile);
260 }
261 if (ImGui::IsItemHovered())
262 ImGui::SetTooltip(tr("Previous page (PageUp)"));
263
264 ImGui::SameLine();
265 ImGui::TextDisabled(tr("Page %d/%d"), session_.current_page() + 1,
266 total_pages);
267
268 ImGui::SameLine();
269 if (ImGui::Button(">")) {
270 int new_tile =
271 std::min(total_tiles - 1,
273 RequestTileSwitch(new_tile);
275 }
276 if (ImGui::IsItemHovered())
277 ImGui::SetTooltip(tr("Next page (PageDown)"));
278
279 ImGui::SameLine();
280 if (ImGui::Button(">>")) {
281 RequestTileSwitch(total_tiles - 1);
283 }
284 if (ImGui::IsItemHovered())
285 ImGui::SetTooltip(tr("Last page"));
286
287 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
288 if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
289 int new_tile = std::max(
291 RequestTileSwitch(new_tile);
293 }
294 if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
295 int new_tile =
296 std::min(total_tiles - 1, session_.current_tile16() +
298 RequestTileSwitch(new_tile);
300 }
301 if (ImGui::IsKeyPressed(ImGuiKey_Home)) {
304 }
305 if (ImGui::IsKeyPressed(ImGuiKey_End)) {
306 RequestTileSwitch(total_tiles - 1);
308 }
309
310 if (!ImGui::GetIO().KeyCtrl) {
311 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) &&
312 session_.current_tile16() > 0) {
315 }
316 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) &&
317 session_.current_tile16() < total_tiles - 1) {
320 }
321 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) &&
326 }
327 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) &&
329 total_tiles) {
333 }
334 }
335 }
336
337 return absl::OkStatus();
338}
339
341 bool show_debug_info, bool show_advanced_controls) {
342 absl::Status draw_status;
343 // The four-tile stamp needs room for its full 2x2 Tile16 footprint.
344 const float preview_size = session_.tile8_stamp_size() == 4 ? 128.0f : 64.0f;
345 if (ImGui::BeginChild(
346 "##Tile16FixedCanvas", ImVec2(preview_size + 26, preview_size + 26),
347 true,
348 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
349 // Configure canvas frame options for tile16 editor
350 gui::CanvasFrameOptions tile16_edit_frame_opts;
351 tile16_edit_frame_opts.canvas_size = ImVec2(preview_size, preview_size);
352 tile16_edit_frame_opts.draw_grid = session_.show_tile_grid();
353 tile16_edit_frame_opts.grid_step =
354 kTile8Size *
355 kTile8DisplayScale; // 8x8 source pixels at 4x preview scale.
356 tile16_edit_frame_opts.draw_context_menu = true;
357 tile16_edit_frame_opts.draw_overlay = true;
358 tile16_edit_frame_opts.render_popups = true;
359 tile16_edit_frame_opts.use_child_window = false;
360
361 auto tile16_edit_rt =
362 gui::BeginCanvas(tile16_edit_canvas_, tile16_edit_frame_opts);
363
364 // Draw current tile16 bitmap with dynamic zoom
368 }
369
370 // Handle tile8 painting with improved hover preview
371 if (session_.current_tile8() >= 0 &&
373 static_cast<int>(
375 // Create a display tile that shows the current palette selection
378 8, 8, 8, std::vector<uint8_t>(kTile8PixelCount, 0));
379 }
380
381 // Get the original pixel data (already has sheet offsets from
382 // ProcessGraphicsBuffer)
383 auto& preview_data =
385 std::copy(
387 .begin(),
389 .end(),
390 preview_data.begin());
391
392 // Apply the correct sheet-aware palette slice for the preview
393 const gfx::SnesPalette* display_palette = nullptr;
394 if (session_.overworld_palette().size() >= 256) {
395 display_palette = &session_.overworld_palette();
396 } else if (session_.palette().size() >= 256) {
397 display_palette = &session_.palette();
398 } else {
399 display_palette = session_.current_gfx_bmp()
401 : &session_.palette();
402 }
403
404 if (display_palette && !display_palette->empty()) {
406 CreateRemappedPaletteForViewing(*display_palette,
408 }
409
410 // Apply flips if needed
411 if (session_.x_flip() || session_.y_flip()) {
413
414 if (session_.x_flip()) {
415 for (int y = 0; y < 8; ++y) {
416 for (int x = 0; x < 4; ++x) {
417 std::swap(data[y * 8 + x], data[y * 8 + (7 - x)]);
418 }
419 }
420 }
421
422 if (session_.y_flip()) {
423 for (int y = 0; y < 4; ++y) {
424 for (int x = 0; x < 8; ++x) {
425 std::swap(data[y * 8 + x], data[(7 - y) * 8 + x]);
426 }
427 }
428 }
429 }
430
431 // Push pixel changes to the existing surface before queuing texture work
433
434 // Queue texture creation/update on the persistent preview bitmap to
435 // avoid dangling stack pointers in the arena queue
436 const auto preview_command =
441 preview_command, &session_.mutable_tile8_preview_bitmap());
442
443 // Preview the same mutations the click will publish. Pick/Usage modes
444 // must keep the actual tile visible rather than cover it with a brush.
445 if (tile16_edit_rt.hovered &&
447 const ImVec2 origin = tile16_edit_canvas_.zero_point();
448 const auto mouse = ImGui::GetIO().MousePos;
449 const auto preview_status = session_.BuildStampPreview(
450 {(mouse.x - origin.x) / kTile8DisplayScale,
451 (mouse.y - origin.y) / kTile8DisplayScale},
453 draw_status.Update(preview_status);
454 if (preview_status.ok()) {
462 }
463 }
464
465 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
466 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
467
468 if (left_clicked || right_clicked) {
469 const ImGuiIO& io = ImGui::GetIO();
470 ImVec2 canvas_pos = tile16_edit_canvas_.zero_point();
471 ImVec2 mouse_pos =
472 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
473
474 // The hover tile painter and bitmap share a 0,0 origin, matching
475 // ZScream/HMagic's direct preview hit-test behavior.
476 const ImVec2 tile_position =
478 int tile_x = static_cast<int>(tile_position.x);
479 int tile_y = static_cast<int>(tile_position.y);
480
481 // Clamp to valid range (0-15 for 16x16 tile)
482 tile_x = std::max(0, std::min(15, tile_x));
483 tile_y = std::max(0, std::min(15, tile_y));
484
486 "Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d), mode=%s",
487 mouse_pos.x, mouse_pos.y, tile_x, tile_y,
488 EditModeLabel(session_.edit_mode()));
489
490 draw_status.Update(HandleTile16CanvasClick(
491 ImVec2(tile_x, tile_y), left_clicked, right_clicked));
492 }
493 }
494
495 gui::EndCanvas(tile16_edit_canvas_, tile16_edit_rt, tile16_edit_frame_opts);
496 }
497 ImGui::EndChild();
498
499 Separator();
500
501 // === Compact Controls Section ===
502
503 // Tile8 info and preview
504 if (session_.current_tile8() >= 0 &&
506 static_cast<int>(session_.mutable_current_gfx_individual().size())) {
507 Text(tr("Tile8: %02X"), session_.current_tile8());
508 SameLine();
509 auto* tile8_texture = session_.mutable_tile8_preview_bitmap().texture();
510 if (tile8_texture) {
511 ImGui::Image((ImTextureID)(intptr_t)tile8_texture, ImVec2(24, 24));
512 }
513
514 const int sheet_idx = GetSheetIndexForTile8(session_.current_tile8());
515 ImGui::SameLine();
516 ImGui::TextDisabled(tr("G%d"), sheet_idx);
517 if (ImGui::IsItemHovered()) {
518 ImGui::BeginTooltip();
519 ImGui::Text(tr("Graphics chunk: %d"), sheet_idx);
520 ImGui::EndTooltip();
521 }
522 }
523
524 // Tile8 transform options in compact form
525 Checkbox(tr("X Flip"), session_.mutable_x_flip());
526 SameLine();
527 Checkbox(tr("Y Flip"), session_.mutable_y_flip());
528 SameLine();
529 Checkbox(tr("Priority"), session_.mutable_priority_tile());
530
531 Text(tr("Stamp:"));
532 SameLine();
533 if (ImGui::RadioButton(tr("1x"), session_.tile8_stamp_size() == 1)) {
535 }
536 SameLine();
537 if (ImGui::RadioButton(tr("2x"), session_.tile8_stamp_size() == 2)) {
539 }
540 SameLine();
541 if (ImGui::RadioButton(tr("4x"), session_.tile8_stamp_size() == 4)) {
543 }
545 "1x: paint one quadrant\n2x: fill current tile16 from a 2x2 tile8 "
546 "block\n4x: stamp a 2x2 tile16 patch from a 4x4 tile8 block");
547
548 Text(tr("Edit Mode:"));
549 if (ImGui::RadioButton(tr("Paint (P)"),
552 }
553 SameLine();
554 if (ImGui::RadioButton(tr("Pick (I)"),
557 }
558 SameLine();
559 if (ImGui::RadioButton(tr("Usage (U)"),
563 }
565 "Paint: left-click places Tile8 into Tile16.\n"
566 "Pick: left-click samples Tile8 from Tile16.\n"
567 "Usage: keeps usage overlay visible and samples on click.\n"
568 "Right-click on Tile16 preview always samples.");
569
572 if (ImGui::CollapsingHeader(tr("Usage Navigator"),
573 ImGuiTreeNodeFlags_DefaultOpen)) {
574 const float nav_height = 220.0f;
575 if (BeginChild("##UsageBlocksetNav", ImVec2(0, nav_height), true,
576 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
580 }
583 opts.grid_step = 32.0f;
584 opts.draw_overlay = true;
585 opts.use_child_window = false;
586 auto rt = gui::BeginCanvas(blockset_canvas_, opts);
587 if (session_.tile16_blockset_bmp() != nullptr) {
588 auto result = blockset_selector_.Render(
591 if (result.selection_changed) {
592 RequestTileSwitch(result.selected_tile);
593 }
594 }
597 }
598 EndChild();
599 }
600 }
601
602 Separator();
603
604 draw_status.Update(DrawBrushAndTilePaletteControls(show_debug_info));
605
606 Separator();
607
608 draw_status.Update(DrawPrimaryActionControls());
609
610 // Advanced controls (collapsible)
611 if (show_advanced_controls) {
612 Separator();
613 Text(tr("Advanced:"));
614
615 if (Button(tr("Palette Settings"), ImVec2(-1, 0))) {
618 }
619
620 if (Button(tr("Analyze Data"), ImVec2(-1, 0))) {
622 }
623 HOVER_HINT("Analyze tile8 source data format and palette state");
624
625 if (Button(tr("Manual Edit"), ImVec2(-1, 0))) {
626 ImGui::OpenPopup("ManualTile8Editor");
627 }
628
629 if (Button(tr("Refresh Blockset"), ImVec2(-1, 0))) {
630 draw_status.Update(RefreshTile16Blockset());
631 }
632
633 // Scratch space in compact form
634 Text(tr("Scratch:"));
636
637 // Manual tile8 editor popup
639 }
640
641 // Compact debug information panel
642 if (show_debug_info) {
643 Separator();
644 Text(tr("Debug:"));
645 ImGui::TextDisabled(tr("T16:%02X T8:%d Pal:%d"), session_.current_tile16(),
647
648 if (session_.current_tile8() >= 0) {
649 int sheet_index = GetSheetIndexForTile8(session_.current_tile8());
650 int actual_slot =
652 ImGui::TextDisabled(tr("Sheet:%d Slot:%d"), sheet_index, actual_slot);
653 }
654
655 // Compact palette mapping table
656 if (ImGui::CollapsingHeader(tr("Palette Map"),
657 ImGuiTreeNodeFlags_DefaultOpen)) {
658 ImGui::BeginChild("##PaletteMappingScroll", ImVec2(0, 120), true);
659 if (ImGui::BeginTable("##PalMap", 2,
660 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
661 ImGuiTableFlags_SizingFixedFit)) {
662 ImGui::TableSetupColumn("Btn", ImGuiTableColumnFlags_WidthFixed, 30);
663 ImGui::TableSetupColumn("CGRAM", ImGuiTableColumnFlags_WidthFixed, 70);
664 ImGui::TableHeadersRow();
665
666 for (int i = 0; i < 8; ++i) {
667 ImGui::TableNextRow();
668 ImGui::TableNextColumn();
669 ImGui::Text("%d", i);
670 ImGui::TableNextColumn();
671 ImGui::Text(tr("0x%02X"), GetActualPaletteSlot(i, 0));
672 }
673 ImGui::EndTable();
674 }
675 ImGui::EndChild();
676 }
677
678 // Color preview - compact
679 if (ImGui::CollapsingHeader(tr("Colors"))) {
680 if (session_.overworld_palette().size() >= 256) {
681 int actual_slot = GetActualPaletteSlotForCurrentTile16();
682 ImGui::Text(tr("Slot %d:"), actual_slot);
683
684 for (int i = 0;
685 i < 8 && (actual_slot + i) <
686 static_cast<int>(session_.overworld_palette().size());
687 ++i) {
688 int color_index = actual_slot + i;
689 auto color = session_.overworld_palette()[color_index];
690 ImVec4 display_color = color.rgb();
691
692 ImGui::ColorButton(absl::StrFormat("##c%d", i).c_str(), display_color,
693 ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
694 if (ImGui::IsItemHovered()) {
695 ImGui::SetTooltip(tr("%d:0x%04X"), color_index, color.snes());
696 }
697
698 if ((i + 1) % 4 != 0)
699 ImGui::SameLine();
700 }
701 }
702 }
703 }
704
705 return draw_status;
706}
707
709 bool* show_debug_info, bool* show_advanced_controls) {
710 const float available_width = ImGui::GetContentRegionAvail().x;
711 const int action_count = 4;
712 const int columns = ComputeTile16CompactActionColumnCount(available_width);
713 const int rows = ComputeTile16ActionRowCount(action_count, columns);
714 const float row_height = ImGui::GetTextLineHeightWithSpacing() +
715 rows * ImGui::GetFrameHeightWithSpacing() +
716 ImGui::GetStyle().FramePadding.y * 2.0f;
717 absl::Status action_status = absl::OkStatus();
718
719 if (ImGui::BeginChild(
720 "##Tile16CompactStatus", ImVec2(0, row_height), false,
721 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
722 ImGui::Text(tr("Tile16 0x%03X"), session_.current_tile16());
723 ImGui::SameLine();
724 ImGui::TextDisabled("| %s | Live edits",
725 EditModeLabel(session_.edit_mode()));
726 if (ImGui::IsItemHovered()) {
727 ImGui::SetTooltip(
728 "Edits update every use of this Tile16 in the document.\n"
729 "Use Undo/Redo to revise edits and Save to write the ROM.");
730 }
731
732 if (ImGui::BeginTable("##Tile16CompactActions", columns,
733 ImGuiTableFlags_SizingStretchProp)) {
734 int action_index = 0;
735 auto next_action_cell = [&]() {
736 if (action_index % columns == 0) {
737 ImGui::TableNextRow();
738 }
739 ImGui::TableNextColumn();
740 ++action_index;
741 };
742
743 next_action_cell();
744 if (!session_.CanUndo()) {
745 ImGui::BeginDisabled();
746 }
747 if (ImGui::Button(tr("Undo"),
748 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
749 action_status = Undo();
750 }
751 if (!session_.CanUndo()) {
752 ImGui::EndDisabled();
753 }
754
755 next_action_cell();
756 if (!session_.CanRedo()) {
757 ImGui::BeginDisabled();
758 }
759 if (ImGui::Button(tr("Redo"),
760 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
761 action_status = Redo();
762 }
763 if (!session_.CanRedo()) {
764 ImGui::EndDisabled();
765 }
766
767 next_action_cell();
768 const char* advanced_label = *show_advanced_controls
769 ? "Advanced*##Tile16AdvancedToggle"
770 : "Advanced##Tile16AdvancedToggle";
771 if (ImGui::Button(advanced_label,
772 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
773 *show_advanced_controls = !*show_advanced_controls;
774 }
775
776 next_action_cell();
777 const char* debug_label = *show_debug_info ? "Debug*##Tile16DebugToggle"
778 : "Debug##Tile16DebugToggle";
779 if (ImGui::Button(debug_label,
780 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
781 *show_debug_info = !*show_debug_info;
782 }
783
784 ImGui::EndTable();
785 }
786 }
787 ImGui::EndChild();
788 return action_status;
789}
790
792 bool show_debug_info) {
793 absl::Status draw_status;
794 // Palette selector - this is the paint brush palette for new placements.
795 Text(tr("Brush Palette:"));
796 if (show_debug_info) {
797 SameLine();
798 int actual_slot = GetActualPaletteSlotForCurrentTile16();
799 ImGui::TextDisabled(tr("(Slot %d)"), actual_slot);
800 }
801 ImGui::TextDisabled(tr("Used for new tile8 placements"));
802
803 // Compact palette grid
804 ImGui::BeginGroup();
805 float available_width = ImGui::GetContentRegionAvail().x;
806 float button_size = ComputeTile16PaletteButtonSize(available_width);
807 const gui::ButtonColorSet default_button_colors{
808 gui::GetThemeColor(ImGuiCol_Button),
809 gui::GetThemeColor(ImGuiCol_ButtonHovered),
810 gui::GetThemeColor(ImGuiCol_ButtonActive),
811 };
812 const gui::ButtonColorSet selected_button_colors =
814
815 for (int row = 0; row < 2; ++row) {
816 for (int col = 0; col < 4; ++col) {
817 if (col > 0)
818 ImGui::SameLine();
819
820 int i = row * 4 + col;
821 bool is_current = (session_.current_palette() == i);
822
823 // Modern button styling with better visual hierarchy
824 ImGui::PushID(i);
825
826 const gui::ButtonColorSet& palette_button_colors =
827 is_current ? selected_button_colors : default_button_colors;
828 const ImVec4 palette_button_border =
829 is_current ? gui::GetSuccessColor()
830 : gui::GetThemeColor(ImGuiCol_Border);
831 gui::StyleColorGuard palette_btn_colors(
832 {{ImGuiCol_Button, palette_button_colors.button},
833 {ImGuiCol_ButtonHovered, palette_button_colors.hovered},
834 {ImGuiCol_ButtonActive, palette_button_colors.active},
835 {ImGuiCol_Border, palette_button_border}});
836 gui::StyleVarGuard palette_btn_border(ImGuiStyleVar_FrameBorderSize,
837 1.0f);
838
839 if (ImGui::Button(absl::StrFormat("%d", i).c_str(),
840 ImVec2(button_size, button_size))) {
841 if (session_.current_palette() != i) {
843 auto status = RefreshAllPalettes();
844 if (!status.ok()) {
845 util::logf("Failed to refresh palettes: %s",
846 status.message().data());
847 } else {
848 util::logf("Palette successfully changed to %d",
850 }
851 }
852 }
853
854 ImGui::PopID();
855
856 // Right-click menu. Applying to all quadrants rewrites the tile's
857 // per-quadrant palettes, so it confirms instead of firing on RMB.
858 gui::ItemContextMenu(nullptr, [&, i]() {
859 std::vector<gui::MenuItemSpec> items;
860 items.emplace_back("Use Palette", ICON_MD_BRUSH, [this, i]() {
861 if (session_.current_palette() == i) {
862 return;
863 }
865 auto status = RefreshAllPalettes();
866 if (!status.ok()) {
867 util::logf("Failed to refresh palettes: %s",
868 status.message().data());
869 }
870 });
871 items.push_back(gui::MenuItemSpec::Destructive(
872 "Apply to All Quadrants...", ICON_MD_FORMAT_COLOR_FILL,
873 [this, i, &draw_status]() {
874 session_.set_current_palette(static_cast<uint8_t>(i));
875 draw_status.Update(ApplyPaletteToAll(session_.current_palette()));
876 }));
877 items.back().tooltip = "Sets all four quadrants to this palette.";
878 items.back().separator_after = true;
879
880 const auto& ow_palette = session_.overworld_palette();
881 const int slot = GetActualPaletteSlot(i, 0);
882 std::string colors;
883 for (int c = slot;
884 c < slot + 16 && c < static_cast<int>(ow_palette.size()); ++c) {
885 colors += absl::StrFormat("%s$%04X", colors.empty() ? "" : ",",
886 ow_palette[c].snes());
887 }
888 auto copy_item =
889 gui::CopyToClipboardItem("Copy Palette Colors", colors);
890 copy_item.enabled_condition = [has = !colors.empty()]() {
891 return has;
892 };
893 items.push_back(std::move(copy_item));
894 return items;
895 });
896
897 // Tooltip with palette info
898 if (ImGui::IsItemHovered()) {
899 ImGui::BeginTooltip();
900 if (show_debug_info) {
901 ImGui::Text(tr("Palette %d -> CGRAM 0x%02X"), i,
903 } else {
904 ImGui::Text(tr("Brush Palette %d"), i);
905 ImGui::TextDisabled(tr("Applied to new tile8 placements"));
906 ImGui::TextDisabled(tr("Right-click: palette actions"));
907 ImGui::TextDisabled(tr("Quadrant metadata is shown in strip below"));
908 ImGui::TextDisabled(
909 tr("Hotkeys: Ctrl+1..8 palette, 1..4 quadrant focus"));
910 if (is_current) {
912 }
913 }
914 ImGui::EndTooltip();
915 }
916 }
917 }
918 ImGui::EndGroup();
919
920 if (auto* tile_data = GetCurrentTile16Data(); tile_data != nullptr) {
922 Text(tr("Quadrant Focus:"));
923 SameLine();
924 ImGui::TextDisabled("1-4");
925 static constexpr std::array<const char*, 4> kQuadrantLabels = {"TL", "TR",
926 "BL", "BR"};
927 const float quadrant_button_width = std::max(58.0f, button_size + 24.0f);
928 const gui::ButtonColorSet active_quadrant_colors =
930 for (int q = 0; q < 4; ++q) {
931 if (q > 0) {
932 SameLine();
933 }
934
935 const gfx::TileInfo& info = zelda3::Tile16QuadrantInfo(*tile_data, q);
936 const uint8_t quadrant_palette = info.palette_;
937 const bool is_active_quadrant = (session_.active_quadrant() == q);
938 const bool matches_brush =
939 (quadrant_palette == session_.current_palette());
940
941 ImGui::PushID(100 + q);
942 const gui::ButtonColorSet& quadrant_button_colors =
943 is_active_quadrant ? active_quadrant_colors
944 : (matches_brush ? selected_button_colors
945 : default_button_colors);
946 const ImVec4 quadrant_border =
947 is_active_quadrant
949 : (matches_brush ? gui::GetSuccessColor()
950 : gui::GetThemeColor(ImGuiCol_Border));
951 gui::StyleColorGuard quadrant_btn_colors(
952 {{ImGuiCol_Button, quadrant_button_colors.button},
953 {ImGuiCol_ButtonHovered, quadrant_button_colors.hovered},
954 {ImGuiCol_ButtonActive, quadrant_button_colors.active},
955 {ImGuiCol_Border, quadrant_border}});
956 gui::StyleVarGuard quadrant_btn_border(ImGuiStyleVar_FrameBorderSize,
957 is_active_quadrant ? 2.0f : 1.0f);
958
959 if (ImGui::Button(absl::StrFormat("%d %s:%d", q + 1, kQuadrantLabels[q],
960 quadrant_palette)
961 .c_str(),
962 ImVec2(quadrant_button_width, 0))) {
964 if (session_.current_palette() != quadrant_palette) {
965 session_.set_current_palette(quadrant_palette);
966 auto status = RefreshAllPalettes();
967 if (!status.ok()) {
968 util::logf("Failed to refresh palettes: %s",
969 status.message().data());
970 }
971 }
972 }
973
974 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
976 draw_status.Update(
978 }
979
980 if (ImGui::IsItemHovered()) {
981 ImGui::BeginTooltip();
982 ImGui::Text(tr("Quadrant %s metadata"), kQuadrantLabels[q]);
983 ImGui::Separator();
984 ImGui::Text(tr("Tile8: %02X"), info.id_);
985 ImGui::Text(tr("Palette: %d"), quadrant_palette);
986 ImGui::Text(tr("Flip: H:%s V:%s"), info.horizontal_mirror_ ? "Y" : "N",
987 info.vertical_mirror_ ? "Y" : "N");
988 ImGui::Text(tr("Priority: %s"), info.over_ ? "Y" : "N");
989 ImGui::TextDisabled(tr("LMB: set brush palette from this quadrant"));
990 ImGui::TextDisabled(
991 tr("RMB: apply current brush palette to this quadrant"));
992 ImGui::TextDisabled(tr("Keys 1..4: focus TL/TR/BL/BR"));
993 ImGui::EndTooltip();
994 }
995
996 ImGui::PopID();
997 }
998
999 const gfx::TileInfo& active_info =
1001 ImGui::TextDisabled(tr("Active %s: Tile8 %02X | P%d | H:%s V:%s | Pri:%s"),
1002 kQuadrantLabels[session_.active_quadrant()],
1003 active_info.id_, active_info.palette_,
1004 active_info.horizontal_mirror_ ? "Y" : "N",
1005 active_info.vertical_mirror_ ? "Y" : "N",
1006 active_info.over_ ? "Y" : "N");
1007
1008 if (Button(tr("Apply Brush to Active Quadrant"), ImVec2(-1, 0))) {
1009 draw_status.Update(ApplyPaletteToQuadrant(session_.active_quadrant(),
1011 }
1012 HOVER_HINT(
1013 "Copy the Brush Palette into the selected quadrant metadata.\n"
1014 "Use keys 1..4 to change active quadrant quickly.");
1015 }
1016
1017 // Copy the current brush palette into all stored quadrant palette fields.
1018 if (Button(tr("Apply Brush to All Quadrants"), ImVec2(-1, 0))) {
1019 draw_status.Update(ApplyPaletteToAll(session_.current_palette()));
1020 }
1021 HOVER_HINT(
1022 "Copy the Brush Palette into Tile Palette metadata for all 4 "
1023 "quadrants.\n"
1024 "Tip: right-click any brush palette button above for a one-step apply.");
1025 return draw_status;
1026}
1027
1028absl::Status Tile16Editor::DrawTile8SourcePanel(float preferred_height) {
1029 absl::Status draw_status;
1032 ImGui::Text(tr("Tile8 Source"));
1033 ImGui::SameLine();
1034 ImGui::Checkbox(tr("Grid##Tile16GridToggle"),
1036 ImGui::SameLine();
1037 if (ImGui::Checkbox(tr("IDs##Tile16CollisionIds"),
1041 }
1042
1043 int source_bitmap_height = 0;
1047 if (session_.current_gfx_bmp() != nullptr &&
1050 ComputeTile8SourceDisplayScale(ImGui::GetContentRegionAvail().x,
1052 source_bitmap_height = session_.current_gfx_bmp()->height();
1054 ImVec2(session_.current_gfx_bmp()->width() *
1058 ImGui::SameLine();
1059 ImGui::TextDisabled(tr("%.1fx"), session_.tile8_source_display_scale());
1060 }
1061
1062 const float panel_height = ComputeTile8SourcePanelHeight(
1063 ImGui::GetContentRegionAvail().y, source_bitmap_height,
1064 session_.tile8_source_display_scale(), preferred_height);
1065
1066 if (BeginChild("##Tile8SourceScrollable", ImVec2(0, panel_height), true,
1067 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1068 gui::CanvasFrameOptions tile8_frame_opts;
1069 tile8_frame_opts.draw_grid = session_.show_tile_grid();
1070 tile8_frame_opts.grid_step =
1071 8.0f * session_.tile8_source_display_scale(); // Tile8 grid
1072 tile8_frame_opts.draw_context_menu = true;
1073 tile8_frame_opts.draw_overlay = true;
1074 tile8_frame_opts.render_popups = true;
1075 tile8_frame_opts.use_child_window = false;
1076
1077 auto tile8_rt = gui::BeginCanvas(tile8_source_canvas_, tile8_frame_opts);
1078
1081
1082 const bool left_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
1083 const bool right_clicked = ImGui::IsItemClicked(ImGuiMouseButton_Right);
1084
1085 // ZScream parity: hold right-click on tile8 source to show usage overlay.
1086 const bool temporary_usage = ComputeTile8UsageHighlight(
1087 ImGui::IsItemHovered(), ImGui::IsMouseDown(ImGuiMouseButton_Right));
1090 temporary_usage;
1091
1092 if (left_clicked || right_clicked) {
1093 draw_status.Update(HandleTile8SourceSelection(
1094 right_clicked, session_.tile8_source_display_scale()));
1095 }
1096
1097 if (session_.current_gfx_bmp() != nullptr) {
1100 }
1101
1102 gui::EndCanvas(tile8_source_canvas_, tile8_rt, tile8_frame_opts);
1103 }
1104 EndChild();
1105
1106 return draw_status;
1107}
1108
1109absl::Status Tile16Editor::HandleTile8SourceSelection(bool right_clicked,
1110 float display_scale) {
1111 const ImGuiIO& io = ImGui::GetIO();
1112 ImVec2 canvas_pos = tile8_source_canvas_.zero_point();
1113 ImVec2 mouse_pos =
1114 ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
1115
1116 const int new_tile8 = ComputeTile8IndexFromCanvasMouse(
1117 mouse_pos.x, mouse_pos.y, session_.current_gfx_bmp()->width(),
1118 static_cast<int>(session_.mutable_current_gfx_individual().size()),
1119 display_scale);
1120 if (new_tile8 < 0 || new_tile8 == session_.current_tile8()) {
1121 return absl::OkStatus();
1122 }
1123
1124 session_.set_current_tile8(new_tile8);
1126 if (right_clicked) {
1128 }
1129 util::logf("Selected Tile8: %d", session_.current_tile8());
1130
1131 return absl::OkStatus();
1132}
1133
1135 // Local tile-shaping actions stay in the right column.
1136 if (Button(tr("Clear"), ImVec2(-1, 0))) {
1138 }
1139
1140 if (Button(tr("Copy"), ImVec2(-1, 0))) {
1142 }
1143
1144 if (Button(tr("Paste"), ImVec2(-1, 0))) {
1146 }
1147
1148 return absl::OkStatus();
1149}
1150
1154 return;
1155 }
1156
1158 auto cache_status = session_.RebuildTile8UsageCache();
1159 if (!cache_status.ok()) {
1160 util::logf("Tile8 usage cache rebuild failed: %s",
1161 cache_status.message().data());
1162 return;
1163 }
1164 }
1165
1166 const auto& hits = session_.tile8_usage_cache()[session_.current_tile8()];
1167 if (hits.empty()) {
1168 return;
1169 }
1170
1171 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1172 const ImVec2 canvas_pos = blockset_canvas_.zero_point();
1173 const float scale = blockset_canvas_.GetGlobalScale();
1174 const float tile16_display = 32.0f * scale;
1175 const float quadrant_display = 16.0f * scale;
1176 const int tiles_per_row =
1177 std::max(1, session_.tile16_blockset_bmp()->width() / kTile16Size);
1178
1179 for (const auto& hit : hits) {
1180 const int tile_x = hit.tile16_id % tiles_per_row;
1181 const int tile_y = hit.tile16_id / tiles_per_row;
1182 const int quad_x = hit.quadrant % 2;
1183 const int quad_y = hit.quadrant / 2;
1184
1185 const ImVec2 min(
1186 canvas_pos.x + (tile_x * tile16_display) + (quad_x * quadrant_display),
1187 canvas_pos.y + (tile_y * tile16_display) + (quad_y * quadrant_display));
1188 const ImVec2 max(min.x + quadrant_display, min.y + quadrant_display);
1189
1190 // Mirrors ZScream's right-click usage tint (purple, transparent).
1191 draw_list->AddRectFilled(min, max, IM_COL32(150, 0, 210, 80));
1192 draw_list->AddRect(min, max, IM_COL32(215, 170, 255, 180));
1193 }
1194}
1195
1198 if (Begin("Advanced Palette Settings",
1200 Text(tr("Pixel Normalization & Color Correction:"));
1201
1202 int mask_value = static_cast<int>(session_.palette_normalization_mask());
1203 if (SliderInt(tr("Normalization Mask"), &mask_value, 1, 255, "0x%02X")) {
1205 static_cast<uint8_t>(mask_value);
1206 }
1207
1208 Checkbox(tr("Auto Normalize Pixels"),
1210
1211 if (Button(tr("Apply to All Graphics"))) {
1212 auto reload_result = LoadTile8();
1213 if (!reload_result.ok()) {
1214 Text(tr("Error: %s"), reload_result.message().data());
1215 }
1216 }
1217
1218 SameLine();
1219 if (Button(tr("Reset Defaults"))) {
1222 auto reload_result = LoadTile8();
1223 (void)reload_result; // Suppress warning
1224 }
1225
1226 Separator();
1227 Text(tr("Current State:"));
1228 static constexpr std::array<const char*, 7> palette_group_names = {
1229 "OW Main", "OW Aux", "OW Anim", "Dungeon",
1230 "Sprites", "Armor", "Sword"};
1231 Text(tr("Palette Group: %d (%s)"), session_.current_palette_group(),
1233 ? palette_group_names[session_.current_palette_group()]
1234 : "Unknown");
1235 Text(tr("Current Palette: %d"), session_.current_palette());
1236
1237 Separator();
1238 Text(tr("Sheet-Specific Fixes:"));
1239
1240 // Sheet-specific palette fixes
1241 static bool fix_sheet_0 = true;
1242 static bool fix_sprite_sheets = true;
1243 static bool use_transparent_for_terrain = false;
1244
1245 if (Checkbox(tr("Fix Sheet 0 (Trees)"), &fix_sheet_0)) {
1246 auto reload_result = LoadTile8();
1247 if (!reload_result.ok()) {
1248 Text(tr("Error reloading: %s"), reload_result.message().data());
1249 }
1250 }
1251 HOVER_HINT(
1252 "Use direct palette for sheet 0 instead of transparent palette");
1253
1254 if (Checkbox(tr("Fix Sprite Sheets"), &fix_sprite_sheets)) {
1255 auto reload_result = LoadTile8();
1256 if (!reload_result.ok()) {
1257 Text(tr("Error reloading: %s"), reload_result.message().data());
1258 }
1259 }
1260 HOVER_HINT("Use direct palette for sprite graphics sheets");
1261
1262 if (Checkbox(tr("Transparent for Terrain"),
1263 &use_transparent_for_terrain)) {
1264 auto reload_result = LoadTile8();
1265 if (!reload_result.ok()) {
1266 Text(tr("Error reloading: %s"), reload_result.message().data());
1267 }
1268 }
1269 HOVER_HINT("Force transparent palette for terrain graphics");
1270
1271 Separator();
1272 Text(tr("Color Analysis:"));
1273 if (session_.current_tile8() >= 0 &&
1275 static_cast<int>(
1277 Text(tr("Selected Tile8 Analysis:"));
1278 const auto& tile_data =
1280 std::map<uint8_t, int> pixel_counts;
1281 for (uint8_t pixel : tile_data) {
1282 pixel_counts[pixel & 0x0F]++; // Normalize to 4-bit
1283 }
1284
1285 Text(tr("Pixel Value Distribution:"));
1286 for (const auto& pair : pixel_counts) {
1287 int value = pair.first;
1288 int count = pair.second;
1289 Text(tr(" Value %d (0x%X): %d pixels"), value, value, count);
1290 }
1291
1292 Text(tr("Palette Colors Used:"));
1293 const gfx::SnesPalette* analysis_palette =
1295 if (analysis_palette == nullptr || analysis_palette->empty()) {
1296 analysis_palette = session_.current_gfx_bmp()
1298 : &session_.palette();
1299 }
1300 for (const auto& pair : pixel_counts) {
1301 int value = pair.first;
1302 int count = pair.second;
1303 if (value < static_cast<int>(analysis_palette->size())) {
1304 auto color = (*analysis_palette)[value];
1305 ImVec4 display_color = color.rgb();
1306 ImGui::ColorButton(("##analysis" + std::to_string(value)).c_str(),
1307 display_color, ImGuiColorEditFlags_NoTooltip,
1308 ImVec2(16, 16));
1309 if (ImGui::IsItemHovered()) {
1310 ImGui::SetTooltip(tr("Index %d: 0x%04X (%d pixels)"), value,
1311 color.snes(), count);
1312 }
1313 if (value % 8 != 7)
1314 ImGui::SameLine();
1315 }
1316 }
1317 }
1318
1319 // Enhanced ROM Palette Management Section
1320 Separator();
1321 if (CollapsingHeader(tr("ROM Palette Manager")) && session_.rom()) {
1322 Text(tr("Experimental ROM Palette Selection:"));
1323 HOVER_HINT(
1324 "Use ROM palettes to experiment with different color schemes");
1325
1326 if (Button(tr("Open Enhanced Palette Editor"))) {
1328 }
1329 SameLine();
1330 if (Button(tr("Show Color Analysis"))) {
1332 }
1333
1334 // Quick palette application
1335 static int quick_group = 0;
1336 static int quick_index = 0;
1337
1338 SliderInt(tr("ROM Group"), &quick_group, 0, 6);
1339 SliderInt(tr("Palette Index"), &quick_index, 0, 7);
1340
1341 if (Button(tr("Apply to Tile8 Source"))) {
1342 if (tile8_source_canvas_.ApplyROMPalette(quick_group, quick_index)) {
1343 util::logf("Applied ROM palette group %d, index %d to Tile8 source",
1344 quick_group, quick_index);
1345 }
1346 }
1347 SameLine();
1348 if (Button(tr("Apply to Tile16 Editor"))) {
1349 if (tile16_edit_canvas_.ApplyROMPalette(quick_group, quick_index)) {
1350 util::logf(
1351 "Applied ROM palette group %d, index %d to Tile16 editor",
1352 quick_group, quick_index);
1353 }
1354 }
1355 }
1356 }
1357 End();
1358 }
1359}
1360
1362 Text(tr("Layout Scratch:"));
1363 for (int i = 0; i < 4; ++i) {
1364 ImGui::PushID(i);
1365 std::string slot_name = "S" + std::to_string(i + 1);
1366
1367 if (Button((slot_name + " Save").c_str(), ImVec2(70, 20))) {
1369 }
1370 SameLine();
1371
1372 bool can_load = session_.layout_scratch(i).in_use;
1373 if (!can_load) {
1374 ImGui::BeginDisabled();
1375 }
1376 if (Button((slot_name + " Load").c_str(), ImVec2(70, 20)) && can_load) {
1378 }
1379 if (!can_load) {
1380 ImGui::EndDisabled();
1381 }
1382 SameLine();
1383 TextDisabled("%s", session_.layout_scratch(i).name.c_str());
1384 ImGui::PopID();
1385 }
1386}
1387
1389 if (ImGui::BeginPopupModal("ManualTile8Editor", nullptr,
1390 ImGuiWindowFlags_AlwaysAutoResize)) {
1391 if (manual_edit_group_ == 0)
1393 ImGui::Text("Tile16 0x%03X — quadrant properties",
1395 auto edited = session_.current_tile16_data();
1396 bool changed = false;
1397 const char* names[] = {"Top left", "Top right", "Bottom left",
1398 "Bottom right"};
1399 for (int q = 0; q < 4; ++q) {
1400 ImGui::PushID(q);
1401 ImGui::SeparatorText(names[q]);
1402 auto& info = TileInfoForQuadrant(&edited, q);
1403 int id = info.id_;
1404 if (ImGui::InputInt("Tile8", &id)) {
1405 info.id_ = std::clamp(id, 0, 1023);
1406 changed = true;
1407 }
1408 int palette = info.palette_;
1409 if (ImGui::SliderInt("Palette", &palette, 0, 7)) {
1410 info.palette_ = palette;
1411 changed = true;
1412 }
1413 changed |= ImGui::Checkbox("X Flip", &info.horizontal_mirror_);
1414 ImGui::SameLine();
1415 changed |= ImGui::Checkbox("Y Flip", &info.vertical_mirror_);
1416 ImGui::SameLine();
1417 changed |= ImGui::Checkbox("Priority", &info.over_);
1418 ImGui::PopID();
1419 }
1420 if (changed)
1422 if (ImGui::Button("Close"))
1423 ImGui::CloseCurrentPopup();
1424 ImGui::EndPopup();
1425 } else {
1427 }
1428}
1429
1430} // namespace editor
1431} // 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)
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 ReplaceCurrentTile(const gfx::Tile16 &data, uint64_t edit_group=0)
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:1295
void ShowScalingControls()
Definition canvas.cc:1841
void ShowAdvancedCanvasProperties()
Definition canvas.cc:1716
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1231
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:450
bool ApplyROMPalette(int group_index, int palette_index)
Definition canvas.cc:476
void ShowColorAnalysis()
Definition canvas.cc:464
static void SectionHeader(const char *label)
RAII guard for ImGui style colors.
Definition style_guard.h:29
RAII guard for ImGui style vars.
Definition style_guard.h:71
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="")