diff --git a/CMakeLists.txt b/CMakeLists.txt
index f0e6bc0884..348e0d9797 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -187,6 +187,7 @@ if (WITH_GUI)
add_subdirectory(imgui)
add_subdirectory(wpigui)
add_subdirectory(glass)
+ add_subdirectory(outlineviewer)
endif()
if (WITH_CSCORE)
diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp
index 7a1c6b7ed1..cea8be538f 100644
--- a/glass/src/libnt/native/cpp/NetworkTables.cpp
+++ b/glass/src/libnt/native/cpp/NetworkTables.cpp
@@ -741,30 +741,20 @@ void glass::DisplayNetworkTables(NetworkTablesModel* model,
ImGui::Columns();
}
-void NetworkTablesView::Display() {
- auto& storage = GetStorage();
- auto pTreeView =
- storage.GetBoolRef("tree", m_defaultFlags & NetworkTablesFlags_TreeView);
- auto pShowConnections = storage.GetBoolRef(
- "connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
- auto pShowFlags = storage.GetBoolRef(
- "flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
- auto pShowTimestamp = storage.GetBoolRef(
- "timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
- auto pCreateNoncanonicalKeys = storage.GetBoolRef(
- "createNonCanonical",
- m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
-
- if (ImGui::BeginPopupContextItem()) {
- ImGui::MenuItem("Tree View", "", pTreeView);
- ImGui::MenuItem("Show Connections", "", pShowConnections);
- ImGui::MenuItem("Show Flags", "", pShowFlags);
- ImGui::MenuItem("Show Timestamp", "", pShowTimestamp);
- ImGui::Separator();
- ImGui::MenuItem("Allow creation of non-canonical keys", "",
- pCreateNoncanonicalKeys);
-
- ImGui::EndPopup();
+void NetworkTablesFlagsSettings::Update() {
+ if (!m_pTreeView) {
+ auto& storage = GetStorage();
+ m_pTreeView = storage.GetBoolRef(
+ "tree", m_defaultFlags & NetworkTablesFlags_TreeView);
+ m_pShowConnections = storage.GetBoolRef(
+ "connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
+ m_pShowFlags = storage.GetBoolRef(
+ "flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
+ m_pShowTimestamp = storage.GetBoolRef(
+ "timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
+ m_pCreateNoncanonicalKeys = storage.GetBoolRef(
+ "createNonCanonical",
+ m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
}
m_flags &=
@@ -772,11 +762,32 @@ void NetworkTablesView::Display() {
NetworkTablesFlags_ShowFlags | NetworkTablesFlags_ShowTimestamp |
NetworkTablesFlags_CreateNoncanonicalKeys);
m_flags |=
- (*pTreeView ? NetworkTablesFlags_TreeView : 0) |
- (*pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
- (*pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
- (*pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
- (*pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
- : 0);
- DisplayNetworkTables(m_model, m_flags);
+ (*m_pTreeView ? NetworkTablesFlags_TreeView : 0) |
+ (*m_pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
+ (*m_pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
+ (*m_pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
+ (*m_pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
+ : 0);
+}
+
+void NetworkTablesFlagsSettings::DisplayMenu() {
+ if (!m_pTreeView) {
+ return;
+ }
+ ImGui::MenuItem("Tree View", "", m_pTreeView);
+ ImGui::MenuItem("Show Connections", "", m_pShowConnections);
+ ImGui::MenuItem("Show Flags", "", m_pShowFlags);
+ ImGui::MenuItem("Show Timestamp", "", m_pShowTimestamp);
+ ImGui::Separator();
+ ImGui::MenuItem("Allow creation of non-canonical keys", "",
+ m_pCreateNoncanonicalKeys);
+}
+
+void NetworkTablesView::Display() {
+ m_flags.Update();
+ if (ImGui::BeginPopupContextItem()) {
+ m_flags.DisplayMenu();
+ ImGui::EndPopup();
+ }
+ DisplayNetworkTables(m_model, m_flags.GetFlags());
}
diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
index f515764f42..e7dbbece83 100644
--- a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
+++ b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h
@@ -104,19 +104,39 @@ void DisplayNetworkTables(
NetworkTablesModel* model,
NetworkTablesFlags flags = NetworkTablesFlags_Default);
+class NetworkTablesFlagsSettings {
+ public:
+ explicit NetworkTablesFlagsSettings(
+ NetworkTablesFlags defaultFlags = NetworkTablesFlags_Default)
+ : m_defaultFlags{defaultFlags}, m_flags{defaultFlags} {}
+
+ void Update();
+ void DisplayMenu();
+
+ NetworkTablesFlags GetFlags() const { return m_flags; }
+
+ private:
+ bool* m_pTreeView = nullptr;
+ bool* m_pShowConnections = nullptr;
+ bool* m_pShowFlags = nullptr;
+ bool* m_pShowTimestamp = nullptr;
+ bool* m_pCreateNoncanonicalKeys = nullptr;
+ NetworkTablesFlags m_defaultFlags; // NOLINT
+ NetworkTablesFlags m_flags; // NOLINT
+};
+
class NetworkTablesView : public View {
public:
explicit NetworkTablesView(
NetworkTablesModel* model,
NetworkTablesFlags defaultFlags = NetworkTablesFlags_Default)
- : m_model{model}, m_defaultFlags{defaultFlags}, m_flags{defaultFlags} {}
+ : m_model{model}, m_flags{defaultFlags} {}
void Display() override;
private:
NetworkTablesModel* m_model;
- NetworkTablesFlags m_defaultFlags;
- NetworkTablesFlags m_flags;
+ NetworkTablesFlagsSettings m_flags;
};
} // namespace glass
diff --git a/outlineviewer/.styleguide b/outlineviewer/.styleguide
new file mode 100644
index 0000000000..c8e055a4ca
--- /dev/null
+++ b/outlineviewer/.styleguide
@@ -0,0 +1,27 @@
+cppHeaderFileInclude {
+ \.h$
+ \.inc$
+ \.inl$
+}
+
+cppSrcFileInclude {
+ \.cpp$
+}
+
+generatedFileExclude {
+ src/main/native/resources/
+ src/main/native/win/outlineviewer.ico
+ src/main/native/mac/ov.icns
+}
+
+repoRootNameOverride {
+ outlineviewer
+}
+
+includeOtherLibs {
+ ^GLFW
+ ^imgui
+ ^ntcore
+ ^wpi/
+ ^wpigui
+}
diff --git a/outlineviewer/CMakeLists.txt b/outlineviewer/CMakeLists.txt
new file mode 100644
index 0000000000..537134d3eb
--- /dev/null
+++ b/outlineviewer/CMakeLists.txt
@@ -0,0 +1,28 @@
+project(outlineviewer)
+
+include(CompileWarnings)
+include(GenResources)
+include(LinkMacOSGUI)
+
+configure_file(src/main/generate/WPILibVersion.cpp.in WPILibVersion.cpp)
+GENERATE_RESOURCES(src/main/native/resources generated/main/cpp OV ov outlineviewer_resources_src)
+
+file(GLOB outlineviewer_src src/main/native/cpp/*.cpp ${CMAKE_CURRENT_BINARY_DIR}/WPILibVersion.cpp)
+
+if (WIN32)
+ set(outlineviewer_rc src/main/native/win/outlineviewer.rc)
+elseif(APPLE)
+ set(MACOSX_BUNDLE_ICON_FILE ov.icns)
+ set(APP_ICON_MACOSX src/main/native/mac/ov.icns)
+ set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
+endif()
+
+add_executable(outlineviewer ${outlineviewer_src} ${outlineviewer_resources_src} ${outlineviewer_rc} ${APP_ICON_MACOSX})
+wpilib_link_macos_gui(outlineviewer)
+target_link_libraries(outlineviewer libglassnt libglass)
+
+if (WIN32)
+ set_target_properties(outlineviewer PROPERTIES WIN32_EXECUTABLE YES)
+elseif(APPLE)
+ set_target_properties(outlineviewer PROPERTIES MACOSX_BUNDLE YES OUTPUT_NAME "OutlineViewer")
+endif()
diff --git a/outlineviewer/Info.plist b/outlineviewer/Info.plist
new file mode 100644
index 0000000000..a3e8a85d91
--- /dev/null
+++ b/outlineviewer/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleName
+ OutlineViewer
+ CFBundleExecutable
+ outlineviewer
+ CFBundleDisplayName
+ OutlineViewer
+ CFBundleIdentifier
+ edu.wpi.first.tools.OutlineViewer
+ CFBundleIconFile
+ ov.icns
+ CFBundlePackageType
+ APPL
+ CFBundleSupportedPlatforms
+
+ MacOSX
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleShortVersionString
+ 2021
+ CFBundleVersion
+ 2021
+ LSMinimumSystemVersion
+ 10.11
+ NSHighResolutionCapable
+
+
+
diff --git a/outlineviewer/build.gradle b/outlineviewer/build.gradle
new file mode 100644
index 0000000000..5b4ee6ed40
--- /dev/null
+++ b/outlineviewer/build.gradle
@@ -0,0 +1,119 @@
+import org.gradle.internal.os.OperatingSystem
+
+if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
+
+ description = "NetworkTables Viewer"
+
+ apply plugin: 'cpp'
+ apply plugin: 'c'
+ apply plugin: 'google-test-test-suite'
+ apply plugin: 'visual-studio'
+ apply plugin: 'edu.wpi.first.NativeUtils'
+
+ if (OperatingSystem.current().isWindows()) {
+ apply plugin: 'windows-resources'
+ }
+
+ ext {
+ nativeName = 'outlineviewer'
+ }
+
+ apply from: "${rootDir}/shared/resources.gradle"
+ apply from: "${rootDir}/shared/config.gradle"
+
+ def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
+ def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
+
+ task generateCppVersion() {
+ description = 'Generates the wpilib version class'
+ group = 'WPILib'
+
+ outputs.file wpilibVersionFileOutput
+ inputs.file wpilibVersionFileInput
+
+ if (wpilibVersioning.releaseMode) {
+ outputs.upToDateWhen { false }
+ }
+
+ // We follow a simple set of checks to determine whether we should generate a new version file:
+ // 1. If the release type is not development, we generate a new version file
+ // 2. If there is no generated version number, we generate a new version file
+ // 3. If there is a generated build number, and the release type is development, then we will
+ // only generate if the publish task is run.
+ doLast {
+ def version = wpilibVersioning.version.get()
+ println "Writing version ${version} to $wpilibVersionFileOutput"
+
+ if (wpilibVersionFileOutput.exists()) {
+ wpilibVersionFileOutput.delete()
+ }
+ def read = wpilibVersionFileInput.text.replace('${wpilib_version}', version)
+ wpilibVersionFileOutput.write(read)
+ }
+ }
+
+ gradle.taskGraph.addTaskExecutionGraphListener { graph ->
+ def willPublish = graph.hasTask(publish)
+ if (willPublish) {
+ generateCppVersion.outputs.upToDateWhen { false }
+ }
+ }
+
+ def generateTask = createGenerateResourcesTask('main', 'OV', 'ov', project)
+
+ project(':').libraryBuild.dependsOn build
+ tasks.withType(CppCompile) {
+ dependsOn generateTask
+ dependsOn generateCppVersion
+ }
+
+ model {
+ components {
+ // By default, a development executable will be generated. This is to help the case of
+ // testing specific functionality of the library.
+ "${nativeName}"(NativeExecutableSpec) {
+ baseName = 'outlineviewer'
+ sources {
+ cpp {
+ source {
+ srcDirs 'src/main/native/cpp', "$buildDir/generated/main/cpp"
+ include '**/*.cpp'
+ }
+ exportedHeaders {
+ srcDirs 'src/main/native/include'
+ }
+ }
+ if (OperatingSystem.current().isWindows()) {
+ rc {
+ source {
+ srcDirs 'src/main/native/win'
+ include '*.rc'
+ }
+ }
+ }
+ }
+ binaries.all {
+ if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
+ it.buildable = false
+ return
+ }
+ lib project: ':glass', library: 'glassnt', linkage: 'static'
+ lib project: ':glass', library: 'glass', linkage: 'static'
+ lib project: ':ntcore', library: 'ntcore', linkage: 'static'
+ lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
+ lib project: ':wpigui', library: 'wpigui', linkage: 'static'
+ nativeUtils.useRequiredLibrary(it, 'imgui_static')
+ if (it.targetPlatform.operatingSystem.isWindows()) {
+ it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
+ } else if (it.targetPlatform.operatingSystem.isMacOsX()) {
+ it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
+ } else {
+ it.linker.args << '-lX11'
+ }
+ }
+ }
+ }
+ }
+
+ apply from: 'publish.gradle'
+}
diff --git a/outlineviewer/publish.gradle b/outlineviewer/publish.gradle
new file mode 100644
index 0000000000..85605c47e3
--- /dev/null
+++ b/outlineviewer/publish.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'maven-publish'
+
+def baseArtifactId = 'OutlineViewer'
+def artifactGroupId = 'edu.wpi.first.tools'
+def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_OutlineViewer_CLS'
+
+def outputsFolder = file("$project.buildDir/outputs")
+
+model {
+ publishing {
+ def outlineViewerTaskList = []
+ $.components.each { component ->
+ component.binaries.each { binary ->
+ if (binary in NativeExecutableBinarySpec && binary.component.name.contains("outlineviewer")) {
+ if (binary.buildable && binary.name.contains("Release")) {
+ // We are now in the binary that we want.
+ // This is the default application path for the ZIP task.
+ def applicationPath = binary.executable.file
+ def icon = file("$project.projectDir/src/main/native/mac/ov.icns")
+
+ // Create the macOS bundle.
+ def bundleTask = project.tasks.create("bundleOutlineViewerOsxApp", Copy) {
+ description("Creates a macOS application bundle for OutlineViewer")
+ from(file("$project.projectDir/Info.plist"))
+ into(file("$project.buildDir/outputs/bundles/OutlineViewer.app/Contents"))
+ into("MacOS") { with copySpec { from binary.executable.file } }
+ into("Resources") { with copySpec { from icon } }
+
+ doLast {
+ if (project.hasProperty("developerID")) {
+ // Get path to binary.
+ exec {
+ workingDir rootDir
+ def args = [
+ "sh",
+ "-c",
+ "codesign --force --strict --deep " +
+ "--timestamp --options=runtime " +
+ "--verbose -s ${project.findProperty("developerID")} " +
+ "$project.buildDir/outputs/bundles/OutlineViewer.app/"
+ ]
+ commandLine args
+ }
+ }
+ }
+ }
+
+ // Reset the application path if we are creating a bundle.
+ if (binary.targetPlatform.operatingSystem.isMacOsX()) {
+ applicationPath = file("$project.buildDir/outputs/bundles")
+ project.build.dependsOn bundleTask
+ }
+
+ // Create the ZIP.
+ def task = project.tasks.create("copyOutlineViewerExecutable", Zip) {
+ description("Copies the OutlineViewer executable to the outputs directory.")
+ destinationDirectory = outputsFolder
+
+ archiveBaseName = '_M_' + zipBaseName
+ duplicatesStrategy = 'exclude'
+ classifier = nativeUtils.getPublishClassifier(binary)
+
+ from(licenseFile) {
+ into '/'
+ }
+
+ from(applicationPath)
+ into(nativeUtils.getPlatformPath(binary))
+ }
+
+ if (binary.targetPlatform.operatingSystem.isMacOsX()) {
+ bundleTask.dependsOn binary.tasks.link
+ task.dependsOn(bundleTask)
+ }
+
+ task.dependsOn binary.tasks.link
+ outlineViewerTaskList.add(task)
+ project.build.dependsOn task
+ project.artifacts { task }
+ addTaskToCopyAllOutputs(task)
+ }
+ }
+ }
+ }
+
+ publications {
+ outlineViewer(MavenPublication) {
+ outlineViewerTaskList.each { artifact it }
+
+ artifactId = baseArtifactId
+ groupId = artifactGroupId
+ version wpilibVersioning.version.get()
+ }
+ }
+ }
+}
diff --git a/outlineviewer/src/main/generate/WPILibVersion.cpp.in b/outlineviewer/src/main/generate/WPILibVersion.cpp.in
new file mode 100644
index 0000000000..b0a4490520
--- /dev/null
+++ b/outlineviewer/src/main/generate/WPILibVersion.cpp.in
@@ -0,0 +1,7 @@
+/*
+ * Autogenerated file! Do not manually edit this file. This version is regenerated
+ * any time the publish task is run, or when this file is deleted.
+ */
+const char* GetWPILibVersion() {
+ return "${wpilib_version}";
+}
diff --git a/outlineviewer/src/main/native/cpp/main.cpp b/outlineviewer/src/main/native/cpp/main.cpp
new file mode 100644
index 0000000000..0bfd91614e
--- /dev/null
+++ b/outlineviewer/src/main/native/cpp/main.cpp
@@ -0,0 +1,229 @@
+// 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
+
+#include
+#include
+#include
+#include
+#include
+
+#include "glass/Context.h"
+#include "glass/Model.h"
+#include "glass/networktables/NetworkTables.h"
+#include "glass/networktables/NetworkTablesSettings.h"
+#include "glass/other/Log.h"
+
+namespace gui = wpi::gui;
+
+const char* GetWPILibVersion();
+
+namespace ov {
+wpi::StringRef GetResource_ov_16_png();
+wpi::StringRef GetResource_ov_32_png();
+wpi::StringRef GetResource_ov_48_png();
+wpi::StringRef GetResource_ov_64_png();
+wpi::StringRef GetResource_ov_128_png();
+wpi::StringRef GetResource_ov_256_png();
+wpi::StringRef GetResource_ov_512_png();
+} // namespace ov
+
+static std::unique_ptr gModel;
+static std::unique_ptr gSettings;
+static glass::LogData gLog;
+static glass::NetworkTablesFlagsSettings gFlagsSettings;
+
+static void NtInitialize() {
+ // update window title when connection status changes
+ auto inst = nt::GetDefaultInstance();
+ auto poller = nt::CreateConnectionListenerPoller(inst);
+ nt::AddPolledConnectionListener(poller, true);
+ gui::AddEarlyExecute([inst, poller] {
+ auto win = gui::GetSystemWindow();
+ if (!win) {
+ return;
+ }
+ bool timedOut;
+ for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
+ if ((nt::GetNetworkMode(inst) & NT_NET_MODE_SERVER) != 0) {
+ // for server mode, just print number of clients connected
+ wpi::SmallString<64> title;
+ glfwSetWindowTitle(win, ("OutlineViewer - " +
+ wpi::Twine{nt::GetConnections(inst).size()} +
+ " Clients Connected")
+ .toNullTerminatedStringRef(title)
+ .data());
+ } else if (event.connected) {
+ wpi::SmallString<64> title;
+ title = "OutlineViewer - Connected (";
+ title += event.conn.remote_ip;
+ title += ')';
+ glfwSetWindowTitle(win, title.c_str());
+ } else {
+ glfwSetWindowTitle(win, "OutlineViewer - DISCONNECTED");
+ }
+ }
+ });
+
+ // 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: ";
+ }
+ gLog.Append(wpi::Twine{level} + msg.message + wpi::Twine{" ("} +
+ msg.filename + wpi::Twine{':'} + wpi::Twine{msg.line} +
+ wpi::Twine{")\n"});
+ }
+ });
+
+ // NetworkTables table window
+ gModel = std::make_unique();
+ gui::AddEarlyExecute([] { gModel->Update(); });
+
+ // NetworkTables settings window
+ gSettings = std::make_unique();
+ gui::AddEarlyExecute([] { gSettings->Update(); });
+}
+
+static void DisplayGui() {
+ ImGui::GetStyle().WindowRounding = 0;
+
+ // fill entire OS window with this window
+ ImGui::SetNextWindowPos(ImVec2(0, 0));
+ int width, height;
+ glfwGetWindowSize(gui::GetSystemWindow(), &width, &height);
+ ImGui::SetNextWindowSize(ImVec2(width, height));
+
+ ImGui::Begin("Entries", nullptr,
+ ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_MenuBar |
+ ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoCollapse);
+
+ gFlagsSettings.Update();
+
+ // can't create popups from within menu, so use flags
+ bool settings = false;
+ bool log = false;
+ bool about = false;
+
+ // main menu
+ ImGui::BeginMenuBar();
+ gui::EmitViewMenu();
+ if (ImGui::BeginMenu("View")) {
+ gFlagsSettings.DisplayMenu();
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Options")) {
+ if (ImGui::MenuItem("Settings")) {
+ settings = true;
+ }
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Info")) {
+ if (ImGui::MenuItem("Log")) {
+ log = true;
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("About")) {
+ about = true;
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenuBar();
+
+ // settings popup
+ if (settings) {
+ ImGui::OpenPopup("Settings");
+ }
+ if (ImGui::BeginPopupModal("Settings", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ if (gSettings->Display()) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Cancel")) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+
+ // log popup
+ if (log) {
+ ImGui::OpenPopup("Log");
+ }
+ if (ImGui::BeginPopupModal("Log", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ if (ImGui::Button("Close")) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::BeginChild("Lines", ImVec2(width * 0.75f, height * 0.75f));
+ glass::DisplayLog(&gLog, true);
+ ImGui::EndChild();
+ ImGui::EndPopup();
+ }
+
+ // about popup
+ if (about) {
+ ImGui::OpenPopup("About");
+ }
+ if (ImGui::BeginPopupModal("About", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::Text("OutlineViewer");
+ ImGui::Separator();
+ ImGui::Text("v%s", GetWPILibVersion());
+ if (ImGui::Button("Close")) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+
+ // display table view
+ glass::DisplayNetworkTables(gModel.get(), gFlagsSettings.GetFlags());
+
+ ImGui::End();
+}
+
+#ifdef _WIN32
+int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
+ int nCmdShow) {
+#else
+int main() {
+#endif
+ gui::CreateContext();
+ glass::CreateContext();
+
+ gui::AddIcon(ov::GetResource_ov_16_png());
+ gui::AddIcon(ov::GetResource_ov_32_png());
+ gui::AddIcon(ov::GetResource_ov_48_png());
+ gui::AddIcon(ov::GetResource_ov_64_png());
+ gui::AddIcon(ov::GetResource_ov_128_png());
+ gui::AddIcon(ov::GetResource_ov_256_png());
+ gui::AddIcon(ov::GetResource_ov_512_png());
+
+ gui::ConfigurePlatformSaveFile("outlineviewer.ini");
+ gui::AddInit(NtInitialize);
+
+ gui::AddLateExecute(DisplayGui);
+
+ gui::Initialize("OutlineViewer - DISCONNECTED", 1024, 768);
+ gui::Main();
+
+ gModel.reset();
+ gSettings.reset();
+
+ glass::DestroyContext();
+ gui::DestroyContext();
+}
diff --git a/outlineviewer/src/main/native/mac/ov.icns b/outlineviewer/src/main/native/mac/ov.icns
new file mode 100644
index 0000000000..b96e85928b
Binary files /dev/null and b/outlineviewer/src/main/native/mac/ov.icns differ
diff --git a/outlineviewer/src/main/native/resources/ov-128.png b/outlineviewer/src/main/native/resources/ov-128.png
new file mode 100644
index 0000000000..950d0ca2db
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-128.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-16.png b/outlineviewer/src/main/native/resources/ov-16.png
new file mode 100644
index 0000000000..db84366bd9
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-16.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-256.png b/outlineviewer/src/main/native/resources/ov-256.png
new file mode 100644
index 0000000000..7b3b2b72f6
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-256.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-32.png b/outlineviewer/src/main/native/resources/ov-32.png
new file mode 100644
index 0000000000..4db8e8b589
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-32.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-48.png b/outlineviewer/src/main/native/resources/ov-48.png
new file mode 100644
index 0000000000..026491c286
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-48.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-512.png b/outlineviewer/src/main/native/resources/ov-512.png
new file mode 100644
index 0000000000..d4fd60ce6b
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-512.png differ
diff --git a/outlineviewer/src/main/native/resources/ov-64.png b/outlineviewer/src/main/native/resources/ov-64.png
new file mode 100644
index 0000000000..9cd5d968a0
Binary files /dev/null and b/outlineviewer/src/main/native/resources/ov-64.png differ
diff --git a/outlineviewer/src/main/native/win/outlineviewer.ico b/outlineviewer/src/main/native/win/outlineviewer.ico
new file mode 100644
index 0000000000..7156af8d6a
Binary files /dev/null and b/outlineviewer/src/main/native/win/outlineviewer.ico differ
diff --git a/outlineviewer/src/main/native/win/outlineviewer.rc b/outlineviewer/src/main/native/win/outlineviewer.rc
new file mode 100644
index 0000000000..85bdcae2ef
--- /dev/null
+++ b/outlineviewer/src/main/native/win/outlineviewer.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON "outlineviewer.ico"
diff --git a/settings.gradle b/settings.gradle
index 89cba97761..0cd962f700 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -29,6 +29,7 @@ include 'wpilibjExamples'
include 'wpilibjIntegrationTests'
include 'wpilibj'
include 'glass'
+include 'outlineviewer'
include 'simulation:gz_msgs'
include 'simulation:frc_gazebo_plugins'
include 'simulation:halsim_gazebo'