yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_editor.cc
Go to the documentation of this file.
1#include "tile16_editor.h"
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <array>
6#include <memory>
7
8#include "absl/status/status.h"
9#include "absl/strings/str_format.h"
14#include "app/gfx/core/bitmap.h"
19#include "app/gui/core/input.h"
20#include "app/gui/core/style.h"
24#include "imgui/imgui.h"
25#include "rom/rom.h"
26#include "util/hex.h"
27#include "util/log.h"
28#include "util/macro.h"
29#include "zelda3/game_data.h"
35
36namespace yaze {
37namespace editor {
38
39using namespace ImGui;
40
42 gfx::Bitmap& tile16_blockset_bmp, gfx::Bitmap& current_gfx_bmp,
43 std::array<uint8_t, 0x200>& all_tiles_types) {
45 [this](int tile_id) { blockset_selector_.SetSelectedTile(tile_id); });
46 session_.set_on_tile8_sheet_resized([this](float width, float height) {
47 tile8_source_canvas_.SetCanvasSize(ImVec2(width, height));
48 });
49
50 RETURN_IF_ERROR(session_.InitializeBitmaps(tile16_blockset_bmp,
51 current_gfx_bmp, all_tiles_types));
52
55
58
61
62 if (session_.rom()) {
65 }
66
67 ImVector<std::string> tile16_names;
68 for (int i = 0; i < 0x200; ++i) {
69 std::string str = util::HexByte(all_tiles_types[i]);
70 tile16_names.push_back(str);
71 }
72 *tile8_source_canvas_.mutable_labels(0) = tile16_names;
75
76 gui::AddTableColumn(tile_edit_table_, "##tile16ID", [&]() {
77 Text(tr("Tile16: %02X"), session_.current_tile16());
78 });
79 gui::AddTableColumn(tile_edit_table_, "##tile8ID", [&]() {
80 Text(tr("Tile8: %02X"), session_.current_tile8());
81 });
82 gui::AddTableColumn(tile_edit_table_, "##tile16Flip", [&]() {
83 Checkbox(tr("X Flip"), session_.mutable_x_flip());
84 Checkbox(tr("Y Flip"), session_.mutable_y_flip());
85 Checkbox(tr("Priority"), session_.mutable_priority_tile());
86 });
87
88 return absl::OkStatus();
89}
90
91absl::Status Tile16Editor::Update() {
92 // Legacy MenuBar shell removed — single chrome path is UpdateAsPanel().
93 return UpdateAsPanel();
94}
95
99
102 return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
103 }
104
105 // Menu button for context menu
106 if (Button(ICON_MD_MENU " Menu")) {
107 OpenPopup("##Tile16EditorContextMenu");
108 }
109 SameLine();
110 TextDisabled(tr("Right-click for more options"));
111
112 if (Button("Edit Graphics")) {
114 }
115 SameLine();
116 TextDisabled("Start at quadrant %d", session_.active_quadrant() + 1);
117 if (!graphics_open_status_.ok()) {
118 ImGui::TextWrapped("Graphics edit unavailable: %s",
119 graphics_open_status_.message().data());
120 }
121
122 // Context menu
124
125 // About popup
126 if (BeginPopupModal("About Tile16 Editor", NULL,
127 ImGuiWindowFlags_AlwaysAutoResize)) {
128 Text(tr("Tile16 Editor for Link to the Past"));
129 Text(tr("This editor allows you to edit 16x16 tiles used in the game."));
130 Text(tr("Features:"));
131 BulletText(
132 tr("Edit Tile16 graphics by placing 8x8 tiles in the quadrants"));
133 BulletText(tr("Copy and paste Tile16 graphics"));
134 BulletText(tr("Save and load Tile16 graphics to/from scratch space"));
135 BulletText(tr("Preview Tile16 graphics at a larger size"));
136 Separator();
137 if (Button(tr("Close"))) {
138 CloseCurrentPopup();
139 }
140 EndPopup();
141 }
142
143 // Handle keyboard shortcuts (shared implementation)
145
147 if (!status_.ok()) {
148 ImGui::TextWrapped("Tile16 edit failed: %s", status_.message().data());
149 }
152
153 return absl::OkStatus();
154}
155
158 gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
159
160 // Tile ID search/jump bar
163 }
164
165 // Configure canvas frame options for blockset view
166 gui::CanvasFrameOptions frame_opts;
167 frame_opts.draw_grid = true;
168 frame_opts.grid_step = 32.0f; // Tile16 grid
169 frame_opts.draw_context_menu = true;
170 frame_opts.draw_overlay = true;
171 frame_opts.render_popups = true;
172 frame_opts.use_child_window = false;
173
174 auto canvas_rt = gui::BeginCanvas(blockset_canvas_, frame_opts);
176
177 // Ensure selector is synced with current selection
180 }
181
182 if (session_.tile16_blockset_bmp() == nullptr) {
183 return absl::FailedPreconditionError(
184 "Tile16 blockset bitmap not initialized");
185 }
186
187 // Render the selector widget (handles bitmap, grid, highlights, interaction)
188 auto result =
190
191 if (result.selection_changed) {
192 // Selection does not create an edit or interrupt document history.
193 RequestTileSwitch(result.selected_tile);
194 util::logf("Selected Tile16 from blockset: %d", result.selected_tile);
195 }
196
197 gui::EndCanvas(blockset_canvas_, canvas_rt, frame_opts);
198 EndChild();
199
200 return absl::OkStatus();
201}
202
204 // Child windows of this panel only. RootAndChildWindows is true whenever
205 // the overworld map canvas (a sibling under the same root) is focused, so
206 // Command+Z was consumed as a Tile16 undo while hovering the map.
207 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) &&
208 !ImGui::IsAnyItemActive()) {
209 const ImGuiIO& io = ImGui::GetIO();
210#if defined(__APPLE__)
211 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
212#else
213 const bool platform_primary_held = io.KeyCtrl;
214#endif
215 const bool ctrl_held = platform_primary_held ||
216 ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
217 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
218
219 // Editing shortcuts (only fire without Ctrl to avoid conflicts)
220 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
222 }
223 if (ImGui::IsKeyPressed(ImGuiKey_H) && !ctrl_held) {
225 }
226 if (ImGui::IsKeyPressed(ImGuiKey_V) && !ctrl_held) {
228 }
229 if (ImGui::IsKeyPressed(ImGuiKey_R) && !ctrl_held) {
231 }
232 if (ImGui::IsKeyPressed(ImGuiKey_F) && !ctrl_held) {
233 if (session_.current_tile8() >= 0 &&
235 static_cast<int>(
238 }
239 }
240
241 if (!ctrl_held) {
242 if (ImGui::IsKeyPressed(ImGuiKey_P)) {
244 }
245 if (ImGui::IsKeyPressed(ImGuiKey_I)) {
247 }
248 if (ImGui::IsKeyPressed(ImGuiKey_U)) {
250 }
251 }
252
253 // Palette shortcuts
254 if (ImGui::IsKeyPressed(ImGuiKey_Q)) {
255 status_ = CyclePalette(false);
256 }
257 if (ImGui::IsKeyPressed(ImGuiKey_E)) {
258 status_ = CyclePalette(true);
259 }
260
261 // Numeric shortcuts:
262 // - 1..4 focus tile16 quadrants
263 // - Ctrl+1..8 switch brush palette rows
264 for (int i = 0; i < 8; ++i) {
265 if (!ImGui::IsKeyPressed(static_cast<ImGuiKey>(ImGuiKey_1 + i))) {
266 continue;
267 }
268 const Tile16NumericShortcutResult shortcut =
269 ResolveTile16NumericShortcut(ctrl_held, i);
270 if (shortcut.quadrant_focus.has_value()) {
272 }
273 if (shortcut.palette_id.has_value()) {
276 }
277 }
278
279 // Ctrl-modified shortcuts
280 if (ctrl_held) {
281 if (!session_.has_shared_history() && ImGui::IsKeyPressed(ImGuiKey_Z)) {
282 status_ = io.KeyShift ? Redo() : Undo();
283 }
284 if (!session_.has_shared_history() && ImGui::IsKeyPressed(ImGuiKey_Y)) {
285 status_ = Redo();
286 }
287 if (ImGui::IsKeyPressed(ImGuiKey_C)) {
289 }
290 if (ImGui::IsKeyPressed(ImGuiKey_V)) {
292 }
293 }
294 }
295}
296
297absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 pos,
298 const gfx::Bitmap* source_tile) {
299 return session_.DrawToCurrentTile16({pos.x, pos.y}, source_tile);
300}
301
302absl::Status Tile16Editor::HandleTile16CanvasClick(const ImVec2& tile_position,
303 bool left_click,
304 bool right_click) {
305 return session_.HandleTile16CanvasClick({tile_position.x, tile_position.y},
306 left_click, right_click);
307}
308
310 const ImVec2& display_position) {
311 const Tile16LocalPos local =
313 {display_position.x, display_position.y});
314 return ImVec2(local.x, local.y);
315}
316
317absl::Status Tile16Editor::PickTile8FromTile16(const ImVec2& position) {
318 return session_.PickTile8FromTile16({position.x, position.y});
319}
320
324 selected_tiles_.clear();
325 selected_tiles_.reserve(64);
326 for (int y = 0; y < 8; ++y) {
327 for (int x = 0; x < 8; ++x) {
328 const int tile_id = session_.layout_scratch(slot).tile_layout[y][x];
329 if (tile_id >= 0) {
330 selected_tiles_.push_back(tile_id);
331 }
332 }
333 }
335 return absl::OkStatus();
336}
337
338} // namespace editor
339} // namespace yaze
absl::Status HandleTile16CanvasClick(Tile16LocalPos tile_position, bool left_click, bool right_click)
absl::Status InitializeBitmaps(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status PickTile8FromTile16(Tile16LocalPos position)
absl::Status DrawToCurrentTile16(Tile16LocalPos pos, const gfx::Bitmap *source_tile=nullptr)
void set_on_tile_selection_changed(std::function< void(int)> callback)
Optional: UI can sync blockset selector when current tile changes.
absl::Status LoadLayoutFromScratch(int slot)
Tile16LayoutScratch & layout_scratch(int i)
std::vector< zelda3::Tile8PixelData > & mutable_current_gfx_individual()
static Tile16LocalPos Tile16PreviewDisplayPixelToTilePosition(Tile16LocalPos display_position)
void set_on_tile8_sheet_resized(std::function< void(float width, float height)> callback)
Optional: UI updates Tile8 source canvas size after LoadTile8.
void set_edit_mode(Tile16EditMode mode)
absl::Status LoadLayoutFromScratch(int slot)
static ImVec2 Tile16PreviewDisplayPixelToTilePosition(const ImVec2 &display_position)
absl::Status CyclePalette(bool forward=true)
absl::Status HandleTile16CanvasClick(const ImVec2 &tile_position, bool left_click, bool right_click)
absl::Status FillTile16WithTile8(int tile8_id)
absl::Status PasteTile16FromClipboard()
gui::TileSelectorWidget blockset_selector_
absl::Status FlipTile16Horizontal()
absl::Status OpenGraphicsForActiveQuadrant()
absl::Status RefreshAllPalettes()
void RequestTileSwitch(int target_tile_id)
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap *source_tile=nullptr)
absl::Status UpdateLivePreview()
absl::Status Initialize(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status CopyTile16ToClipboard(int tile_id)
absl::Status FlipTile16Vertical()
Tile16EditSession session_
absl::Status PickTile8FromTile16(const ImVec2 &position)
std::vector< int > selected_tiles_
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
auto custom_labels_enabled()
Definition canvas.h:414
void SetCanvasSize(ImVec2 canvas_size)
Definition canvas.h:388
auto mutable_labels(int i)
Definition canvas.h:457
void InitializePaletteEditor(Rom *rom)
Definition canvas.cc:433
void InitializeDefaults()
Definition canvas.cc:259
void SetAutoResize(bool auto_resize)
Definition canvas.h:291
RenderResult Render(gfx::Bitmap &atlas, bool atlas_ready)
#define ICON_MD_MENU
Definition icons.h:1196
Definition input.cc:30
Tile16NumericShortcutResult ResolveTile16NumericShortcut(bool ctrl_held, int number_index)
void EndCanvas(Canvas &canvas)
void BeginPadding(int i)
Definition style.cc:296
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:771
void EndPadding()
Definition style.cc:300
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:312
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:33
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:146
constexpr int kNumTile16Individual
Definition overworld.h:243
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::array< std::array< int, 8 >, 8 > tile_layout
std::optional< float > grid_step