blob: 03107569fdf3c61696ca40dc03ce36a42ac46425 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include <memory>
6
7#include <GLFW/glfw3.h>
8#include <fmt/format.h>
James Kuszmaulb13e13f2023-11-22 20:44:04 -08009#include <glass/Context.h>
10#include <glass/MainMenuBar.h>
11#include <glass/Model.h>
12#include <glass/Storage.h>
13#include <glass/networktables/NetworkTables.h>
14#include <glass/networktables/NetworkTablesSettings.h>
15#include <glass/other/Log.h>
Austin Schuh812d0d12021-11-04 20:16:48 -070016#include <imgui.h>
17#include <ntcore_cpp.h>
18#include <wpigui.h>
James Kuszmaulb13e13f2023-11-22 20:44:04 -080019#include <wpigui_openurl.h>
Austin Schuh812d0d12021-11-04 20:16:48 -070020
21namespace gui = wpi::gui;
22
23const char* GetWPILibVersion();
24
25namespace ov {
26std::string_view GetResource_ov_16_png();
27std::string_view GetResource_ov_32_png();
28std::string_view GetResource_ov_48_png();
29std::string_view GetResource_ov_64_png();
30std::string_view GetResource_ov_128_png();
31std::string_view GetResource_ov_256_png();
32std::string_view GetResource_ov_512_png();
33} // namespace ov
34
35static std::unique_ptr<glass::NetworkTablesModel> gModel;
36static std::unique_ptr<glass::NetworkTablesSettings> gSettings;
37static glass::LogData gLog;
38static glass::NetworkTablesFlagsSettings gFlagsSettings;
Austin Schuh75263e32022-02-22 18:05:32 -080039static glass::MainMenuBar gMainMenu;
James Kuszmaulb13e13f2023-11-22 20:44:04 -080040static unsigned int gPrevMode = NT_NET_MODE_NONE;
41
42/**
43 * Generates the proper title bar title based on current instance state and
44 * event.
45 */
46static std::string MakeTitle(NT_Inst inst, nt::Event event) {
47 auto mode = nt::GetNetworkMode(inst);
48 if (mode & NT_NET_MODE_SERVER) {
49 auto numClients = nt::GetConnections(inst).size();
50 return fmt::format("OutlineViewer - {} Client{} Connected", numClients,
51 (numClients == 1 ? "" : "s"));
52 } else if (mode & NT_NET_MODE_CLIENT3 || mode & NT_NET_MODE_CLIENT4) {
53 if (event.Is(NT_EVENT_CONNECTED)) {
54 return fmt::format("OutlineViewer - Connected ({})",
55 event.GetConnectionInfo()->remote_ip);
56 }
57 }
58 return "OutlineViewer - DISCONNECTED";
59}
Austin Schuh812d0d12021-11-04 20:16:48 -070060
61static void NtInitialize() {
Austin Schuh812d0d12021-11-04 20:16:48 -070062 auto inst = nt::GetDefaultInstance();
James Kuszmaulcf324122023-01-14 14:07:17 -080063 auto poller = nt::CreateListenerPoller(inst);
James Kuszmaulb13e13f2023-11-22 20:44:04 -080064 nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
65 nt::AddPolledLogger(poller, 0, 100);
Austin Schuh812d0d12021-11-04 20:16:48 -070066 gui::AddEarlyExecute([inst, poller] {
67 auto win = gui::GetSystemWindow();
68 if (!win) {
69 return;
70 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -080071 bool updateTitle = false;
72 nt::Event connectionEvent;
73 if (nt::GetNetworkMode(inst) != gPrevMode) {
74 gPrevMode = nt::GetNetworkMode(inst);
75 updateTitle = true;
76 }
James Kuszmaulcf324122023-01-14 14:07:17 -080077 for (auto&& event : nt::ReadListenerQueue(poller)) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -080078 if (event.Is(NT_EVENT_CONNECTION)) {
79 updateTitle = true;
80 connectionEvent = event;
James Kuszmaulcf324122023-01-14 14:07:17 -080081 } else if (auto msg = event.GetLogMessage()) {
82 // handle NetworkTables log messages
83 const char* level = "";
84 if (msg->level >= NT_LOG_CRITICAL) {
85 level = "CRITICAL: ";
86 } else if (msg->level >= NT_LOG_ERROR) {
87 level = "ERROR: ";
88 } else if (msg->level >= NT_LOG_WARNING) {
89 level = "WARNING: ";
90 }
91 gLog.Append(fmt::format("{}{} ({}:{})\n", level, msg->message,
92 msg->filename, msg->line));
Austin Schuh812d0d12021-11-04 20:16:48 -070093 }
94 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -080095
96 if (updateTitle) {
97 glfwSetWindowTitle(win, MakeTitle(inst, connectionEvent).c_str());
98 }
Austin Schuh812d0d12021-11-04 20:16:48 -070099 });
100
Austin Schuh812d0d12021-11-04 20:16:48 -0700101 // NetworkTables table window
102 gModel = std::make_unique<glass::NetworkTablesModel>();
103 gui::AddEarlyExecute([] { gModel->Update(); });
104
105 // NetworkTables settings window
Austin Schuh75263e32022-02-22 18:05:32 -0800106 gSettings = std::make_unique<glass::NetworkTablesSettings>(
James Kuszmaulcf324122023-01-14 14:07:17 -0800107 "outlineviewer",
Austin Schuh75263e32022-02-22 18:05:32 -0800108 glass::GetStorageRoot().GetChild("NetworkTables Settings"));
Austin Schuh812d0d12021-11-04 20:16:48 -0700109 gui::AddEarlyExecute([] { gSettings->Update(); });
110}
111
112static void DisplayGui() {
113 ImGui::GetStyle().WindowRounding = 0;
114
115 // fill entire OS window with this window
116 ImGui::SetNextWindowPos(ImVec2(0, 0));
117 int width, height;
118 glfwGetWindowSize(gui::GetSystemWindow(), &width, &height);
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800119 ImGui::SetNextWindowSize(
120 ImVec2(static_cast<float>(width), static_cast<float>(height)));
Austin Schuh812d0d12021-11-04 20:16:48 -0700121
122 ImGui::Begin("Entries", nullptr,
123 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_MenuBar |
124 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
125 ImGuiWindowFlags_NoCollapse);
126
127 gFlagsSettings.Update();
128
129 // can't create popups from within menu, so use flags
130 bool settings = false;
131 bool log = false;
132 bool about = false;
133
134 // main menu
135 ImGui::BeginMenuBar();
Austin Schuh75263e32022-02-22 18:05:32 -0800136 gMainMenu.WorkspaceMenu();
Austin Schuh812d0d12021-11-04 20:16:48 -0700137 gui::EmitViewMenu();
138 if (ImGui::BeginMenu("View")) {
139 gFlagsSettings.DisplayMenu();
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800140 glass::DisplayNetworkTablesAddMenu(gModel.get());
Austin Schuh812d0d12021-11-04 20:16:48 -0700141 ImGui::EndMenu();
142 }
143
144 if (ImGui::BeginMenu("Options")) {
145 if (ImGui::MenuItem("Settings")) {
146 settings = true;
147 }
148 ImGui::EndMenu();
149 }
150
151 if (ImGui::BeginMenu("Info")) {
152 if (ImGui::MenuItem("Log")) {
153 log = true;
154 }
155 ImGui::Separator();
156 if (ImGui::MenuItem("About")) {
157 about = true;
158 }
159 ImGui::EndMenu();
160 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800161
162 if (ImGui::BeginMenu("Docs")) {
163 if (ImGui::MenuItem("Online documentation")) {
164 wpi::gui::OpenURL(
165 "https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/"
166 "outlineviewer/");
167 }
168 ImGui::EndMenu();
169 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700170 ImGui::EndMenuBar();
171
172 // settings popup
173 if (settings) {
174 ImGui::OpenPopup("Settings");
175 }
176 if (ImGui::BeginPopupModal("Settings", nullptr,
177 ImGuiWindowFlags_AlwaysAutoResize)) {
178 if (gSettings->Display()) {
179 ImGui::CloseCurrentPopup();
180 }
181 ImGui::SameLine();
182 if (ImGui::Button("Cancel")) {
183 ImGui::CloseCurrentPopup();
184 }
185 ImGui::EndPopup();
186 }
187
188 // log popup
189 if (log) {
190 ImGui::OpenPopup("Log");
191 }
192 if (ImGui::BeginPopupModal("Log", nullptr,
193 ImGuiWindowFlags_AlwaysAutoResize)) {
194 if (ImGui::Button("Close")) {
195 ImGui::CloseCurrentPopup();
196 }
197 ImGui::BeginChild("Lines", ImVec2(width * 0.75f, height * 0.75f));
198 glass::DisplayLog(&gLog, true);
199 ImGui::EndChild();
200 ImGui::EndPopup();
201 }
202
203 // about popup
204 if (about) {
205 ImGui::OpenPopup("About");
206 }
207 if (ImGui::BeginPopupModal("About", nullptr,
208 ImGuiWindowFlags_AlwaysAutoResize)) {
209 ImGui::Text("OutlineViewer");
210 ImGui::Separator();
211 ImGui::Text("v%s", GetWPILibVersion());
Austin Schuh75263e32022-02-22 18:05:32 -0800212 ImGui::Separator();
213 ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800214 ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
215 ImGui::GetIO().Framerate);
Austin Schuh812d0d12021-11-04 20:16:48 -0700216 if (ImGui::Button("Close")) {
217 ImGui::CloseCurrentPopup();
218 }
219 ImGui::EndPopup();
220 }
221
222 // display table view
James Kuszmaulcf324122023-01-14 14:07:17 -0800223 glass::DisplayNetworkTablesInfo(gModel.get());
224 ImGui::Separator();
Austin Schuh812d0d12021-11-04 20:16:48 -0700225 glass::DisplayNetworkTables(gModel.get(), gFlagsSettings.GetFlags());
226
227 ImGui::End();
228}
229
230#ifdef _WIN32
231int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
232 int nCmdShow) {
Austin Schuh75263e32022-02-22 18:05:32 -0800233 int argc = __argc;
234 char** argv = __argv;
Austin Schuh812d0d12021-11-04 20:16:48 -0700235#else
Austin Schuh75263e32022-02-22 18:05:32 -0800236int main(int argc, char** argv) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700237#endif
Austin Schuh75263e32022-02-22 18:05:32 -0800238 std::string_view saveDir;
239 if (argc == 2) {
240 saveDir = argv[1];
241 }
242
Austin Schuh812d0d12021-11-04 20:16:48 -0700243 gui::CreateContext();
244 glass::CreateContext();
245
246 gui::AddIcon(ov::GetResource_ov_16_png());
247 gui::AddIcon(ov::GetResource_ov_32_png());
248 gui::AddIcon(ov::GetResource_ov_48_png());
249 gui::AddIcon(ov::GetResource_ov_64_png());
250 gui::AddIcon(ov::GetResource_ov_128_png());
251 gui::AddIcon(ov::GetResource_ov_256_png());
252 gui::AddIcon(ov::GetResource_ov_512_png());
253
Austin Schuh75263e32022-02-22 18:05:32 -0800254 glass::SetStorageName("outlineviewer");
255 glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
256 : saveDir);
257
Austin Schuh812d0d12021-11-04 20:16:48 -0700258 gui::AddInit(NtInitialize);
259
260 gui::AddLateExecute(DisplayGui);
261
262 gui::Initialize("OutlineViewer - DISCONNECTED", 600, 400);
263 gui::Main();
264
265 gModel.reset();
266 gSettings.reset();
267
268 glass::DestroyContext();
269 gui::DestroyContext();
Austin Schuh75263e32022-02-22 18:05:32 -0800270
271 return 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700272}