Files
scummvm/engines/qdengine/debugger/debugtools.cpp
T
D G Turner 4a4431f488 IMGUI: Disable Obselete ImGui APIs and Migrate Engine Code To Newer
This is a fairly trivial change to use the newer API in DIRECTOR,
QDENGINE and TWP engines, so not likely to cause any regressions.

This has the side effect of removing some GCC compiler warnings.
2024-11-14 01:27:35 +00:00

566 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/imgui/IconsMaterialSymbols.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "backends/imgui/imgui.h"
#include "backends/imgui/imgui_fonts.h"
#include "common/archive.h"
#include "common/compression/unzip.h"
#include "common/debug.h"
#include "common/path.h"
#include "common/stack.h"
#include "common/system.h"
#include "graphics/managed_surface.h"
#include "qdengine/debugger/dt-internal.h"
#include "qdengine/qd_fwd.h"
#include "qdengine/qdcore/qd_animation.h"
#include "qdengine/qdcore/qd_animation_frame.h"
#include "qdengine/qdcore/qd_file_manager.h"
#include "qdengine/qdcore/qd_game_dispatcher.h"
#include "qdengine/qdcore/qd_game_object.h"
#include "qdengine/qdcore/qd_game_object_moving.h"
#include "qdengine/qdcore/qd_game_scene.h"
#include "qdengine/qdengine.h"
#include "qdengine/system/graphics/gr_dispatcher.h"
namespace QDEngine {
const int TILES_ID = -1337;
ImGuiState *_state = nullptr;
ImGuiImage getImageID(Common::Path filename, int frameNum) {
Common::String key = Common::String::format("%s:%d", filename.toString().c_str(), frameNum);
if (_state->_frames.contains(key))
return _state->_frames[key];
int sx = 10, sy = 10;
Graphics::ManagedSurface *surface = nullptr;
if (_state->_displayMode == kDisplayQDA) {
// Load the animation
qdAnimation *animation = new qdAnimation();
animation->qda_load(filename);
_state->_qdaToDisplayFrameCount = animation->num_frames();
if (frameNum == TILES_ID) {
if (animation->tileAnimation()) {
surface = animation->tileAnimation()->dumpTiles(25);
sx = surface->w;
sy = surface->h;
}
} else if (frameNum < 0) { // Tiles
if (animation->tileAnimation()) {
surface = animation->tileAnimation()->dumpFrameTiles(-frameNum + 1, 0.91670f);
sx = surface->w;
sy = surface->h;
}
} else {
if (animation->tileAnimation()) {
Vect2i size = animation->tileAnimation()->frameSize();
sx = size.x;
sy = size.y;
} else {
qdAnimationFrame *frame = animation->get_frame(frameNum);
if (frame) {
sx = frame->size_x();
sy = frame->size_y();
}
}
surface = new Graphics::ManagedSurface(sx, sy, g_engine->_pixelformat);
animation->set_cur_frame(frameNum);
grDispatcher::instance()->surfaceOverride(surface);
animation->redraw(sx / 2, sy / 2, 0, 0.91670f, 0);
grDispatcher::instance()->resetSurfaceOverride();
}
delete animation;
} else if (_state->_displayMode == kDisplayTGA) {
qdSprite *sprite = new qdSprite();
if (sprite->load(filename)) {
sx = sprite->size_x();
sy = sprite->size_y();
surface = new Graphics::ManagedSurface(sx, sy, g_engine->_pixelformat);
grDispatcher::instance()->surfaceOverride(surface);
sprite->redraw(sx / 2, sy / 2, 0);
grDispatcher::instance()->resetSurfaceOverride();
} else {
warning("Error loading TGA file '%s'", transCyrillic(filename.toString()));
}
delete sprite;
}
if (surface)
_state->_frames[key] = { (ImTextureID)g_system->getImGuiTexture(*surface->surfacePtr()), sx, sy };
delete surface;
return _state->_frames[key];
}
void showImage(const ImGuiImage &image, const char *name, float scale) {
ImVec2 size = { (float)image.width * scale, (float)image.height * scale };
ImGui::BeginGroup();
ImVec2 screenPos = ImGui::GetCursorScreenPos();
ImGui::GetWindowDrawList()->AddRect(screenPos, screenPos + ImVec2(size.x, size.y), 0xFFFFFFFF);
ImGui::Image(image.id, size);
ImGui::EndGroup();
//setToolTipImage(image, name);
}
FileTree::FileTree(Common::Path *p, Common::String n, bool node, int i) {
id = i;
if (!node) {
name = (char *)transCyrillic(n);
} else {
path = *p;
name = (char *)transCyrillic(p->baseName());
}
}
void populateFileList() {
Common::Array<Common::Path> files;
// Iterate through the 3 resource pak files
for (int i = 0; i < qdFileManager::instance().get_num_packages(); i++) {
Common::Archive *archive = qdFileManager::instance().get_package(i);
Common::ArchiveMemberList members;
if (archive)
archive->listMembers(members);
for (auto &it : members)
files.push_back(it->getPathInArchive());
}
Common::sort(files.begin(), files.end());
// Now build a tree
Common::Path curr;
Common::Stack<FileTree *> treeStack;
_state->_files.name = "Resource";
treeStack.push(&_state->_files);
int id = 0;
for (unsigned int f = 0; f < files.size(); f++) {
// Skip duplicates between the archives
if (f && files[f] == files[f - 1])
continue;
Common::Path parent = files[f].getParent();
if (parent != curr) {
Common::StringArray curArr = curr.splitComponents();
Common::StringArray newArr = parent.splitComponents();
if (curArr.back().empty())
curArr.pop_back();
if (newArr.back().empty())
newArr.pop_back();
unsigned int pos = 0;
while (pos < curArr.size() && pos < newArr.size() && curArr[pos] == newArr[pos])
pos++;
// if we need to close directories
if (pos < curArr.size()) {
for (unsigned int i = pos; i < curArr.size(); i++)
(void)treeStack.pop();
}
for (; pos < newArr.size(); pos++) {
if (id == 0 && newArr[pos] == "Resource") // Skip the root node
continue;
treeStack.top()->children.push_back(new FileTree(nullptr, newArr[pos], false, ++id));
treeStack.push(treeStack.top()->children.back());
}
curr = parent;
}
treeStack.top()->children.push_back(new FileTree(&files[f], "", true, ++id));
id++;
}
}
static void displayQDA() {
int totalFrames = _state->_qdaToDisplayFrameCount;
ImGuiImage imgID;
if (!_state->_fileToDisplay.empty()) {
imgID = getImageID(_state->_fileToDisplay, _state->_qdaToDisplayFrame);
ImGui::Text("Frame %s: %d of %d [%d x %d]", transCyrillic(_state->_fileToDisplay.toString()), _state->_qdaToDisplayFrame + 1,
totalFrames, imgID.width, imgID.height);
} else {
ImGui::Text("Frame <none>");
}
ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None;
if (ImGui::BeginTabBar("FrameTabBar", tab_bar_flags)) {
if (ImGui::BeginTabItem("Animation")) {
if (ImGui::Button(ICON_MS_FAST_REWIND)) {
_state->_qdaToDisplayFrame = 0;
_state->_qdaIsPlaying = false;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MS_SKIP_PREVIOUS)) {
_state->_qdaToDisplayFrame = _state->_qdaToDisplayFrame + totalFrames - 1;
_state->_qdaToDisplayFrame %= totalFrames;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MS_PLAY_ARROW))
_state->_qdaIsPlaying = !_state->_qdaIsPlaying;
ImGui::SameLine();
if (ImGui::Button(ICON_MS_SKIP_NEXT)) {
_state->_qdaToDisplayFrame += 1;
_state->_qdaToDisplayFrame %= totalFrames;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MS_FAST_FORWARD)) {
_state->_qdaToDisplayFrame = totalFrames - 1;
_state->_qdaIsPlaying = false;
}
ImGui::SameLine();
// Frame Count
char buf[6];
snprintf(buf, 6, "%d", _state->_qdaToDisplayFrame);
ImGui::SetNextItemWidth(35);
ImGui::InputText("##frame", buf, 5, ImGuiInputTextFlags_CharsDecimal);
ImGui::SetItemTooltip("Frame");
ImGui::Separator();
if (!_state->_fileToDisplay.empty()) {
showImage(imgID, (char *)transCyrillic(_state->_fileToDisplay.toString()), 1.0);
} else {
ImGui::InvisibleButton("##canvas", ImVec2(32.f, 32.f));
}
ImGui::SameLine();
imgID = getImageID(_state->_fileToDisplay, -_state->_qdaToDisplayFrame - 1);
showImage(imgID, (char *)transCyrillic(_state->_fileToDisplay.toString()), 1.0);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Tiles")) {
if (!_state->_fileToDisplay.empty()) {
imgID = getImageID(_state->_fileToDisplay, TILES_ID);
showImage(imgID, (char *)transCyrillic(_state->_fileToDisplay.toString()), 1.0);
} else {
ImGui::InvisibleButton("##canvas", ImVec2(32.f, 32.f));
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
static void displayTGA() {
ImGuiImage imgID;
imgID = getImageID(_state->_fileToDisplay, 0);
ImGui::Text("TGA %s: [%d x %d]", transCyrillic(_state->_fileToDisplay.toString()), imgID.width, imgID.height);
ImGui::Separator();
showImage(imgID, (char *)transCyrillic(_state->_fileToDisplay.toString()), 1.0);
}
void displayTree(FileTree *tree) {
if (tree->children.empty()) { // It is a file
if (ImGui::Selectable(tree->name.c_str(), _state->_fileToDisplay == tree->path)) {
_state->_fileToDisplay = tree->path;
if (tree->name.hasSuffixIgnoreCase(".qda")) {
_state->_qdaToDisplayFrame = 0;
_state->_qdaIsPlaying = false;
_state->_displayMode = kDisplayQDA;
} else if (tree->name.hasSuffixIgnoreCase(".tga")) {
_state->_displayMode = kDisplayTGA;
} else {
_state->_displayMode = -1;
}
}
return;
}
if (ImGui::TreeNode((void*)(intptr_t)(tree->id), tree->name.c_str())) {
for (auto &it : tree->children)
displayTree(it);
ImGui::TreePop();
}
}
void showArchives() {
if (!_state->_showArchives)
return;
// Calculate the viewport size
ImVec2 viewportSize = ImGui::GetMainViewport()->Size;
// Calculate the window size
ImVec2 windowSize = ImVec2(
viewportSize.x * 0.9f,
viewportSize.y * 0.9f
);
// Calculate the centered position
ImVec2 centeredPosition = ImVec2(
(viewportSize.x - windowSize.x) * 0.5f,
(viewportSize.y - windowSize.y) * 0.5f
);
// Set the next window position and size
ImGui::SetNextWindowPos(centeredPosition, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Archives", &_state->_showArchives)) {
ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.4f, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_None);
ImGui::Button(ICON_MS_FILTER_ALT);
ImGui::SameLine();
_state->_nameFilter.Draw();
ImGui::Separator();
if (_state->_files.children.empty())
populateFileList();
displayTree(&_state->_files);
ImGui::EndChild();
ImGui::SameLine();
{ // Right pane
ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
if (_state->_displayMode == kDisplayQDA) {
displayQDA();
} else if (_state->_displayMode == kDisplayTGA) {
displayTGA();
}
ImGui::EndChild();
}
}
ImGui::End();
}
void showSceneObjects() {
if (!_state->_showSceneObjects)
return;
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(300, 250), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Scene Objects", &_state->_showSceneObjects)) {
qdGameScene *scene;
qdGameDispatcher *dp = qdGameDispatcher::get_dispatcher();
if (dp && ((scene = dp->get_active_scene()))) {
if (!scene->object_list().empty()) {
for (auto &it : g_engine->_visible_objects) {
if (ImGui::Selectable((char *)transCyrillic(it->name()), _state->_objectToDisplay == it->name())) {
_state->_objectToDisplay = it->name();
}
}
}
}
}
ImGui::End();
}
void showScenePersonages() {
if (!_state->_showScenePersonages)
return;
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(300, 250), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Scene Personages", &_state->_showScenePersonages)) {
qdGameScene *scene;
qdGameDispatcher *dp = qdGameDispatcher::get_dispatcher();
if (dp && ((scene = dp->get_active_scene()))) {
if (!scene->getPersonages()->empty()) {
if (ImGui::BeginTable("Personages", 8, ImGuiTableFlags_Borders)) {
ImGuiTableFlags flags = ImGuiTableColumnFlags_WidthFixed;
ImGui::TableSetupColumn("Name", flags);
ImGui::TableSetupColumn("Flags", flags);
ImGui::TableSetupColumn("Control", flags);
ImGui::TableSetupColumn("Movement", flags);
ImGui::TableSetupColumn("Frame", flags);
ImGui::TableSetupColumn("Time", flags);
ImGui::TableSetupColumn("Anim Flags", flags);
ImGui::TableSetupColumn("Anim Status", flags);
ImGui::TableHeadersRow();
for (auto &it : *scene->getPersonages()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text((char *)transCyrillic(it->name()));
qdGameObjectState *st = it->get_state(it->cur_state());
ImGui::TableNextColumn();
ImGui::Text("%s", st ? qdGameObjectState::flag2str(st->flags(), true, true).c_str() : "<none>");
ImGui::SetItemTooltip("%s", st ? qdGameObjectState::flag2str(st->flags(), true).c_str() : "<none>");
ImGui::TableNextColumn();
ImGui::Text(qdGameObjectMoving::control2str(it->get_control_types(), true).c_str());
ImGui::TableNextColumn();
ImGui::Text(qdGameObjectMoving::movement2str(it->get_movement_mode(), true).c_str());
qdAnimation *anim = it->get_animation();
ImGui::TableNextColumn();
ImGui::Text("%d / %d", anim->get_cur_frame_number(), anim->num_frames());
ImGui::TableNextColumn();
ImGui::Text("%f / %f", anim->cur_time(), anim->length());
ImGui::TableNextColumn();
ImGui::Text(qdAnimation::flag2str(anim->flags(), true, true).c_str());
ImGui::SetItemTooltip(qdAnimation::flag2str(anim->flags(), true).c_str());
ImGui::TableNextColumn();
ImGui::Text(qdAnimation::status2str(anim->status(), true).c_str());
}
ImGui::EndTable();
}
}
}
}
ImGui::End();
}
void onImGuiInit() {
ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontDefault();
static const ImWchar cyrillic_ranges[] = {
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x0400, 0x04FF, // Cyrillic
0
};
io.FontDefault = ImGui::addTTFFontFromArchive("FreeSans.ttf", 16.0f, nullptr, cyrillic_ranges);;
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = false;
icons_config.OversampleH = 3;
icons_config.OversampleV = 3;
icons_config.GlyphOffset = {0, 4};
static const ImWchar icons_ranges[] = {ICON_MIN_MS, ICON_MAX_MS, 0};
ImGui::addTTFFontFromArchive("MaterialSymbolsSharp.ttf", 16.f, &icons_config, icons_ranges);
_state = new ImGuiState();
}
void onImGuiRender() {
if (!debugChannelSet(-1, kDebugImGui)) {
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
return;
}
if (!_state)
return;
if (_state->_qdaIsPlaying && (int)g_system->getMillis() > _state->_qdaNextFrameTimestamp) {
_state->_qdaToDisplayFrame++;
_state->_qdaToDisplayFrame %= _state->_qdaToDisplayFrameCount;
_state->_qdaNextFrameTimestamp = g_system->getMillis() + 50; // 20 fps
}
ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::SeparatorText("Windows");
ImGui::MenuItem("Archives", NULL, &_state->_showArchives);
ImGui::MenuItem("Scene Objects", NULL, &_state->_showSceneObjects);
ImGui::MenuItem("Scene Personages", NULL, &_state->_showScenePersonages);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
showArchives();
showSceneObjects();
showScenePersonages();
}
void onImGuiCleanup() {
delete _state;
_state = nullptr;
}
} // namespace QDEngine