You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
8.9 KiB
278 lines
8.9 KiB
2 years ago
|
/*
|
||
|
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
* SPDX-FileCopyrightText: Copyright (c) 2014-2022 NVIDIA CORPORATION
|
||
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
*/
|
||
|
|
||
|
#include "imgui.h"
|
||
|
#include "imgui_internal.h"
|
||
|
|
||
|
namespace nvvkhl {
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
// This is an element to the application that can redirect all logs to a ImGui window in the
|
||
|
// application
|
||
|
//
|
||
|
// Usage:
|
||
|
// static nvvkhl::SampleAppLog g_logger;
|
||
|
// nvprintSetCallback([](int /*level*/, const char* fmt) { g_logger.addLog("%s", fmt); });
|
||
|
//
|
||
|
// app->addElement(std::make_unique<nvvkhl::ElementLogger>(&g_logger, true)); // Add logger window
|
||
|
//
|
||
|
struct SampleAppLog
|
||
|
{
|
||
|
public:
|
||
|
SampleAppLog() { clear(); }
|
||
|
|
||
|
void setLogLevel(uint32_t level) { m_levelFilter = level; }
|
||
|
uint32_t getLogLevel() { return m_levelFilter; }
|
||
|
|
||
|
void clear()
|
||
|
{
|
||
|
m_buf.clear();
|
||
|
m_lineOffsets.clear();
|
||
|
m_lineOffsets.push_back(0);
|
||
|
}
|
||
|
|
||
|
void addLog(uint32_t level, const char* fmt, ...)
|
||
|
{
|
||
|
if((m_levelFilter & (1 << level)) == 0)
|
||
|
return;
|
||
|
|
||
|
int old_size = m_buf.size();
|
||
|
va_list args = {};
|
||
|
va_start(args, fmt);
|
||
|
m_buf.appendfv(fmt, args);
|
||
|
va_end(args);
|
||
|
for(int new_size = m_buf.size(); old_size < new_size; old_size++)
|
||
|
{
|
||
|
if(m_buf[old_size] == '\n')
|
||
|
{
|
||
|
m_lineOffsets.push_back(old_size + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void draw(const char* title, bool* p_open = nullptr)
|
||
|
{
|
||
|
if(!ImGui::Begin(title, p_open))
|
||
|
{
|
||
|
ImGui::End();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Options menu
|
||
|
if(ImGui::BeginPopup("Options"))
|
||
|
{
|
||
|
ImGui::Checkbox("Auto-scroll", &m_autoScroll);
|
||
|
ImGui::EndPopup();
|
||
|
}
|
||
|
|
||
|
// Main window
|
||
|
if(ImGui::Button("Options"))
|
||
|
{
|
||
|
ImGui::OpenPopup("Options");
|
||
|
}
|
||
|
ImGui::SameLine();
|
||
|
bool do_clear = ImGui::Button("Clear");
|
||
|
ImGui::SameLine();
|
||
|
bool copy = ImGui::Button("Copy");
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("All", &m_levelFilter, LOGBITS_ALL);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("Stats", &m_levelFilter, LOGBIT_STATS);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("Debug", &m_levelFilter, LOGBIT_DEBUG);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("Info", &m_levelFilter, LOGBIT_INFO);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("Warnings", &m_levelFilter, LOGBIT_WARNING);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::CheckboxFlags("Errors", &m_levelFilter, LOGBIT_ERROR);
|
||
|
ImGui::SameLine();
|
||
|
ImGui::Text("Filter");
|
||
|
ImGui::SameLine();
|
||
|
m_filter.Draw("##Filter", -100.0F);
|
||
|
ImGui::SameLine();
|
||
|
bool clear_filter = ImGui::SmallButton("X");
|
||
|
|
||
|
ImGui::Separator();
|
||
|
ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||
|
|
||
|
if(do_clear)
|
||
|
{
|
||
|
clear();
|
||
|
}
|
||
|
if(copy)
|
||
|
{
|
||
|
ImGui::SetClipboardText(m_buf.c_str());
|
||
|
}
|
||
|
if(clear_filter)
|
||
|
{
|
||
|
m_filter.Clear();
|
||
|
}
|
||
|
|
||
|
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||
|
const char* buf = m_buf.begin();
|
||
|
const char* buf_end = m_buf.end();
|
||
|
if(m_filter.IsActive())
|
||
|
{
|
||
|
// In this example we don't use the clipper when Filter is enabled.
|
||
|
// This is because we don't have a random access on the result on our filter.
|
||
|
// A real application processing logs with ten of thousands of entries may want to store the result of
|
||
|
// search/filter.. especially if the filtering function is not trivial (e.g. reg-exp).
|
||
|
for(int line_no = 0; line_no < m_lineOffsets.Size; line_no++)
|
||
|
{
|
||
|
const char* line_start = buf + m_lineOffsets[line_no];
|
||
|
const char* line_end = (line_no + 1 < m_lineOffsets.Size) ? (buf + m_lineOffsets[line_no + 1] - 1) : buf_end;
|
||
|
if(m_filter.PassFilter(line_start, line_end))
|
||
|
{
|
||
|
ImGui::TextUnformatted(line_start, line_end);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The simplest and easy way to display the entire buffer:
|
||
|
// ImGui::TextUnformatted(buf_begin, buf_end);
|
||
|
// And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward
|
||
|
// to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are
|
||
|
// within the visible area.
|
||
|
// If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them
|
||
|
// on your side is recommended. Using ImGuiListClipper requires
|
||
|
// - A) random access into your data
|
||
|
// - B) items all being the same height,
|
||
|
// both of which we can handle since we an array pointing to the beginning of each line of text.
|
||
|
// When using the filter (in the block of code above) we don't have random access into the data to display
|
||
|
// anymore, which is why we don't use the clipper. Storing or skimming through the search result would make
|
||
|
// it possible (and would be recommended if you want to search through tens of thousands of entries).
|
||
|
ImGuiListClipper clipper;
|
||
|
clipper.Begin(m_lineOffsets.Size);
|
||
|
while(clipper.Step())
|
||
|
{
|
||
|
for(int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
|
||
|
{
|
||
|
const char* line_start = buf + m_lineOffsets[line_no];
|
||
|
const char* line_end = (line_no + 1 < m_lineOffsets.Size) ? (buf + m_lineOffsets[line_no + 1] - 1) : buf_end;
|
||
|
ImGui::TextUnformatted(line_start, line_end);
|
||
|
}
|
||
|
}
|
||
|
clipper.End();
|
||
|
}
|
||
|
ImGui::PopStyleVar();
|
||
|
|
||
|
if(m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
|
||
|
{
|
||
|
ImGui::SetScrollHereY(1.0F);
|
||
|
}
|
||
|
|
||
|
ImGui::EndChild();
|
||
|
ImGui::End();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
ImGuiTextBuffer m_buf{};
|
||
|
ImGuiTextFilter m_filter{};
|
||
|
ImVector<int> m_lineOffsets; // Index to lines offset. We maintain this with AddLog() calls.
|
||
|
bool m_autoScroll{true}; // Keep scrolling if already at the bottom.
|
||
|
uint32_t m_levelFilter = LOGBITS_WARNINGS;
|
||
|
};
|
||
|
|
||
|
|
||
|
//extern SampleAppLog g_logger;
|
||
|
struct ElementLogger : public nvvkhl::IAppElement
|
||
|
{
|
||
|
explicit ElementLogger(SampleAppLog* logger, bool show = false)
|
||
|
: m_showLog(show)
|
||
|
, m_logger(logger)
|
||
|
{
|
||
|
addSettingsHandler();
|
||
|
}
|
||
|
|
||
|
virtual ~ElementLogger() = default;
|
||
|
|
||
|
void onUIRender() override
|
||
|
{
|
||
|
if(ImGui::IsKeyDown(ImGuiKey_ModCtrl) && ImGui::IsKeyDown(ImGuiKey_ModShift) && !ImGui::IsKeyDown(ImGuiKey_ModAlt))
|
||
|
{
|
||
|
if(ImGui::IsKeyPressed(ImGuiKey_L))
|
||
|
{
|
||
|
m_showLog = !m_showLog;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!m_showLog)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ImGui::SetNextWindowCollapsed(false, ImGuiCond_Appearing);
|
||
|
ImGui::SetNextWindowSize({400, 200}, ImGuiCond_Appearing);
|
||
|
ImGui::SetNextWindowBgAlpha(0.7F);
|
||
|
m_logger->draw("Log", &m_showLog);
|
||
|
|
||
|
} // Called for anything related to UI
|
||
|
|
||
|
void onUIMenu() override
|
||
|
{
|
||
|
if(ImGui::BeginMenu("Help"))
|
||
|
{
|
||
|
ImGui::MenuItem("Log Window", "Ctrl+Shift+L", &m_showLog);
|
||
|
ImGui::EndMenu();
|
||
|
}
|
||
|
} // This is the menubar to create
|
||
|
|
||
|
// This goes in the .ini file and remember the state of the window [open/close]
|
||
|
void addSettingsHandler()
|
||
|
{
|
||
|
// Persisting the window
|
||
|
ImGuiSettingsHandler ini_handler{};
|
||
|
ini_handler.TypeName = "LoggerEngine";
|
||
|
ini_handler.TypeHash = ImHashStr("LoggerEngine");
|
||
|
ini_handler.ClearAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
|
||
|
ini_handler.ApplyAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler*) {};
|
||
|
ini_handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) -> void* { return (void*)1; };
|
||
|
ini_handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, void* entry, const char* line) {
|
||
|
ElementLogger* s = (ElementLogger*)handler->UserData;
|
||
|
int x;
|
||
|
if(sscanf(line, "ShowLoader=%d", &x) == 1)
|
||
|
{
|
||
|
s->m_showLog = (x == 1);
|
||
|
}
|
||
|
else if(sscanf(line, "Level=%d", &x) == 1)
|
||
|
{
|
||
|
s->m_logger->setLogLevel(x);
|
||
|
}
|
||
|
};
|
||
|
ini_handler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) {
|
||
|
ElementLogger* s = (ElementLogger*)handler->UserData;
|
||
|
buf->appendf("[%s][State]\n", handler->TypeName);
|
||
|
buf->appendf("ShowLoader=%d\n", s->m_showLog ? 1 : 0);
|
||
|
buf->appendf("Level=%d\n", s->m_logger->getLogLevel());
|
||
|
buf->appendf("\n");
|
||
|
};
|
||
|
ini_handler.UserData = this;
|
||
|
ImGui::AddSettingsHandler(&ini_handler);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
bool m_showLog{false};
|
||
|
SampleAppLog* m_logger{nullptr};
|
||
|
};
|
||
|
|
||
|
} // namespace nvvkhl
|