2022-06-17 21:37:58 -04:00

1570 lines
50 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 "gui/dialog.h"
#include "graphics/fontman.h"
#include "common/hash-ptr.h"
#include "mtropolis/debug.h"
#include "mtropolis/render.h"
#include "mtropolis/runtime.h"
namespace MTropolis {
static const byte g_sceneTreeGraphic[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte g_inspectorGraphic[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte g_stepThroughGraphic[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0,
0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const byte g_resizeGraphic[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
class DebugToolWindowBase : public Window {
public:
DebugToolWindowBase(DebuggerTool tool, const Common::String &title, Debugger *debugger, const WindowParameters &windowParams);
virtual void update() {}
void render();
void trySetScrollOffset(int32 scrollOffset);
protected:
const int kTopBarHeight = 12;
const int kScrollBarWidth = 12;
const int kCloseWidth = 12;
const int kResizeHeight = 12;
const int kMinScrollBarHandleSize = 20;
void onMouseDown(int32 x, int32 y, int mouseButton) override;
void onMouseMove(int32 x, int32 y) override;
void onMouseUp(int32 x, int32 y, int mouseButton) override;
virtual void toolOnMouseDown(int32 x, int32 y, int mouseButton) {}
virtual void toolOnMouseMove(int32 x, int32 y) {}
virtual void toolOnMouseUp(int32 x, int32 y, int mouseButton) {}
virtual void toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) {}
void setDirty();
Common::SharedPtr<Graphics::ManagedSurface> _toolSurface;
Debugger *_debugger;
private:
void refreshChrome();
void cancelScrolling();
enum ToolWindowWidget {
kToolWindowWidgetNone,
kToolWindowWidgetClose,
kToolWindowWidgetScrollEmpty,
kToolWindowWidgetScrollHandle,
kToolWindowWidgetScrollChannelUp,
kToolWindowWidgetScrollChannelDown,
kToolWindowWidgetResize,
kToolWindowWidgetMove,
};
ToolWindowWidget _activeWidget;
bool _isMouseCaptured;
int32 _dragStartX;
int32 _dragStartY;
int32 _resizeStartWidth;
int32 _resizeStartHeight;
DebuggerTool _tool;
Common::String _title;
bool _isDirty;
bool _forceRedrawChrome;
int _scrollOffset;
int _scrollBarHandleSize;
int _scrollBarHandleOffset;
int _scrollBarHandleMaxOffset;
int _maxScrollOffset;
bool _haveScrollBar;
int32 _scrollStartOffset;
bool _havePreferredScrollOffset;
int32 _preferredScrollOffset;
};
DebugToolWindowBase::DebugToolWindowBase(DebuggerTool tool, const Common::String &title, Debugger *debugger, const WindowParameters &windowParams)
: Window(windowParams), _debugger(debugger), _tool(tool), _title(title), _activeWidget(kToolWindowWidgetNone), _isMouseCaptured(false),
_isDirty(true), _scrollOffset(0), _haveScrollBar(false), _havePreferredScrollOffset(false), _forceRedrawChrome(false) {
refreshChrome();
}
void DebugToolWindowBase::render() {
if (_isDirty) {
_isDirty = false;
bool needChromeUpdate = _forceRedrawChrome;
_forceRedrawChrome = false;
int oldWidth = 0;
int oldHeight = 0;
if (_toolSurface) {
oldWidth = _toolSurface->w;
oldHeight = _toolSurface->h;
} else
needChromeUpdate = true;
int32 renderWidth = getWidth() - kScrollBarWidth;
int32 renderHeight = getHeight() - kTopBarHeight;
toolRenderSurface(renderWidth, renderHeight);
if (_toolSurface && !needChromeUpdate) {
if (oldWidth != _toolSurface->w || oldHeight != _toolSurface->h)
needChromeUpdate = true;
}
if (_toolSurface) {
bool needToResetHandle = false;
if (_havePreferredScrollOffset) {
_scrollOffset = _preferredScrollOffset;
_havePreferredScrollOffset = false;
needToResetHandle = true;
}
int32 contentsBottom = _toolSurface->h - _scrollOffset;
if (contentsBottom < renderHeight) {
_scrollOffset -= (renderHeight - contentsBottom);
}
if (_scrollOffset < 0)
_scrollOffset = 0;
int surfaceHeight = _toolSurface->h;
if (surfaceHeight > renderHeight) {
int channelSpace = getHeight() - kTopBarHeight - kMinScrollBarHandleSize - kResizeHeight;
if (!_haveScrollBar || oldHeight != surfaceHeight || needToResetHandle) {
_maxScrollOffset = surfaceHeight - renderHeight;
_scrollBarHandleSize = renderHeight * channelSpace / surfaceHeight + kMinScrollBarHandleSize;
_scrollBarHandleMaxOffset = (channelSpace + kMinScrollBarHandleSize - _scrollBarHandleSize);
_scrollBarHandleOffset = _scrollOffset * _scrollBarHandleMaxOffset / _maxScrollOffset;
}
_haveScrollBar = true;
} else {
_haveScrollBar = false;
cancelScrolling();
}
int32 srcLeft = 0;
int32 srcTop = 0;
int32 srcRight = _toolSurface->w;
int32 srcBottom = _toolSurface->h;
int32 destLeft = 0;
int32 destRight = _toolSurface->w;
int32 destTop = -_scrollOffset;
int32 destBottom = _toolSurface->h - _scrollOffset;
if (srcTop < 0) {
int32 adjust = -srcTop;
destTop += adjust;
srcTop += adjust;
}
if (destTop < 0) {
int32 adjust = -destTop;
destTop += adjust;
srcTop += adjust;
}
if (srcBottom > _toolSurface->h) {
int32 adjust = srcBottom - _toolSurface->h;
destBottom += adjust;
srcBottom += adjust;
}
if (destBottom > renderHeight) {
int32 adjust = destBottom - renderHeight;
destBottom += adjust;
srcBottom += adjust;
}
if (srcLeft < 0) {
int32 adjust = -srcLeft;
destLeft += adjust;
srcLeft += adjust;
}
if (destLeft < 0) {
// coverity[dead_error_begin]
int32 adjust = -destLeft;
destLeft += adjust;
srcLeft += adjust;
}
if (srcRight > _toolSurface->w) {
int32 adjust = srcRight - _toolSurface->w;
destRight += adjust;
srcRight += adjust;
}
if (destRight > renderWidth) {
int32 adjust = destRight - renderWidth;
destRight += adjust;
srcRight += adjust;
}
if (srcLeft >= srcRight || srcTop >= srcBottom)
return;
getSurface()->fillRect(Common::Rect(0, kTopBarHeight, renderWidth, getHeight()), getSurface()->format.RGBToColor(255, 255, 255));
getSurface()->rawBlitFrom(*_toolSurface.get(), Common::Rect(srcLeft, srcTop, srcRight, srcBottom), Common::Point(destLeft, destTop + kTopBarHeight), nullptr);
} else {
_haveScrollBar = false;
cancelScrolling();
}
if (needChromeUpdate)
refreshChrome();
}
}
void DebugToolWindowBase::trySetScrollOffset(int32 scrollOffset) {
_havePreferredScrollOffset = true;
_preferredScrollOffset = scrollOffset;
}
void DebugToolWindowBase::onMouseDown(int32 x, int32 y, int mouseButton) {
if (mouseButton != Actions::kMouseButtonLeft)
return;
if (_isMouseCaptured)
return;
_isMouseCaptured = true;
_dragStartX = x;
_dragStartY = y;
if (y < kTopBarHeight) {
if (x < kCloseWidth)
_activeWidget = kToolWindowWidgetClose;
else
_activeWidget = kToolWindowWidgetMove;
_dragStartX = x;
_dragStartY = y;
} else if (x >= getWidth() - kScrollBarWidth) {
if (y >= getHeight() - kResizeHeight) {
_activeWidget = kToolWindowWidgetResize;
_resizeStartWidth = getWidth();
_resizeStartHeight = getHeight();
} else {
if (_haveScrollBar) {
int32 relativeToScrollHandle = y - kTopBarHeight - _scrollBarHandleOffset;
if (relativeToScrollHandle < 0)
_activeWidget = kToolWindowWidgetScrollChannelUp;
else if (relativeToScrollHandle >= _scrollBarHandleSize)
_activeWidget = kToolWindowWidgetScrollChannelDown;
else {
_activeWidget = kToolWindowWidgetScrollHandle;
_scrollStartOffset = _scrollBarHandleOffset;
}
setDirty();
_forceRedrawChrome = true;
} else {
_activeWidget = kToolWindowWidgetScrollEmpty;
}
}
} else {
_activeWidget = kToolWindowWidgetNone;
toolOnMouseDown(x, y - kTopBarHeight + _scrollOffset, mouseButton);
}
}
void DebugToolWindowBase::onMouseMove(int32 x, int32 y) {
if (_activeWidget == kToolWindowWidgetNone)
toolOnMouseMove(x, y - kTopBarHeight + _scrollOffset);
else {
if (_activeWidget == kToolWindowWidgetMove) {
int32 relX = x - _dragStartX;
int32 relY = y - _dragStartY;
setPosition(getX() + relX, getY() + relY);
} else if (_activeWidget == kToolWindowWidgetResize) {
int32 relX = x - _dragStartX;
int32 relY = y - _dragStartY;
int32 newWidth = _resizeStartWidth + relX;
int32 newHeight = _resizeStartHeight + relY;
if (newWidth < 100)
newWidth = 100;
if (newHeight < 100)
newHeight = 100;
if (newWidth != getWidth() || newHeight != getHeight()) {
_toolSurface.reset();
resizeWindow(newWidth, newHeight);
setDirty();
}
} else if (_activeWidget == kToolWindowWidgetScrollHandle) {
int32 desiredOffset = y - _dragStartY + _scrollStartOffset;
if (desiredOffset < 0)
desiredOffset = 0;
else if (desiredOffset > _scrollBarHandleMaxOffset)
desiredOffset = _scrollBarHandleMaxOffset;
// Try to avoid scrolling unnecessarily, especially with zero offset
if (desiredOffset != _scrollBarHandleOffset) {
_scrollBarHandleOffset = desiredOffset;
_scrollOffset = _scrollBarHandleOffset * _maxScrollOffset / _scrollBarHandleMaxOffset;
_forceRedrawChrome = true;
setDirty();
}
}
}
}
void DebugToolWindowBase::onMouseUp(int32 x, int32 y, int mouseButton) {
if (mouseButton != Actions::kMouseButtonLeft)
return;
if (!_isMouseCaptured)
return;
_isMouseCaptured = false;
if (_activeWidget == kToolWindowWidgetNone)
toolOnMouseUp(x, y - kTopBarHeight + _scrollOffset, mouseButton);
else {
if (_activeWidget == kToolWindowWidgetClose) {
if (x < kCloseWidth && y < kTopBarHeight) {
_debugger->closeToolWindow(_tool);
return;
}
}
if (_activeWidget == kToolWindowWidgetScrollHandle) {
setDirty();
_forceRedrawChrome = true;
}
_activeWidget = kToolWindowWidgetNone;
}
}
void DebugToolWindowBase::setDirty() {
_isDirty = true;
}
void DebugToolWindowBase::cancelScrolling() {
if (_activeWidget == kToolWindowWidgetScrollChannelDown || _activeWidget == kToolWindowWidgetScrollChannelUp || _activeWidget == kToolWindowWidgetScrollHandle)
_activeWidget = kToolWindowWidgetNone;
}
void DebugToolWindowBase::refreshChrome() {
Graphics::ManagedSurface *surface = getSurface().get();
const Graphics::PixelFormat &fmt = surface->rawSurface().format;
uint32 blackColor = fmt.RGBToColor(0, 0, 0);
uint32 whiteColor = fmt.RGBToColor(255, 255, 255);
uint32 closeColor = fmt.RGBToColor(255, 0, 0);
uint32 topBarColor = fmt.RGBToColor(192, 192, 192);
uint32 topTextColor = blackColor;
uint32 scrollBarChannelColor = fmt.RGBToColor(225, 225, 225);
uint32 scrollBarHandleInactiveColor = fmt.RGBToColor(160, 160, 160);
uint32 scrollBarHandleActiveColor = fmt.RGBToColor(128, 128, 128);
int width = surface->w;
int height = surface->h;
for (int y = 0; y < 12; y++) {
for (int x = 0; x < 12; x++) {
uint32 pixelColor = (g_resizeGraphic[y * 12 + x] == 0) ? blackColor : whiteColor;
surface->setPixel(width - 12 + x, height - 12 + y, pixelColor);
}
}
surface->fillRect(Common::Rect(0, 0, width, kTopBarHeight), topBarColor);
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
int titleWidth = font->getStringWidth(_title);
int titleAvailableWidth = width - kCloseWidth;
if (titleWidth < titleAvailableWidth)
titleWidth = titleAvailableWidth;
int titleY = (kTopBarHeight - font->getFontAscent()) / 2;
font->drawString(surface, _title, kCloseWidth, titleY, titleAvailableWidth, topTextColor, Graphics::kTextAlignCenter, 0, true);
surface->fillRect(Common::Rect(width - kScrollBarWidth, kTopBarHeight, width, height - kResizeHeight), scrollBarChannelColor);
if (_haveScrollBar) {
uint32 scrollHandleColor = scrollBarHandleInactiveColor;
if (_activeWidget == kToolWindowWidgetScrollHandle)
scrollHandleColor = scrollBarHandleActiveColor;
surface->fillRect(Common::Rect(width - kScrollBarWidth, kTopBarHeight + _scrollBarHandleOffset, width, kTopBarHeight + _scrollBarHandleOffset + _scrollBarHandleSize), scrollHandleColor);
}
surface->fillRect(Common::Rect(0, 0, kCloseWidth, kTopBarHeight), closeColor);
surface->drawThickLine(2, 2, kCloseWidth - 4, kTopBarHeight - 4, 2, 2, whiteColor);
surface->drawThickLine(kCloseWidth - 4, 2, 2, kTopBarHeight - 4, 2, 2, whiteColor);
}
class DebugSceneTreeWindow : public DebugToolWindowBase {
public:
DebugSceneTreeWindow(Debugger *debugger, const WindowParameters &windowParams);
void update() override;
void toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) override;
void toolOnMouseDown(int32 x, int32 y, int mouseButton) override;
private:
static const int kRowHeight = 12;
static const int kBaseLeftPadding = 14;
static const int kExpanderLeftOffset = 8;
static const int kPerLevelSpacing = 14;
static const int kTypeIndicatorPadding = 12;
static const int kSceneStackBaseHeight = 18;
static const int kSceneStackRowHeight = 14;
static const int kSceneStackGoToButtonWidth = 36;
static const int kSceneStackGoToButtonFirstY = 15;
static const int kSceneStackGoToButtonHeight = 12;
static const int kSceneStackGoToButtonX = 2;
struct SceneTreeEntryUIState {
SceneTreeEntryUIState();
bool expanded;
bool selected;
};
struct SceneTreeEntry {
SceneTreeEntryUIState uiState;
size_t parentIndex;
int level;
bool hasChildren;
Common::WeakPtr<RuntimeObject> object;
};
struct RenderEntry {
size_t treeIndex;
size_t parentRenderIndex;
};
static void recursiveBuildTree(int level, size_t parentIndex, RuntimeObject *object, Common::Array<SceneTreeEntry> &tree);
static uint32 getColorForObject(const RuntimeObject *object, const Graphics::PixelFormat &fmt);
int32 _treeYOffset;
Common::Array<SceneTreeEntry> _tree;
Common::Array<RenderEntry> _renderEntries;
Common::Array<Common::WeakPtr<Structural> > _sceneStack;
Common::WeakPtr<Structural> _mainScene;
Common::WeakPtr<Structural> _sharedScene;
Common::WeakPtr<RuntimeObject> _latentScrollTo;
bool _forceRender;
};
DebugSceneTreeWindow::SceneTreeEntryUIState::SceneTreeEntryUIState() : expanded(false), selected(false) {
}
DebugSceneTreeWindow::DebugSceneTreeWindow(Debugger *debugger, const WindowParameters &windowParams)
: DebugToolWindowBase(kDebuggerToolSceneTree, "Project", debugger, windowParams), _forceRender(true), _treeYOffset(20) {
}
void DebugSceneTreeWindow::update() {
bool needRerender = _forceRender;
Runtime *runtime = _debugger->getRuntime();
Common::Array<Common::SharedPtr<Structural> > newSceneStack;
runtime->getSceneStack(newSceneStack);
bool sceneStackChanged = false;
if (newSceneStack.size() != _sceneStack.size())
sceneStackChanged = true;
else {
for (size_t i = 0; i < newSceneStack.size(); i++) {
if (newSceneStack[i] != _sceneStack[i].lock()) {
sceneStackChanged = true;
break;
}
}
}
if (sceneStackChanged) {
needRerender = true;
_sceneStack.clear();
for (size_t i = 0; i < newSceneStack.size(); i++)
_sceneStack.push_back(newSceneStack[i]);
}
Common::SharedPtr<Structural> sharedScene = runtime->getActiveSharedScene();
Common::SharedPtr<Structural> mainScene = runtime->getActiveMainScene();
if (_sharedScene.lock() != sharedScene) {
_sharedScene = sharedScene;
needRerender = true;
}
if (_mainScene.lock() != mainScene) {
_mainScene = mainScene;
needRerender = true;
}
// This is super expensive but still less expensive than a redraw and we're only using it to debug,
// so kind of just eating the massive perf hit...
Common::HashMap<RuntimeObject *, SceneTreeEntryUIState> stateCache;
for (const SceneTreeEntry &treeEntry : _tree) {
Common::SharedPtr<RuntimeObject> obj = treeEntry.object.lock();
if (obj) {
stateCache[obj.get()] = treeEntry.uiState;
} else {
needRerender = true;
continue;
}
}
size_t oldSize = _tree.size();
// Keep existing reserve
_tree.resize(0);
// Generate the tree
Project *project = _debugger->getRuntime()->getProject();
if (project)
recursiveBuildTree(0, 0, project, _tree);
// If the tree changed, we need to re-render
if (_tree.size() != oldSize)
needRerender = true;
for (SceneTreeEntry &treeEntry : _tree) {
Common::HashMap<RuntimeObject *, SceneTreeEntryUIState>::const_iterator oldStateIt = stateCache.find(treeEntry.object.lock().get());
if (oldStateIt != stateCache.end())
treeEntry.uiState = oldStateIt->_value;
}
if (!_latentScrollTo.expired()) {
Common::SharedPtr<RuntimeObject> scrollToTarget = _latentScrollTo.lock();
for (SceneTreeEntry &treeEntry : _tree) {
if (treeEntry.object.lock() == scrollToTarget) {
size_t parentIndex = treeEntry.parentIndex;
do {
_tree[parentIndex].uiState.expanded = true;
parentIndex = _tree[parentIndex].parentIndex;
} while (parentIndex != 0);
_tree[0].uiState.expanded = true;
break;
}
}
needRerender = true;
}
if (needRerender) {
setDirty();
_renderEntries.clear();
_forceRender = false;
}
}
void DebugSceneTreeWindow::toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) {
if (_tree.size() == 0)
return;
// Render tree
Common::HashMap<const SceneTreeEntry *, size_t> treeToRenderIndex;
_renderEntries.clear();
treeToRenderIndex[&_tree[0]] = 0;
size_t lastParentIndex = _tree.size(); // So we can skip some hash map lookups, yuck
size_t lastParentRenderIndex = 0;
bool lastParentExpanded = true;
size_t numTreeNodes = _tree.size();
for (size_t i = 0; i < numTreeNodes; i++) {
const SceneTreeEntry &entry = _tree[i];
size_t parentIndex = entry.parentIndex;
size_t parentRenderIndex = 0;
bool isParentExpanded = false;
if (i == 0) {
isParentExpanded = true;
parentRenderIndex = 0;
} else if (parentIndex == lastParentIndex) {
isParentExpanded = lastParentExpanded;
parentRenderIndex = lastParentRenderIndex;
} else {
const SceneTreeEntry *parent = &_tree[entry.parentIndex];
if (parent->uiState.expanded) {
// Parent is expanded, figure out if it's actually rendered
Common::HashMap<const SceneTreeEntry *, size_t>::const_iterator t2r = treeToRenderIndex.find(parent);
if (t2r != treeToRenderIndex.end()) {
isParentExpanded = true;
parentRenderIndex = t2r->_value;
}
}
lastParentIndex = entry.parentIndex;
lastParentRenderIndex = parentRenderIndex;
lastParentExpanded = isParentExpanded;
}
if (isParentExpanded) {
treeToRenderIndex[&entry] = _renderEntries.size();
RenderEntry renderEntry;
renderEntry.treeIndex = i;
renderEntry.parentRenderIndex = parentRenderIndex;
_renderEntries.push_back(renderEntry);
}
}
// Draw
_treeYOffset = kSceneStackBaseHeight + kSceneStackRowHeight * _sceneStack.size();
Graphics::PixelFormat fmt = getSurface()->format;
int32 width = subAreaWidth;
int32 height = static_cast<int32>(_renderEntries.size()) * kRowHeight + _treeYOffset;
if (!_toolSurface || (height != _toolSurface->h || width != _toolSurface->w)) {
_toolSurface.reset();
_toolSurface.reset(new Graphics::ManagedSurface(subAreaWidth, height, fmt));
}
uint32 whiteColor = fmt.RGBToColor(255, 255, 255);
uint32 lightGrayColor = fmt.RGBToColor(192, 192, 192);
uint32 blackColor = fmt.RGBToColor(0, 0, 0);
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
_toolSurface->fillRect(Common::Rect(0, 0, width, height), whiteColor);
// Draw scene stack
font->drawString(_toolSurface.get(), "Scene stack:", 2, 2, width, blackColor);
for (size_t i = 0; i < _sceneStack.size(); i++) {
Common::SharedPtr<Structural> structural = _sceneStack[i].lock();
if (!structural)
continue;
Common::String str = structural->getName();
if (structural == _mainScene.lock())
str += " (Main)";
if (structural == _sharedScene.lock())
str += " (Shared)";
font->drawString(_toolSurface.get(), str, kSceneStackGoToButtonWidth + 4, 16 + i * kSceneStackRowHeight, width, blackColor);
int buttonX = kSceneStackGoToButtonX;
int buttonY = kSceneStackGoToButtonFirstY + i * kSceneStackRowHeight;
_toolSurface->frameRect(Common::Rect(buttonX, buttonY, buttonX + kSceneStackGoToButtonWidth, buttonY + kSceneStackGoToButtonHeight), blackColor);
font->drawString(_toolSurface.get(), "Go To", buttonX + 1, buttonY + 1, kSceneStackGoToButtonWidth - 2, blackColor, Graphics::kTextAlignCenter);
}
// Draw tree
for (size_t row = 0; row < _renderEntries.size(); row++) {
const RenderEntry &renderEntry = _renderEntries[row];
const SceneTreeEntry &entry = _tree[renderEntry.treeIndex];
Common::SharedPtr<RuntimeObject> obj = entry.object.lock();
if (!obj)
continue; // ???
int32 rowTopY = static_cast<int32>(row) * kRowHeight + _treeYOffset;
int32 rowBottomY = rowTopY + kRowHeight;
int32 y = rowTopY + (kRowHeight - font->getFontAscent()) / 2;
int32 x = kBaseLeftPadding + kPerLevelSpacing * entry.level;
Common::String name;
if (obj->isModifier())
name = static_cast<const Modifier *>(obj.get())->getName();
else if (obj->isStructural())
name = static_cast<const Structural *>(obj.get())->getName();
uint32 objIndicatorColor = getColorForObject(obj.get(), fmt);
{
int32 indicatorCenterX = x + kTypeIndicatorPadding / 2;
int32 indicatorCenterY = rowTopY + (kRowHeight / 2);
_toolSurface->fillRect(Common::Rect(indicatorCenterX - 4, indicatorCenterY - 4, indicatorCenterX + 4, indicatorCenterY + 4), objIndicatorColor);
}
int textX = x + kTypeIndicatorPadding;
int strWidth = font->getStringWidth(name);
if (strWidth > width - textX)
strWidth = width - textX;
bool isSelected = entry.uiState.selected;
if (isSelected)
_toolSurface->fillRect(Common::Rect(textX - 1, rowTopY, textX + strWidth + 1, rowBottomY), blackColor);
font->drawString(_toolSurface.get(), name, textX, y, strWidth, isSelected ? whiteColor : blackColor, Graphics::kTextAlignLeft, 0, true);
if (entry.hasChildren) {
int32 expanderCenterX = x - kExpanderLeftOffset;
int32 expanderCenterY = rowTopY + (kRowHeight / 2);
_toolSurface->frameRect(Common::Rect(expanderCenterX - 4, expanderCenterY - 4, expanderCenterX + 5, expanderCenterY + 5), blackColor);
_toolSurface->drawLine(expanderCenterX - 2, expanderCenterY, expanderCenterX + 2, expanderCenterY, blackColor);
if (!entry.uiState.expanded)
_toolSurface->drawLine(expanderCenterX, expanderCenterY - 2, expanderCenterX, expanderCenterY + 2, blackColor);
}
}
Common::Array<bool> haveRenderedParentTracers;
haveRenderedParentTracers.resize(_renderEntries.size());
for (size_t i = 0; i < _renderEntries.size(); i++)
haveRenderedParentTracers[i] = false;
for (size_t ri = 0; ri < _renderEntries.size(); ri++) {
size_t row = _renderEntries.size() - 1 - ri;
const RenderEntry &renderEntry = _renderEntries[row];
if (row == 0)
continue;
const RenderEntry &parentRenderEntry = _renderEntries[renderEntry.parentRenderIndex];
const SceneTreeEntry &treeEntry = _tree[renderEntry.treeIndex];
const SceneTreeEntry &parentTreeEntry = _tree[parentRenderEntry.treeIndex];
int32 rowTopY = static_cast<int32>(row) * kRowHeight + _treeYOffset;
int32 x = kBaseLeftPadding + kPerLevelSpacing * treeEntry.level;
int32 parentTracerRightX = x - 2;
int32 parentTracerY = rowTopY + (kRowHeight / 2);
if (treeEntry.hasChildren)
parentTracerRightX -= kExpanderLeftOffset + 7;
int32 parentTracerLeftX = kBaseLeftPadding + kPerLevelSpacing * parentTreeEntry.level - kExpanderLeftOffset;
_toolSurface->drawLine(parentTracerRightX, parentTracerY, parentTracerLeftX, parentTracerY, lightGrayColor);
if (!haveRenderedParentTracers[renderEntry.parentRenderIndex]) {
haveRenderedParentTracers[renderEntry.parentRenderIndex] = true;
int32 parentTracerTopY = static_cast<int32>(renderEntry.parentRenderIndex + 1) * kRowHeight + _treeYOffset;
_toolSurface->drawLine(parentTracerLeftX, parentTracerY, parentTracerLeftX, parentTracerTopY, lightGrayColor);
}
if (!_latentScrollTo.expired()) {
Common::SharedPtr<RuntimeObject> scrollToTarget = _latentScrollTo.lock();
if (treeEntry.object.lock() == scrollToTarget)
trySetScrollOffset(rowTopY);
}
}
_latentScrollTo.reset();
}
void DebugSceneTreeWindow::toolOnMouseDown(int32 x, int32 y, int mouseButton) {
if (mouseButton != Actions::kMouseButtonLeft)
return;
for (uint i = 0; i < _sceneStack.size(); i++) {
int buttonLeft = kSceneStackGoToButtonX;
int buttonRight = buttonLeft + kSceneStackGoToButtonWidth;
int buttonTop = kSceneStackGoToButtonFirstY + static_cast<int>(i) * kSceneStackRowHeight;
int buttonBottom = buttonTop + kSceneStackGoToButtonHeight;
if (x >= buttonLeft && x < buttonRight && y >= buttonTop && y < buttonBottom) {
_forceRender = true;
_latentScrollTo = _sceneStack[i];
return;
}
}
if (y < _treeYOffset)
return;
int32 row = (y - _treeYOffset) / kRowHeight;
if (static_cast<uint32>(row) >= _renderEntries.size())
return;
const RenderEntry &renderEntry = _renderEntries[row];
SceneTreeEntry &treeEntry = _tree[renderEntry.treeIndex];
int32 expanderCenterX = kBaseLeftPadding - kExpanderLeftOffset + treeEntry.level * kPerLevelSpacing;
int32 expanderCenterY = row * kRowHeight + kRowHeight / 2 + _treeYOffset;
if (x >= expanderCenterX - 5 && x <= expanderCenterX + 5 && y >= expanderCenterX - 5 && y <= expanderCenterY + 5) {
// Clicked the expander
treeEntry.uiState.expanded = !treeEntry.uiState.expanded;
_forceRender = true;
return;
} else if (x >= kBaseLeftPadding + treeEntry.level * kPerLevelSpacing) {
if (!treeEntry.uiState.selected) {
for (SceneTreeEntry &clearTreeEntry : _tree)
clearTreeEntry.uiState.selected = false;
treeEntry.uiState.selected = true;
setDirty();
_debugger->tryInspectObject(treeEntry.object.lock().get());
}
}
}
void DebugSceneTreeWindow::recursiveBuildTree(int level, size_t parentIndex, RuntimeObject *object, Common::Array<SceneTreeEntry> &tree) {
SceneTreeEntry treeEntry;
treeEntry.level = level;
treeEntry.object = object->getSelfReference();
treeEntry.parentIndex = parentIndex;
treeEntry.hasChildren = false;
size_t thisIndex = tree.size();
tree.push_back(treeEntry);
if (object->isStructural()) {
Structural *structural = static_cast<Structural *>(object);
for (const Common::SharedPtr<Modifier> &modifier : structural->getModifiers())
recursiveBuildTree(level + 1, thisIndex, modifier.get(), tree);
for (const Common::SharedPtr<Structural> &child : structural->getChildren())
recursiveBuildTree(level + 1, thisIndex, child.get(), tree);
} else if (object->isModifier()) {
IModifierContainer *childContainer = static_cast<Modifier *>(object)->getChildContainer();
if (childContainer) {
for (const Common::SharedPtr<Modifier> &child : childContainer->getModifiers())
recursiveBuildTree(level + 1, thisIndex, child.get(), tree);
}
}
if (tree.size() - thisIndex > 1)
tree[thisIndex].hasChildren = true;
}
uint32 DebugSceneTreeWindow::getColorForObject(const RuntimeObject *object, const Graphics::PixelFormat &fmt) {
if (object->isStructural()) {
return fmt.RGBToColor(128, 128, 128);
} else if (object->isModifier()) {
const Modifier *mod = static_cast<const Modifier *>(object);
if (mod->isAlias())
return fmt.RGBToColor(255, 0, 255);
else if (mod->isVariable())
return fmt.RGBToColor(0, 0, 255);
else if (mod->isBehavior())
return fmt.RGBToColor(196, 0, 208);
else if (mod->isCompoundVariable())
return fmt.RGBToColor(100, 100, 200);
else
return fmt.RGBToColor(0, 196, 128);
} else {
return fmt.RGBToColor(0, 0, 0);
}
}
class DebugInspectorWindow : public DebugToolWindowBase, private IDebugInspectionReport {
public:
DebugInspectorWindow(Debugger *debugger, const WindowParameters &windowParams);
void update() override;
void toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) override;
private:
static const int kRowHeight = 14;
struct InspectorLabeledRow {
Common::String label;
Common::String text;
};
struct InspectorUnlabeledRow {
Common::String str;
};
bool declareStatic(const char *name) override;
void declareStaticContents(const Common::String &data) override;
void declareDynamic(const char *name, const Common::String &data) override;
void declareLoose(const Common::String &data) override;
Common::SharedPtr<DebugInspector> _inspector;
Common::Array<InspectorLabeledRow> _labeledRow;
Common::Array<InspectorUnlabeledRow> _unlabeledRow;
int32 _maxLabelWidth;
size_t _declLabeledRow;
size_t _declUnlabeledRow;
};
DebugInspectorWindow::DebugInspectorWindow(Debugger *debugger, const WindowParameters &windowParams)
: DebugToolWindowBase(kDebuggerToolInspector, "Inspector", debugger, windowParams), _maxLabelWidth(0), _declLabeledRow(0), _declUnlabeledRow(0) {
}
void DebugInspectorWindow::update() {
const Common::SharedPtr<DebugInspector> inspector = _debugger->getInspector();
if (inspector != _inspector) {
_maxLabelWidth = 0;
_labeledRow.clear();
_unlabeledRow.clear();
_inspector = inspector;
setDirty();
}
_declLabeledRow = 0;
_declUnlabeledRow = 0;
if (inspector == nullptr || inspector->getDebuggable() == nullptr) {
_unlabeledRow.resize(1);
_unlabeledRow[0].str = "No object selected";
_labeledRow.clear();
} else {
inspector->getDebuggable()->debugInspect(this);
_unlabeledRow.resize(_declUnlabeledRow);
setDirty();
}
}
bool DebugInspectorWindow::declareStatic(const char *name) {
if (_labeledRow.size() <= _declLabeledRow) {
InspectorLabeledRow newRow;
newRow.label = name;
_labeledRow.push_back(newRow);
return true;
} else {
_declLabeledRow++;
return false;
}
}
void DebugInspectorWindow::declareStaticContents(const Common::String &data) {
assert(_declLabeledRow + 1 == _labeledRow.size());
_labeledRow[_declLabeledRow].text = data;
_declLabeledRow++;
}
void DebugInspectorWindow::declareDynamic(const char *name, const Common::String &data) {
if (_declLabeledRow == _labeledRow.size()) {
InspectorLabeledRow row;
row.label = name;
_labeledRow.push_back(row);
}
_labeledRow[_declLabeledRow].text = data;
_declLabeledRow++;
}
void DebugInspectorWindow::declareLoose(const Common::String &data) {
if (_declUnlabeledRow == _unlabeledRow.size()) {
InspectorUnlabeledRow row;
row.str = data;
_unlabeledRow.push_back(row);
} else
_unlabeledRow[_declUnlabeledRow].str = data;
_declUnlabeledRow++;
}
void DebugInspectorWindow::toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) {
const Graphics::PixelFormat fmt = _debugger->getRuntime()->getRenderPixelFormat();
uint32 whiteColor = fmt.RGBToColor(255, 255, 255);
uint32 blackColor = fmt.RGBToColor(0, 0, 0);
const size_t numLabeledRows = _labeledRow.size();
const size_t numUnlabeledRows = _unlabeledRow.size();
int32 renderHeight = (_labeledRow.size() + _unlabeledRow.size()) * kRowHeight;
int32 renderWidth = subAreaWidth;
if (!_toolSurface || renderWidth != _toolSurface->w || renderHeight != _toolSurface->h) {
_toolSurface.reset();
_toolSurface.reset(new Graphics::ManagedSurface(renderWidth, renderHeight, fmt));
}
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
if (_maxLabelWidth == 0) {
for (const InspectorLabeledRow &row : _labeledRow) {
int width = font->getStringWidth(row.label);
if (width > _maxLabelWidth)
_maxLabelWidth = width;
}
}
_toolSurface->fillRect(Common::Rect(0, 0, renderWidth, renderHeight), whiteColor);
for (size_t i = 0; i < numLabeledRows; i++) {
const InspectorLabeledRow &row = _labeledRow[i];
int32 startY = i * kRowHeight;
int32 labelX = 4;
int32 labelW = renderWidth - labelX;
if (labelW > 1)
font->drawString(_toolSurface.get(), row.label, labelX, startY + 2, labelW, blackColor);
int32 valueX = _maxLabelWidth + 8;
int32 valueW = renderWidth - valueX;
if (valueW > 1)
font->drawString(_toolSurface.get(), row.text, valueX, startY + 2, valueW, blackColor, Graphics::kTextAlignLeft, 0, true);
}
for (size_t i = 0; i < numUnlabeledRows; i++) {
const InspectorUnlabeledRow &row = _unlabeledRow[i];
int32 startY = (i + numLabeledRows) * kRowHeight;
int32 contentsX = 4;
int32 contentsW = renderWidth - contentsX;
if (contentsW > 1)
font->drawString(_toolSurface.get(), row.str, contentsX, startY + 2, contentsW, blackColor);
}
}
// Step through ("debugger") window
class DebugStepThroughWindow : public DebugToolWindowBase {
public:
DebugStepThroughWindow(Debugger *debugger, const WindowParameters &windowParams);
void update() override;
void toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) override;
void toolOnMouseDown(int32 x, int32 y, int mouseButton) override;
private:
static const int kRowHeight = 14;
Common::Array<Common::SharedPtr<DebugPrimaryTaskList> > _primaryTasks;
Common::Array<size_t> _primaryTaskRowStarts;
Common::Array<size_t> _primaryTaskNumEntries;
size_t _totalRows;
};
DebugStepThroughWindow::DebugStepThroughWindow(Debugger *debugger, const WindowParameters &windowParams)
: DebugToolWindowBase(kDebuggerToolStepThrough, "Debugger", debugger, windowParams), _totalRows(0) {
}
void DebugStepThroughWindow::update() {
setDirty();
_primaryTasks.clear();
_debugger->getRuntime()->debugGetPrimaryTaskList(_primaryTasks);
_primaryTaskRowStarts.resize(_primaryTasks.size());
_primaryTaskNumEntries.resize(_primaryTasks.size());
_totalRows = 0;
for (size_t i = 0; i < _primaryTasks.size(); i++) {
_totalRows++;
_primaryTaskRowStarts[i] = _totalRows;
size_t numEntries = _primaryTasks[i]->getItems().size();
_primaryTaskNumEntries[i] = numEntries;
_totalRows += numEntries;
}
}
void DebugStepThroughWindow::toolRenderSurface(int32 subAreaWidth, int32 subAreaHeight) {
const Graphics::PixelFormat fmt = _debugger->getRuntime()->getRenderPixelFormat();
uint32 blackColor = fmt.RGBToColor(0, 0, 0);
int32 renderHeight = subAreaHeight;
int32 renderWidth = subAreaWidth;
if (_primaryTasks.size() > 0)
renderHeight = static_cast<int32>(_primaryTaskRowStarts.back() + _primaryTaskNumEntries.back()) * kRowHeight;
if (!_toolSurface || renderWidth != _toolSurface->w || renderHeight != _toolSurface->h) {
_toolSurface.reset();
_toolSurface.reset(new Graphics::ManagedSurface(renderWidth, renderHeight, fmt));
}
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
for (size_t catIndex = 0; catIndex < _primaryTasks.size(); catIndex++) {
int32 startY = (_primaryTaskRowStarts[catIndex] - 1) * kRowHeight;
const DebugPrimaryTaskList *taskList = _primaryTasks[catIndex].get();
font->drawString(_toolSurface.get(), taskList->getName(), 2, startY + 2, renderWidth - 2, blackColor);
const Common::Array<IDebuggable *> &items = taskList->getItems();
const size_t numChildren = items.size();
for (size_t chIndex = 0; chIndex < numChildren; chIndex++) {
int32 rowY = (_primaryTaskRowStarts[catIndex] + chIndex) * kRowHeight;
font->drawString(_toolSurface.get(), items[chIndex]->debugGetName(), 10, rowY + 2, renderWidth - 2, blackColor);
}
}
}
void DebugStepThroughWindow::toolOnMouseDown(int32 x, int32 y, int mouseButton) {
if (mouseButton != Actions::kMouseButtonLeft)
return;
}
class DebugToolsWindow : public Window {
public:
DebugToolsWindow(Debugger *debugger, const WindowParameters &windowParams);
void onMouseDown(int32 x, int32 y, int mouseButton) override;
private:
Debugger *_debugger;
};
DebugToolsWindow::DebugToolsWindow(Debugger *debugger, const WindowParameters &windowParams)
: Window(windowParams), _debugger(debugger) {
}
void DebugToolsWindow::onMouseDown(int32 x, int32 y, int mouseButton) {
int tool = 0;
if (y > 1)
tool = (y - 1) / 17;
_debugger->openToolWindow(static_cast<DebuggerTool>(tool));
}
Debuggable::Debuggable() {
}
Debuggable::Debuggable(const Debuggable &other) : _inspector(nullptr) {
}
Debuggable::Debuggable(Debuggable &&other) : _inspector(other._inspector) {
if (_inspector) {
_inspector->changePrimaryInstance(this);
other._inspector.reset();
}
}
Debuggable::~Debuggable() {
if (_inspector)
_inspector->onDestroyed(this);
}
const Common::SharedPtr<DebugInspector> &Debuggable::debugGetInspector() {
if (!_inspector)
_inspector.reset(new DebugInspector(this));
return _inspector;
}
DebugInspector::DebugInspector(IDebuggable *debuggable) : _instance(debuggable) {
}
DebugInspector::~DebugInspector() {
}
void DebugInspector::onDestroyed(IDebuggable *debuggable) {
if (_instance == debuggable)
_instance = nullptr;
}
void DebugInspector::changePrimaryInstance(IDebuggable *instance) {
_instance = instance;
}
IDebuggable *DebugInspector::getDebuggable() const {
return _instance;
}
DebugPrimaryTaskList::DebugPrimaryTaskList(const Common::String &name) : _name(name) {
}
void DebugPrimaryTaskList::addItem(IDebuggable *debuggable) {
_primaryTasks.push_back(debuggable);
}
const Common::Array<IDebuggable*> &DebugPrimaryTaskList::getItems() const {
return _primaryTasks;
}
const Common::String &DebugPrimaryTaskList::getName() const {
return _name;
}
Debugger::Debugger(Runtime *runtime) : _paused(false), _runtime(runtime) {
refreshSceneStatus();
const Graphics::PixelFormat renderFmt = runtime->getRenderPixelFormat();
const byte *toolGraphics[kDebuggerToolCount] = {
g_sceneTreeGraphic,
g_inspectorGraphic,
g_stepThroughGraphic,
};
_toolsWindow.reset(new DebugToolsWindow(this, WindowParameters(runtime, 0, 0, 18, 1 + kDebuggerToolCount * 17, renderFmt)));
Graphics::ManagedSurface *toolWindowSurface = _toolsWindow->getSurface().get();
uint32 whiteColor = renderFmt.RGBToColor(255, 255, 255);
uint32 blackColor = renderFmt.RGBToColor(0, 0, 0);
const uint32 toolGraphicPalette[] = {blackColor, whiteColor};
for (int y = 0; y < 1 + kDebuggerToolCount * 17; y++) {
for (int x = 0; x < 18; x++) {
toolWindowSurface->setPixel(x, y, whiteColor);
}
}
for (int tool = 0; tool < kDebuggerToolCount; tool++) {
const byte *toolGraphic = toolGraphics[tool];
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
toolWindowSurface->setPixel(x + 1, tool * 17 + 1 + y, toolGraphicPalette[toolGraphic[y * 16 + x]]);
}
}
}
_toolsWindow->setStrata(1);
runtime->addWindow(_toolsWindow);
}
Debugger::~Debugger() {
if (_runtime) {
_runtime->removeWindow(_sceneStatusWindow.get());
_runtime->removeWindow(_toolsWindow.get());
for (int i = 0; i < kDebuggerToolCount; i++)
_runtime->removeWindow(_toolWindows[i].get());
}
}
void Debugger::runFrame(uint32 msec) {
for (size_t ri = _toastNotifications.size(); ri > 0; ri--) {
size_t i = ri - 1;
ToastNotification &toastNotification = _toastNotifications[i];
uint64 realTime = _runtime->getRealTime();
Window &window = *toastNotification.window;
if (realTime >= toastNotification.dismissTime) {
_runtime->removeWindow(&window);
_toastNotifications.remove_at(i);
}
else {
uint64 timeRemaining = toastNotification.dismissTime - realTime;
uint32 dismissDuration = 250;
if (timeRemaining < dismissDuration) {
int32 offset = window.getSurface()->w * static_cast<int32>(dismissDuration - timeRemaining) / static_cast<int32>(dismissDuration);
window.setPosition(-offset, window.getY());
}
}
}
for (const Common::SharedPtr<DebugToolWindowBase> &toolWindow : _toolWindows) {
if (toolWindow) {
toolWindow->update();
toolWindow->render();
}
}
}
void Debugger::setPaused(bool paused) {
_paused = paused;
}
bool Debugger::isPaused() const {
return _paused;
}
Runtime *Debugger::getRuntime() const {
return _runtime;
}
void Debugger::notify(DebugSeverity severity, const Common::String& str) {
const int toastNotificationHeight = 15;
uint16 displayWidth, displayHeight;
_runtime->getDisplayResolution(displayWidth, displayHeight);
int horizPadding = 10;
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
int width = font->getStringWidth(str) + horizPadding * 2;
if (width > displayWidth)
width = displayWidth;
const Graphics::PixelFormat pixelFmt = _runtime->getRenderPixelFormat();
ToastNotification toastNotification;
toastNotification.window.reset(new Window(WindowParameters(_runtime, 0, displayHeight, width, toastNotificationHeight, pixelFmt)));
toastNotification.window->setStrata(3);
toastNotification.window->setMouseTransparent(true);
byte fillColor[3] = {255, 255, 255};
if (severity == kDebugSeverityError) {
fillColor[0] = 255;
fillColor[1] = 100;
fillColor[2] = 100;
} else if (severity == kDebugSeverityWarning) {
fillColor[0] = 255;
fillColor[1] = 225;
fillColor[2] = 120;
}
Graphics::ManagedSurface &managedSurface = *toastNotification.window->getSurface();
managedSurface.fillRect(Common::Rect(0, 0, width, toastNotificationHeight), Render::resolveRGB(fillColor[0], fillColor[1], fillColor[2], pixelFmt));
font->drawString(&managedSurface, str, 10, (toastNotificationHeight - font->getFontAscent()) / 2, width - horizPadding * 2, Render::resolveRGB(0, 0, 0, pixelFmt));
toastNotification.dismissTime = _runtime->getRealTime() + 5250;
_toastNotifications.push_back(toastNotification);
_runtime->addWindow(toastNotification.window);
for (size_t i = 0; i < _toastNotifications.size(); i++) {
Window &window = *_toastNotifications[i].window;
window.setPosition(window.getX(), window.getY() - toastNotificationHeight);
}
debug(1, "%s", str.c_str());
}
void Debugger::notifyFmt(DebugSeverity severity, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
this->vnotifyFmt(severity, fmt, args);
va_end(args);
}
void Debugger::vnotifyFmt(DebugSeverity severity, const char* fmt, va_list args) {
Common::String str(Common::String::vformat(fmt, args));
this->notify(severity, str);
}
void Debugger::refreshSceneStatus() {
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
Common::Array<Common::String> sceneStrs;
Structural *sharedScene = _runtime->getActiveSharedScene().get();
if (sharedScene)
sceneStrs.push_back(Common::String("Shar: ") + sharedScene->debugGetName());
Structural *mainScene = _runtime->getActiveMainScene().get();
if (mainScene)
sceneStrs.push_back(Common::String("Main: ") + mainScene->debugGetName());
const uint horizPadding = 10;
const uint vertSpacing = 15;
int width = 0;
for (uint i = 0; i < sceneStrs.size(); i++) {
int lineWidth = font->getStringWidth(sceneStrs[i]);
if (lineWidth > width)
width = lineWidth;
}
if (_sceneStatusWindow.get()) {
_runtime->removeWindow(_sceneStatusWindow.get());
_sceneStatusWindow.reset();
}
const Graphics::PixelFormat pixelFmt = _runtime->getRenderPixelFormat();
_sceneStatusWindow.reset(new Window(WindowParameters(_runtime, 0, 0, horizPadding * 2 + width, vertSpacing * sceneStrs.size(), pixelFmt)));
_sceneStatusWindow->setMouseTransparent(true);
_sceneStatusWindow->setStrata(1);
_runtime->addWindow(_sceneStatusWindow);
for (uint i = 0; i < sceneStrs.size(); i++) {
font->drawString(_sceneStatusWindow->getSurface().get(), sceneStrs[i], horizPadding, vertSpacing * i + (vertSpacing - font->getFontAscent()) / 2, width, Render::resolveRGB(255, 255, 255, pixelFmt));
}
if (_toolsWindow)
_toolsWindow->setPosition(0, _sceneStatusWindow->getHeight());
}
void Debugger::complainAboutUnfinished(Structural *structural) {
Common::HashMap<Common::String, SupportStatus> unfinishedModifiers;
Common::HashMap<Common::String, SupportStatus> unfinishedElements;
scanStructuralStatus(structural, unfinishedModifiers, unfinishedElements);
const SupportStatus supportStatusBins[2] = {kSupportStatusPartial,
kSupportStatusNone};
const char *supportStatusNames[2] = {"partially-finished", "unimplemented"};
const Common::HashMap<Common::String, SupportStatus> *typeBins[2] = {&unfinishedModifiers, &unfinishedElements};
const char *typeNames[2] = {"modifier", "element"};
for (int ssi = 0; ssi < 2; ssi++) {
for (int ti = 0; ti < 2; ti++) {
Common::Array<Common::String> names;
for (Common::HashMap<Common::String, SupportStatus>::const_iterator it = typeBins[ti]->begin(), itEnd = typeBins[ti]->end(); it != itEnd; ++it) {
if (it->_value == supportStatusBins[ssi])
names.push_back(it->_key);
}
Common::sort(names.begin(), names.end());
for (size_t i = 0; i < names.size(); i++) {
Common::String message = "Scene '" + structural->debugGetName() + "' contains " + supportStatusNames[ssi] + " " + typeNames[ti] + ": " + names[i];
this->notify(DebugSeverity::kDebugSeverityWarning, message);
}
}
}
}
void Debugger::openToolWindow(DebuggerTool tool) {
if (tool < 0 || tool >= kDebuggerToolCount)
return; // This should never happen
Common::SharedPtr<DebugToolWindowBase> &windowRef = _toolWindows[tool];
if (windowRef)
return;
switch (tool) {
case kDebuggerToolSceneTree:
windowRef.reset(new DebugSceneTreeWindow(this, WindowParameters(_runtime, 32, 32, 250, 120, _runtime->getRenderPixelFormat())));
break;
case kDebuggerToolInspector:
windowRef.reset(new DebugInspectorWindow(this, WindowParameters(_runtime, 32, 32, 100, 320, _runtime->getRenderPixelFormat())));
break;
case kDebuggerToolStepThrough:
windowRef.reset(new DebugStepThroughWindow(this, WindowParameters(_runtime, 32, 32, 100, 320, _runtime->getRenderPixelFormat())));
break;
default:
assert(false);
return;
}
_runtime->addWindow(windowRef);
}
void Debugger::closeToolWindow(DebuggerTool tool) {
_runtime->removeWindow(_toolWindows[tool].get());
_toolWindows[tool].reset();
}
void Debugger::inspectObject(IDebuggable *debuggable) {
_inspector = debuggable->debugGetInspector();
}
void Debugger::tryInspectObject(RuntimeObject *object) {
if (!object)
return;
if (object->isStructural())
inspectObject(static_cast<Structural *>(object));
else if (object->isModifier())
inspectObject(static_cast<Modifier *>(object));
}
const Common::SharedPtr<DebugInspector>& Debugger::getInspector() const {
return _inspector;
}
void Debugger::scanStructuralStatus(Structural *structural, Common::HashMap<Common::String, SupportStatus> &unfinishedModifiers, Common::HashMap<Common::String, SupportStatus> &unfinishedElements) {
for (Common::Array<Common::SharedPtr<Structural>>::const_iterator it = structural->getChildren().begin(), itEnd = structural->getChildren().end(); it != itEnd; ++it) {
scanStructuralStatus(it->get(), unfinishedModifiers, unfinishedElements);
}
for (Common::Array<Common::SharedPtr<Modifier> >::const_iterator it = structural->getModifiers().begin(), itEnd = structural->getModifiers().end(); it != itEnd; ++it) {
scanModifierStatus(it->get(), unfinishedModifiers, unfinishedElements);
}
scanDebuggableStatus(structural, unfinishedElements);
}
void Debugger::scanModifierStatus(Modifier *modifier, Common::HashMap<Common::String, SupportStatus> &unfinishedModifiers, Common::HashMap<Common::String, SupportStatus> &unfinishedElements) {
IModifierContainer *children = modifier->getChildContainer();
if (children) {
for (Common::Array<Common::SharedPtr<Modifier> >::const_iterator it = children->getModifiers().begin(), itEnd = children->getModifiers().end(); it != itEnd; ++it) {
scanModifierStatus(it->get(), unfinishedModifiers, unfinishedElements);
}
}
scanDebuggableStatus(modifier, unfinishedModifiers);
}
void Debugger::scanDebuggableStatus(IDebuggable *debuggable, Common::HashMap<Common::String, SupportStatus> &unfinished) {
SupportStatus supportStatus = debuggable->debugGetSupportStatus();
if (supportStatus != kSupportStatusDone)
unfinished[Common::String(debuggable->debugGetTypeName())] = supportStatus;
}
} // End of namespace MTropolis