diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4214968d..f9b59aec96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,8 @@ add_library(${PROJECT_NAME} OBJECT src/scene.cpp src/scene_debug.cpp src/scene_debug.h + src/scene_debug_picture.cpp + src/scene_debug_picture.h src/scene_end.cpp src/scene_end.h src/scene_equip.cpp @@ -420,6 +422,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_command.h src/window_command_horizontal.cpp src/window_command_horizontal.h + src/window_debug_picture.cpp + src/window_debug_picture.h src/window.cpp src/window_equip.cpp src/window_equip.h diff --git a/Makefile.am b/Makefile.am index 5d00e6cdcd..6067bf14f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -280,6 +280,8 @@ libeasyrpg_player_a_SOURCES = \ src/scene_battle_rpg2k3.h \ src/scene_debug.cpp \ src/scene_debug.h \ + src/scene_debug_picture.cpp \ + src/scene_debug_picture.h \ src/scene_end.cpp \ src/scene_end.h \ src/scene_equip.cpp \ @@ -426,6 +428,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_numberinput.h \ src/window_paramstatus.cpp \ src/window_paramstatus.h \ + src/window_debug_picture.cpp \ + src/window_debug_picture.h \ src/window_savefile.cpp \ src/window_savefile.h \ src/window_selectable.cpp \ diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 3e4842ba67..62b3858956 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4967,12 +4967,244 @@ bool Game_Interpreter::CommandManiacControlGlobalSave(lcf::rpg::EventCommand con return true; } -bool Game_Interpreter::CommandManiacChangePictureId(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacChangePictureId(lcf::rpg::EventCommand const& com) { + /* + TPC Structure Reference: + @pic[target1].setId .move(target2, size) .ignoreError + @pic[target1].setId .swap(target2, size) .ignoreError + @pic[target1].setId .slide(distance, size) .ignoreError + + Parameters: + [0] Operation: 0 = Move, 1 = Swap, 2 = Slide + [1] Packing: + Bits 0-3: Target 1 Mode (0: Const, 1: Var, 2: Indirect) + Bits 4-7: Size Mode (0: Const, 1: Var, 2: Indirect) + Bits 8-11: Object 2 / Distance Mode (0: Const, 1: Var, 2: Indirect) + [2] Target 1 Value + [3] Size Value + [4] Object 2 / Distance Value + [5] Ignore Error (1 = Ignore) + */ + if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command ChangePictureId not supported"); + int operation = com.parameters[0]; + int target1 = ValueOrVariableBitfield(com, 1, 0, 2); + int size = ValueOrVariableBitfield(com, 1, 1, 3); + int arg3 = ValueOrVariableBitfield(com, 1, 2, 4); // Target 2 or Distance + + bool ignore_error = com.parameters.size() > 5 && com.parameters[5] != 0; + + if (size <= 0) { + return true; + } + + auto& pictures = *Main_Data::game_pictures; + auto& windows = *Main_Data::game_windows; + + auto isValidId = [](int id) { + return id > 0; + }; + + // Helper to move a single picture from src to dst + auto move_picture = [&](int src, int dst) { + // Ensure existence in vectors to avoid reference invalidation during assignments + int max_id = std::max(src, dst); + pictures.GetPicture(max_id); + windows.GetWindow(max_id); + + auto& src_pic = pictures.GetPicture(src); + auto& dst_pic = pictures.GetPicture(dst); + + // If source is empty, erase destination + if (!src_pic.Exists() && !src_pic.IsWindowAttached()) { + dst_pic.Erase(); + return; + } + + // 1. Handle Window Data (String Pictures) + if (src_pic.IsWindowAttached()) { + auto& src_win = windows.GetWindow(src); + auto& dst_win = windows.GetWindow(dst); + dst_win.data = src_win.data; + dst_win.data.ID = dst; + src_win.Erase(); + } + else { + // If overwriting a window picture with a normal one, clear the old window data + // (Safe to call even if dst wasn't a window before) + windows.GetWindow(dst).Erase(); + } + + // 2. Handle Picture Data + BitmapRef src_bmp = src_pic.sprite ? src_pic.sprite->GetBitmap() : nullptr; + auto request_id = src_pic.request_id; + src_pic.request_id = nullptr; // Prevent cancellation on Erase + + dst_pic.data = src_pic.data; + dst_pic.data.ID = dst; + dst_pic.request_id = request_id; + + src_pic.Erase(); + + // 3. Refresh Sprite + if (dst_pic.IsWindowAttached()) { + // Re-attach window to generate sprite + bool async; + windows.GetWindow(dst).Refresh(async); + } + else if (!dst_pic.data.name.empty()) { + if (!dst_pic.sprite) dst_pic.CreateSprite(); + if (src_bmp) { + dst_pic.sprite->SetBitmap(src_bmp); + dst_pic.sprite->OnPictureShow(); + dst_pic.sprite->SetVisible(true); + } + } + else { + dst_pic.sprite.reset(); + } + }; + + // Helper to swap two pictures + auto swap_picture = [&](int id1, int id2) { + // Ensure existence in vectors to avoid reference invalidation during assignments + int max_id = std::max(id1, id2); + pictures.GetPicture(max_id); + windows.GetWindow(max_id); + + auto& p1 = pictures.GetPicture(id1); + auto& p2 = pictures.GetPicture(id2); + + // Swap Window Data + auto& w1 = windows.GetWindow(id1); + auto& w2 = windows.GetWindow(id2); + std::swap(w1.data, w2.data); + w1.data.ID = id1; + w2.data.ID = id2; + + // Swap Picture Data + BitmapRef b1 = p1.sprite ? p1.sprite->GetBitmap() : nullptr; + BitmapRef b2 = p2.sprite ? p2.sprite->GetBitmap() : nullptr; + + using std::swap; + swap(p1.data, p2.data); + swap(p1.request_id, p2.request_id); + + p1.data.ID = id1; + p2.data.ID = id2; + + // Refresh Sprites Helper + auto refresh = [&](Game_Pictures::Picture& p, BitmapRef bmp) { + if (p.IsWindowAttached()) { + bool async; + windows.GetWindow(p.data.ID).Refresh(async); + } + else if (!p.data.name.empty()) { + if (!p.sprite) p.CreateSprite(); + if (bmp) { + p.sprite->SetBitmap(bmp); + p.sprite->OnPictureShow(); + p.sprite->SetVisible(true); + } + } + else { + p.sprite.reset(); + } + }; + + refresh(p1, b2); + refresh(p2, b1); + }; + + if (operation == 0 || operation == 2) { + // Move (0) or Slide (2) + int target2 = (operation == 0) ? arg3 : (target1 + arg3); + + int start = 0; + int end = size; + int step = 1; + + // Handle overlapping ranges + if (target2 > target1) { + start = size - 1; + end = -1; + step = -1; + } + + for (int i = start; i != end; i += step) { + int src_id = target1 + i; + int dst_id = target2 + i; + + if (!isValidId(src_id)) { + if (!ignore_error) { + Output::Warning("Maniac ChangePictureId {}: Invalid Picture ID {}", (operation == 0 ? "Move (Source)" : "Slide (Source)"), src_id); + return true; + } + continue; + } + + if (!isValidId(dst_id)) { + if (!ignore_error) { + Output::Warning("Maniac ChangePictureId {}: Invalid Picture ID {}", (operation == 0 ? "Move (Dest)" : "Slide (Dest)"), dst_id); + return true; + } + + // "If you use the "Ignore out-of-range errors" setting, moving from/to an out-of-range ID will be replaced by a simple delete operation." + // If destination is invalid, delete source. + auto& src_pic = pictures.GetPicture(src_id); + if (src_pic.Exists() || src_pic.IsWindowAttached()) { + pictures.Erase(src_id); + } + continue; + } + + if (src_id != dst_id) { + move_picture(src_id, dst_id); + } + } + } + else if (operation == 1) { + // Swap + int target2 = arg3; + + for (int i = 0; i < size; ++i) { + int id1 = target1 + i; + int id2 = target2 + i; + + bool valid1 = isValidId(id1); + bool valid2 = isValidId(id2); + + if (!valid1 && !ignore_error) { + Output::Warning("Maniac ChangePictureId Swap: Invalid Picture ID {}", id1); + return true; + } + if (!valid2 && !ignore_error) { + Output::Warning("Maniac ChangePictureId Swap: Invalid Picture ID {}", id2); + return true; + } + + if (valid1 && valid2) { + swap_picture(id1, id2); + } + else if (valid1 && !valid2) { + // Valid swap with invalid -> erase valid + pictures.Erase(id1); + } + else if (!valid1 && valid2) { + // Invalid swap with valid -> erase valid + pictures.Erase(id2); + } + } + } + else { + Output::Warning("Maniac ChangePictureId: Unknown operation {}", operation); + } + + Game_Map::SetNeedRefresh(true); + return true; } diff --git a/src/scene.cpp b/src/scene.cpp index bc95a3d3f4..e5977e1ade 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -44,7 +44,7 @@ std::shared_ptr Scene::instance; std::vector > Scene::old_instances; std::vector > Scene::instances; -const char Scene::scene_names[SceneMax][12] = +const char Scene::scene_names[SceneMax][13] = { "Null", "Title", @@ -69,7 +69,8 @@ const char Scene::scene_names[SceneMax][12] = "GameBrowser", "Teleport", "Settings", - "Language" + "Language", + "DebugPicture" }; enum PushPopOperation { @@ -116,6 +117,8 @@ lcf::rpg::SaveSystem::Scene Scene::rpgRtSceneFromSceneType(SceneType t) { return lcf::rpg::SaveSystem::Scene_game_over; case Debug: return lcf::rpg::SaveSystem::Scene_debug; + case DebugPicture: + return lcf::rpg::SaveSystem::Scene_debug; } return lcf::rpg::SaveSystem::Scene(-1); } diff --git a/src/scene.h b/src/scene.h index 51d077b43c..d00af5e624 100644 --- a/src/scene.h +++ b/src/scene.h @@ -60,6 +60,7 @@ class Scene { Teleport, Settings, LanguageMenu, + DebugPicture, SceneMax }; @@ -203,7 +204,7 @@ class Scene { static std::vector > old_instances; /** Contains name of the Scenes. For debug purposes. */ - static const char scene_names[SceneMax][12]; + static const char scene_names[SceneMax][13]; /** * Called by the graphic system to request drawing of a background, usually a system color background diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 9206a73af0..d2c9f114f5 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -34,6 +34,7 @@ #include "scene_menu.h" #include "scene_save.h" #include "scene_map.h" +#include "scene_debug_picture.h" #include "scene_battle.h" #include "player.h" #include "window_command.h" @@ -621,6 +622,10 @@ void Scene_Debug::vUpdate() { PushUiRangeList(); } break; + case ePictureTool: + Scene::Push(std::make_shared()); + mode = eMain; + return; case eInterpreter: if (sz == 3) { auto action = interpreter_window->GetSelectedAction(); @@ -776,6 +781,7 @@ void Scene_Debug::UpdateRangeListWindow() { addItem("Call MapEvent", Scene::Find(Scene::Map) != nullptr); addItem("Call BtlEvent", is_battle); addItem("Strings", Player::IsPatchManiac()); + addItem("Pictures"); addItem("Interpreter"); addItem("Open Menu", !is_battle); } diff --git a/src/scene_debug.h b/src/scene_debug.h index a3efc68e6e..2c04ed97e5 100644 --- a/src/scene_debug.h +++ b/src/scene_debug.h @@ -72,6 +72,7 @@ class Scene_Debug : public Scene { eCallMapEvent, eCallBattleEvent, eString, + ePictureTool, eInterpreter, eOpenMenu, eLastMainMenuOption, diff --git a/src/scene_debug_picture.cpp b/src/scene_debug_picture.cpp new file mode 100644 index 0000000000..ca3b221d7a --- /dev/null +++ b/src/scene_debug_picture.cpp @@ -0,0 +1,68 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "scene_debug_picture.h" +#include "input.h" +#include "player.h" +#include "game_system.h" +#include "main_data.h" + +Scene_DebugPicture::Scene_DebugPicture() { + type = Scene::DebugPicture; +} + +void Scene_DebugPicture::Start() { + // Make list window narrow (just IDs) to maximize info space + int list_w = 64; + + list_window = std::make_unique( + Player::menu_offset_x, + Player::menu_offset_y, + list_w, + MENU_HEIGHT + ); + + info_window = std::make_unique( + Player::menu_offset_x + list_w, + Player::menu_offset_y, + MENU_WIDTH - list_w, + MENU_HEIGHT + ); + + list_window->SetActive(true); + list_window->SetIndex(0); + + // Initialize info window with first item + info_window->SetPictureId(list_window->GetPictureId()); +} + +void Scene_DebugPicture::vUpdate() { + list_window->Update(); + info_window->Update(); + + // Update info window based on selection + if (list_window->GetActive()) { + // Always refresh info window to see real-time coordinate updates + info_window->SetPictureId(list_window->GetPictureId()); + info_window->Refresh(); + } + + if (Input::IsTriggered(Input::CANCEL)) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); + Scene::Pop(); + } +} diff --git a/src/scene_debug_picture.h b/src/scene_debug_picture.h new file mode 100644 index 0000000000..dcc5d920dc --- /dev/null +++ b/src/scene_debug_picture.h @@ -0,0 +1,36 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_SCENE_DEBUG_PICTURE_H +#define EP_SCENE_DEBUG_PICTURE_H + +#include "scene.h" +#include "window_debug_picture.h" +#include + +class Scene_DebugPicture : public Scene { +public: + Scene_DebugPicture(); + void Start() override; + void vUpdate() override; + +private: + std::unique_ptr list_window; + std::unique_ptr info_window; +}; + +#endif diff --git a/src/window_debug_picture.cpp b/src/window_debug_picture.cpp new file mode 100644 index 0000000000..09a057566c --- /dev/null +++ b/src/window_debug_picture.cpp @@ -0,0 +1,320 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "window_debug_picture.h" +#include "game_pictures.h" +#include "game_windows.h" +#include "main_data.h" +#include "bitmap.h" +#include "font.h" +#include "utils.h" +#include +#include +#include +#include +#include + +Window_DebugPictureList::Window_DebugPictureList(int x, int y, int w, int h) : + Window_Selectable(x, y, w, h) +{ + SetMenuItemHeight(16); + SetColumnMax(1); + SetContents(Bitmap::Create(width, height)); + Refresh(); +} + +void Window_DebugPictureList::Refresh() { + picture_ids.clear(); + + // Scan all allocated pictures + for (int i = 1; ; ++i) { + auto* pic = Main_Data::game_pictures->GetPicturePtr(i); + if (!pic) { + break; + } + + if (pic->Exists() || pic->IsWindowAttached()) { + picture_ids.push_back(i); + } + } + + item_max = picture_ids.size(); + + int required_height = std::max(height - 32, (int)(item_max * menu_item_height)); + if (contents->GetHeight() != required_height) { + SetContents(Bitmap::Create(contents->GetWidth(), required_height)); + } + + contents->Clear(); + + if (item_max == 0) { + contents->TextDraw(0, 0, Font::ColorDisabled, "No Pic"); + } + else { + for (int i = 0; i < item_max; ++i) { + Rect rect = GetItemRect(i); + int id = picture_ids[i]; + auto* pic = Main_Data::game_pictures->GetPicturePtr(id); + + std::string suffix = ""; + if (pic->IsWindowAttached()) suffix = " T"; // Text/String + + std::string text = fmt::format("{:04d}{}", id, suffix); + contents->TextDraw(rect.x, rect.y, Font::ColorDefault, text); + } + } +} + +int Window_DebugPictureList::GetPictureId() const { + if (index >= 0 && index < static_cast(picture_ids.size())) { + return picture_ids[index]; + } + return 0; +} + +// --------------------------------------------------------------------------- + +Window_DebugPictureInfo::Window_DebugPictureInfo(int x, int y, int w, int h) : + Window_Base(x, y, w, h) +{ + SetContents(Bitmap::Create(width, height)); +} + +void Window_DebugPictureInfo::SetPictureId(int id) { + if (picture_id != id) { + picture_id = id; + Refresh(); + } +} + +int Window_DebugPictureInfo::DrawLine(int y, std::string_view label, std::string_view value) { + contents->TextDraw(0, y, Font::ColorDefault, label); + int val_x = 40; + contents->TextDraw(val_x, y, Font::ColorHeal, value); + return y + 16; +} + +int Window_DebugPictureInfo::DrawDualLine(int y, std::string_view l1, std::string_view v1, std::string_view l2, std::string_view v2) { + contents->TextDraw(0, y, Font::ColorDefault, l1); + contents->TextDraw(40, y, Font::ColorHeal, v1); + + contents->TextDraw(110, y, Font::ColorDefault, l2); + contents->TextDraw(150, y, Font::ColorHeal, v2); + return y + 16; +} + +int Window_DebugPictureInfo::DrawSeparator(int y) { + // Draw a dim line + Color col; + col.alpha = 128; + contents->FillRect(Rect(0, y + 7, contents->GetWidth(), 1), col); + return y + 16; +} + +int Window_DebugPictureInfo::DrawFlags(int y, const std::vector& flags) { + int x = 0; + int row_start_y = y; + + for (const auto& flag : flags) { + int w = Text::GetSize(*Font::Default(), flag.name).width + 4; + if (x + w > contents->GetWidth()) { + x = 0; + y += 16; + } + + // Draw bracketed flag like [FlipX] + std::string text = fmt::format("[{}]", flag.name); + contents->TextDraw(x, y, flag.active ? Font::ColorHeal : Font::ColorDisabled, text); + + x += w + 12; + } + + return y + 16; +} + +void Window_DebugPictureInfo::Refresh() { + contents->Clear(); + + if (picture_id <= 0) { + contents->TextDraw(0, 0, Font::ColorDisabled, "No Selection"); + return; + } + + auto* pic = Main_Data::game_pictures->GetPicturePtr(picture_id); + if (!pic) { + contents->TextDraw(0, 0, Font::ColorCritical, "Invalid ID"); + return; + } + + bool is_str = pic->IsWindowAttached(); + if (!pic->Exists() && !is_str) { + contents->TextDraw(0, 0, Font::ColorDisabled, "Empty"); + return; + } + + const auto& d = pic->data; + int y = 0; + + // === COMMON PROPERTIES === + + // ID & Type + std::string type_str = is_str ? "String" : "Image"; + DrawDualLine(y, "ID", fmt::format("{}", picture_id), "Type", type_str); + y += 16; + + // Position & Movement + std::string pos_str = fmt::format("{:.0f},{:.0f}", d.current_x, d.current_y); + y = DrawLine(y, "Pos", pos_str); + + if (d.time_left > 0 || d.current_x != d.finish_x || d.current_y != d.finish_y) { + std::string goal_str = fmt::format("{:.0f},{:.0f} ({}f)", d.finish_x, d.finish_y, d.time_left); + y = DrawLine(y, "Goal", goal_str); + } + + // Scale + std::string scale_str = fmt::format("{:.0f}", d.current_magnify); + if (d.maniac_current_magnify_height != d.current_magnify) { + scale_str += fmt::format(" / {:.0f}", d.maniac_current_magnify_height); + } + + + // Transparency + std::string trans_str; + if (d.current_top_trans == d.current_bot_trans) { + trans_str = fmt::format("{:.0f}", d.current_top_trans); + } + else { + trans_str = fmt::format("{:.0f}/{:.0f}", d.current_top_trans, d.current_bot_trans); + } + + + y = DrawDualLine(y, "Scale", scale_str + "%", "Trans", trans_str + "%"); + + // Blend & Layer + std::string blend = "None"; + if (d.easyrpg_blend_mode == 1) blend = "Multiply"; + if (d.easyrpg_blend_mode == 2) blend = "Addition"; + if (d.easyrpg_blend_mode == 3) blend = "Overlay"; + + std::string layer = fmt::format("M:{} B:{}", d.map_layer, d.battle_layer); + y = DrawDualLine(y, "Blend", blend, "Layer", layer); + + // Tone (R,G,B,S) + std::string tone_str = fmt::format("{:.0f},{:.0f},{:.0f},{:.0f}", d.current_red, d.current_green, d.current_blue, d.current_sat); + y = DrawLine(y, "Tone", tone_str); + + // Effects + if (d.effect_mode != lcf::rpg::SavePicture::Effect_none) { + std::string effect; + switch (d.effect_mode) { + case lcf::rpg::SavePicture::Effect_rotation: effect = "Rot"; break; + case lcf::rpg::SavePicture::Effect_wave: effect = "Wave"; break; + case lcf::rpg::SavePicture::Effect_maniac_fixed_angle: effect = "Ang"; break; + } + effect += fmt::format(" {:.1f}", d.current_effect_power); + if (d.effect_mode == lcf::rpg::SavePicture::Effect_rotation || d.effect_mode == lcf::rpg::SavePicture::Effect_maniac_fixed_angle) { + effect += fmt::format(" ({:.1f})", d.current_rotation); + } + y = DrawLine(y, "FX", effect); + } + + y = DrawSeparator(y); + // Common Flags + std::vector flags = { + { "Fixed", d.fixed_to_map }, + { "Chroma", d.use_transparent_color }, + { "Tint", d.flags.affected_by_tint }, + { "Flash", d.flags.affected_by_flash }, + { "Shake", d.flags.affected_by_shake }, + { "EraseOnMapChange", d.flags.erase_on_map_change }, + { "EraseAfterBattle", d.flags.erase_on_battle_end }, + { "FlipX", (bool)(d.easyrpg_flip & lcf::rpg::SavePicture::EasyRpgFlip_x) }, + { "FlipY", (bool)(d.easyrpg_flip & lcf::rpg::SavePicture::EasyRpgFlip_y) } + }; + y = DrawFlags(y, flags); + + y = DrawSeparator(y); + + // === SPECIFIC PROPERTIES === + + if (!is_str) { + // FILE PICTURE + std::string name_str = std::string(d.name); + if (name_str.length() > 18) name_str = "..." + name_str.substr(name_str.length() - 15); + y = DrawLine(y, "File", name_str); + + if (d.spritesheet_cols > 1 || d.spritesheet_rows > 1) { + std::string cell_str = fmt::format("#{} ({}x{})", d.spritesheet_frame, d.spritesheet_cols, d.spritesheet_rows); + y = DrawLine(y, "Cell", cell_str); + + if (d.spritesheet_speed > 0) { + y = DrawLine(y, "Anim", fmt::format("Spd: {} {}", d.spritesheet_speed, d.spritesheet_play_once ? "[Once]" : "[Loop]")); + } + } + } + else { + // STRING PICTURE + auto& win = Main_Data::game_windows->GetWindow(picture_id); + const auto& wd = win.data; + + std::string dims = fmt::format("{}x{}", wd.width, wd.height); + y = DrawLine(y, "Size", dims); + + std::string skin = std::string(wd.system_name); + if (skin.empty()) skin = "Default"; + y = DrawLine(y, "Skin", skin); + + if (!wd.texts.empty()) { + const auto& txt = wd.texts[0]; // Usually only one text chunk for string pics + + std::string font_info = std::string(txt.font_name); + if (font_info.empty()) font_info = "Sys"; + font_info += fmt::format(" {}pt", txt.font_size); + y = DrawLine(y, "Font", font_info); + + y = DrawDualLine(y, "LSpc", std::to_string(txt.letter_spacing), "HSpc", std::to_string(txt.line_spacing)); + + // String Flags + std::vector str_flags = { + { "Frame", wd.flags.draw_frame }, + { "Grad", txt.flags.draw_gradient }, + { "Shdw", txt.flags.draw_shadow }, + { "Bold", txt.flags.bold }, + { "Ital", txt.flags.italic }, + { "Marg", wd.flags.border_margin } + }; + + // Background type (Stretch/Tile/None) + std::string bg_type = "Stretch"; + if (wd.message_stretch == 0) bg_type = "Tile"; + if (wd.message_stretch == 2) bg_type = "None"; // easyrpg_none + + y = DrawLine(y, "BG", bg_type); + y = DrawFlags(y, str_flags); + + // Content preview + y = DrawSeparator(y); + std::string content = ToString(txt.text); + // Simple replace newlines for preview + content = Utils::ReplaceAll(content, "\n", "\\n"); + if (content.length() > 22) content = content.substr(0, 20) + "..."; + + contents->TextDraw(0, y, Font::ColorDefault, "Text:"); + contents->TextDraw(40, y, Font::ColorDefault, content); + } + } +} diff --git a/src/window_debug_picture.h b/src/window_debug_picture.h new file mode 100644 index 0000000000..54e10cf074 --- /dev/null +++ b/src/window_debug_picture.h @@ -0,0 +1,70 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_WINDOW_DEBUG_PICTURE_H +#define EP_WINDOW_DEBUG_PICTURE_H + +#include "window_selectable.h" +#include "window_base.h" +#include +#include + + /** + * Debug window showing the list of active pictures. + */ +class Window_DebugPictureList : public Window_Selectable { +public: + Window_DebugPictureList(int x, int y, int w, int h); + + void Refresh(); + int GetPictureId() const; + +private: + std::vector picture_ids; +}; + +/** + * Debug window showing details of a specific picture. + */ +class Window_DebugPictureInfo : public Window_Base { +public: + Window_DebugPictureInfo(int x, int y, int w, int h); + + void SetPictureId(int id); + void Refresh(); + +private: + int picture_id = 0; + + // Draw label and value. Returns next Y. + int DrawLine(int y, std::string_view label, std::string_view value); + + // Draw two label/value pairs on one line. Returns next Y. + int DrawDualLine(int y, std::string_view l1, std::string_view v1, std::string_view l2, std::string_view v2); + + // Draw a separator line. + int DrawSeparator(int y); + + // Helper for drawing boolean flags compactly + struct FlagInfo { + const char* name; + bool active; + }; + int DrawFlags(int y, const std::vector& flags); +}; + +#endif