Unravel Engine C++ Reference
Loading...
Searching...
No Matches
console_log_panel.cpp
Go to the documentation of this file.
1#include "console_log_panel.h"
2#include "../panels_defs.h"
3
8#include <engine/engine.h>
9
10
11#include <imgui/imgui.h>
12#include <imgui/imgui_internal.h>
13#include <imgui_widgets/utils.h>
14
16#include <map>
17
18namespace unravel
19{
20static const std::array<ImColor, size_t(level::n_levels)> colors{ImColor{255, 255, 255},
21 ImColor{255, 255, 255},
22 ImColor{255, 255, 255},
23 ImColor{255, 255, 0},
24 ImColor{255, 0, 0},
25 ImColor{180, 0, 0},
26 ImColor{255, 255, 255}};
27
28static const std::array<const char*, size_t(level::n_levels)> icons{ICON_MDI_ALERT_CIRCLE_CHECK,
35
36static const std::array<const char*, size_t(level::n_levels)>
37 levels{"Trace", "Debug", "Info", "Warning", "Error", "Critical", ""};
38
39auto extract_lines(hpp::string_view text, int num_lines, int& found_lines) -> hpp::string_view
40{
41 auto pos = hpp::string_view::size_type{0};
42 found_lines = 1;
43 for(int i = 0; i < num_lines; ++i)
44 {
45 pos = text.find('\n', pos);
46 if(pos == hpp::string_view::npos)
47 {
48 break;
49 }
50 ++pos;
51 found_lines = i + 1;
52 }
53 return text.substr(0, pos);
54}
55
56void open_log_in_environment(const fs::path& entry, int line)
57{
58 if(ex::is_format<script>(entry.extension().string()))
59 {
61 }
62 else
63 {
64 // fs::show_in_graphical_env(entry);
65 }
66}
67
69{
70 set_pattern("[%H:%M:%S] %v");
71
72 enabled_categories_.fill(true);
73 enabled_categories_[level::trace] = false;
74 enabled_categories_[level::debug] = false;
75}
76
77void console_log_panel::sink_it_(const details::log_msg& msg)
78{
79 {
80 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
81
82 auto log_msg = msg;
83 log_msg.color_range_start = 0;
84 log_msg.color_range_end = 0;
85 log_msg.source = {};
86 memory_buf_t formatted;
87 formatter_->format(log_msg, formatted);
89 entry.formatted.resize(formatted.size());
90 std::memcpy(entry.formatted.data(), formatted.data(), formatted.size() * sizeof(char));
91
92 if(msg.source.filename)
93 {
94 entry.source.filename = msg.source.filename;
95 }
96 if(msg.source.funcname)
97 {
98 entry.source.funcname = msg.source.funcname;
99 }
100 if(msg.source.line)
101 {
102 entry.source.line = msg.source.line;
103 }
104
105 entry.level = msg.level;
106
107 entry.id = current_id_++;
108
109 if(new_entries_begin_idx_ == -1)
110 {
111 new_entries_begin_idx_ = int64_t(entries_.size());
112 }
113 entries_.emplace_back(std::move(entry));
114 }
115 has_new_entries_ = true;
116}
117
121
122void console_log_panel::clear_log()
123{
124 {
125 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
126 entries_.clear();
127 selected_log_ = {};
128 }
129 has_new_entries_ = false;
130}
131
132auto console_log_panel::has_new_entries() const -> bool
133{
134 return has_new_entries_;
135}
136
137void console_log_panel::set_has_new_entries(bool val)
138{
139 has_new_entries_ = val;
140}
141
142void console_log_panel::draw_range(const hpp::string_view& formatted, size_t start, size_t end)
143{
144 if(end > start)
145 {
146 auto end_clamped = std::min(end, formatted.size());
147 auto size = (end_clamped - start);
148 auto text_start = formatted.data() + start;
149 auto text_end = text_start + size;
150 ImGui::TextUnformatted(text_start, text_end);
151 }
152}
153
154void console_log_panel::draw_filter_button(level::level_enum level)
155{
156 auto c = colors[level].Value;
157 auto multiplier = enabled_categories_[level] ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 0.8f);
158 ImGui::PushStyleColor(ImGuiCol_Text, c * multiplier);
159 if(ImGui::MenuItem(icons[level], nullptr, enabled_categories_[level]))
160 {
161 enabled_categories_[level] = !enabled_categories_[level];
162 }
163 ImGui::PopStyleColor();
164 ImGui::SetItemTooltipEx("%s", fmt::format("Enables/Disables {} logs.", levels[level]).c_str());
165}
166
167auto console_log_panel::draw_log(const log_entry& msg, int num_lines) -> bool
168{
169 ImGui::BeginGroup();
170 auto col = colors[size_t(msg.level)];
171 auto icon = icons[size_t(msg.level)];
172 auto level = levels[size_t(msg.level)];
173
174 ImGui::PushStyleColor(ImGuiCol_Text, col.Value);
175 ImGui::AlignTextToFramePadding();
176
177 int found_lines = 1;
178 auto view = extract_lines({msg.formatted.data(), msg.formatted.size()}, 1, found_lines);
179 ImGui::PushWindowFontSize(ImGui::GetFontSize() * num_lines);
180 ImGui::TextUnformatted(icon);
182 ImGui::SameLine();
183 ImGui::BeginGroup();
184
185 draw_range(view, 0, view.size());
186 if(found_lines != num_lines)
187 {
188 ImGui::TextUnformatted(level);
189 }
190
191 ImGui::EndGroup();
192
193 ImGui::PopStyleColor();
194 ImGui::SameLine();
195 ImGui::Dummy({ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight() * num_lines});
196 ImGui::EndGroup();
197
198 bool clicked = ImGui::IsItemClicked();
199 if(clicked)
200 {
201 select_log(msg);
202 }
203
204 if(ImGui::IsItemDoubleClicked())
205 {
206 open_log(msg);
207 }
208 return clicked;
209}
210
212{
213 name_ = name;
214 if(ImGui::Begin(name, nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoScrollbar))
215 {
216 // ImGui::WindowTimeBlock block(ImGui::GetFont(ImGui::Font::Mono));
217 draw();
218 }
219 ImGui::End();
220}
221
223{
224 auto avail = ImGui::GetContentRegionAvail();
225 if(avail.x < 1.0f || avail.y < 1.0f)
226 {
227 return;
228 }
229
230 if(ImGui::BeginMenuBar())
231 {
232 ImGui::DrawFilterWithHint(filter_, ICON_MDI_TEXT_BOX_SEARCH " Search...", 200.0f);
233 ImGui::DrawItemActivityOutline();
234 ImGui::SameLine();
235 if(ImGui::MenuItem("Clear"))
236 {
237 clear_log();
238 }
239 ImGui::SameLine();
240 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, true))
241 {
242 if(ImGui::MenuItem("Clear on Play", nullptr, clear_on_play_))
243 {
244 clear_on_play_ = !clear_on_play_;
245 }
246 if(ImGui::MenuItem("Clear on Recompile", nullptr, clear_on_recompile_))
247 {
248 clear_on_recompile_ = !clear_on_recompile_;
249 }
250 ImGui::EndMenu();
251 }
252
253 draw_filter_button(level::err);
254 draw_filter_button(level::warn);
255 draw_filter_button(level::info);
256 draw_filter_button(level::trace);
257 draw_filter_button(level::debug);
258
259 ImGui::EndMenuBar();
260 }
261
262 avail = ImGui::GetContentRegionAvail();
263
264 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 100.0f), ImVec2(FLT_MAX, FLT_MAX));
265 // ImGui::SetNextWindowSizeConstraints({-1.0f, 100.0f}, {-1.0f, -1.0f});
266 // Display every line as a separate entry so we can change their color or add custom widgets. If you only
267 // want raw text you can use ImGui::TextUnformatted(log.begin(), log.end());
268 // NB- if you have thousands of entries this approach may be too inefficient. You can seek and display
269 // only the lines that are visible - CalcListClipping() is a helper to compute this information.
270 // If your items are of variable size you may want to implement code similar to what CalcListClipping()
271 // does. Or split your data into fixed height items to allow random-seeking into your list.
272
273 ImGui::BeginChild("ScrollingRegion", avail * ImVec2(1.0f, 0.8f), ImGuiChildFlags_ResizeY);
274 if(ImGui::BeginPopupContextWindowEx())
275 {
276 if(ImGui::Selectable("Clear"))
277 clear_log();
278 ImGui::EndPopup();
279 }
280 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing
281
282 display_entries_t entries;
283 {
284 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
285 for(size_t i = 0; i < entries_.size(); ++i)
286 {
287 const auto& msg = entries_[i];
288
289 if(!enabled_categories_[msg.level])
290 continue;
291
292 if(!filter_.PassFilter(msg.formatted.data(), msg.formatted.data() + msg.formatted.size()))
293 continue;
294
295 entries.emplace_back(msg);
296 }
297 }
298
299
300 ImGuiListClipper clipper;
301 clipper.Begin(int(entries.size()));
302 while(clipper.Step())
303 {
304 for(int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
305 {
306 const auto& msg = entries[i];
307
308 if(selected_log_)
309 {
310 const auto& selected = *selected_log_;
311 if(selected.id == msg.id)
312 {
313 auto min = ImGui::GetCursorScreenPos();
314 auto max = min + ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetFrameHeight() * 2);
315 ImGui::RenderFrame(min, max, ImColor(80, 80, 0));
316 }
317 }
318
319 draw_log(msg, 2);
320 }
321 }
322
323
324 if(has_new_entries() && ImGui::GetScrollY() > (ImGui::GetScrollMaxY() - 0.01f))
325 ImGui::SetScrollHereY();
326
327 set_has_new_entries(false);
328
329 ImGui::PopStyleVar();
330 ImGui::EndChild();
331
332 // ImGui::SetNextWindowSizeConstraints({-1.0f, 100.0f}, {-1.0f, -1.0f});
333 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 100.0f), ImVec2(FLT_MAX, FLT_MAX));
334 avail = ImGui::GetContentRegionAvail();
335 avail.y = ImMax(avail.y, 100.0f);
336 ImGui::PushStyleColor(ImGuiCol_Separator, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
337 ImGui::Separator();
338 ImGui::PopStyleColor();
339 ImGui::BeginChild("DetailsArea", avail, ImGuiChildFlags_None);
340
341 draw_details();
342 ImGui::EndChild();
343}
344
346{
347 log_entry msg;
348
349 {
350 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
351 for(auto it = std::rbegin(entries_); it != std::rend(entries_); ++it)
352 {
353 const auto& check = *it;
354 if(!enabled_categories_[check.level])
355 {
356 continue;
357 }
358
359 if(!filter_.PassFilter(check.formatted.data(), check.formatted.data() + check.formatted.size()))
360 {
361 continue;
362 }
363 msg = check;
364 break;
365 }
366 }
367
368 if(msg.formatted.empty())
369 {
370 return false;
371 }
372
373 draw_log(msg, 1);
374
375 return true;
376}
377
379{
380 auto pos = ImGui::GetCursorPos();
381
382 if(draw_last_log())
383 {
384 ImGui::SetCursorPos(pos);
385
386 if(ImGui::InvisibleButton("shortcut", ImGui::GetItemRectSize()))
387 {
388 ImGui::FocusWindow(ImGui::FindWindowByName(name_.c_str()));
389 }
390 }
391
392 uint64_t errors_count = 0;
393 if(new_entries_begin_idx_ != -1)
394 {
395 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
396 for(size_t i = new_entries_begin_idx_; i < entries_.size(); ++i)
397 {
398 const auto& msg = entries_[i];
399 if(msg.level == level::err)
400 {
401 errors_count++;
402 }
403 }
404
405 new_entries_begin_idx_ = -1;
406 }
407
408 if(errors_count > 0)
409 {
410 ImGui::PushNotification({ ImGuiToastType_Error, 2000, fmt::format("{} Error(s)...", errors_count).c_str() });
411 }
412}
413
415{
416 std::lock_guard<std::recursive_mutex> lock(entries_mutex_);
417
418 if(selected_log_)
419 {
420 const auto& msg = *selected_log_;
421 string_view_t str(msg.formatted.data(), msg.formatted.size());
422 auto desc = fmt::format("{0}{1}() (at [{2}:{3}]({2}:{3}))",
423 str,
424 msg.source.funcname,
425 msg.source.filename,
426 msg.source.line);
427
428 ImGui::MarkdownConfig config{};
429 config.linkCallback = [&](const char* link, uint32_t link_length)
430 {
431 open_log_in_environment(fs::path(msg.source.filename), msg.source.line);
432 };
433 ImGui::Markdown(desc.data(), int32_t(desc.size()), config);
434 }
435}
436
437void console_log_panel::select_log(const log_entry& entry)
438{
439 selected_log_ = entry;
440}
441
442void console_log_panel::open_log(const log_entry& entry)
443{
444 open_log_in_environment(entry.source.filename, entry.source.line);
445}
446
448{
449 if(clear_on_play_)
450 {
451 clear_log();
452 }
453}
454
456{
457 if(clear_on_recompile_)
458 {
459 clear_log();
460 }
461}
462
463} // namespace unravel
void on_frame_ui_render(rtti::context &ctx, const char *name)
hpp::small_vector< log_entry, 1024 > display_entries_t
void sink_it_(const details::log_msg &msg) override
std::string name
Definition hub.cpp:27
#define ICON_MDI_ARROW_DOWN_BOLD
#define ICON_MDI_ALERT_BOX
#define ICON_MDI_ALERT_CIRCLE
#define ICON_MDI_ALERT_CIRCLE_CHECK
#define ICON_MDI_BUG_CHECK_OUTLINE
#define ICON_MDI_TEXT_BOX_SEARCH
#define ICON_MDI_ALERT_OCTAGON
@ ImGuiToastType_Error
const char * icon
void PushWindowFontSize(int size)
Definition imgui.cpp:666
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
void PopWindowFontSize()
Definition imgui.cpp:682
auto is_format(const std::string &ex) -> bool
auto start(seq_action action, const seq_scope_policy &scope_policy, hpp::source_location location) -> seq_id_t
Starts a new action.
Definition seq.cpp:8
auto extract_lines(hpp::string_view text, int num_lines, int &found_lines) -> hpp::string_view
void open_log_in_environment(const fs::path &entry, int line)
float x
mem_buf formatted
static void open_workspace_on_file(const fs::path &file, int line=0)