4#include "absl/strings/str_format.h"
7#include "imgui/imgui.h"
14 : renderer_(renderer), rom_(rom) {
15 tile_editor_ = std::make_unique<zelda3::ObjectTileEditor>(rom);
29 std::string message) {
62 auto layout_or =
tile_editor_->CaptureObjectLayout(object_id, room,
71 const std::string& filename,
72 int16_t object_id,
int room_id,
105 return absl::StrFormat(
112 return absl::StrFormat(
117 return absl::StrFormat(
ICON_MD_GRID_ON " Object 0x%03X - %s###ObjTileEditor",
139 current_object_id_ < 0 || rom_ == nullptr || !rom_->is_loaded()) {
152 current_room_id_ < static_cast<int>(
rooms_->
size()) &&
174 ImGui::SetNextWindowSize(ImVec2(550, 500), ImGuiCond_FirstUseEver);
175 if (!ImGui::Begin(title.c_str(), &
is_open_)) {
187 if (ImGui::BeginTable(
188 "##TileEditorLayout", 2,
189 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
190 ImGui::TableSetupColumn(
"Tile Grid", ImGuiTableColumnFlags_WidthFixed,
192 ImGui::TableSetupColumn(
"Source Sheet", ImGuiTableColumnFlags_WidthStretch);
194 ImGui::TableNextRow();
195 ImGui::TableNextColumn();
198 ImGui::TableNextColumn();
213 ImGui::OpenPopup(
"Shared Tile Data");
216 if (ImGui::BeginPopupModal(
"Shared Tile Data",
nullptr,
217 ImGuiWindowFlags_AlwaysAutoResize)) {
218 ImGui::Text(tr(
"This tile data is shared by %d objects."),
220 ImGui::Text(tr(
"Changes will affect all of them."));
222 if (ImGui::Button(tr(
"Apply Anyway"), ImVec2(120, 0))) {
224 ImGui::CloseCurrentPopup();
227 if (ImGui::Button(tr(
"Cancel"), ImVec2(120, 0))) {
228 ImGui::CloseCurrentPopup();
283 const int cell_palette =
static_cast<int>(cell.tile_info.palette_);
298 constexpr float kScale = 4.0f;
302 ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
303 ImVec2 canvas_size(grid_width, grid_height);
310 ImGui::Dummy(canvas_size);
313 ImDrawList* draw_list = ImGui::GetWindowDrawList();
317 float line_x = canvas_pos.x + gx * 8 *
kScale;
318 draw_list->AddLine(ImVec2(line_x, canvas_pos.y),
319 ImVec2(line_x, canvas_pos.y + grid_height),
320 IM_COL32(128, 128, 128, 80));
323 float line_y = canvas_pos.y + gy * 8 *
kScale;
324 draw_list->AddLine(ImVec2(canvas_pos.x, line_y),
325 ImVec2(canvas_pos.x + grid_width, line_y),
326 IM_COL32(128, 128, 128, 80));
333 ImVec2 cell_min(canvas_pos.x + cell.rel_x * 8 *
kScale,
334 canvas_pos.y + cell.rel_y * 8 *
kScale);
335 ImVec2 cell_max(cell_min.x + 8 *
kScale, cell_min.y + 8 *
kScale);
336 draw_list->AddRect(cell_min, cell_max, IM_COL32(255, 255, 0, 255), 0, 0,
341 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
342 ImVec2 mouse = ImGui::GetMousePos();
344 static_cast<int>((mouse.x - canvas_pos.x) / (8 *
kScale));
346 static_cast<int>((mouse.y - canvas_pos.y) / (8 *
kScale));
361 int modified_count = 0;
379 ImGui::SetNextItemWidth(80);
384 constexpr float kAtlasScale = 2.0f;
388 ImVec2 atlas_pos = ImGui::GetCursorScreenPos();
389 ImVec2 atlas_size(display_width, display_height);
392 ImGui::BeginChild(
"##AtlasScroll", ImVec2(display_width + 16, 300),
true,
393 ImGuiWindowFlags_HorizontalScrollbar);
395 atlas_pos = ImGui::GetCursorScreenPos();
400 ImGui::Dummy(atlas_size);
403 ImDrawList* draw_list = ImGui::GetWindowDrawList();
407 float line_x = atlas_pos.x + gx * 8 * kAtlasScale;
408 draw_list->AddLine(ImVec2(line_x, atlas_pos.y),
409 ImVec2(line_x, atlas_pos.y + display_height),
410 IM_COL32(64, 64, 64, 60));
413 float line_y = atlas_pos.y + gy * 8 * kAtlasScale;
414 draw_list->AddLine(ImVec2(atlas_pos.x, line_y),
415 ImVec2(atlas_pos.x + display_width, line_y),
416 IM_COL32(64, 64, 64, 60));
425 ImVec2 sel_min(atlas_pos.x + src_col * 8 * kAtlasScale,
426 atlas_pos.y + src_row * 8 * kAtlasScale);
427 ImVec2 sel_max(sel_min.x + 8 * kAtlasScale, sel_min.y + 8 * kAtlasScale);
428 draw_list->AddRect(sel_min, sel_max, IM_COL32(0, 255, 255, 255), 0, 0,
433 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) {
434 ImVec2 mouse = ImGui::GetMousePos();
436 static_cast<int>((mouse.x - atlas_pos.x) / (8 * kAtlasScale));
438 static_cast<int>((mouse.y - atlas_pos.y) / (8 * kAtlasScale));
440 if (click_col >= 0 &&
453 cell.tile_info.id_ =
static_cast<uint16_t
>(tile_id);
455 cell.modified =
true;
472 ImGui::TextDisabled(tr(
"Select a tile cell to edit properties"));
477 ImGui::Text(tr(
"Cell (%d, %d)"), cell.rel_x, cell.rel_y);
481 int tile_id = cell.tile_info.id_;
482 ImGui::SetNextItemWidth(80);
483 if (ImGui::InputInt(tr(
"ID"), &tile_id, 1, 16)) {
484 cell.tile_info.id_ =
static_cast<uint16_t
>(tile_id & 0x3FF);
485 cell.modified =
true;
493 int pal = cell.tile_info.palette_;
494 ImGui::SetNextItemWidth(60);
495 if (ImGui::SliderInt(tr(
"Pal"), &pal, 0, 7)) {
496 cell.tile_info.palette_ =
static_cast<uint8_t
>(pal);
497 cell.modified =
true;
505 if (ImGui::Checkbox(tr(
"H"), &cell.tile_info.horizontal_mirror_)) {
506 cell.modified =
true;
511 if (ImGui::Checkbox(tr(
"V"), &cell.tile_info.vertical_mirror_)) {
512 cell.modified =
true;
517 if (ImGui::Checkbox(tr(
"Pri"), &cell.tile_info.over_)) {
518 cell.modified =
true;
527 if (confirm_shared && shared_count > 1) {
533 " Confirm shared apply: %d objects use this tile data.",
546 room.MarkObjectsDirty();
547 room.RenderRoomGraphics();
554 cell.modified =
false;
569 if (shared_count > 1) {
574 " Applied changes to shared tile data used by %d objects.",
584 absl::StrFormat(
ICON_MD_ERROR " Apply failed: %s", status.message()));
589 int modified_count = 0;
595 bool has_mods = modified_count > 0;
598 ImGui::Text(tr(
"%d tile(s) modified"), modified_count);
604 if (shared_count > 1) {
607 if (ImGui::IsItemHovered()) {
608 ImGui::SetItemTooltip(
609 tr(
"This object reuses tile data with %d objects.\nApplying changes "
610 "will update every object in that shared data group."),
618 ImGui::BeginDisabled();
623 ImGui::EndDisabled();
629 ImGui::BeginDisabled();
637 ImGui::EndDisabled();
663 ImGui::PushStyleColor(ImGuiCol_Text, status_color);
664 ImGui::PushTextWrapPos(0.0f);
666 ImGui::PopTextWrapPos();
667 ImGui::PopStyleColor();
673 if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
681 auto find_neighbor = [&](
int dx,
int dy) ->
int {
685 int target_x = cur.rel_x + dx;
686 int target_y = cur.rel_y + dy;
687 for (
int i = 0; i < cell_count; ++i) {
696 if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow,
false)) {
700 if (ImGui::IsKeyPressed(ImGuiKey_RightArrow,
false)) {
704 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow,
false)) {
708 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow,
false)) {
716 for (
int key = 0; key <= 7; ++key) {
717 if (ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(ImGuiKey_0 + key),
false)) {
718 cell.tile_info.palette_ =
static_cast<uint8_t
>(key);
719 cell.modified =
true;
727 if (ImGui::IsKeyPressed(ImGuiKey_H,
false)) {
728 cell.tile_info.horizontal_mirror_ = !cell.tile_info.horizontal_mirror_;
729 cell.modified =
true;
735 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
736 cell.tile_info.vertical_mirror_ = !cell.tile_info.vertical_mirror_;
737 cell.modified =
true;
743 if (ImGui::IsKeyPressed(ImGuiKey_P,
false)) {
744 cell.tile_info.over_ = !cell.tile_info.over_;
745 cell.modified =
true;
752 if (ImGui::IsKeyPressed(ImGuiKey_Escape,
false)) {
761 if (ImGui::IsKeyPressed(ImGuiKey_Tab,
false)) {
762 if (cell_count > 0) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void ClearRenderedBitmaps()
void ApplyChanges(bool confirm_shared=true)
void DrawTileProperties()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
ObjectTileEditorPanel(gfx::IRenderer *renderer, Rom *rom)
void OpenForObject(int16_t object_id, int room_id, DungeonRoomStore *rooms)
gfx::PaletteGroup current_palette_group_
ActionStatusTone action_status_tone_
std::string BuildWindowTitle() const
std::unique_ptr< zelda3::ObjectTileEditor > tile_editor_
void ResetTransientState()
bool HasRenderableRoomContext() const
std::function< void(int, const std::string &) on_object_created_)
void RenderObjectPreview()
int16_t current_object_id_
void RefreshRenderedViewsFromCurrentRoom()
gfx::Bitmap object_preview_bmp_
void HandleKeyboardShortcuts()
zelda3::ObjectTileLayout current_layout_
int shared_tile_data_usage_override_
void SyncSourceSelectionFromSelectedCell()
bool HasSharedTileDataConflict() const
std::string action_status_message_
int selected_source_tile_
void Draw(bool *p_open) override
Draw the panel content.
bool show_shared_confirm_
gfx::Bitmap tile8_atlas_bmp_
void OpenForNewObject(int width, int height, const std::string &filename, int16_t object_id, int room_id, DungeonRoomStore *rooms)
void SelectFirstCellIfAvailable()
int GetSharedTileDataUsageCount() const
DungeonRoomStore * rooms_
void SetActionStatus(ActionStatusTone tone, std::string message)
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
TextureHandle texture() const
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Defines an abstract interface for all rendering operations.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
static constexpr int kAtlasTilesPerRow
static constexpr int kAtlasTileRows
static constexpr int kAtlasWidthPx
static constexpr int kAtlasHeightPx
#define ICON_MD_CHECK_CIRCLE
uint16_t TileInfoToWord(TileInfo tile_info)
ImVec4 ConvertColorToImVec4(const Color &color)
std::string GetObjectName(int object_id)
Represents a group of palettes.
static ObjectTileLayout CreateEmpty(int width, int height, int16_t object_id, const std::string &filename)
std::vector< Cell > cells
std::string custom_filename