diff --git a/glass/src/app/native/cpp/main.cpp b/glass/src/app/native/cpp/main.cpp index 393b2a578e..36c7f5538e 100644 --- a/glass/src/app/native/cpp/main.cpp +++ b/glass/src/app/native/cpp/main.cpp @@ -16,6 +16,7 @@ #include "glass/View.h" #include "glass/networktables/NetworkTables.h" #include "glass/networktables/NetworkTablesProvider.h" +#include "glass/other/Log.h" #include "glass/other/Plot.h" namespace gui = wpi::gui; @@ -35,8 +36,10 @@ static std::unique_ptr gNtProvider; static std::unique_ptr gNetworkTablesModel; static std::unique_ptr gNetworkTablesSettings; +static glass::LogData gNetworkTablesLog; static glass::Window* gNetworkTablesWindow; static glass::Window* gNetworkTablesSettingsWindow; +static glass::Window* gNetworkTablesLogWindow; static void NtInitialize() { // update window title when connection status changes @@ -62,6 +65,36 @@ static void NtInitialize() { } }); + // handle NetworkTables log messages + auto logPoller = nt::CreateLoggerPoller(inst); + nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100); + gui::AddEarlyExecute([logPoller] { + bool timedOut; + for (auto&& msg : nt::PollLogger(logPoller, 0, &timedOut)) { + const char* level = ""; + if (msg.level >= NT_LOG_CRITICAL) { + level = "CRITICAL: "; + } else if (msg.level >= NT_LOG_ERROR) { + level = "ERROR: "; + } else if (msg.level >= NT_LOG_WARNING) { + level = "WARNING: "; + } + gNetworkTablesLog.Append( + wpi::Twine{level} + msg.message + wpi::Twine{" ("} + msg.filename + + wpi::Twine{':'} + wpi::Twine{msg.line} + wpi::Twine{")\n"}); + } + }); + + gNetworkTablesLogWindow = gNtProvider->AddWindow( + "NetworkTables Log", + std::make_unique(&gNetworkTablesLog)); + if (gNetworkTablesLogWindow) { + gNetworkTablesLogWindow->SetDefaultPos(250, 615); + gNetworkTablesLogWindow->SetDefaultSize(600, 130); + gNetworkTablesLogWindow->SetVisible(false); + gNetworkTablesLogWindow->DisableRenamePopup(); + } + // NetworkTables table window gNetworkTablesModel = std::make_unique(); gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); }); @@ -132,6 +165,9 @@ int main() { if (gNetworkTablesWindow) { gNetworkTablesWindow->DisplayMenuItem("NetworkTables View"); } + if (gNetworkTablesLogWindow) { + gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log"); + } ImGui::Separator(); gNtProvider->DisplayMenu(); ImGui::EndMenu(); diff --git a/glass/src/lib/native/cpp/other/Log.cpp b/glass/src/lib/native/cpp/other/Log.cpp new file mode 100644 index 0000000000..5c6fe2809f --- /dev/null +++ b/glass/src/lib/native/cpp/other/Log.cpp @@ -0,0 +1,70 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "glass/other/Log.h" + +#include +#include + +using namespace glass; + +void LogData::Clear() { + m_buf.clear(); + m_lineOffsets.clear(); + m_lineOffsets.push_back(0); +} + +void LogData::Append(const wpi::Twine& msg) { + if (m_lineOffsets.size() >= m_maxLines) { + Clear(); + } + + size_t oldSize = m_buf.size(); + wpi::raw_vector_ostream os{m_buf}; + msg.print(os); + for (size_t newSize = m_buf.size(); oldSize < newSize; ++oldSize) { + if (m_buf[oldSize] == '\n') { + m_lineOffsets.push_back(oldSize + 1); + } + } +} + +void glass::DisplayLog(LogData* data, bool autoScroll) { + if (data->m_buf.empty()) { + return; + } + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = data->m_buf.data(); + const char* bufEnd = buf + data->m_buf.size(); + ImGuiListClipper clipper; + clipper.Begin(data->m_lineOffsets.size()); + while (clipper.Step()) { + for (size_t lineNum = clipper.DisplayStart; + lineNum < static_cast(clipper.DisplayEnd); lineNum++) { + const char* lineStart = buf + data->m_lineOffsets[lineNum]; + const char* lineEnd = (lineNum + 1 < data->m_lineOffsets.size()) + ? (buf + data->m_lineOffsets[lineNum + 1] - 1) + : bufEnd; + ImGui::TextUnformatted(lineStart, lineEnd); + } + } + clipper.End(); + ImGui::PopStyleVar(); + + if (autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } +} + +void LogView::Display() { + if (ImGui::BeginPopupContextItem()) { + ImGui::Checkbox("Auto-scroll", &m_autoScroll); + if (ImGui::Selectable("Clear")) { + m_data->Clear(); + } + ImGui::EndPopup(); + } + + DisplayLog(m_data, m_autoScroll); +} diff --git a/glass/src/lib/native/include/glass/other/Log.h b/glass/src/lib/native/include/glass/other/Log.h new file mode 100644 index 0000000000..316f61f565 --- /dev/null +++ b/glass/src/lib/native/include/glass/other/Log.h @@ -0,0 +1,43 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include + +#include "glass/View.h" + +namespace glass { + +class LogData { + friend void DisplayLog(LogData*, bool); + + public: + explicit LogData(size_t maxLines = 10000) : m_maxLines{maxLines} {} + + void Clear(); + void Append(const wpi::Twine& msg); + + private: + size_t m_maxLines; + std::vector m_buf; + std::vector m_lineOffsets{0}; +}; + +void DisplayLog(LogData* data, bool autoScroll); + +class LogView : public View { + public: + explicit LogView(LogData* data) : m_data{data} {} + + void Display() override; + + private: + LogData* m_data; + bool m_autoScroll{true}; +}; + +} // namespace glass