[glass] Add glass: an application for display of robot data

This reuses many pieces of the current simulation GUI.  The common pieces have
been refactored into the libglass library.

The libglass library is designed to be usable for other standalone data
visualization applications (e.g. viewing data logs).

The name "glass" comes from "glass cockpit", as the application features
several multi-function displays that can be adjusted to display robot
information as needed.
This commit is contained in:
Peter Johnson
2020-09-12 10:55:46 -07:00
parent 727940d847
commit 2a5ca77454
151 changed files with 10386 additions and 4565 deletions

View File

@@ -7,337 +7,22 @@
#include "HALSimGui.h"
#include <algorithm>
#include <hal/simulation/DriverStationData.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/StringMap.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
using namespace halsimgui;
namespace gui = wpi::gui;
glass::MainMenuBar HALSimGui::mainMenu;
glass::WindowManager HALSimGui::manager{"SimWindow"};
HALProvider HALSimGui::halProvider{"HALProvider"};
glass::NetworkTablesProvider HALSimGui::ntProvider{"NTProvider"};
namespace {
struct WindowInfo {
WindowInfo() = default;
explicit WindowInfo(const char* name_) : name{name_} {}
WindowInfo(const char* name_, std::function<void()> display_,
ImGuiWindowFlags flags_)
: name{name_}, display{std::move(display_)}, flags{flags_} {}
void HALSimGui::GlobalInit() {
manager.GlobalInit();
halProvider.GlobalInit();
ntProvider.GlobalInit();
std::string name;
std::function<void()> display;
ImGuiWindowFlags flags = 0;
bool visible = true;
bool enabled = true;
ImGuiCond posCond = 0;
ImGuiCond sizeCond = 0;
ImVec2 pos;
ImVec2 size;
bool setPadding = false;
ImVec2 padding;
};
} // namespace
wpi::gui::AddLateExecute([] { mainMenu.Display(); });
static std::vector<WindowInfo> gWindows;
static wpi::StringMap<int> gWindowMap; // index into gWindows
static std::vector<int> gSortedWindows; // index into gWindows
static std::vector<std::function<void()>> gOptionMenus;
static std::vector<std::function<void()>> gMenus;
static bool gDisableOutputsOnDSDisable = true;
// read/write open state to ini file
static void* SimWindowsReadOpen(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
const char* name) {
if (wpi::StringRef{name} == "GLOBAL") return &gDisableOutputsOnDSDisable;
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
if (index == static_cast<int>(gWindows.size())) {
gSortedWindows.push_back(index);
gWindows.emplace_back(name);
std::sort(gSortedWindows.begin(), gSortedWindows.end(),
[](int a, int b) { return gWindows[a].name < gWindows[b].name; });
}
return &gWindows[index];
glass::AddStandardNetworkTablesViews(ntProvider);
}
static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr) {
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
if (entry == &gDisableOutputsOnDSDisable) {
int num;
if (value.getAsInteger(10, num)) return;
if (name == "disableOutputsOnDS") {
gDisableOutputsOnDSDisable = num;
}
return;
}
auto element = static_cast<WindowInfo*>(entry);
if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return;
element->visible = num;
} else if (name == "enabled") {
int num;
if (value.getAsInteger(10, num)) return;
element->enabled = num;
}
}
static void SimWindowsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
out_buf->appendf("[SimWindow][GLOBAL]\ndisableOutputsOnDS=%d\n\n",
gDisableOutputsOnDSDisable ? 1 : 0);
for (auto&& window : gWindows)
out_buf->appendf("[SimWindow][%s]\nvisible=%d\nenabled=%d\n\n",
window.name.c_str(), window.visible ? 1 : 0,
window.enabled ? 1 : 0);
}
void HALSimGui::Add(std::function<void()> initialize) {
gui::AddInit(std::move(initialize));
}
void HALSimGui::AddExecute(std::function<void()> execute) {
gui::AddEarlyExecute(std::move(execute));
}
void HALSimGui::AddWindow(const char* name, std::function<void()> display,
int flags) {
if (display) {
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
if (index < static_cast<int>(gWindows.size())) {
if (gWindows[index].display) {
wpi::errs() << "halsim_gui: ignoring duplicate window '" << name
<< "'\n";
} else {
gWindows[index].display = display;
gWindows[index].flags = flags;
}
return;
}
gSortedWindows.push_back(index);
gWindows.emplace_back(name, std::move(display),
static_cast<ImGuiWindowFlags>(flags));
std::sort(gSortedWindows.begin(), gSortedWindows.end(),
[](int a, int b) { return gWindows[a].name < gWindows[b].name; });
}
}
void HALSimGui::AddMainMenu(std::function<void()> menu) {
if (menu) gMenus.emplace_back(std::move(menu));
}
void HALSimGui::AddOptionMenu(std::function<void()> menu) {
if (menu) gOptionMenus.emplace_back(std::move(menu));
}
void HALSimGui::SetWindowVisibility(const char* name,
WindowVisibility visibility) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
switch (visibility) {
case kHide:
window.visible = false;
window.enabled = true;
break;
case kShow:
window.visible = true;
window.enabled = true;
break;
case kDisabled:
window.enabled = false;
break;
}
}
void HALSimGui::SetDefaultWindowPos(const char* name, float x, float y) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.posCond = ImGuiCond_FirstUseEver;
window.pos = ImVec2{x, y};
}
void HALSimGui::SetDefaultWindowSize(const char* name, float width,
float height) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.sizeCond = ImGuiCond_FirstUseEver;
window.size = ImVec2{width, height};
}
void HALSimGui::SetWindowPadding(const char* name, float x, float y) {
auto it = gWindowMap.find(name);
if (it == gWindowMap.end()) return;
auto& window = gWindows[it->second];
window.setPadding = true;
window.padding = ImVec2{x, y};
}
bool HALSimGui::AreOutputsDisabled() {
return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
}
void HALSimGui::GlobalInit() { gui::CreateContext(); }
bool HALSimGui::Initialize() {
gui::AddInit([] {
// Hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = "SimWindow";
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = SimWindowsReadOpen;
iniHandler.ReadLineFn = SimWindowsReadLine;
iniHandler.WriteAllFn = SimWindowsWriteAll;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
});
gui::AddWindowScaler([](float windowScale) {
// scale default window positions
for (auto&& window : gWindows) {
if ((window.posCond & ImGuiCond_FirstUseEver) != 0) {
window.pos.x *= windowScale;
window.pos.y *= windowScale;
window.size.x *= windowScale;
window.size.y *= windowScale;
}
}
});
gui::AddLateExecute([] {
{
ImGui::BeginMainMenuBar();
if (ImGui::BeginMenu("Options")) {
ImGui::MenuItem("Disable outputs on DS disable", nullptr,
&gDisableOutputsOnDSDisable, true);
for (auto&& menu : gOptionMenus) {
if (menu) menu();
}
ImGui::EndMenu();
}
gui::EmitViewMenu();
if (ImGui::BeginMenu("Window")) {
for (auto&& windowIndex : gSortedWindows) {
auto& window = gWindows[windowIndex];
ImGui::MenuItem(window.name.c_str(), nullptr, &window.visible,
window.enabled);
}
ImGui::EndMenu();
}
for (auto&& menu : gMenus) {
if (menu) menu();
}
#if 0
char str[64];
std::snprintf(str, sizeof(str), "%.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize(str).x -
10);
ImGui::Text("%s", str);
#endif
ImGui::EndMainMenuBar();
}
for (auto&& window : gWindows) {
if (window.display && window.visible && window.enabled) {
if (window.posCond != 0)
ImGui::SetNextWindowPos(window.pos, window.posCond);
if (window.sizeCond != 0)
ImGui::SetNextWindowSize(window.size, window.sizeCond);
if (window.setPadding)
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, window.padding);
if (ImGui::Begin(window.name.c_str(), &window.visible, window.flags))
window.display();
ImGui::End();
if (window.setPadding) ImGui::PopStyleVar();
}
}
});
if (!gui::Initialize("Simulation GUI", 1280, 720)) return false;
return true;
}
void HALSimGui::Main(void*) {
gui::Main();
gui::DestroyContext();
}
void HALSimGui::Exit(void*) { gui::Exit(); }
extern "C" {
void HALSIMGUI_Add(void* param, void (*initialize)(void*)) {
if (initialize) {
HALSimGui::Add([=] { initialize(param); });
}
}
void HALSIMGUI_AddExecute(void* param, void (*execute)(void*)) {
if (execute) {
HALSimGui::AddExecute([=] { execute(param); });
}
}
void HALSIMGUI_AddWindow(const char* name, void* param, void (*display)(void*),
int32_t flags) {
if (display) {
HALSimGui::AddWindow(
name, [=] { display(param); }, flags);
}
}
void HALSIMGUI_AddMainMenu(void* param, void (*menu)(void*)) {
if (menu) {
HALSimGui::AddMainMenu([=] { menu(param); });
}
}
void HALSIMGUI_AddOptionMenu(void* param, void (*menu)(void*)) {
if (menu) {
HALSimGui::AddOptionMenu([=] { menu(param); });
}
}
void HALSIMGUI_SetWindowVisibility(const char* name, int32_t visibility) {
HALSimGui::SetWindowVisibility(
name, static_cast<HALSimGui::WindowVisibility>(visibility));
}
void HALSIMGUI_SetDefaultWindowPos(const char* name, float x, float y) {
HALSimGui::SetDefaultWindowPos(name, x, y);
}
void HALSIMGUI_SetDefaultWindowSize(const char* name, float width,
float height) {
HALSimGui::SetDefaultWindowSize(name, width, height);
}
void HALSIMGUI_SetWindowPadding(const char* name, float x, float y) {
HALSimGui::SetDefaultWindowSize(name, x, y);
}
int HALSIMGUI_AreOutputsDisabled(void) {
return HALSimGui::AreOutputsDisabled();
}
} // extern "C"