RunicVTT Open Source Virtual Tabletop for TTRPG using P2P
Loading...
Searching...
No Matches
DebugConsole.h
Go to the documentation of this file.
1#pragma once
2#include "imgui.h"
3#include <string>
4#include <vector>
5#include <functional>
6#include "Logger.h"
7
8// A simple struct to register toggleable debug features.
9// DebugConsole.h (top)
11{
12 std::string label;
13 bool* flagPtr = nullptr; // the checkbox controls this flag
14 std::function<void(bool enabled)> onChanged; // optional: run once on toggle change
15 std::function<void()> onTick; // optional: run every frame while enabled
16};
17
19{
20public:
21 // Provide LT start/stop handlers (button actions on the left panel).
22 // Start returns the LT URL (or ""). Stop is void.
23 static void setLocalTunnelHandlers(std::function<std::string()> startHandler, std::function<void()> stopHandler)
24 {
25 ltStart_ = std::move(startHandler);
26 ltStop_ = std::move(stopHandler);
27 Logger::instance().log("localtunnel", Logger::Level::Success, "Start and Stop Handlers Set!!!");
28 }
29
30 static void setIdentityLogger(std::function<std::string()> identityLogger)
31 {
32 identityLogger_ = std::move(identityLogger);
33 }
34
35 // Add/remove toggle entries for the left panel.
36 static void addToggle(const DebugToggle& t)
37 {
38 toggles_.push_back(t);
39 }
40 static void clearToggles()
41 {
42 toggles_.clear();
43 }
44
46 {
48 return;
49 for (auto& t : toggles_)
50 {
51 if (t.flagPtr && *t.flagPtr && t.onTick)
52 {
53 t.onTick(); // execute the active debug behavior for this frame
54 }
55 }
56 }
57
58 static void setDebugExecEnabled(bool v)
59 {
61 }
62 static bool isDebugExecEnabled()
63 {
64 return debugExecEnabled_;
65 }
66
67 // Console window visibility
68 static void setVisible(bool v)
69 {
70 visible_ = v;
71 }
72 static bool isVisible()
73 {
74 return visible_;
75 }
76
77 // Active channel selection
78 static void setActiveChannel(const std::string& ch)
79 {
80 activeChannel_ = ch;
81 }
82 static const std::string& activeChannel()
83 {
84 return activeChannel_;
85 }
86
87 // Call once during app init to capture std::cout/cerr into "main".
88 static void bootstrapStdCapture()
89 {
91 Logger::instance().log("main", "DebugConsole ready");
92 Logger::instance().log("localtunnel", "(lt ready)");
93 }
94
95 // Render the window (call each frame if visible)
96 static void Render()
97 {
98 if (!visible_)
99 return;
100
101 ImGui::SetNextWindowSize(ImVec2(980, 560), ImGuiCond_FirstUseEver);
102 ImGui::SetNextWindowBgAlpha(1.0f); // ensure opaque window
103 if (ImGui::Begin("Console", &visible_, ImGuiWindowFlags_NoCollapse))
104 {
105 float leftWidth = 320.0f;
106
107 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.12f, 0.14f, 1.0f));
108 ImGui::BeginChild("LeftPanel", ImVec2(leftWidth, 0), true);
110 ImGui::EndChild();
111 ImGui::PopStyleColor();
112
113 ImGui::SameLine();
114
115 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.10f, 0.10f, 0.12f, 1.0f));
116 ImGui::BeginChild("RightPanel", ImVec2(0, 0), true);
118 ImGui::EndChild();
119 ImGui::PopStyleColor();
120 }
121 ImGui::End();
122 }
123
124private:
125 // Build display names and channel order with "Main" and "LocalTunnel" first
126 static void buildChannelLists_(std::vector<std::string>& display,
127 std::vector<std::string>& order)
128 {
129 auto chans = Logger::instance().channels();
130
131 auto has = [&](const std::string& name)
132 {
133 return std::find(chans.begin(), chans.end(), name) != chans.end();
134 };
135 display.clear();
136 order.clear();
137
138 if (has("main"))
139 {
140 display.push_back("Main");
141 order.push_back("main");
142 }
143 if (has("localtunnel"))
144 {
145 display.push_back("LocalTunnel");
146 order.push_back("localtunnel");
147 }
148
149 for (auto& c : chans)
150 {
151 if (c != "main" && c != "localtunnel")
152 {
153 display.push_back(c);
154 order.push_back(c);
155 }
156 }
157 if (order.empty())
158 { // ensure something is present
159 display.push_back("Main");
160 order.push_back("main");
161 }
162 }
163
164 static void renderLeftPanel_()
165 {
166 ImGui::TextUnformatted("Console Select");
167 ImGui::Separator();
168
170 int sel = 0;
171 for (int i = 0; i < (int)channelOrder_.size(); ++i)
172 {
174 {
175 sel = i;
176 break;
177 }
178 }
179
180 if (ImGui::BeginCombo("##ChannelCombo", displayNames_.empty() ? "<none>" : displayNames_[sel].c_str()))
181 {
182 for (int i = 0; i < (int)displayNames_.size(); ++i)
183 {
184 bool selected = (i == sel);
185 if (ImGui::Selectable(displayNames_[i].c_str(), selected))
186 {
187 sel = i;
189 }
190
191 if (selected)
192 ImGui::SetItemDefaultFocus();
193 }
194 ImGui::EndCombo();
195 }
196
197 if (ImGui::Button("Clear Console"))
198 {
200 }
201
202 ImGui::Dummy(ImVec2(0, 6));
203 ImGui::Separator();
204 ImGui::TextUnformatted("LocalTunnel");
205 if (ImGui::Button("Start"))
206 {
207 if (ltStart_)
208 {
209 auto url = ltStart_();
210 if (!url.empty())
211 Logger::instance().log("localtunnel", Logger::Level::Success, "Started: " + url);
212 else
213 Logger::instance().log("localtunnel", Logger::Level::Error, "Failed to start LT");
214 }
215 else
216 {
217 Logger::instance().log("localtunnel", Logger::Level::Error, "Start handler not set");
218 }
219 }
220 ImGui::SameLine();
221 if (ImGui::Button("Stop"))
222 {
223 if (ltStop_)
224 {
225 ltStop_();
226 Logger::instance().log("localtunnel", "Stopped");
227 }
228 else
229 {
230 Logger::instance().log("localtunnel", "Stop handler not set");
231 }
232 }
233
234 ImGui::Dummy(ImVec2(0, 6));
235 ImGui::Separator();
236
237 if (ImGui::Button("Print Identity"))
238 {
239 if (identityLogger_)
240 {
241 auto log = identityLogger_();
242 Logger::instance().log("identity", log);
243 }
244 else
245 {
246 Logger::instance().log("localtunnel", "Identity handler not set");
247 }
248 }
249
250 ImGui::Dummy(ImVec2(0, 6));
251 ImGui::Separator();
252 // Master switch for executing debug actions per-frame
253 ImGui::Checkbox("Run Debug Actions", &debugExecEnabled_);
254 ImGui::SetItemTooltip("When ON, the per-frame debug callbacks of active toggles will run.");
255
256 ImGui::TextUnformatted("Debug Toggles");
257 ImGui::BeginChild("TogglesScroll", ImVec2(0, -30), false, ImGuiWindowFlags_HorizontalScrollbar);
258 for (auto& t : toggles_)
259 {
260 bool val = t.flagPtr ? *t.flagPtr : false;
261 if (ImGui::Checkbox(t.label.c_str(), &val))
262 {
263 if (t.flagPtr)
264 *t.flagPtr = val;
265 if (t.onChanged)
266 t.onChanged(val); // run once on change
267 }
268 }
269 ImGui::EndChild();
270 }
271
272 static void renderRightPanel_()
273 {
274 ImGui::Checkbox("Auto-scroll", &autoScroll_);
275 ImGui::SameLine();
276 static bool showTimestamps = true; // default ON
277 ImGui::Checkbox("Timestamps", &showTimestamps);
278
279 ImGui::Separator();
280
282
283 ImGui::BeginChild("LogScroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
284 for (auto& e : entries)
285 {
286 ImVec4 col = colorForLevel_(e.level); // see helper below
287
288 if (showTimestamps)
289 {
290 if (e.tsStr.empty())
291 e.tsStr = Logger::formatTs(e.tsMs);
292 ImGui::PushStyleColor(ImGuiCol_Text, colorForLevel_(e.level)); // if you color by level
293 ImGui::TextWrapped("%s", e.text.c_str()); // wraps at window edge
294 ImGui::PopStyleColor();
295 }
296 else
297 {
298 ImGui::TextColored(col, "%s", e.text.c_str());
299 }
300 }
301 if (autoScroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 5.0f)
302 ImGui::SetScrollHereY(1.0f);
303 ImGui::EndChild();
304 }
305
306 static ImVec4 colorForLevel_(Logger::Level lvl)
307 {
308 switch (lvl)
309 {
311 return ImVec4(0.65f, 0.65f, 0.65f, 1.0f);
313 return ImVec4(0.85f, 0.85f, 0.85f, 1.0f);
315 return ImVec4(0.60f, 0.80f, 1.00f, 1.0f);
317 return ImVec4(1.00f, 0.85f, 0.40f, 1.0f);
319 return ImVec4(0.45f, 0.95f, 0.55f, 1.0f);
321 return ImVec4(1.00f, 0.45f, 0.45f, 1.0f);
322 }
323 return ImVec4(0.85f, 0.85f, 0.85f, 1.0f);
324 }
325
326private:
327 inline static bool visible_ = false;
328 inline static std::string activeChannel_ = "main";
329 inline static bool autoScroll_ = true;
330
331 inline static std::function<std::string()> ltStart_;
332 inline static std::function<std::string()> identityLogger_;
333 inline static std::function<void()> ltStop_;
334 inline static std::vector<DebugToggle> toggles_;
335 inline static bool debugExecEnabled_ = false;
336
337 inline static std::vector<std::string> displayNames_;
338 inline static std::vector<std::string> channelOrder_;
339};
static bool autoScroll_
static void setLocalTunnelHandlers(std::function< std::string()> startHandler, std::function< void()> stopHandler)
static void bootstrapStdCapture()
static std::vector< std::string > displayNames_
static const std::string & activeChannel()
static bool isVisible()
static void buildChannelLists_(std::vector< std::string > &display, std::vector< std::string > &order)
static std::string activeChannel_
static std::vector< DebugToggle > toggles_
static std::function< std::string()> ltStart_
static ImVec4 colorForLevel_(Logger::Level lvl)
static std::function< void()> ltStop_
static std::vector< std::string > channelOrder_
static void renderLeftPanel_()
static void addToggle(const DebugToggle &t)
static void renderRightPanel_()
static bool debugExecEnabled_
static void setVisible(bool v)
static bool visible_
static std::function< std::string()> identityLogger_
static void RunActiveDebugToggles()
static void Render()
static void setActiveChannel(const std::string &ch)
static void setIdentityLogger(std::function< std::string()> identityLogger)
static bool isDebugExecEnabled()
static void setDebugExecEnabled(bool v)
static void clearToggles()
std::vector< LogEntry > getChannel(const std::string &channel)
Definition Logger.h:64
static std::string formatTs(uint64_t ms)
Definition Logger.h:135
std::vector< std::string > channels() const
Definition Logger.h:73
void log(const std::string &channel, const std::string &line)
Definition Logger.h:48
void installStdCapture()
Definition Logger.h:118
Level
Definition Logger.h:19
static Logger & instance()
Definition Logger.h:39
void clearChannel(const std::string &channel)
Definition Logger.h:83
std::function< void(bool enabled)> onChanged
std::function< void()> onTick
std::string label
bool * flagPtr