feat: Add ImGui-based developer console overlay
Integrates Dear ImGui for an in-game debug console that replaces the blocking Python REPL. Press ~ (grave/tilde) to toggle the console. Features: - Python code execution without blocking the game loop - Output capture with color coding (yellow=input, red=errors, gray=output) - Expression results show repr() automatically - Command history navigation with up/down arrows - Word wrapping for long output lines - Auto-scroll that doesn't fight manual scrolling - mcrfpy.setDevConsole(bool) API to disable for shipping Technical changes: - Update imgui submodule to v1.89.9 (stable) - Update imgui-sfml submodule to 2.6.x branch (SFML 2.x compatible) - Add ImGui sources to CMakeLists.txt with OpenGL dependency - Integrate ImGui lifecycle into GameEngine - Add ImGuiConsole class for console overlay closes #36, closes #65, closes #75 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8e2c603c54
commit
3f6ea4fe33
|
|
@ -17,16 +17,36 @@ include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
||||||
|
|
||||||
|
# ImGui and ImGui-SFML include directories
|
||||||
|
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
|
||||||
|
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml)
|
||||||
|
|
||||||
|
# ImGui source files
|
||||||
|
set(IMGUI_SOURCES
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/imgui/imgui.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_draw.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_tables.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_widgets.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/modules/imgui-sfml/imgui-SFML.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# Collect all the source files
|
# Collect all the source files
|
||||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||||
|
|
||||||
|
# Add ImGui sources to the build
|
||||||
|
list(APPEND SOURCES ${IMGUI_SOURCES})
|
||||||
|
|
||||||
|
# Find OpenGL (required by ImGui-SFML)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
# Create a list of libraries to link against
|
# Create a list of libraries to link against
|
||||||
set(LINK_LIBS
|
set(LINK_LIBS
|
||||||
sfml-graphics
|
sfml-graphics
|
||||||
sfml-window
|
sfml-window
|
||||||
sfml-system
|
sfml-system
|
||||||
sfml-audio
|
sfml-audio
|
||||||
tcod)
|
tcod
|
||||||
|
OpenGL::GL)
|
||||||
|
|
||||||
# On Windows, add any additional libs and include directories
|
# On Windows, add any additional libs and include directories
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 313676d200f093e2694b5cfca574f72a2b116c85
|
Subproject commit c6e0284ac58b3f205c95365478888f7b53b077e2
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit de565ac8f2b795dedc0307b60830cb006afd2ecd
|
Subproject commit bf9023d1bc6ec422769559a5eff60bd00597354f
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "Animation.h"
|
#include "Animation.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui-SFML.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||||
|
|
@ -31,6 +33,11 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
||||||
window->create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close | sf::Style::Resize);
|
window->create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close | sf::Style::Resize);
|
||||||
window->setFramerateLimit(60);
|
window->setFramerateLimit(60);
|
||||||
render_target = window.get();
|
render_target = window.get();
|
||||||
|
|
||||||
|
// Initialize ImGui for the window
|
||||||
|
if (ImGui::SFML::Init(*window)) {
|
||||||
|
imguiInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visible = render_target->getDefaultView();
|
visible = render_target->getDefaultView();
|
||||||
|
|
@ -116,6 +123,12 @@ void GameEngine::cleanup()
|
||||||
McRFPy_API::game = nullptr;
|
McRFPy_API::game = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown ImGui before closing window
|
||||||
|
if (imguiInitialized) {
|
||||||
|
ImGui::SFML::Shutdown();
|
||||||
|
imguiInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Force close the window if it's still open
|
// Force close the window if it's still open
|
||||||
if (window && window->isOpen()) {
|
if (window && window->isOpen()) {
|
||||||
window->close();
|
window->close();
|
||||||
|
|
@ -224,6 +237,11 @@ void GameEngine::run()
|
||||||
|
|
||||||
if (!headless) {
|
if (!headless) {
|
||||||
sUserInput();
|
sUserInput();
|
||||||
|
|
||||||
|
// Update ImGui
|
||||||
|
if (imguiInitialized) {
|
||||||
|
ImGui::SFML::Update(*window, clock.getElapsedTime());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!paused)
|
if (!paused)
|
||||||
{
|
{
|
||||||
|
|
@ -262,6 +280,12 @@ void GameEngine::run()
|
||||||
profilerOverlay->render(*render_target);
|
profilerOverlay->render(*render_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render ImGui console overlay
|
||||||
|
if (imguiInitialized && !headless) {
|
||||||
|
console.render();
|
||||||
|
ImGui::SFML::Render(*window);
|
||||||
|
}
|
||||||
|
|
||||||
// Display the frame
|
// Display the frame
|
||||||
if (headless) {
|
if (headless) {
|
||||||
headless_renderer->display();
|
headless_renderer->display();
|
||||||
|
|
@ -420,6 +444,26 @@ void GameEngine::sUserInput()
|
||||||
sf::Event event;
|
sf::Event event;
|
||||||
while (window && window->pollEvent(event))
|
while (window && window->pollEvent(event))
|
||||||
{
|
{
|
||||||
|
// Process event through ImGui first
|
||||||
|
if (imguiInitialized) {
|
||||||
|
ImGui::SFML::ProcessEvent(*window, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle grave/tilde key for console toggle (before other processing)
|
||||||
|
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Grave) {
|
||||||
|
console.toggle();
|
||||||
|
continue; // Don't pass grave key to game
|
||||||
|
}
|
||||||
|
|
||||||
|
// If console wants keyboard, don't pass keyboard events to game
|
||||||
|
if (console.wantsKeyboardInput()) {
|
||||||
|
// Still process non-keyboard events (mouse, window close, etc.)
|
||||||
|
if (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased ||
|
||||||
|
event.type == sf::Event::TextEntered) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
processEvent(event);
|
processEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "HeadlessRenderer.h"
|
#include "HeadlessRenderer.h"
|
||||||
#include "SceneTransition.h"
|
#include "SceneTransition.h"
|
||||||
#include "Profiler.h"
|
#include "Profiler.h"
|
||||||
|
#include "ImGuiConsole.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
|
@ -62,6 +63,10 @@ private:
|
||||||
int overlayUpdateCounter = 0; // Only update overlay every N frames
|
int overlayUpdateCounter = 0; // Only update overlay every N frames
|
||||||
ProfilerOverlay* profilerOverlay = nullptr; // The actual overlay renderer
|
ProfilerOverlay* profilerOverlay = nullptr; // The actual overlay renderer
|
||||||
|
|
||||||
|
// ImGui console overlay
|
||||||
|
ImGuiConsole console;
|
||||||
|
bool imguiInitialized = false;
|
||||||
|
|
||||||
void updateViewport();
|
void updateViewport();
|
||||||
|
|
||||||
void testTimers();
|
void testTimers();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
#include "ImGuiConsole.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
#include <Python.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// Static member initialization
|
||||||
|
bool ImGuiConsole::enabled = true;
|
||||||
|
|
||||||
|
ImGuiConsole::ImGuiConsole() {
|
||||||
|
addOutput("McRogueFace Python Console", false);
|
||||||
|
addOutput("Type Python commands and press Enter to execute.", false);
|
||||||
|
addOutput("", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::toggle() {
|
||||||
|
if (enabled) {
|
||||||
|
visible = !visible;
|
||||||
|
if (visible) {
|
||||||
|
// Focus input when opening
|
||||||
|
ImGui::SetWindowFocus("Console");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGuiConsole::wantsKeyboardInput() const {
|
||||||
|
return visible && enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::addOutput(const std::string& text, bool isError) {
|
||||||
|
// Split text by newlines and add each line separately
|
||||||
|
std::istringstream stream(text);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(stream, line)) {
|
||||||
|
outputHistory.push_back({line, isError, false});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim history if too long
|
||||||
|
while (outputHistory.size() > MAX_HISTORY) {
|
||||||
|
outputHistory.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::executeCommand(const std::string& command) {
|
||||||
|
if (command.empty()) return;
|
||||||
|
|
||||||
|
// Add command to output with >>> prefix
|
||||||
|
outputHistory.push_back({">>> " + command, false, true});
|
||||||
|
|
||||||
|
// Add to command history
|
||||||
|
commandHistory.push_back(command);
|
||||||
|
historyIndex = -1;
|
||||||
|
|
||||||
|
// Capture Python output
|
||||||
|
// Redirect stdout/stderr to capture output
|
||||||
|
std::string captureCode = R"(
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
_console_stdout = io.StringIO()
|
||||||
|
_console_stderr = io.StringIO()
|
||||||
|
_old_stdout = sys.stdout
|
||||||
|
_old_stderr = sys.stderr
|
||||||
|
sys.stdout = _console_stdout
|
||||||
|
sys.stderr = _console_stderr
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::string restoreCode = R"(
|
||||||
|
sys.stdout = _old_stdout
|
||||||
|
sys.stderr = _old_stderr
|
||||||
|
_stdout_val = _console_stdout.getvalue()
|
||||||
|
_stderr_val = _console_stderr.getvalue()
|
||||||
|
)";
|
||||||
|
|
||||||
|
// Set up capture
|
||||||
|
PyRun_SimpleString(captureCode.c_str());
|
||||||
|
|
||||||
|
// Try to evaluate as expression first (for things like "2+2")
|
||||||
|
PyObject* main_module = PyImport_AddModule("__main__");
|
||||||
|
PyObject* main_dict = PyModule_GetDict(main_module);
|
||||||
|
|
||||||
|
// First try eval (for expressions that return values)
|
||||||
|
PyObject* result = PyRun_String(command.c_str(), Py_eval_input, main_dict, main_dict);
|
||||||
|
bool showedResult = false;
|
||||||
|
|
||||||
|
if (result == nullptr) {
|
||||||
|
// Clear the error from eval attempt
|
||||||
|
PyErr_Clear();
|
||||||
|
|
||||||
|
// Try exec (for statements)
|
||||||
|
result = PyRun_String(command.c_str(), Py_file_input, main_dict, main_dict);
|
||||||
|
|
||||||
|
if (result == nullptr) {
|
||||||
|
// Real error - capture it
|
||||||
|
PyErr_Print(); // This prints to stderr which we're capturing
|
||||||
|
}
|
||||||
|
} else if (result != Py_None) {
|
||||||
|
// Expression returned a non-None value - show its repr
|
||||||
|
PyObject* repr = PyObject_Repr(result);
|
||||||
|
if (repr) {
|
||||||
|
const char* repr_str = PyUnicode_AsUTF8(repr);
|
||||||
|
if (repr_str) {
|
||||||
|
addOutput(repr_str, false);
|
||||||
|
showedResult = true;
|
||||||
|
}
|
||||||
|
Py_DECREF(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_XDECREF(result);
|
||||||
|
|
||||||
|
// Restore stdout/stderr
|
||||||
|
PyRun_SimpleString(restoreCode.c_str());
|
||||||
|
|
||||||
|
// Get captured stdout (only if we didn't already show a result)
|
||||||
|
PyObject* stdout_val = PyObject_GetAttrString(main_module, "_stdout_val");
|
||||||
|
if (stdout_val && PyUnicode_Check(stdout_val)) {
|
||||||
|
const char* stdout_str = PyUnicode_AsUTF8(stdout_val);
|
||||||
|
if (stdout_str && strlen(stdout_str) > 0) {
|
||||||
|
addOutput(stdout_str, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_XDECREF(stdout_val);
|
||||||
|
|
||||||
|
// Get captured stderr
|
||||||
|
PyObject* stderr_val = PyObject_GetAttrString(main_module, "_stderr_val");
|
||||||
|
if (stderr_val && PyUnicode_Check(stderr_val)) {
|
||||||
|
const char* stderr_str = PyUnicode_AsUTF8(stderr_val);
|
||||||
|
if (stderr_str && strlen(stderr_str) > 0) {
|
||||||
|
addOutput(stderr_str, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_XDECREF(stderr_val);
|
||||||
|
|
||||||
|
// Clean up temporary variables
|
||||||
|
PyRun_SimpleString("del _console_stdout, _console_stderr, _old_stdout, _old_stderr, _stdout_val, _stderr_val");
|
||||||
|
|
||||||
|
scrollToBottom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiConsole::render() {
|
||||||
|
if (!visible || !enabled) return;
|
||||||
|
|
||||||
|
// Set up console window
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y * 0.4f), ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse;
|
||||||
|
|
||||||
|
if (!ImGui::Begin("Console", &visible, flags)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output area (scrollable, no horizontal scrollbar - use word wrap)
|
||||||
|
float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||||
|
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_None);
|
||||||
|
|
||||||
|
// Render output lines with word wrap
|
||||||
|
for (const auto& line : outputHistory) {
|
||||||
|
if (line.isInput) {
|
||||||
|
// User input - yellow/gold color
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.9f, 0.4f, 1.0f));
|
||||||
|
} else if (line.isError) {
|
||||||
|
// Error - red color
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||||
|
} else {
|
||||||
|
// Normal output - default color
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextWrapped("%s", line.text.c_str());
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-scroll to bottom when new content is added
|
||||||
|
if (scrollToBottom || ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
|
||||||
|
ImGui::SetScrollHereY(1.0f);
|
||||||
|
}
|
||||||
|
scrollToBottom = false;
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
// Input line
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Input field
|
||||||
|
ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_EnterReturnsTrue |
|
||||||
|
ImGuiInputTextFlags_CallbackHistory |
|
||||||
|
ImGuiInputTextFlags_CallbackCompletion;
|
||||||
|
|
||||||
|
bool reclaimFocus = false;
|
||||||
|
|
||||||
|
// Custom callback for history navigation
|
||||||
|
auto callback = [](ImGuiInputTextCallbackData* data) -> int {
|
||||||
|
ImGuiConsole* console = static_cast<ImGuiConsole*>(data->UserData);
|
||||||
|
|
||||||
|
if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) {
|
||||||
|
if (console->commandHistory.empty()) return 0;
|
||||||
|
|
||||||
|
if (data->EventKey == ImGuiKey_UpArrow) {
|
||||||
|
if (console->historyIndex < 0) {
|
||||||
|
console->historyIndex = static_cast<int>(console->commandHistory.size()) - 1;
|
||||||
|
} else if (console->historyIndex > 0) {
|
||||||
|
console->historyIndex--;
|
||||||
|
}
|
||||||
|
} else if (data->EventKey == ImGuiKey_DownArrow) {
|
||||||
|
if (console->historyIndex >= 0) {
|
||||||
|
console->historyIndex++;
|
||||||
|
if (console->historyIndex >= static_cast<int>(console->commandHistory.size())) {
|
||||||
|
console->historyIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update input buffer
|
||||||
|
if (console->historyIndex >= 0 && console->historyIndex < static_cast<int>(console->commandHistory.size())) {
|
||||||
|
const std::string& historyEntry = console->commandHistory[console->historyIndex];
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
data->InsertChars(0, historyEntry.c_str());
|
||||||
|
} else {
|
||||||
|
data->DeleteChars(0, data->BufTextLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::PushItemWidth(-1); // Full width
|
||||||
|
if (ImGui::InputText("##Input", inputBuffer, sizeof(inputBuffer), inputFlags, callback, this)) {
|
||||||
|
std::string command(inputBuffer);
|
||||||
|
inputBuffer[0] = '\0';
|
||||||
|
executeCommand(command);
|
||||||
|
reclaimFocus = true;
|
||||||
|
}
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
// Keep focus on input
|
||||||
|
ImGui::SetItemDefaultFocus();
|
||||||
|
if (reclaimFocus || (visible && !ImGui::IsAnyItemActive())) {
|
||||||
|
ImGui::SetKeyboardFocusHere(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ImGui-based debug console for Python REPL
|
||||||
|
*
|
||||||
|
* Provides an overlay console that can execute Python code
|
||||||
|
* without blocking the main game loop. Activated by grave/tilde key.
|
||||||
|
*/
|
||||||
|
class ImGuiConsole {
|
||||||
|
public:
|
||||||
|
ImGuiConsole();
|
||||||
|
|
||||||
|
// Core functionality
|
||||||
|
void render(); // Render the console UI
|
||||||
|
void toggle(); // Toggle visibility
|
||||||
|
bool isVisible() const { return visible; }
|
||||||
|
void setVisible(bool v) { visible = v; }
|
||||||
|
|
||||||
|
// Configuration (for Python API)
|
||||||
|
static bool isEnabled() { return enabled; }
|
||||||
|
static void setEnabled(bool e) { enabled = e; }
|
||||||
|
|
||||||
|
// Input handling
|
||||||
|
bool wantsKeyboardInput() const; // Returns true if ImGui wants keyboard
|
||||||
|
|
||||||
|
private:
|
||||||
|
void executeCommand(const std::string& command);
|
||||||
|
void addOutput(const std::string& text, bool isError = false);
|
||||||
|
|
||||||
|
// State
|
||||||
|
bool visible = false;
|
||||||
|
static bool enabled; // Global enable/disable (for shipping games)
|
||||||
|
|
||||||
|
// Input buffer
|
||||||
|
char inputBuffer[1024] = {0};
|
||||||
|
|
||||||
|
// Output history
|
||||||
|
struct OutputLine {
|
||||||
|
std::string text;
|
||||||
|
bool isError;
|
||||||
|
bool isInput; // True if this was user input (for styling)
|
||||||
|
};
|
||||||
|
std::deque<OutputLine> outputHistory;
|
||||||
|
static constexpr size_t MAX_HISTORY = 500;
|
||||||
|
|
||||||
|
// Command history for up/down navigation
|
||||||
|
std::vector<std::string> commandHistory;
|
||||||
|
int historyIndex = -1;
|
||||||
|
|
||||||
|
// Scroll state
|
||||||
|
bool scrollToBottom = true;
|
||||||
|
};
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "PyWindow.h"
|
#include "PyWindow.h"
|
||||||
#include "PySceneObject.h"
|
#include "PySceneObject.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
|
#include "ImGuiConsole.h"
|
||||||
#include "UI.h"
|
#include "UI.h"
|
||||||
#include "UILine.h"
|
#include "UILine.h"
|
||||||
#include "UICircle.h"
|
#include "UICircle.h"
|
||||||
|
|
@ -202,6 +203,16 @@ static PyMethodDef mcrfpyMethods[] = {
|
||||||
MCRF_RETURNS("dict: Performance data with keys: frame_time (last frame duration in seconds), avg_frame_time (average frame time), fps (frames per second), draw_calls (number of draw calls), ui_elements (total UI element count), visible_elements (visible element count), current_frame (frame counter), runtime (total runtime in seconds)")
|
MCRF_RETURNS("dict: Performance data with keys: frame_time (last frame duration in seconds), avg_frame_time (average frame time), fps (frames per second), draw_calls (number of draw calls), ui_elements (total UI element count), visible_elements (visible element count), current_frame (frame counter), runtime (total runtime in seconds)")
|
||||||
)},
|
)},
|
||||||
|
|
||||||
|
{"setDevConsole", McRFPy_API::_setDevConsole, METH_VARARGS,
|
||||||
|
MCRF_FUNCTION(setDevConsole,
|
||||||
|
MCRF_SIG("(enabled: bool)", "None"),
|
||||||
|
MCRF_DESC("Enable or disable the developer console overlay."),
|
||||||
|
MCRF_ARGS_START
|
||||||
|
MCRF_ARG("enabled", "True to enable the console (default), False to disable")
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("When disabled, the grave/tilde key will not open the console. Use this to ship games without debug features.")
|
||||||
|
)},
|
||||||
|
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1195,6 +1206,16 @@ PyObject* McRFPy_API::_getMetrics(PyObject* self, PyObject* args) {
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* McRFPy_API::_setDevConsole(PyObject* self, PyObject* args) {
|
||||||
|
int enabled;
|
||||||
|
if (!PyArg_ParseTuple(args, "p", &enabled)) { // "p" for boolean predicate
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiConsole::setEnabled(enabled);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Exception handling implementation
|
// Exception handling implementation
|
||||||
void McRFPy_API::signalPythonException() {
|
void McRFPy_API::signalPythonException() {
|
||||||
// Check if we should exit on exception (consult config via game)
|
// Check if we should exit on exception (consult config via game)
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,9 @@ public:
|
||||||
// Profiling/metrics
|
// Profiling/metrics
|
||||||
static PyObject* _getMetrics(PyObject*, PyObject*);
|
static PyObject* _getMetrics(PyObject*, PyObject*);
|
||||||
|
|
||||||
|
// Developer console
|
||||||
|
static PyObject* _setDevConsole(PyObject*, PyObject*);
|
||||||
|
|
||||||
// Scene lifecycle management for Python Scene objects
|
// Scene lifecycle management for Python Scene objects
|
||||||
static void triggerSceneChange(const std::string& from_scene, const std::string& to_scene);
|
static void triggerSceneChange(const std::string& from_scene, const std::string& to_scene);
|
||||||
static void updatePythonScenes(float dt);
|
static void updatePythonScenes(float dt);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue