[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

@@ -186,6 +186,7 @@ endif()
if (WITH_GUI)
add_subdirectory(imgui)
add_subdirectory(wpigui)
add_subdirectory(glass)
endif()
if (WITH_CSCORE)

69
glass/CMakeLists.txt Normal file
View File

@@ -0,0 +1,69 @@
project(glass)
include(CompileWarnings)
include(LinkMacOSGUI)
#
# libglass
#
file(GLOB_RECURSE libglass_src src/lib/native/cpp/*.cpp)
add_library(libglass STATIC ${libglass_src})
set_target_properties(libglass PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glass")
set_property(TARGET libglass PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET libglass PROPERTY FOLDER "libraries")
wpilib_target_warnings(libglass)
target_link_libraries(libglass PUBLIC wpigui wpimath wpiutil)
target_include_directories(libglass PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/lib/native/include>
$<INSTALL_INTERFACE:${include_dest}/glass>)
install(TARGETS libglass EXPORT libglass DESTINATION "${main_lib_dest}")
install(DIRECTORY src/lib/native/include/ DESTINATION "${include_dest}/glass")
#
# libglassnt
#
file(GLOB_RECURSE libglassnt_src src/libnt/native/cpp/*.cpp)
add_library(libglassnt STATIC ${libglassnt_src})
set_target_properties(libglassnt PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glassnt")
set_property(TARGET libglassnt PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET libglassnt PROPERTY FOLDER "libraries")
wpilib_target_warnings(libglassnt)
target_link_libraries(libglassnt PUBLIC ntcore libglass)
target_include_directories(libglassnt PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/libnt/native/include>
$<INSTALL_INTERFACE:${include_dest}/glass>)
install(TARGETS libglassnt EXPORT libglassnt DESTINATION "${main_lib_dest}")
install(DIRECTORY src/libnt/native/include/ DESTINATION "${include_dest}/glass")
#
# glass application
#
file(GLOB glass_src src/app/native/cpp/*.cpp)
add_executable(glass ${glass_src})
wpilib_link_macos_gui(glass)
target_link_libraries(glass libglassnt libglass)
if (WIN32)
set_target_properties(glass PROPERTIES WIN32_EXECUTABLE YES)
endif()
#if (MSVC OR FLAT_INSTALL_WPILIB)
# set (wpigui_config_dir ${wpilib_dest})
#else()
# set (wpigui_config_dir share/wpigui)
#endif()
#configure_file(wpigui-config.cmake.in ${CMAKE_BINARY_DIR}/wpigui-config.cmake )
#install(FILES ${CMAKE_BINARY_DIR}/wpigui-config.cmake DESTINATION ${wpigui_config_dir})
#install(EXPORT wpigui DESTINATION ${wpigui_config_dir})

30
glass/Info.plist Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Glass</string>
<key>CFBundleExecutable</key>
<string>glassApp</string>
<key>CFBundleDisplayName</key>
<string>Glass</string>
<key>CFBundleIdentifier</key>
<string>edu.wpi.first.tools.Glass</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>2021</string>
<key>CFBundleVersion</key>
<string>2021</string>
<key>LSMinimumSystemVersion</key>
<string>10.11</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

131
glass/build.gradle Normal file
View File

@@ -0,0 +1,131 @@
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
description = "A different kind of dashboard"
apply plugin: 'cpp'
apply plugin: 'c'
apply plugin: 'google-test-test-suite'
apply plugin: 'visual-studio'
apply plugin: 'edu.wpi.first.NativeUtils'
ext {
nativeName = 'glass'
}
apply from: "${rootDir}/shared/config.gradle"
project(':').libraryBuild.dependsOn build
nativeUtils.exportsConfigs {
glass {
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
}
}
model {
components {
"${nativeName}"(NativeLibrarySpec) {
sources {
cpp {
source {
srcDirs = ['src/lib/native/cpp']
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/lib/native/include'
}
}
}
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
}
if (it instanceof SharedLibraryBinarySpec) {
it.buildable = false
return
}
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
}
appendDebugPathToBinaries(binaries)
}
"${nativeName}nt"(NativeLibrarySpec) {
sources {
cpp {
source {
srcDirs = ['src/libnt/native/cpp']
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/libnt/native/include'
}
}
}
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
}
if (it instanceof SharedLibraryBinarySpec) {
it.buildable = false
return
}
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
}
appendDebugPathToBinaries(binaries)
}
// By default, a development executable will be generated. This is to help the case of
// testing specific functionality of the library.
"${nativeName}App"(NativeExecutableSpec) {
sources {
cpp {
source {
srcDirs 'src/app/native/cpp'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/app/native/include'
}
}
}
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 library: 'glassnt', linkage: 'static'
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpimath', library: 'wpimath', 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'
}

55
glass/publish.gradle Normal file
View File

@@ -0,0 +1,55 @@
apply plugin: 'maven-publish'
def baseArtifactId = 'Glass'
def artifactGroupId = 'edu.wpi.first.tools'
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_Glass_CLS'
model {
publishing {
def tasks = []
$.components.each { component ->
component.binaries.each { binary ->
if (binary in NativeExecutableBinarySpec && binary.application.name.contains("glassApp")) {
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
// Create the ZIP.
def outputsFolder = file("$project.buildDir/outputs")
def task = project.tasks.create("copyGlassExecutable", Zip) {
description("Copies the Glass executable to the outputs directory.")
destinationDir(outputsFolder)
archiveBaseName = '_M_' + zipBaseName
duplicatesStrategy = 'exclude'
classifier = binary.targetPlatform.name + binary.buildType.name
from(licenseFile) {
into '/'
}
from(applicationPath)
into(nativeUtils.getPlatformPath(binary))
}
task.dependsOn binary.tasks.link
tasks.add(task)
project.build.dependsOn task
project.artifacts { task }
addTaskToCopyAllOutputs(task)
}
}
}
}
publications {
cpp(MavenPublication) {
tasks.each { artifact it }
artifactId = baseArtifactId
groupId = artifactGroupId
version wpilibVersioning.version.get()
}
}
}
}

View File

@@ -0,0 +1,71 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "NetworkTablesSettings.h"
#include <utility>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ntcore_cpp.h>
#include <wpi/SmallVector.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include "glass/Context.h"
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
const char* storageName)
: m_inst{inst} {
auto& storage = glass::GetStorage(storageName);
m_pMode = storage.GetIntRef("mode");
m_pIniName = storage.GetStringRef("iniName", "networktables.ini");
m_pServerTeam = storage.GetStringRef("serverTeam");
m_pListenAddress = storage.GetStringRef("listenAddress");
}
void NetworkTablesSettings::Update() {
if (!m_restart) return;
m_restart = false;
nt::StopClient(m_inst);
nt::StopServer(m_inst);
nt::StopLocal(m_inst);
if (*m_pMode == 1) {
wpi::StringRef serverTeam{*m_pServerTeam};
unsigned int team;
if (!serverTeam.contains('.') && !serverTeam.getAsInteger(10, team)) {
nt::StartClientTeam(m_inst, team, NT_DEFAULT_PORT);
} else {
wpi::SmallVector<wpi::StringRef, 4> serverNames;
wpi::SmallVector<std::pair<wpi::StringRef, unsigned int>, 4> servers;
serverTeam.split(serverNames, ',', -1, false);
for (auto&& serverName : serverNames)
servers.emplace_back(serverName, NT_DEFAULT_PORT);
nt::StartClient(m_inst, servers);
}
} else if (*m_pMode == 2) {
nt::StartServer(m_inst, m_pIniName->c_str(), m_pListenAddress->c_str(),
NT_DEFAULT_PORT);
}
}
void NetworkTablesSettings::Display() {
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
ImGui::Combo("Mode", m_pMode, modeOptions, 3);
switch (*m_pMode) {
case 1:
ImGui::InputText("Team/IP", m_pServerTeam);
break;
case 2:
ImGui::InputText("Listen Address", m_pListenAddress);
ImGui::InputText("ini Filename", m_pIniName);
break;
default:
break;
}
if (ImGui::Button("Apply")) m_restart = true;
}

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <ntcore_cpp.h>
namespace wpi {
template <typename T>
class SmallVectorImpl;
} // namespace wpi
class NetworkTablesSettings {
public:
explicit NetworkTablesSettings(
NT_Inst inst = nt::GetDefaultInstance(),
const char* storageName = "NetworkTables Settings");
void Update();
void Display();
private:
NT_Inst m_inst;
bool m_restart = true;
int* m_pMode;
std::string* m_pIniName;
std::string* m_pServerTeam;
std::string* m_pListenAddress;
};

View File

@@ -0,0 +1,137 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include <memory>
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <ntcore_cpp.h>
#include <wpi/SmallString.h>
#include <wpigui.h>
#include "NetworkTablesSettings.h"
#include "glass/Context.h"
#include "glass/Model.h"
#include "glass/View.h"
#include "glass/networktables/NetworkTables.h"
#include "glass/networktables/NetworkTablesProvider.h"
#include "glass/other/Plot.h"
namespace gui = wpi::gui;
static std::unique_ptr<glass::PlotProvider> gPlotProvider;
static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<NetworkTablesSettings> gNetworkTablesSettings;
static glass::Window* gNetworkTablesWindow;
static glass::Window* gNetworkTablesSettingsWindow;
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([poller] {
auto win = gui::GetSystemWindow();
if (!win) return;
bool timedOut;
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
if (event.connected) {
wpi::SmallString<64> title;
title = "Glass - Connected (";
title += event.conn.remote_ip;
title += ')';
glfwSetWindowTitle(win, title.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
}
}
});
// NetworkTables table window
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
gNetworkTablesWindow = gNtProvider->AddWindow(
"NetworkTables",
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
if (gNetworkTablesWindow) {
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
}
// NetworkTables settings window
gNetworkTablesSettings = std::make_unique<NetworkTablesSettings>();
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
"NetworkTables Settings", [] { gNetworkTablesSettings->Display(); });
if (gNetworkTablesSettingsWindow) {
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
gNetworkTablesSettingsWindow->DisableRenamePopup();
}
}
#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
#else
int main() {
#endif
gui::CreateContext();
glass::CreateContext();
gPlotProvider = std::make_unique<glass::PlotProvider>("Plot");
gNtProvider = std::make_unique<glass::NetworkTablesProvider>("NTProvider");
gui::AddInit([] { ImGui::GetIO().IniFilename = "glass.ini"; });
gPlotProvider->GlobalInit();
gui::AddInit([] { gPlotProvider->ResetTime(); });
gNtProvider->GlobalInit();
gui::AddInit(NtInitialize);
glass::AddStandardNetworkTablesViews(*gNtProvider);
gui::AddLateExecute([] {
ImGui::BeginMainMenuBar();
gui::EmitViewMenu();
if (ImGui::BeginMenu("NetworkTables")) {
if (gNetworkTablesSettingsWindow)
gNetworkTablesSettingsWindow->DisplayMenuItem("NetworkTables Settings");
if (gNetworkTablesWindow)
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
ImGui::Separator();
gNtProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Plot")) {
bool paused = gPlotProvider->IsPaused();
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
gPlotProvider->SetPaused(paused);
}
if (ImGui::MenuItem("Reset Plot Time")) gPlotProvider->ResetTime();
ImGui::Separator();
gPlotProvider->DisplayMenu();
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
});
gui::Initialize("Glass - DISCONNECTED", 1024, 768);
gui::Main();
gNetworkTablesModel.reset();
gNetworkTablesSettings.reset();
gNtProvider.reset();
gPlotProvider.reset();
glass::DestroyContext();
gui::DestroyContext();
}

View File

@@ -0,0 +1,422 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/Context.h"
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <wpi/StringRef.h>
#include <wpigui.h>
#include "glass/ContextInternal.h"
using namespace glass;
Context* glass::gContext;
static bool ConvertInt(Storage::Value* value) {
value->type = Storage::Value::kInt;
if (value->stringVal.empty()) {
return false;
} else {
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->intVal))
return false;
}
return true;
}
static bool ConvertInt64(Storage::Value* value) {
value->type = Storage::Value::kInt64;
if (value->stringVal.empty()) {
return false;
} else {
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->int64Val))
return false;
}
return true;
}
static bool ConvertBool(Storage::Value* value) {
value->type = Storage::Value::kBool;
if (value->stringVal.empty()) {
return false;
} else {
int val;
if (wpi::StringRef{value->stringVal}.getAsInteger(10, val)) {
return false;
}
value->boolVal = (val != 0);
}
return true;
}
static bool ConvertFloat(Storage::Value* value) {
value->type = Storage::Value::kFloat;
if (value->stringVal.empty()) {
return false;
} else {
if (std::sscanf(value->stringVal.c_str(), "%f", &value->floatVal) != 1)
return false;
}
return true;
}
static bool ConvertDouble(Storage::Value* value) {
value->type = Storage::Value::kDouble;
if (value->stringVal.empty()) {
return false;
} else {
if (std::sscanf(value->stringVal.c_str(), "%lf", &value->doubleVal) != 1)
return false;
}
return true;
}
static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
const char* name) {
auto ctx = static_cast<Context*>(handler->UserData);
auto& storage = ctx->storage[name];
if (!storage) storage = std::make_unique<Storage>();
return storage.get();
}
static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
void* entry, const char* line) {
auto storage = static_cast<Storage*>(entry);
auto [key, val] = wpi::StringRef{line}.split('=');
auto& keys = storage->GetKeys();
auto& values = storage->GetValues();
auto it = std::find(keys.begin(), keys.end(), key);
if (it == keys.end()) {
keys.emplace_back(key);
values.emplace_back(std::make_unique<Storage::Value>(val));
} else {
auto& value = *values[it - keys.begin()];
value.stringVal = val;
switch (value.type) {
case Storage::Value::kInt:
ConvertInt(&value);
break;
case Storage::Value::kInt64:
ConvertInt64(&value);
break;
case Storage::Value::kBool:
ConvertBool(&value);
break;
case Storage::Value::kFloat:
ConvertFloat(&value);
break;
case Storage::Value::kDouble:
ConvertDouble(&value);
break;
default:
break;
}
}
}
static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
auto ctx = static_cast<Context*>(handler->UserData);
// sort for output
std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
sorted.emplace_back(it);
}
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
return a->getKey() < b->getKey();
});
for (auto&& entryIt : sorted) {
auto& entry = *entryIt;
out_buf->append("[GlassStorage][");
out_buf->append(entry.first().begin(), entry.first().end());
out_buf->append("]\n");
auto& keys = entry.second->GetKeys();
auto& values = entry.second->GetValues();
for (size_t i = 0; i < keys.size(); ++i) {
out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
out_buf->append("=");
auto& value = *values[i];
switch (value.type) {
case Storage::Value::kInt:
out_buf->appendf("%d\n", value.intVal);
break;
case Storage::Value::kInt64:
out_buf->appendf("%" PRId64 "\n", value.int64Val);
break;
case Storage::Value::kBool:
out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
break;
case Storage::Value::kFloat:
out_buf->appendf("%f\n", value.floatVal);
break;
case Storage::Value::kDouble:
out_buf->appendf("%f\n", value.doubleVal);
break;
case Storage::Value::kNone:
case Storage::Value::kString:
out_buf->append(value.stringVal.data(),
value.stringVal.data() + value.stringVal.size());
out_buf->append("\n");
break;
}
}
out_buf->append("\n");
}
}
static void Initialize(Context* ctx) {
wpi::gui::AddInit([=] {
ImGuiSettingsHandler ini_handler;
ini_handler.TypeName = "GlassStorage";
ini_handler.TypeHash = ImHashStr("GlassStorage");
ini_handler.ReadOpenFn = GlassStorageReadOpen;
ini_handler.ReadLineFn = GlassStorageReadLine;
ini_handler.WriteAllFn = GlassStorageWriteAll;
ini_handler.UserData = ctx;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
ctx->sources.Initialize();
});
}
static void Shutdown(Context* ctx) {}
Context* glass::CreateContext() {
Context* ctx = new Context;
if (!gContext) SetCurrentContext(ctx);
Initialize(ctx);
return ctx;
}
void glass::DestroyContext(Context* ctx) {
if (!ctx) ctx = gContext;
Shutdown(ctx);
if (gContext == ctx) SetCurrentContext(nullptr);
delete ctx;
}
Context* glass::GetCurrentContext() { return gContext; }
void glass::SetCurrentContext(Context* ctx) { gContext = ctx; }
Storage::Value& Storage::GetValue(wpi::StringRef key) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>());
return *m_values.back();
} else {
return *m_values[it - m_keys.begin()];
}
}
#define DEFUN(CapsName, LowerName, CType) \
CType Storage::Get##CapsName(wpi::StringRef key, CType defaultVal) const { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) return defaultVal; \
Value& value = *m_values[it - m_keys.begin()]; \
if (value.type != Value::k##CapsName) { \
if (!Convert##CapsName(&value)) value.LowerName##Val = defaultVal; \
} \
return value.LowerName##Val; \
} \
\
void Storage::Set##CapsName(wpi::StringRef key, CType val) { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) { \
m_keys.emplace_back(key); \
m_values.emplace_back(std::make_unique<Value>()); \
m_values.back()->type = Value::k##CapsName; \
m_values.back()->LowerName##Val = val; \
} else { \
Value& value = *m_values[it - m_keys.begin()]; \
value.type = Value::k##CapsName; \
value.LowerName##Val = val; \
} \
} \
\
CType* Storage::Get##CapsName##Ref(wpi::StringRef key, CType defaultVal) { \
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
if (it == m_keys.end()) { \
m_keys.emplace_back(key); \
m_values.emplace_back(std::make_unique<Value>()); \
m_values.back()->type = Value::k##CapsName; \
m_values.back()->LowerName##Val = defaultVal; \
return &m_values.back()->LowerName##Val; \
} else { \
Value& value = *m_values[it - m_keys.begin()]; \
if (value.type != Value::k##CapsName) { \
if (!Convert##CapsName(&value)) value.LowerName##Val = defaultVal; \
} \
return &value.LowerName##Val; \
} \
}
DEFUN(Int, int, int)
DEFUN(Int64, int64, int64_t)
DEFUN(Bool, bool, bool)
DEFUN(Float, float, float)
DEFUN(Double, double, double)
std::string Storage::GetString(wpi::StringRef key,
const std::string& defaultVal) const {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) return defaultVal;
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return value.stringVal;
}
void Storage::SetString(wpi::StringRef key, const wpi::Twine& val) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>(val));
m_values.back()->type = Value::kString;
} else {
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
value.stringVal = val.str();
}
}
std::string* Storage::GetStringRef(wpi::StringRef key,
wpi::StringRef defaultVal) {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
m_keys.emplace_back(key);
m_values.emplace_back(std::make_unique<Value>(defaultVal));
m_values.back()->type = Value::kString;
return &m_values.back()->stringVal;
} else {
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return &value.stringVal;
}
}
Storage& glass::GetStorage() {
auto& storage = gContext->storage[gContext->curId];
if (!storage) storage = std::make_unique<Storage>();
return *storage;
}
Storage& glass::GetStorage(wpi::StringRef id) {
auto& storage = gContext->storage[id];
if (!storage) storage = std::make_unique<Storage>();
return *storage;
}
static void PushIDStack(wpi::StringRef label_id) {
gContext->idStack.emplace_back(gContext->curId.size());
auto [label, id] = wpi::StringRef{label_id}.split("###");
// if no ###id, use label as id
if (id.empty()) id = label;
if (!gContext->curId.empty()) gContext->curId += "###";
gContext->curId += id;
}
static void PopIDStack() {
gContext->curId.resize(gContext->idStack.back());
gContext->idStack.pop_back();
}
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
PushIDStack(name);
return ImGui::Begin(name, p_open, flags);
}
void glass::End() {
ImGui::End();
PopIDStack();
}
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
ImGuiWindowFlags flags) {
PushIDStack(str_id);
return ImGui::BeginChild(str_id, size, border, flags);
}
void glass::EndChild() {
ImGui::EndChild();
PopIDStack();
}
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
wpi::SmallString<64> openKey;
auto [name, id] = wpi::StringRef{label}.split("###");
// if no ###id, use name as id
if (id.empty()) id = name;
openKey = id;
openKey += "###open";
bool* open = GetStorage().GetBoolRef(openKey);
*open = ImGui::CollapsingHeader(
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
return *open;
}
bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
PushIDStack(label);
bool* open = GetStorage().GetBoolRef("open");
*open = ImGui::TreeNodeEx(
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
if (!*open) PopIDStack();
return *open;
}
void glass::TreePop() {
ImGui::TreePop();
PopIDStack();
}
void glass::PushID(const char* str_id) {
PushIDStack(str_id);
ImGui::PushID(str_id);
}
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
PushIDStack(wpi::StringRef(str_id_begin, str_id_end - str_id_begin));
ImGui::PushID(str_id_begin, str_id_end);
}
void glass::PushID(int int_id) {
char buf[16];
std::snprintf(buf, sizeof(buf), "%d", int_id);
PushIDStack(buf);
ImGui::PushID(int_id);
}
void glass::PopID() {
ImGui::PopID();
PopIDStack();
}
bool glass::PopupEditName(const char* label, std::string* name) {
bool rv = false;
if (ImGui::BeginPopupContextItem(label)) {
ImGui::Text("Edit name:");
if (ImGui::InputText("##editname", name)) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}

View File

@@ -0,0 +1,142 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/DataSource.h"
#include "glass/ContextInternal.h"
using namespace glass;
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
DataSource::DataSource(const wpi::Twine& id) : m_id{id.str()} {
auto it = gContext->sources.try_emplace(m_id, this);
auto& srcName = it.first->getValue();
m_name = srcName.name.get();
if (!srcName.source) srcName.source = this;
sourceCreated(m_id.c_str(), this);
}
DataSource::DataSource(const wpi::Twine& id, int index)
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(']')} {}
DataSource::DataSource(const wpi::Twine& id, int index, int index2)
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(',') +
wpi::Twine(index2) + wpi::Twine(']')} {}
DataSource::~DataSource() {
if (!gContext) return;
auto it = gContext->sources.find(m_id);
if (it == gContext->sources.end()) return;
auto& srcName = it->getValue();
if (srcName.source == this) srcName.source = nullptr;
}
void DataSource::SetName(const wpi::Twine& name) { m_name->SetName(name); }
const char* DataSource::GetName() const { return m_name->GetName(); }
void DataSource::PushEditNameId(int index) { m_name->PushEditNameId(index); }
void DataSource::PushEditNameId(const char* name) {
m_name->PushEditNameId(name);
}
bool DataSource::PopupEditName(int index) {
return m_name->PopupEditName(index);
}
bool DataSource::PopupEditName(const char* name) {
return m_name->PopupEditName(name);
}
bool DataSource::InputTextName(const char* label_id,
ImGuiInputTextFlags flags) {
return m_name->InputTextName(label_id, flags);
}
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
va_list args;
va_start(args, fmt);
LabelTextV(label, fmt, args);
va_end(args);
}
// Add a label+text combo aligned to other label+value widgets
void DataSource::LabelTextV(const char* label, const char* fmt,
va_list args) const {
ImGui::PushID(label);
ImGui::LabelTextV("##input", fmt, args);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
ImGui::PopID();
EmitDrag();
}
bool DataSource::Combo(const char* label, int* current_item,
const char* const items[], int items_count,
int popup_max_height_in_items) const {
ImGui::PushID(label);
bool rv = ImGui::Combo("##input", current_item, items, items_count,
popup_max_height_in_items);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::SliderFloat(const char* label, float* v, float v_min,
float v_max, const char* format,
float power) const {
ImGui::PushID(label);
bool rv = ImGui::SliderFloat("##input", v, v_min, v_max, format, power);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::InputDouble(const char* label, double* v, double step,
double step_fast, const char* format,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputDouble("##input", v, step, step_fast, format, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
bool DataSource::InputInt(const char* label, int* v, int step, int step_fast,
ImGuiInputTextFlags flags) const {
ImGui::PushID(label);
bool rv = ImGui::InputInt("##input", v, step, step_fast, flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
EmitDrag();
ImGui::PopID();
return rv;
}
void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
if (ImGui::BeginDragDropSource(flags)) {
auto self = this;
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self));
const char* name = GetName();
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
ImGui::EndDragDropSource();
}
}
DataSource* DataSource::Find(wpi::StringRef id) {
auto it = gContext->sources.find(id);
if (it == gContext->sources.end()) return nullptr;
return it->getValue().source;
}

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/MainMenuBar.h"
#include <cstdio>
#include <wpigui.h>
using namespace glass;
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
if (menu) m_menus.emplace_back(std::move(menu));
}
void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
if (menu) m_optionMenus.emplace_back(std::move(menu));
}
void MainMenuBar::Display() {
ImGui::BeginMainMenuBar();
if (!m_optionMenus.empty()) {
if (ImGui::BeginMenu("Options")) {
for (auto&& menu : m_optionMenus) {
if (menu) menu();
}
ImGui::EndMenu();
}
}
wpi::gui::EmitViewMenu();
for (auto&& menu : m_menus) {
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();
}

View File

@@ -5,13 +5,8 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "glass/Model.h"
namespace halsimgui {
using namespace glass;
class PlotGui {
public:
static void Initialize();
};
} // namespace halsimgui
bool Model::IsReadOnly() { return false; }

View File

@@ -0,0 +1,30 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/View.h"
using namespace glass;
namespace {
class FunctionView : public View {
public:
explicit FunctionView(wpi::unique_function<void()> display)
: m_display(std::move(display)) {}
void Display() override { m_display(); }
private:
wpi::unique_function<void()> m_display;
};
} // namespace
std::unique_ptr<View> glass::MakeFunctionView(
wpi::unique_function<void()> display) {
return std::make_unique<FunctionView>(std::move(display));
}
void View::Hidden() {}

View File

@@ -0,0 +1,102 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/Window.h"
#include <imgui_internal.h>
#include <wpi/StringRef.h>
#include "glass/Context.h"
using namespace glass;
void Window::SetVisibility(Visibility visibility) {
switch (visibility) {
case kHide:
m_visible = false;
m_enabled = true;
break;
case kShow:
m_visible = true;
m_enabled = true;
break;
case kDisabled:
m_enabled = false;
break;
}
}
void Window::Display() {
if (!m_view) return;
if (!m_visible || !m_enabled) {
PushID(m_id);
m_view->Hidden();
PopID();
return;
}
if (m_posCond != 0) ImGui::SetNextWindowPos(m_pos, m_posCond);
if (m_sizeCond != 0) ImGui::SetNextWindowSize(m_size, m_sizeCond);
if (m_setPadding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, m_padding);
char label[128];
std::snprintf(label, sizeof(label), "%s###%s",
m_name.empty() ? m_id.c_str() : m_name.c_str(), m_id.c_str());
if (Begin(label, &m_visible, m_flags)) {
if (m_renamePopupEnabled) PopupEditName(nullptr, &m_name);
m_view->Display();
} else {
m_view->Hidden();
}
End();
if (m_setPadding) ImGui::PopStyleVar();
}
bool Window::DisplayMenuItem(const char* label) {
bool wasVisible = m_visible;
ImGui::MenuItem(
label ? label : (m_name.empty() ? m_id.c_str() : m_name.c_str()), nullptr,
&m_visible, m_enabled);
return !wasVisible && m_visible;
}
void Window::ScaleDefault(float scale) {
if ((m_posCond & ImGuiCond_FirstUseEver) != 0) {
m_pos.x *= scale;
m_pos.y *= scale;
}
if ((m_sizeCond & ImGuiCond_FirstUseEver) != 0) {
m_size.x *= scale;
m_size.y *= scale;
}
}
void Window::IniReadLine(const char* lineStr) {
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
if (name == "name") {
m_name = value;
} else if (name == "visible") {
int num;
if (value.getAsInteger(10, num)) return;
m_visible = num;
} else if (name == "enabled") {
int num;
if (value.getAsInteger(10, num)) return;
m_enabled = num;
}
}
void Window::IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf) {
out_buf->appendf("[%s][%s]\nname=%s\nvisible=%d\nenabled=%d\n\n", typeName,
m_id.c_str(), m_name.c_str(), m_visible ? 1 : 0,
m_enabled ? 1 : 0);
}

View File

@@ -0,0 +1,107 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/WindowManager.h"
#include <algorithm>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
using namespace glass;
WindowManager::WindowManager(const wpi::Twine& iniName)
: m_iniSaver{iniName, this} {}
// read/write open state to ini file
void* WindowManager::IniSaver::IniReadOpen(const char* name) {
return m_manager->GetOrAddWindow(name, true);
}
void WindowManager::IniSaver::IniReadLine(void* entry, const char* lineStr) {
static_cast<Window*>(entry)->IniReadLine(lineStr);
}
void WindowManager::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
const char* typeName = GetTypeName();
for (auto&& window : m_manager->m_windows) {
window->IniWriteAll(typeName, out_buf);
}
}
Window* WindowManager::AddWindow(wpi::StringRef id,
wpi::unique_function<void()> display) {
auto win = GetOrAddWindow(id, false);
if (!win) return nullptr;
if (win->HasView()) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
win->SetView(MakeFunctionView(std::move(display)));
return win;
}
Window* WindowManager::AddWindow(wpi::StringRef id,
std::unique_ptr<View> view) {
auto win = GetOrAddWindow(id, false);
if (!win) return nullptr;
if (win->HasView()) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
win->SetView(std::move(view));
return win;
}
Window* WindowManager::GetOrAddWindow(wpi::StringRef id, bool duplicateOk) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
if (it != m_windows.end() && (*it)->GetId() == id) {
if (!duplicateOk) {
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
return nullptr;
}
return it->get();
}
// insert before (keeps sort)
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
}
Window* WindowManager::GetWindow(wpi::StringRef id) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
if (it == m_windows.end() || (*it)->GetId() != id) return nullptr;
return it->get();
}
void WindowManager::GlobalInit() {
wpi::gui::AddInit([this] { m_iniSaver.Initialize(); });
wpi::gui::AddWindowScaler([this](float scale) {
// scale default window positions
for (auto&& window : m_windows) {
window->ScaleDefault(scale);
}
});
wpi::gui::AddLateExecute([this] { DisplayWindows(); });
}
void WindowManager::DisplayMenu() {
for (auto&& window : m_windows) {
window->DisplayMenuItem();
}
}
void WindowManager::DisplayWindows() {
for (auto&& window : m_windows) {
window->Display();
}
}

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Accelerometer.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAccelerometerDevice(AccelerometerModel* model) {
if (!model->Exists()) return;
if (BeginDevice("BuiltInAccel")) {
// Range
{
int value = model->GetRange();
static const char* rangeOptions[] = {"2G", "4G", "8G"};
DeviceEnum("Range", true, &value, rangeOptions, 3);
}
// X Accel
if (auto xData = model->GetXData()) {
double value = xData->GetValue();
if (DeviceDouble("X Accel", false, &value, xData)) {
model->SetX(value);
}
}
// Y Accel
if (auto yData = model->GetYData()) {
double value = yData->GetValue();
if (DeviceDouble("Y Accel", false, &value, yData)) {
model->SetY(value);
}
}
// Z Accel
if (auto zData = model->GetZData()) {
double value = zData->GetValue();
if (DeviceDouble("Z Accel", false, &value, zData)) {
model->SetZ(value);
}
}
EndDevice();
}
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogGyro.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogGyroDevice(AnalogGyroModel* model, int index) {
char name[32];
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", index);
if (BeginDevice(name)) {
// angle
if (auto angleData = model->GetAngleData()) {
double value = angleData->GetValue();
if (DeviceDouble("Angle", false, &value, angleData)) {
model->SetAngle(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (DeviceDouble("Rate", false, &value, rateData)) {
model->SetRate(value);
}
}
EndDevice();
}
}
void glass::DisplayAnalogGyrosDevice(AnalogGyrosModel* model) {
model->ForEachAnalogGyro(
[&](AnalogGyroModel& gyro, int i) { DisplayAnalogGyroDevice(&gyro, i); });
}

View File

@@ -0,0 +1,66 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogInput.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
auto voltageData = model->GetVoltageData();
if (!voltageData) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "In[%d]###name", index);
}
if (model->IsGyro()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "AnalogGyro[%d]", index);
ImGui::PopStyleColor();
} else if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "%s", simDevice);
ImGui::PopStyleColor();
} else {
float val = voltageData->GetValue();
if (voltageData->SliderFloat(label, &val, 0.0, 5.0)) model->SetVoltage(val);
}
// context menu to change name
if (PopupEditName("name", name)) voltageData->SetName(name->c_str());
}
void glass::DisplayAnalogInputs(AnalogInputsModel* model,
wpi::StringRef noneMsg) {
ImGui::Text("(Use Ctrl+Click to edit value)");
bool hasAny = false;
bool first = true;
model->ForEachAnalogInput([&](AnalogInputModel& input, int i) {
if (!first) {
ImGui::Spacing();
ImGui::Spacing();
} else {
first = false;
}
PushID(i);
DisplayAnalogInput(&input, i);
PopID();
hasAny = true;
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,47 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/AnalogOutput.h"
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
int count = 0;
model->ForEachAnalogOutput([&](auto&, int) { ++count; });
if (count == 0) return;
if (BeginDevice("Analog Outputs")) {
model->ForEachAnalogOutput([&](auto& analogOut, int i) {
auto analogOutData = analogOut.GetVoltageData();
if (!analogOutData) return;
PushID(i);
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
} else {
std::snprintf(label, sizeof(label), "Out[%d]###name", i);
}
double value = analogOutData->GetValue();
DeviceDouble(label, true, &value, analogOutData);
if (PopupEditName("name", name)) {
if (analogOutData) analogOutData->SetName(name->c_str());
}
PopID();
});
EndDevice();
}
}

View File

@@ -0,0 +1,118 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/DIO.h"
#include <imgui.h>
#include "glass/DataSource.h"
#include "glass/hardware/Encoder.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static void LabelSimDevice(const char* name, const char* simDeviceName) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(name, "%s", simDeviceName);
ImGui::PopStyleColor();
}
void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
auto dpwm = model->GetDPWM();
auto dutyCycle = model->GetDutyCycle();
auto encoder = model->GetEncoder();
auto dioData = model->GetValueData();
auto dpwmData = dpwm ? dpwm->GetValueData() : nullptr;
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
bool exists = model->Exists();
auto& info = dioData->GetNameInfo();
char label[128];
if (exists && dpwmData) {
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
if (auto simDevice = dpwm->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
dpwmData->LabelText(label, "%0.3f", dpwmData->GetValue());
}
} else if (exists && encoder) {
info.GetLabel(label, sizeof(label), " In", index);
if (auto simDevice = encoder->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "Encoder[%d,%d]", encoder->GetChannelA(),
encoder->GetChannelB());
ImGui::PopStyleColor();
}
} else if (exists && dutyCycleData) {
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
if (auto simDevice = dutyCycle->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
double val = dutyCycleData->GetValue();
if (dutyCycleData->InputDouble(label, &val)) {
dutyCycle->SetValue(val);
}
}
} else {
const char* name = model->GetName();
if (name[0] != '\0')
info.GetLabel(label, sizeof(label), name);
else
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
index);
if (auto simDevice = model->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
if (!exists) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
dioData->LabelText(label, "unknown");
ImGui::PopStyleColor();
} else if (model->IsReadOnly()) {
dioData->LabelText(
label, "%s",
outputsEnabled ? (dioData->GetValue() != 0 ? "1 (high)" : "0 (low)")
: "1 (disabled)");
} else {
static const char* options[] = {"0 (low)", "1 (high)"};
int val = dioData->GetValue() != 0 ? 1 : 0;
if (dioData->Combo(label, &val, options, 2)) {
model->SetValue(val);
}
}
}
}
if (info.PopupEditName(index)) {
if (dpwmData) dpwmData->SetName(info.GetName());
if (dutyCycleData) dutyCycleData->SetName(info.GetName());
}
}
void glass::DisplayDIO(DIOModel* model, int index, bool outputsEnabled) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
DisplayDIOImpl(model, index, outputsEnabled);
ImGui::PopItemWidth();
}
void glass::DisplayDIOs(DIOsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
model->ForEachDIO([&](DIOModel& dio, int i) {
hasAny = true;
ImGui::PushID(i);
DisplayDIOImpl(&dio, i, outputsEnabled);
ImGui::PopID();
});
ImGui::PopItemWidth();
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,165 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Encoder.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void EncoderModel::SetName(const wpi::Twine& name) {
if (name.str().empty()) {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName("");
}
if (auto count = GetCountData()) {
count->SetName("");
}
if (auto period = GetPeriodData()) {
period->SetName("");
}
if (auto direction = GetDirectionData()) {
direction->SetName("");
}
if (auto distance = GetDistanceData()) {
distance->SetName("");
}
if (auto rate = GetRateData()) {
rate->SetName("");
}
} else {
if (auto distancePerPulse = GetDistancePerPulseData()) {
distancePerPulse->SetName(name + " Distance/Count");
}
if (auto count = GetCountData()) {
count->SetName(name + " Count");
}
if (auto period = GetPeriodData()) {
period->SetName(name + " Period");
}
if (auto direction = GetDirectionData()) {
direction->SetName(name + " Direction");
}
if (auto distance = GetDistanceData()) {
distance->SetName(name + " Distance");
}
if (auto rate = GetRateData()) {
rate->SetName(name + " Rate");
}
}
}
void glass::DisplayEncoder(EncoderModel* model) {
if (auto simDevice = model->GetSimDevice()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::TextUnformatted(simDevice);
ImGui::PopStyleColor();
return;
}
int chA = model->GetChannelA();
int chB = model->GetChannelB();
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
chB);
} else {
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
}
// header
bool open = CollapsingHeader(label);
// context menu to change name
if (PopupEditName("name", name)) {
model->SetName(name->c_str());
}
if (!open) return;
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
// distance per pulse
if (auto distancePerPulseData = model->GetDistancePerPulseData()) {
double value = distancePerPulseData->GetValue();
distancePerPulseData->LabelText("Dist/Count", "%.6f", value);
}
// count
if (auto countData = model->GetCountData()) {
int value = countData->GetValue();
if (ImGui::InputInt("##input", &value)) model->SetCount(value);
ImGui::SameLine();
if (ImGui::Button("Reset")) {
model->SetCount(0);
}
ImGui::SameLine();
ImGui::Selectable("Count");
countData->EmitDrag();
}
// max period
{
double maxPeriod = model->GetMaxPeriod();
ImGui::LabelText("Max Period", "%.6f", maxPeriod);
}
// period
if (auto periodData = model->GetPeriodData()) {
double value = periodData->GetValue();
if (periodData->InputDouble("Period", &value, 0, 0, "%.6g")) {
model->SetPeriod(value);
}
}
// reverse direction
ImGui::LabelText("Reverse Direction", "%s",
model->GetReverseDirection() ? "true" : "false");
// direction
if (auto directionData = model->GetDirectionData()) {
static const char* options[] = {"reverse", "forward"};
int value = directionData->GetValue() ? 1 : 0;
if (directionData->Combo("Direction", &value, options, 2)) {
model->SetDirection(value != 0);
}
}
// distance
if (auto distanceData = model->GetDistanceData()) {
double value = distanceData->GetValue();
if (distanceData->InputDouble("Distance", &value, 0, 0, "%.6g")) {
model->SetDistance(value);
}
}
// rate
if (auto rateData = model->GetRateData()) {
double value = rateData->GetValue();
if (rateData->InputDouble("Rate", &value, 0, 0, "%.6g")) {
model->SetRate(value);
}
}
ImGui::PopItemWidth();
}
void glass::DisplayEncoders(EncodersModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachEncoder([&](EncoderModel& encoder, int i) {
hasAny = true;
PushID(i);
DisplayEncoder(&encoder);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,91 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/LEDDisplay.h"
#include "glass/Context.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
namespace {
struct IndicatorData {
std::vector<int> values;
std::vector<ImU32> colors;
};
} // namespace
void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
wpi::SmallVector<LEDDisplayModel::Data, 64> dataBuf;
auto data = model->GetData(dataBuf);
int length = data.size();
bool running = model->IsRunning();
auto& storage = GetStorage();
int* numColumns = storage.GetIntRef("columns", 10);
bool* serpentine = storage.GetBoolRef("serpentine", false);
int* order = storage.GetIntRef("order", LEDConfig::RowMajor);
int* start = storage.GetIntRef("start", LEDConfig::UpperLeft);
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
ImGui::Combo("Order", order, options, 2);
}
{
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
"Lower Right"};
ImGui::Combo("Start", start, options, 4);
}
ImGui::Checkbox("Serpentine", serpentine);
if (*numColumns < 1) *numColumns = 1;
ImGui::PopItemWidth();
// show as LED indicators
auto iData = storage.GetData<IndicatorData>();
if (!iData) {
storage.SetData(std::make_shared<IndicatorData>());
iData = storage.GetData<IndicatorData>();
}
if (length > static_cast<int>(iData->values.size()))
iData->values.resize(length);
if (length > static_cast<int>(iData->colors.size()))
iData->colors.resize(length);
if (!running) {
iData->colors[0] = IM_COL32(128, 128, 128, 255);
for (int j = 0; j < length; ++j) iData->values[j] = -1;
} else {
for (int j = 0; j < length; ++j) {
iData->values[j] = j + 1;
iData->colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
}
}
LEDConfig config;
config.serpentine = *serpentine;
config.order = static_cast<LEDConfig::Order>(*order);
config.start = static_cast<LEDConfig::Start>(*start);
DrawLEDs(iData->values.data(), length, *numColumns, iData->colors.data(), 0,
0, config);
}
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
bool hasAny = false;
model->ForEachLEDDisplay([&](LEDDisplayModel& display, int i) {
hasAny = true;
if (model->GetNumLEDDisplays() > 1) ImGui::Text("LEDs[%d]", i);
PushID(i);
DisplayLEDDisplay(&display, i);
PopID();
});
if (!hasAny) ImGui::Text("No addressable LEDs");
}

View File

@@ -0,0 +1,150 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PCM.h"
#include <cstdio>
#include <cstring>
#include <imgui.h>
#include <wpi/SmallVector.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/other/DeviceTree.h"
#include "glass/support/ExtraGuiWidgets.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
bool outputsEnabled) {
wpi::SmallVector<int, 16> channels;
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
if (j >= static_cast<int>(channels.size())) channels.resize(j + 1);
channels[j] = (outputsEnabled && data->GetValue()) ? 1 : -1;
}
});
if (channels.empty()) return false;
// show nonexistent channels as empty
for (auto&& ch : channels) {
if (ch == 0) ch = -2;
}
// build header label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
}
// header
bool open = CollapsingHeader(label);
PopupEditName("name", name);
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
// show channels as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(128, 128, 128, 255)};
DrawLEDs(channels.data(), channels.size(), channels.size(), colors);
if (open) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
if (auto data = solenoid.GetOutputData()) {
PushID(j);
char solenoidName[64];
auto& info = data->GetNameInfo();
info.GetLabel(solenoidName, sizeof(solenoidName), "Solenoid", j);
data->LabelText(solenoidName, "%s", channels[j] == 1 ? "On" : "Off");
info.PopupEditName(j);
PopID();
}
});
ImGui::PopItemWidth();
}
return true;
}
void glass::DisplayPCMsSolenoids(PCMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPCM([&](PCMModel& pcm, int i) {
PushID(i);
if (DisplayPCMSolenoids(&pcm, i, outputsEnabled)) hasAny = true;
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}
void glass::DisplayCompressorDevice(PCMModel* model, int index,
bool outputsEnabled) {
auto compressor = model->GetCompressor();
if (!compressor || !compressor->Exists()) return;
DisplayCompressorDevice(compressor, index, outputsEnabled);
}
void glass::DisplayCompressorDevice(CompressorModel* model, int index,
bool outputsEnabled) {
char name[32];
std::snprintf(name, sizeof(name), "Compressor[%d]", index);
if (BeginDevice(name)) {
// output enabled
if (auto runningData = model->GetRunningData()) {
bool value = outputsEnabled && runningData->GetValue();
if (DeviceBoolean("Running", false, &value, runningData)) {
model->SetRunning(value);
}
}
// closed loop enabled
if (auto enabledData = model->GetEnabledData()) {
int value = enabledData->GetValue() ? 1 : 0;
static const char* enabledOptions[] = {"disabled", "enabled"};
if (DeviceEnum("Closed Loop", true, &value, enabledOptions, 2,
enabledData)) {
model->SetEnabled(value != 0);
}
}
// pressure switch
if (auto pressureSwitchData = model->GetPressureSwitchData()) {
int value = pressureSwitchData->GetValue() ? 1 : 0;
static const char* switchOptions[] = {"full", "low"};
if (DeviceEnum("Pressure", false, &value, switchOptions, 2,
pressureSwitchData)) {
model->SetPressureSwitch(value != 0);
}
}
// compressor current
if (auto currentData = model->GetCurrentData()) {
double value = currentData->GetValue();
if (DeviceDouble("Current (A)", false, &value, currentData)) {
model->SetCurrent(value);
}
}
EndDevice();
}
}
void glass::DisplayCompressorsDevice(PCMsModel* model, bool outputsEnabled) {
model->ForEachPCM([&](PCMModel& pcm, int i) {
DisplayCompressorDevice(&pcm, i, outputsEnabled);
});
}

View File

@@ -0,0 +1,92 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PDP.h"
#include <algorithm>
#include <cstdio>
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/IniSaverInfo.h"
using namespace glass;
static float DisplayChannel(PDPModel& pdp, int channel) {
float width = 0;
if (auto currentData = pdp.GetCurrentData(channel)) {
ImGui::PushID(channel);
auto& leftInfo = currentData->GetNameInfo();
char name[64];
leftInfo.GetLabel(name, sizeof(name), "", channel);
double val = currentData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (currentData->InputDouble(name, &val, 0, 0, "%.3f"))
pdp.SetCurrent(channel, val);
width = ImGui::GetItemRectSize().x;
leftInfo.PopupEditName(channel);
ImGui::PopID();
}
return width;
}
void glass::DisplayPDP(PDPModel* model, int index) {
char name[128];
std::snprintf(name, sizeof(name), "PDP[%d]", index);
if (CollapsingHeader(name)) {
// temperature
if (auto tempData = model->GetTemperatureData()) {
double value = tempData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (tempData->InputDouble("Temp", &value, 0, 0, "%.3f")) {
model->SetTemperature(value);
}
}
// voltage
if (auto voltageData = model->GetVoltageData()) {
double value = voltageData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (voltageData->InputDouble("Voltage", &value, 0, 0, "%.3f")) {
model->SetVoltage(value);
}
}
// channel currents; show as two columns laid out like PDP
const int numChannels = model->GetNumChannels();
ImGui::Text("Channel Current (A)");
ImGui::Columns(2, "channels", false);
float maxWidth = ImGui::GetFontSize() * 13;
for (int left = 0, right = numChannels - 1; left < right; ++left, --right) {
float leftWidth = DisplayChannel(*model, left);
ImGui::NextColumn();
float rightWidth = DisplayChannel(*model, right);
ImGui::NextColumn();
float width =
(std::max)(leftWidth, rightWidth) * 2 + ImGui::GetFontSize() * 4;
if (width > maxWidth) maxWidth = width;
}
ImGui::Columns(1);
ImGui::Dummy(ImVec2(maxWidth, 0));
}
}
void glass::DisplayPDPs(PDPsModel* model, wpi::StringRef noneMsg) {
bool hasAny = false;
model->ForEachPDP([&](PDPModel& pdp, int i) {
hasAny = true;
PushID(i);
DisplayPDP(&pdp, i);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/PWM.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
auto data = model->GetSpeedData();
if (!data) return;
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
} else {
std::snprintf(label, sizeof(label), "PWM[%d]###name", index);
}
int led = model->GetAddressableLED();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (led >= 0) {
ImGui::LabelText(label, "LED[%d]", led);
} else {
float val = outputsEnabled ? data->GetValue() : 0;
data->LabelText(label, "%0.3f", val);
}
if (PopupEditName("name", name)) {
data->SetName(name->c_str());
}
}
void glass::DisplayPWMs(PWMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachPWM([&](PWMModel& pwm, int i) {
hasAny = true;
PushID(i);
if (!first)
ImGui::Separator();
else
first = false;
DisplayPWM(&pwm, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,74 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/Relay.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
auto forwardData = model->GetForwardData();
auto reverseData = model->GetReverseData();
if (!forwardData && !reverseData) {
return;
}
bool forward = false;
bool reverse = false;
if (outputsEnabled) {
if (forwardData) forward = forwardData->GetValue();
if (reverseData) reverse = reverseData->GetValue();
}
std::string* name = GetStorage().GetStringRef("name");
ImGui::PushID("name");
if (!name->empty())
ImGui::Text("%s [%d]", name->c_str(), index);
else
ImGui::Text("Relay[%d]", index);
ImGui::PopID();
if (PopupEditName("name", name)) {
if (forwardData) forwardData->SetName(name->c_str());
if (reverseData) reverseData->SetName(name->c_str());
}
ImGui::SameLine();
// show forward and reverse as LED indicators
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
IM_COL32(255, 0, 0, 255),
IM_COL32(128, 128, 128, 255)};
int values[2] = {reverseData ? (reverse ? 2 : -2) : -3,
forwardData ? (forward ? 1 : -1) : -3};
DataSource* sources[2] = {reverseData, forwardData};
DrawLEDSources(values, sources, 2, 2, colors);
}
void glass::DisplayRelays(RelaysModel* model, bool outputsEnabled,
wpi::StringRef noneMsg) {
bool hasAny = false;
bool first = true;
model->ForEachRelay([&](RelayModel& relay, int i) {
hasAny = true;
if (!first)
ImGui::Separator();
else
first = false;
PushID(i);
DisplayRelay(&relay, i, outputsEnabled);
PopID();
});
if (!hasAny && !noneMsg.empty())
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
}

View File

@@ -0,0 +1,87 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/hardware/RoboRio.h"
#include <imgui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
static void DisplayRail(RoboRioRailModel& rail, const char* name) {
if (CollapsingHeader(name)) {
ImGui::PushID(name);
if (auto data = rail.GetVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
rail.SetVoltage(val);
}
}
if (auto data = rail.GetCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
rail.SetCurrent(val);
}
}
if (auto data = rail.GetActiveData()) {
static const char* options[] = {"inactive", "active"};
int val = data->GetValue() ? 1 : 0;
if (data->Combo("Active", &val, options, 2)) {
rail.SetActive(val);
}
}
if (auto data = rail.GetFaultsData()) {
int val = data->GetValue();
if (data->InputInt("Faults", &val)) {
rail.SetFaults(val);
}
}
ImGui::PopID();
}
}
void glass::DisplayRoboRio(RoboRioModel* model) {
ImGui::Button("User Button");
model->SetUserButton(ImGui::IsItemActive());
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
if (CollapsingHeader("RoboRIO Input")) {
ImGui::PushID("RoboRIO Input");
if (auto data = model->GetVInVoltageData()) {
double val = data->GetValue();
if (data->InputDouble("Voltage (V)", &val)) {
model->SetVInVoltage(val);
}
}
if (auto data = model->GetVInCurrentData()) {
double val = data->GetValue();
if (data->InputDouble("Current (A)", &val)) {
model->SetVInCurrent(val);
}
}
ImGui::PopID();
}
if (auto rail = model->GetUser6VRail()) {
DisplayRail(*rail, "6V Rail");
}
if (auto rail = model->GetUser5VRail()) {
DisplayRail(*rail, "5V Rail");
}
if (auto rail = model->GetUser3V3Rail()) {
DisplayRail(*rail, "3.3V Rail");
}
ImGui::PopItemWidth();
}

View File

@@ -0,0 +1,161 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/DeviceTree.h"
#include <cinttypes>
#include <imgui.h>
#include "glass/Context.h"
#include "glass/ContextInternal.h"
#include "glass/DataSource.h"
using namespace glass;
void DeviceTreeModel::Update() {
for (auto&& display : m_displays) {
if (display.first) display.first->Update();
}
}
bool DeviceTreeModel::Exists() {
for (auto&& display : m_displays) {
if (display.first && display.first->Exists()) return true;
}
return false;
}
void DeviceTreeModel::Display() {
for (auto&& display : m_displays) {
if (display.second) display.second(display.first);
}
}
void glass::HideDevice(const char* id) { gContext->deviceHidden[id] = true; }
bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
if (gContext->deviceHidden[id]) return false;
PushID(id);
// build label
std::string* name = GetStorage().GetStringRef("name");
char label[128];
std::snprintf(label, sizeof(label), "%s###name",
name->empty() ? id : name->c_str());
bool open = CollapsingHeader(label, flags);
PopupEditName("name", name);
if (!open) PopID();
return open;
}
void glass::EndDevice() { PopID(); }
static bool DeviceBooleanImpl(const char* name, bool readonly, bool* value) {
if (readonly) {
ImGui::LabelText(name, "%s", *value ? "true" : "false");
} else {
static const char* boolOptions[] = {"false", "true"};
int val = *value ? 1 : 0;
if (ImGui::Combo(name, &val, boolOptions, 2)) {
*value = val;
return true;
}
}
return false;
}
static bool DeviceDoubleImpl(const char* name, bool readonly, double* value) {
if (readonly) {
ImGui::LabelText(name, "%.6f", *value);
return false;
} else {
return ImGui::InputDouble(name, value, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue);
}
}
static bool DeviceEnumImpl(const char* name, bool readonly, int* value,
const char** options, int32_t numOptions) {
if (readonly) {
if (*value < 0 || *value >= numOptions)
ImGui::LabelText(name, "%d (unknown)", *value);
else
ImGui::LabelText(name, "%s", options[*value]);
return false;
} else {
return ImGui::Combo(name, value, options, numOptions);
}
}
static bool DeviceIntImpl(const char* name, bool readonly, int32_t* value) {
if (readonly) {
ImGui::LabelText(name, "%" PRId32, *value);
return false;
} else {
return ImGui::InputScalar(name, ImGuiDataType_S32, value, nullptr, nullptr,
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
}
}
static bool DeviceLongImpl(const char* name, bool readonly, int64_t* value) {
if (readonly) {
ImGui::LabelText(name, "%" PRId64, *value);
return false;
} else {
return ImGui::InputScalar(name, ImGuiDataType_S64, value, nullptr, nullptr,
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
}
}
template <typename F, typename... Args>
static inline bool DeviceValueImpl(const char* name, bool readonly,
const DataSource* source, F&& func,
Args... args) {
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f);
if (!source) {
return func(name, readonly, args...);
} else {
ImGui::PushID(name);
bool rv = func("", readonly, args...);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(name);
source->EmitDrag();
ImGui::PopID();
return rv;
}
}
bool glass::DeviceBoolean(const char* name, bool readonly, bool* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceBooleanImpl, value);
}
bool glass::DeviceDouble(const char* name, bool readonly, double* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceDoubleImpl, value);
}
bool glass::DeviceEnum(const char* name, bool readonly, int* value,
const char** options, int32_t numOptions,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceEnumImpl, value, options,
numOptions);
}
bool glass::DeviceInt(const char* name, bool readonly, int32_t* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceIntImpl, value);
}
bool glass::DeviceLong(const char* name, bool readonly, int64_t* value,
const DataSource* source) {
return DeviceValueImpl(name, readonly, source, DeviceLongImpl, value);
}

View File

@@ -0,0 +1,140 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/FMS.h"
#include <imgui.h>
#include <wpi/SmallString.h>
#include "glass/DataSource.h"
using namespace glass;
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
"Blue 1", "Blue 2", "Blue 3"};
void glass::DisplayFMS(FMSModel* model, bool* matchTimeEnabled) {
if (!model->Exists() || model->IsReadOnly()) return DisplayFMSReadOnly(model);
// FMS Attached
if (auto data = model->GetFmsAttachedData()) {
bool val = data->GetValue();
if (ImGui::Checkbox("FMS Attached", &val)) model->SetFmsAttached(val);
data->EmitDrag();
}
// DS Attached
if (auto data = model->GetDsAttachedData()) {
bool val = data->GetValue();
if (ImGui::Checkbox("DS Attached", &val)) model->SetDsAttached(val);
data->EmitDrag();
}
// Alliance Station
if (auto data = model->GetAllianceStationIdData()) {
int val = data->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::Combo("Alliance Station", &val, stations, 6))
model->SetAllianceStationId(val);
data->EmitDrag();
}
// Match Time
if (auto data = model->GetMatchTimeData()) {
if (matchTimeEnabled)
ImGui::Checkbox("Match Time Enabled", matchTimeEnabled);
double val = data->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f",
ImGuiInputTextFlags_EnterReturnsTrue)) {
model->SetMatchTime(val);
}
data->EmitDrag();
ImGui::SameLine();
if (ImGui::Button("Reset")) {
model->SetMatchTime(0.0);
}
}
// Game Specific Message
// make buffer full 64 width, null terminated, for editability
wpi::SmallString<64> gameSpecificMessage;
model->GetGameSpecificMessage(gameSpecificMessage);
gameSpecificMessage.resize(63);
gameSpecificMessage.push_back('\0');
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputText("Game Specific", gameSpecificMessage.data(),
gameSpecificMessage.size(),
ImGuiInputTextFlags_EnterReturnsTrue)) {
model->SetGameSpecificMessage(gameSpecificMessage.data());
}
}
void glass::DisplayFMSReadOnly(FMSModel* model) {
bool exists = model->Exists();
if (!exists) ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
if (auto data = model->GetEStopData()) {
ImGui::Selectable("E-Stopped: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetEnabledData()) {
ImGui::Selectable("Robot Enabled: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetTestData()) {
ImGui::Selectable("Test Mode: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetAutonomousData()) {
ImGui::Selectable("Autonomous Mode: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetFmsAttachedData()) {
ImGui::Selectable("FMS Attached: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetDsAttachedData()) {
ImGui::Selectable("DS Attached: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
}
if (auto data = model->GetAllianceStationIdData()) {
ImGui::Selectable("Alliance Station: ");
data->EmitDrag();
ImGui::SameLine();
ImGui::TextUnformatted(exists ? stations[static_cast<int>(data->GetValue())]
: "?");
}
if (auto data = model->GetMatchTimeData()) {
ImGui::Selectable("Match Time: ");
data->EmitDrag();
ImGui::SameLine();
if (exists)
ImGui::Text("%.1f", data->GetValue());
else
ImGui::TextUnformatted("?");
}
wpi::SmallString<64> gameSpecificMessage;
model->GetGameSpecificMessage(gameSpecificMessage);
ImGui::Text("Game Specific: %s", exists ? gameSpecificMessage.c_str() : "?");
if (!exists) ImGui::PopStyleColor();
}

View File

@@ -0,0 +1,617 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/Field2D.h"
#include <cmath>
#include <memory>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <portable-file-dialogs.h>
#include <units/angle.h>
#include <units/length.h>
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/StringMap.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
namespace gui = wpi::gui;
namespace {
// Per-frame field data (not persistent)
struct FieldFrameData {
// in screen coordinates
ImVec2 imageMin;
ImVec2 imageMax;
ImVec2 min;
ImVec2 max;
float scale; // scaling from field units to screen units
};
// Object drag state
struct ObjectDragState {
int object = 0;
int corner = 0;
ImVec2 initialOffset;
double initialAngle = 0;
};
// Per-frame object data (not persistent)
class ObjectFrameData {
public:
explicit ObjectFrameData(FieldObjectModel& model, const FieldFrameData& ffd,
float width, float length);
void SetPosition(double x, double y);
// set and get rotation in radians
void SetRotation(double rot);
double GetRotation() const {
return units::convert<units::degrees, units::radians>(m_rot);
}
void UpdateFrameData();
int IsHovered(const ImVec2& cursor) const;
bool HandleDrag(const ImVec2& cursor, int hitCorner, ObjectDragState* drag);
void Draw(ImDrawList* drawList, const gui::Texture& texture,
int hitCorner) const;
// in window coordinates
ImVec2 m_center;
ImVec2 m_corners[4];
ImVec2 m_arrow[3];
private:
FieldObjectModel& m_model;
const FieldFrameData& m_ffd;
// scaled width/2 and length/2, in screen units
float m_width2;
float m_length2;
float m_hitRadius;
double m_x = 0;
double m_y = 0;
double m_rot = 0;
};
class ObjectGroupInfo {
public:
static constexpr float kDefaultWidth = 0.6858f;
static constexpr float kDefaultLength = 0.8204f;
ObjectGroupInfo();
std::unique_ptr<pfd::open_file> m_fileOpener;
float* m_pWidth;
float* m_pLength;
ObjectDragState m_dragState;
void Reset();
void LoadImage();
const gui::Texture& GetTexture() const { return m_texture; }
private:
bool LoadImageImpl(const char* fn);
std::string* m_pFilename;
gui::Texture m_texture;
};
class FieldInfo {
public:
static constexpr float kDefaultWidth = 15.98f;
static constexpr float kDefaultHeight = 8.21f;
FieldInfo();
std::unique_ptr<pfd::open_file> m_fileOpener;
float* m_pWidth;
float* m_pHeight;
void Reset();
void LoadImage();
void LoadJson(const wpi::Twine& jsonfile);
FieldFrameData GetFrameData(ImVec2 min, ImVec2 max) const;
void Draw(ImDrawList* drawList, const FieldFrameData& frameData) const;
wpi::StringMap<std::unique_ptr<ObjectGroupInfo>> m_objectGroups;
private:
bool LoadImageImpl(const char* fn);
std::string* m_pFilename;
gui::Texture m_texture;
int m_imageWidth;
int m_imageHeight;
int* m_pTop;
int* m_pLeft;
int* m_pBottom;
int* m_pRight;
};
} // namespace
FieldInfo::FieldInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
m_pTop = storage.GetIntRef("top", 0);
m_pLeft = storage.GetIntRef("left", 0);
m_pBottom = storage.GetIntRef("bottom", -1);
m_pRight = storage.GetIntRef("right", -1);
m_pWidth = storage.GetFloatRef("width", kDefaultWidth);
m_pHeight = storage.GetFloatRef("height", kDefaultHeight);
}
void FieldInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
m_imageWidth = 0;
m_imageHeight = 0;
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
}
void FieldInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) {
if (wpi::StringRef(result[0]).endswith(".json")) {
LoadJson(result[0]);
} else {
LoadImageImpl(result[0].c_str());
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
}
}
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
}
}
void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
std::error_code ec;
wpi::raw_fd_istream f(jsonfile, ec);
if (ec) {
wpi::errs() << "GUI: could not open field JSON file\n";
return;
}
// parse file
wpi::json j;
try {
j = wpi::json::parse(f);
} catch (const wpi::json::parse_error& e) {
wpi::errs() << "GUI: JSON: could not parse: " << e.what() << '\n';
}
// top level must be an object
if (!j.is_object()) {
wpi::errs() << "GUI: JSON: does not contain a top object\n";
return;
}
// image filename
std::string image;
try {
image = j.at("field-image").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-image: " << e.what()
<< '\n';
return;
}
// corners
int top, left, bottom, right;
try {
top = j.at("field-corners").at("top-left").at(1).get<int>();
left = j.at("field-corners").at("top-left").at(0).get<int>();
bottom = j.at("field-corners").at("bottom-right").at(1).get<int>();
right = j.at("field-corners").at("bottom-right").at(0).get<int>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-corners: " << e.what()
<< '\n';
return;
}
// size
float width;
float height;
try {
width = j.at("field-size").at(0).get<float>();
height = j.at("field-size").at(1).get<float>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-size: " << e.what() << '\n';
return;
}
// units for size
std::string unit;
try {
unit = j.at("field-unit").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "GUI: JSON: could not read field-unit: " << e.what() << '\n';
return;
}
// convert size units to meters
if (unit == "foot" || unit == "feet") {
width = units::convert<units::feet, units::meters>(width);
height = units::convert<units::feet, units::meters>(height);
}
// the image filename is relative to the json file
wpi::SmallString<128> pathname;
jsonfile.toVector(pathname);
wpi::sys::path::remove_filename(pathname);
wpi::sys::path::append(pathname, image);
// load field image
if (!LoadImageImpl(pathname.c_str())) return;
// save to field info
*m_pFilename = pathname.str();
*m_pTop = top;
*m_pLeft = left;
*m_pBottom = bottom;
*m_pRight = right;
*m_pWidth = width;
*m_pHeight = height;
}
bool FieldInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading field image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read field image\n";
return false;
}
m_texture = std::move(texture);
m_imageWidth = m_texture.GetWidth();
m_imageHeight = m_texture.GetHeight();
*m_pFilename = fn;
return true;
}
FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
// fit the image into the window
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0)
gui::MaxFit(&min, &max, m_imageWidth, m_imageHeight);
FieldFrameData ffd;
ffd.imageMin = min;
ffd.imageMax = max;
// size down the box by the image corners (if any)
if (*m_pBottom > 0 && *m_pRight > 0) {
min.x += *m_pLeft * (max.x - min.x) / m_imageWidth;
min.y += *m_pTop * (max.y - min.y) / m_imageHeight;
max.x -= (m_imageWidth - *m_pRight) * (max.x - min.x) / m_imageWidth;
max.y -= (m_imageHeight - *m_pBottom) * (max.y - min.y) / m_imageHeight;
}
// draw the field "active area" as a yellow boundary box
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
ffd.min = min;
ffd.max = max;
ffd.scale = (max.x - min.x) / *m_pWidth;
return ffd;
}
void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
drawList->AddImage(m_texture, ffd.imageMin, ffd.imageMax);
}
// draw the field "active area" as a yellow boundary box
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
}
ObjectGroupInfo::ObjectGroupInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
m_pWidth = storage.GetFloatRef("width", kDefaultWidth);
m_pLength = storage.GetFloatRef("length", kDefaultLength);
}
void ObjectGroupInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
}
void ObjectGroupInfo::LoadImage() {
if (m_fileOpener && m_fileOpener->ready(0)) {
auto result = m_fileOpener->result();
if (!result.empty()) LoadImageImpl(result[0].c_str());
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
}
}
bool ObjectGroupInfo::LoadImageImpl(const char* fn) {
wpi::outs() << "GUI: loading object image '" << fn << "'\n";
auto texture = gui::Texture::CreateFromFile(fn);
if (!texture) {
wpi::errs() << "GUI: could not read object image\n";
return false;
}
m_texture = std::move(texture);
*m_pFilename = fn;
return true;
}
ObjectFrameData::ObjectFrameData(FieldObjectModel& model,
const FieldFrameData& ffd, float width,
float length)
: m_model{model},
m_ffd{ffd},
m_width2(ffd.scale * width / 2),
m_length2(ffd.scale * length / 2),
m_hitRadius((std::min)(m_width2, m_length2) / 2) {
if (auto xData = model.GetXData()) m_x = xData->GetValue();
if (auto yData = model.GetYData()) m_y = yData->GetValue();
if (auto rotationData = model.GetRotationData())
m_rot = rotationData->GetValue();
UpdateFrameData();
}
void ObjectFrameData::SetPosition(double x, double y) {
m_x = x;
m_y = y;
m_model.SetPosition(x, y);
}
void ObjectFrameData::SetRotation(double rot) {
double rotDegrees = units::convert<units::radians, units::degrees>(rot);
// force to -180 to +180 range
rotDegrees = rotDegrees + std::ceil((-rotDegrees - 180) / 360) * 360;
m_rot = rotDegrees;
m_model.SetRotation(rotDegrees);
}
void ObjectFrameData::UpdateFrameData() {
// (0,0) origin is bottom left
ImVec2 center(m_ffd.min.x + m_ffd.scale * m_x,
m_ffd.max.y - m_ffd.scale * m_y);
// build rotated points around center
float length2 = m_length2;
float width2 = m_width2;
double rot = GetRotation();
float cos_a = std::cos(-rot);
float sin_a = std::sin(-rot);
m_corners[0] = center + ImRotate(ImVec2(-length2, -width2), cos_a, sin_a);
m_corners[1] = center + ImRotate(ImVec2(length2, -width2), cos_a, sin_a);
m_corners[2] = center + ImRotate(ImVec2(length2, width2), cos_a, sin_a);
m_corners[3] = center + ImRotate(ImVec2(-length2, width2), cos_a, sin_a);
m_arrow[0] =
center + ImRotate(ImVec2(-length2 / 2, -width2 / 2), cos_a, sin_a);
m_arrow[1] = center + ImRotate(ImVec2(length2 / 2, 0), cos_a, sin_a);
m_arrow[2] =
center + ImRotate(ImVec2(-length2 / 2, width2 / 2), cos_a, sin_a);
m_center = center;
}
int ObjectFrameData::IsHovered(const ImVec2& cursor) const {
// only allow initiation of dragging when invisible button is hovered;
// this prevents the window resize handles from simultaneously activating
// the drag functionality
if (!ImGui::IsItemHovered()) return 0;
float hitRadiusSquared = m_hitRadius * m_hitRadius;
// it's within the hit radius of the center?
if (gui::GetDistSquared(cursor, m_center) < hitRadiusSquared)
return 1;
else if (gui::GetDistSquared(cursor, m_corners[0]) < hitRadiusSquared)
return 2;
else if (gui::GetDistSquared(cursor, m_corners[1]) < hitRadiusSquared)
return 3;
else if (gui::GetDistSquared(cursor, m_corners[2]) < hitRadiusSquared)
return 4;
else if (gui::GetDistSquared(cursor, m_corners[3]) < hitRadiusSquared)
return 5;
else
return 0;
}
bool ObjectFrameData::HandleDrag(const ImVec2& cursor, int hitCorner,
ObjectDragState* drag) {
bool rv = false;
if (hitCorner > 0 && ImGui::IsMouseClicked(0)) {
if (hitCorner == 1) {
drag->corner = hitCorner;
drag->initialOffset = cursor - m_center;
} else {
drag->corner = hitCorner;
ImVec2 off = cursor - m_center;
drag->initialAngle = std::atan2(off.y, off.x) + GetRotation();
}
rv = true;
}
if (drag->corner > 0 && ImGui::IsMouseDown(0)) {
if (drag->corner == 1) {
ImVec2 newPos = cursor - drag->initialOffset;
SetPosition(
(std::clamp(newPos.x, m_ffd.min.x, m_ffd.max.x) - m_ffd.min.x) /
m_ffd.scale,
(m_ffd.max.y - std::clamp(newPos.y, m_ffd.min.y, m_ffd.max.y)) /
m_ffd.scale);
UpdateFrameData();
} else {
ImVec2 off = cursor - m_center;
SetRotation(drag->initialAngle - std::atan2(off.y, off.x));
}
} else {
drag->corner = 0;
}
return rv;
}
void ObjectFrameData::Draw(ImDrawList* drawList, const gui::Texture& texture,
int hitCorner) const {
if (texture) {
drawList->AddImageQuad(texture, m_corners[0], m_corners[1], m_corners[2],
m_corners[3]);
} else {
drawList->AddQuad(m_corners[0], m_corners[1], m_corners[2], m_corners[3],
IM_COL32(255, 0, 0, 255), 4.0);
drawList->AddTriangle(m_arrow[0], m_arrow[1], m_arrow[2],
IM_COL32(0, 255, 0, 255), 4.0);
}
if (hitCorner > 0) {
if (hitCorner == 1) {
drawList->AddCircle(m_center, m_hitRadius, IM_COL32(0, 255, 0, 255));
} else {
drawList->AddCircle(m_corners[hitCorner - 2], m_hitRadius,
IM_COL32(0, 255, 0, 255));
}
}
}
void glass::DisplayField2DSettings(Field2DModel* model) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
field = storage.GetData<FieldInfo>();
}
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
if (ImGui::CollapsingHeader("Field")) {
ImGui::PushID("Field");
if (ImGui::Button("Choose image...")) {
field->m_fileOpener = std::make_unique<pfd::open_file>(
"Choose field image", "",
std::vector<std::string>{"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm",
"PathWeaver JSON File", "*.json"});
}
if (ImGui::Button("Reset image")) {
field->Reset();
}
ImGui::InputFloat("Field Width", field->m_pWidth);
ImGui::InputFloat("Field Height", field->m_pHeight);
// ImGui::InputInt("Field Top", field->m_pTop);
// ImGui::InputInt("Field Left", field->m_pLeft);
// ImGui::InputInt("Field Right", field->m_pRight);
// ImGui::InputInt("Field Bottom", field->m_pBottom);
ImGui::PopID();
}
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
if (!groupModel.Exists()) return;
PushID(name);
auto& objGroupRef = field->m_objectGroups[name];
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
auto objGroup = objGroupRef.get();
wpi::SmallString<64> nameBuf = name;
if (ImGui::CollapsingHeader(nameBuf.c_str())) {
if (ImGui::Button("Choose image...")) {
objGroup->m_fileOpener = std::make_unique<pfd::open_file>(
"Choose object image", "",
std::vector<std::string>{
"Image File",
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
"*.hdr *.pic *.ppm *.pgm"});
}
if (ImGui::Button("Reset image")) {
objGroup->Reset();
}
ImGui::InputFloat("Width", objGroup->m_pWidth);
ImGui::InputFloat("Length", objGroup->m_pLength);
}
PopID();
});
ImGui::PopItemWidth();
}
void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
field = storage.GetData<FieldInfo>();
}
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 mousePos = ImGui::GetIO().MousePos;
// for dragging to work, there needs to be a button (otherwise the window is
// dragged)
if (contentSize.x <= 0 || contentSize.y <= 0) return;
ImVec2 cursorPos = windowPos + ImGui::GetCursorPos(); // screen coords
ImGui::InvisibleButton("field", contentSize);
// field
field->LoadImage();
FieldFrameData ffd = field->GetFrameData(cursorPos, cursorPos + contentSize);
auto drawList = ImGui::GetWindowDrawList();
field->Draw(drawList, ffd);
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
if (!groupModel.Exists()) return;
PushID(name);
auto& objGroupRef = field->m_objectGroups[name];
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
auto objGroup = objGroupRef.get();
objGroup->LoadImage();
int i = 0;
groupModel.ForEachFieldObject([&](auto& objModel) {
++i;
ObjectFrameData ofd{objModel, ffd, *objGroup->m_pWidth,
*objGroup->m_pLength};
int hitCorner = 0;
if (objGroup->m_dragState.object == 0 ||
objGroup->m_dragState.object == i) {
hitCorner = ofd.IsHovered(mousePos);
if (ofd.HandleDrag(mousePos, hitCorner, &objGroup->m_dragState))
objGroup->m_dragState.object = i;
}
// draw
ofd.Draw(drawList, objGroup->GetTexture(), hitCorner);
});
PopID();
});
}
void Field2DView::Display() {
if (ImGui::BeginPopupContextItem()) {
DisplayField2DSettings(m_model);
ImGui::EndPopup();
}
DisplayField2D(m_model, ImGui::GetWindowContentRegionMax() -
ImGui::GetWindowContentRegionMin());
}

View File

@@ -0,0 +1,44 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/other/StringChooser.h"
#include <imgui.h>
using namespace glass;
void glass::DisplayStringChooser(StringChooserModel* model) {
auto& defaultValue = model->GetDefault();
auto& selected = model->GetSelected();
auto& active = model->GetActive();
auto& options = model->GetOptions();
const char* preview =
selected.empty() ? defaultValue.c_str() : selected.c_str();
const char* label;
if (active == preview) {
label = "GOOD##select";
} else {
label = "BAD ##select";
}
if (ImGui::BeginCombo(label, preview)) {
for (auto&& option : options) {
ImGui::PushID(option.c_str());
bool isSelected = (option == selected);
if (ImGui::Selectable(option.c_str(), isSelected)) {
model->SetSelected(option);
}
if (isSelected) ImGui::SetItemDefaultFocus();
ImGui::PopID();
}
ImGui::EndCombo();
}
ImGui::SameLine();
}

View File

@@ -5,13 +5,16 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include "ExtraGuiWidgets.h"
#include "glass/support/ExtraGuiWidgets.h"
#include "GuiDataSource.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
namespace halsimgui {
#include "glass/DataSource.h"
void DrawLEDSources(const int* values, GuiDataSource** sources, int numValues,
namespace glass {
void DrawLEDSources(const int* values, DataSource** sources, int numValues,
int cols, const ImU32* colors, float size, float spacing,
const LEDConfig& config) {
if (numValues == 0 || cols < 1) return;
@@ -106,4 +109,60 @@ void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
config);
}
} // namespace halsimgui
bool DeleteButton(ImGuiID id, const ImVec2& pos) {
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
// We intentionally allow interaction when clipped so that a mechanical
// Alt,Right,Validate sequence close a window. (this isn't the regular
// behavior of buttons, but it doesn't affect the user much because navigation
// tends to keep items visible).
const ImRect bb(
pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
bool is_clipped = !ImGui::ItemAdd(bb, id);
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
if (is_clipped) return pressed;
// Render
ImU32 col =
ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
ImVec2 center = bb.GetCenter();
if (hovered)
window->DrawList->AddCircleFilled(
center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
ImU32 cross_col = ImGui::GetColorU32(ImGuiCol_Text);
window->DrawList->AddCircle(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f),
cross_col, 12);
float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f;
center -= ImVec2(0.5f, 0.5f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent),
center + ImVec2(-cross_extent, -cross_extent),
cross_col, 1.0f);
window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent),
center + ImVec2(-cross_extent, +cross_extent),
cross_col, 1.0f);
return pressed;
}
bool HeaderDeleteButton(const char* label) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiContext& g = *GImGui;
ImGuiItemHoveredDataBackup last_item_backup;
ImGuiID id = window->GetID(label);
float button_size = g.FontSize;
float button_x = ImMax(window->DC.LastItemRect.Min.x,
window->DC.LastItemRect.Max.x -
g.Style.FramePadding.x * 2.0f - button_size);
float button_y = window->DC.LastItemRect.Min.y;
bool rv = DeleteButton(
window->GetID(reinterpret_cast<void*>(static_cast<intptr_t>(id) + 1)),
ImVec2(button_x, button_y));
last_item_backup.Restore();
return rv;
}
} // namespace glass

View File

@@ -0,0 +1,64 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/support/IniSaverBase.h"
#include <imgui_internal.h>
using namespace glass;
namespace {
class ImGuiSaver : public IniSaverBackend {
public:
void Register(IniSaverBase* iniSaver) override;
void Unregister(IniSaverBase* iniSaver) override;
};
} // namespace
void ImGuiSaver::Register(IniSaverBase* iniSaver) {
// hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = iniSaver->GetTypeName();
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name) {
return static_cast<IniSaverBase*>(handler->UserData)->IniReadOpen(name);
};
iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* line) {
static_cast<IniSaverBase*>(handler->UserData)->IniReadLine(entry, line);
};
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
static_cast<IniSaverBase*>(handler->UserData)->IniWriteAll(out_buf);
};
iniHandler.UserData = iniSaver;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
}
void ImGuiSaver::Unregister(IniSaverBase* iniSaver) {
if (auto ctx = ImGui::GetCurrentContext()) {
auto& handlers = ctx->SettingsHandlers;
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
if (it->UserData == iniSaver) {
handlers.erase(it);
return;
}
}
}
}
static ImGuiSaver* GetSaverInstance() {
static ImGuiSaver* inst = new ImGuiSaver;
return inst;
}
IniSaverBase::IniSaverBase(const wpi::Twine& typeName, IniSaverBackend* backend)
: m_typeName(typeName.str()),
m_backend{backend ? backend : GetSaverInstance()} {}
IniSaverBase::~IniSaverBase() { m_backend->Unregister(this); }

View File

@@ -5,14 +5,23 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include "IniSaverInfo.h"
#include "glass/support/IniSaverInfo.h"
#include <cstdio>
#include <cstring>
#include <imgui_internal.h>
#include <wpi/SmallString.h>
using namespace halsimgui;
using namespace glass;
void NameInfo::SetName(const wpi::Twine& name) {
wpi::SmallString<64> nameBuf;
auto nameStr = name.toStringRef(nameBuf);
size_t len = (std::min)(nameStr.size(), sizeof(m_name) - 1);
std::memcpy(m_name, nameStr.data(), len);
m_name[len] = '\0';
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
@@ -98,11 +107,13 @@ bool NameInfo::PopupEditName(int index) {
std::snprintf(id, sizeof(id), "Name%d", index);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit", ImGuiInputTextFlags_EnterReturnsTrue)) {
ImGui::CloseCurrentPopup();
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
@@ -114,11 +125,13 @@ bool NameInfo::PopupEditName(const char* name) {
std::snprintf(id, sizeof(id), "Name%s", name);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit", ImGuiInputTextFlags_EnterReturnsTrue)) {
ImGui::CloseCurrentPopup();
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
@@ -139,3 +152,14 @@ bool OpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("open=%d\n", m_open ? 1 : 0);
}
bool NameOpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (NameInfo::ReadIni(name, value)) return true;
if (OpenInfo::ReadIni(name, value)) return true;
return false;
}
void NameOpenInfo::WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
}

View File

@@ -0,0 +1,147 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
namespace glass {
struct Context;
Context* CreateContext();
void DestroyContext(Context* ctx = nullptr);
Context* GetCurrentContext();
void SetCurrentContext(Context* ctx);
/**
* Storage provides both persistent and non-persistent key/value storage for
* widgets.
*
* Keys are always strings. The storage also provides non-persistent arbitrary
* data storage (via std::shared_ptr<void>).
*
* Storage is automatically indexed internally by the ID stack. Note it is
* necessary to use the glass wrappers for PushID et al to preserve naming in
* the save file (unnamed values are still stored, but this is non-ideal for
* users trying to hand-edit the save file).
*/
class Storage {
public:
struct Value {
Value() = default;
explicit Value(const wpi::Twine& str) : stringVal{str.str()} {}
enum Type { kNone, kInt, kInt64, kBool, kFloat, kDouble, kString };
Type type = kNone;
union {
int intVal;
int64_t int64Val;
bool boolVal;
float floatVal;
double doubleVal;
};
std::string stringVal;
};
int GetInt(wpi::StringRef key, int defaultVal = 0) const;
int64_t GetInt64(wpi::StringRef key, int64_t defaultVal = 0) const;
bool GetBool(wpi::StringRef key, bool defaultVal = false) const;
float GetFloat(wpi::StringRef key, float defaultVal = 0.0f) const;
double GetDouble(wpi::StringRef key, double defaultVal = 0.0) const;
std::string GetString(wpi::StringRef key,
const std::string& defaultVal = {}) const;
void SetInt(wpi::StringRef key, int val);
void SetInt64(wpi::StringRef key, int64_t val);
void SetBool(wpi::StringRef key, bool val);
void SetFloat(wpi::StringRef key, float val);
void SetDouble(wpi::StringRef key, double val);
void SetString(wpi::StringRef key, const wpi::Twine& val);
int* GetIntRef(wpi::StringRef key, int defaultVal = 0);
int64_t* GetInt64Ref(wpi::StringRef key, int64_t defaultVal = 0);
bool* GetBoolRef(wpi::StringRef key, bool defaultVal = false);
float* GetFloatRef(wpi::StringRef key, float defaultVal = 0.0f);
double* GetDoubleRef(wpi::StringRef key, double defaultVal = 0.0);
std::string* GetStringRef(wpi::StringRef key, wpi::StringRef defaultVal = {});
Value& GetValue(wpi::StringRef key);
void SetData(std::shared_ptr<void>&& data) { m_data = std::move(data); }
template <typename T>
T* GetData() const {
return static_cast<T*>(m_data.get());
}
Storage() = default;
Storage(const Storage&) = delete;
Storage& operator=(const Storage&) = delete;
std::vector<std::string>& GetKeys() { return m_keys; }
const std::vector<std::string>& GetKeys() const { return m_keys; }
std::vector<std::unique_ptr<Value>>& GetValues() { return m_values; }
const std::vector<std::unique_ptr<Value>>& GetValues() const {
return m_values;
}
private:
mutable std::vector<std::string> m_keys;
mutable std::vector<std::unique_ptr<Value>> m_values;
std::shared_ptr<void> m_data;
};
Storage& GetStorage();
Storage& GetStorage(wpi::StringRef id);
bool Begin(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0);
void End();
bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0),
bool border = false, ImGuiWindowFlags flags = 0);
void EndChild();
/**
* Saves open status to storage "open" key.
* If returning 'true' the header is open. doesn't indent nor push on ID stack.
* user doesn't have to call TreePop().
*/
bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);
bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0);
void TreePop();
// push string into the ID stack (will hash string).
void PushID(const char* str_id);
// push string into the ID stack (will hash string).
void PushID(const char* str_id_begin, const char* str_id_end);
// push string into the ID stack (will hash string).
inline void PushID(wpi::StringRef str) { PushID(str.begin(), str.end()); }
// push integer into the ID stack (will hash integer).
void PushID(int int_id);
// pop from the ID stack.
void PopID();
bool PopupEditName(const char* label, std::string* name);
} // namespace glass

View File

@@ -0,0 +1,49 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <imgui.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/StringMap.h>
#include "glass/Context.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/IniSaverString.h"
namespace glass {
class DataSource;
class DataSourceName {
public:
DataSourceName() = default;
explicit DataSourceName(DataSource* source) : source{source} {}
bool ReadIni(wpi::StringRef name_, wpi::StringRef value) {
return name->ReadIni(name_, value);
}
void WriteIni(ImGuiTextBuffer* out) { name->WriteIni(out); }
std::unique_ptr<NameInfo> name{new NameInfo};
DataSource* source = nullptr;
};
struct Context {
wpi::SmallString<128> curId;
wpi::SmallVector<size_t, 32> idStack;
wpi::StringMap<std::unique_ptr<Storage>> storage;
wpi::StringMap<bool> deviceHidden;
IniSaverString<DataSourceName> sources{"Data Sources"};
};
extern Context* gContext;
} // namespace glass

View File

@@ -0,0 +1,86 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <stdint.h>
#include <atomic>
#include <string>
#include <imgui.h>
#include <wpi/Signal.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpi/spinlock.h>
namespace glass {
class NameInfo;
/**
* A data source for numeric/boolean data.
*/
class DataSource {
public:
explicit DataSource(const wpi::Twine& id);
DataSource(const wpi::Twine& id, int index);
DataSource(const wpi::Twine& id, int index, int index2);
virtual ~DataSource();
DataSource(const DataSource&) = delete;
DataSource& operator=(const DataSource&) = delete;
const char* GetId() const { return m_id.c_str(); }
void SetName(const wpi::Twine& name);
const char* GetName() const;
NameInfo& GetNameInfo() { return *m_name; }
void PushEditNameId(int index);
void PushEditNameId(const char* name);
bool PopupEditName(int index);
bool PopupEditName(const char* name);
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
void SetValue(double value, uint64_t time = 0) {
m_value = value;
valueChanged(value, time);
}
double GetValue() const { return m_value; }
// drag source helpers
void LabelText(const char* label, const char* fmt, ...) const;
void LabelTextV(const char* label, const char* fmt, va_list args) const;
bool Combo(const char* label, int* current_item, const char* const items[],
int items_count, int popup_max_height_in_items = -1) const;
bool SliderFloat(const char* label, float* v, float v_min, float v_max,
const char* format = "%.3f", float power = 1.0f) const;
bool InputDouble(const char* label, double* v, double step = 0.0,
double step_fast = 0.0, const char* format = "%.6f",
ImGuiInputTextFlags flags = 0) const;
bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100,
ImGuiInputTextFlags flags = 0) const;
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
static DataSource* Find(wpi::StringRef id);
static wpi::sig::Signal<const char*, DataSource*> sourceCreated;
private:
std::string m_id;
NameInfo* m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};
} // namespace glass

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <vector>
namespace glass {
class WindowManager;
/**
* GUI main menu bar.
*/
class MainMenuBar {
public:
/**
* Displays the main menu bar. Should be added to GUI LateExecute.
*/
void Display();
/**
* Adds to GUI's main menu bar. The menu function is called from within a
* ImGui::BeginMainMenuBar()/EndMainMenuBar() block. Usually it's only
* appropriate to create a menu with ImGui::BeginMenu()/EndMenu() inside of
* this function.
*
* @param menu menu display function
*/
void AddMainMenu(std::function<void()> menu);
/**
* Adds to GUI's option menu. The menu function is called from within a
* ImGui::BeginMenu()/EndMenu() block. Usually it's only appropriate to
* create menu items inside of this function.
*
* @param menu menu display function
*/
void AddOptionMenu(std::function<void()> menu);
private:
std::vector<std::function<void()>> m_optionMenus;
std::vector<std::function<void()>> m_menus;
};
} // namespace glass

View File

@@ -7,11 +7,19 @@
#pragma once
namespace halsimgui {
namespace glass {
class NetworkTablesGui {
class Model {
public:
static void Initialize();
Model() = default;
virtual ~Model() = default;
Model(const Model&) = delete;
Model& operator=(const Model&) = delete;
virtual void Update() = 0;
virtual bool Exists() = 0;
virtual bool IsReadOnly();
};
} // namespace halsimgui
} // namespace glass

View File

@@ -0,0 +1,171 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpigui.h>
#include "glass/Model.h"
#include "glass/WindowManager.h"
namespace glass {
namespace detail {
struct ProviderFunctions {
using Exists = std::function<bool()>;
using CreateModel = std::function<std::unique_ptr<Model>()>;
using ViewExists = std::function<bool(Model*)>;
using CreateView = std::function<std::unique_ptr<View>(Window*, Model*)>;
};
} // namespace detail
/**
* Providers are registries of models and views. They have ownership over
* their created Models, Windows, and Views.
*
* GlobalInit() configures Update() to be called during EarlyExecute.
* Calling Update() calls Update() on all created models (Provider
* implementations must ensure this occurs).
*
* @tparam Functions defines functor interface types
*/
template <typename Functions = detail::ProviderFunctions>
class Provider : public WindowManager {
public:
using ExistsFunc = typename Functions::Exists;
using CreateModelFunc = typename Functions::CreateModel;
using ViewExistsFunc = typename Functions::ViewExists;
using CreateViewFunc = typename Functions::CreateView;
/**
* Constructor.
*
* @param iniName Group name to use in ini file
*/
explicit Provider(const wpi::Twine& iniName) : WindowManager{iniName} {}
Provider(const Provider&) = delete;
Provider& operator=(const Provider&) = delete;
/**
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
void GlobalInit() override;
/**
* Show the specified view by default on first load. Has no effect if
* the user previously hid the window (e.g. in a saved prior execution).
*
* @param name View name
*/
void ShowDefault(wpi::StringRef name);
/**
* Register a model and view combination. Equivalent to calling both
* RegisterModel() and RegisterView() with no ViewExistsFunc.
*
* @param name View/model name
* @param exists Functor, returns true if model can be created
* @param createModel Functor for creating model
* @param createView Functor for creating view
*/
void Register(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel, CreateViewFunc createView);
/**
* Register a model.
*
* @param name Model name
* @param exists Functor, returns true if model can be created
* @param createModel Functor for creating model
*/
void RegisterModel(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel);
/**
* Register a view.
*
* @param name View name
* @param modelName Model name
* @param exists Functor, returns true if view can be created
* @param createView Functor for creating view
*/
void RegisterView(wpi::StringRef name, wpi::StringRef modelName,
ViewExistsFunc exists, CreateViewFunc createView);
protected:
virtual void Update();
struct ModelEntry {
ModelEntry(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel)
: name{name},
exists{std::move(exists)},
createModel{std::move(createModel)} {}
virtual ~ModelEntry() = default;
std::string name;
ExistsFunc exists;
CreateModelFunc createModel;
std::unique_ptr<Model> model;
};
struct ViewEntry {
ViewEntry(wpi::StringRef name, ModelEntry* modelEntry,
ViewExistsFunc exists, CreateViewFunc createView)
: name{name},
modelEntry{modelEntry},
exists{std::move(exists)},
createView{std::move(createView)} {}
virtual ~ViewEntry() = default;
std::string name;
ModelEntry* modelEntry;
ViewExistsFunc exists;
CreateViewFunc createView;
Window* window = nullptr;
};
// sorted by name
using ModelEntries = std::vector<std::unique_ptr<ModelEntry>>;
ModelEntries m_modelEntries;
using ViewEntries = std::vector<std::unique_ptr<ViewEntry>>;
ViewEntries m_viewEntries;
typename ModelEntries::iterator FindModelEntry(wpi::StringRef name);
typename ViewEntries::iterator FindViewEntry(wpi::StringRef name);
virtual std::unique_ptr<ModelEntry> MakeModelEntry(
wpi::StringRef name, ExistsFunc exists, CreateModelFunc createModel) {
return std::make_unique<ModelEntry>(name, std::move(exists),
std::move(createModel));
}
virtual std::unique_ptr<ViewEntry> MakeViewEntry(wpi::StringRef name,
ModelEntry* modelEntry,
ViewExistsFunc exists,
CreateViewFunc createView) {
return std::make_unique<ViewEntry>(name, modelEntry, std::move(exists),
std::move(createView));
}
virtual void Show(ViewEntry* entry, Window* window) = 0;
};
} // namespace glass
#include "Provider.inc"

View File

@@ -0,0 +1,90 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <utility>
namespace glass {
template <typename Functions>
void Provider<Functions>::GlobalInit() {
WindowManager::GlobalInit();
wpi::gui::AddEarlyExecute([this] { Update(); });
}
template <typename Functions>
void Provider<Functions>::ShowDefault(wpi::StringRef name) {
auto win = GetWindow(name);
if (win) return;
auto it = FindViewEntry(name);
if (it == m_viewEntries.end() || (*it)->name != name) return;
this->Show(it->get(), (*it)->window);
}
template <typename Functions>
void Provider<Functions>::Register(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel,
CreateViewFunc createView) {
RegisterModel(name, std::move(exists), std::move(createModel));
RegisterView(name, name, nullptr, std::move(createView));
}
template <typename Functions>
void Provider<Functions>::RegisterModel(wpi::StringRef name, ExistsFunc exists,
CreateModelFunc createModel) {
auto it = FindModelEntry(name);
// ignore if exists
if (it != m_modelEntries.end() && (*it)->name == name) return;
// insert in sorted location
m_modelEntries.emplace(
it, MakeModelEntry(name, std::move(exists), std::move(createModel)));
}
template <typename Functions>
void Provider<Functions>::RegisterView(wpi::StringRef name,
wpi::StringRef modelName,
ViewExistsFunc exists,
CreateViewFunc createView) {
// find model; if model doesn't exist, ignore
auto modelIt = FindModelEntry(modelName);
if (modelIt == m_modelEntries.end() || (*modelIt)->name != modelName) return;
auto viewIt = FindViewEntry(name);
// ignore if exists
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) return;
// insert in sorted location
m_viewEntries.emplace(viewIt,
MakeViewEntry(name, modelIt->get(), std::move(exists),
std::move(createView)));
}
template <typename Functions>
void Provider<Functions>::Update() {
// update entries
for (auto&& entry : m_modelEntries) {
if (entry->model) entry->model->Update();
}
}
template <typename Functions>
typename Provider<Functions>::ModelEntries::iterator
Provider<Functions>::FindModelEntry(wpi::StringRef name) {
return std::lower_bound(
m_modelEntries.begin(), m_modelEntries.end(), name,
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
}
template <typename Functions>
typename Provider<Functions>::ViewEntries::iterator
Provider<Functions>::FindViewEntry(wpi::StringRef name) {
return std::lower_bound(
m_viewEntries.begin(), m_viewEntries.end(), name,
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
}
} // namespace glass

View File

@@ -0,0 +1,51 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <wpi/FunctionExtras.h>
namespace glass {
/**
* A view is the contents of a window (1:1 mapping).
* It may reference multiple models.
*
* Typically a view is constructed by a Provider and the View's constructor
* is given the corresponding Model(s).
*
* A view may retain a reference to its parent window for dynamic
* window configuration.
*/
class View {
public:
virtual ~View() = default;
/**
* Displays the window contents. Called by Window::Display() from within an
* ImGui::Begin() / ImGui::End() block.
*/
virtual void Display() = 0;
/**
* Called instead of Display() when the window is hidden (e.g. when
* ImGui::Begin() returns false).
*/
virtual void Hidden();
};
/**
* Make a View for a display functor.
*
* @param display Display function
* @return unique_ptr to View
*/
std::unique_ptr<View> MakeFunctionView(wpi::unique_function<void()> display);
} // namespace glass

View File

@@ -0,0 +1,134 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <utility>
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include "glass/View.h"
namespace glass {
/**
* Managed window information.
* A Window owns the View that displays the window's contents.
*/
class Window {
public:
Window() = default;
explicit Window(wpi::StringRef id) : m_id{id} {}
wpi::StringRef GetId() const { return m_id; }
enum Visibility { kHide = 0, kShow, kDisabled };
bool HasView() { return static_cast<bool>(m_view); }
void SetView(std::unique_ptr<View> view) { m_view = std::move(view); }
View* GetView() { return m_view.get(); }
const View* GetView() const { return m_view.get(); }
bool IsVisible() const { return m_visible; }
void SetVisible(bool visible) { m_visible = visible; }
bool IsEnabled() const { return m_enabled; }
void SetEnabled(bool enabled) { m_enabled = enabled; }
void SetFlags(ImGuiWindowFlags flags) { m_flags = flags; }
void SetName(const wpi::Twine& name) { m_name = name.str(); }
/**
* Normally windows provide a right-click popup menu on the title bar to
* rename the window. Calling this disables that functionality so the
* view can provide its own popup.
*/
void DisableRenamePopup() { m_renamePopupEnabled = false; }
/**
* Sets visibility of window.
*
* @param visibility 0=hide, 1=show, 2=disabled (force-hide)
*/
void SetVisibility(Visibility visibility);
/**
* Sets default position of window.
*
* @param x x location of upper left corner
* @param y y location of upper left corner
*/
void SetDefaultPos(float x, float y) {
m_posCond = ImGuiCond_FirstUseEver;
m_pos = ImVec2{x, y};
}
/**
* Sets default size of window.
*
* @param width width
* @param height height
*/
void SetDefaultSize(float width, float height) {
m_sizeCond = ImGuiCond_FirstUseEver;
m_size = ImVec2{width, height};
}
/**
* Sets internal padding of window.
* @param x horizontal padding
* @param y vertical padding
*/
void SetPadding(float x, float y) {
m_setPadding = true;
m_padding = ImVec2{x, y};
}
/**
* Displays window.
*/
void Display();
/**
* Displays menu item for the window.
* @param label what to display as the menu item label; defaults to
* window ID if nullptr
* @return True if window went from invisible to visible.
*/
bool DisplayMenuItem(const char* label = nullptr);
/**
* Scale default window position and size.
*/
void ScaleDefault(float scale);
void IniReadLine(const char* lineStr);
void IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf);
private:
std::string m_id;
std::string m_name;
std::unique_ptr<View> m_view;
ImGuiWindowFlags m_flags = 0;
bool m_visible = true;
bool m_enabled = true;
bool m_renamePopupEnabled = true;
ImGuiCond m_posCond = 0;
ImGuiCond m_sizeCond = 0;
ImVec2 m_pos;
ImVec2 m_size;
bool m_setPadding = false;
ImVec2 m_padding;
};
} // namespace glass

View File

@@ -0,0 +1,141 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <memory>
#include <type_traits>
#include <vector>
#include <imgui.h>
#include <wpi/FunctionExtras.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include "glass/Window.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
/**
* Window manager.
*
* To properly integrate into an application:
* - Call GlobalInit() from the application main, after calling
* wpi::gui::CreateContext(), but before calling wpi::gui::Initialize().
* - Add DisplayMenu() to the application's MainMenuBar.
*/
class WindowManager {
public:
/**
* Constructor.
*
* @param iniName Group name to use in ini file
*/
explicit WindowManager(const wpi::Twine& iniName);
virtual ~WindowManager() = default;
WindowManager(const WindowManager&) = delete;
WindowManager& operator=(const WindowManager&) = delete;
/**
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
virtual void GlobalInit();
/**
* Displays menu contents, one item for each window.
* See Window::DisplayMenuItem().
*/
virtual void DisplayMenu();
/**
* Adds window to GUI. The display function is called from within a
* ImGui::Begin()/End() block. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (title bar)
* @param display window contents display function
*/
Window* AddWindow(wpi::StringRef id, wpi::unique_function<void()> display);
/**
* Adds window to GUI. The view's display function is called from within a
* ImGui::Begin()/End() block. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (title bar)
* @param view view object
* @return Window, or nullptr on duplicate window
*/
Window* AddWindow(wpi::StringRef id, std::unique_ptr<View> view);
/**
* Adds window to GUI. A View must be assigned to the returned Window
* to display the window contents. While windows can be created within the
* execute function passed to gui::AddExecute(), using this function ensures
* the windows are consistently integrated with the rest of the GUI.
*
* On each Dear ImGui frame, gui::AddExecute() functions are always called
* prior to AddWindow display functions. Note that windows may be shaded or
* completely hidden, in which case this function will not be called.
* It's important to perform any processing steps that must be performed
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (default title bar)
* @return Window, or nullptr on duplicate window
*/
Window* GetOrAddWindow(wpi::StringRef id, bool duplicateOk = false);
/**
* Gets existing window. If none exists, returns nullptr.
*
* @param id unique identifier of the window (default title bar)
* @return Window, or nullptr if window does not exist
*/
Window* GetWindow(wpi::StringRef id);
protected:
virtual void DisplayWindows();
// kept sorted by id
std::vector<std::unique_ptr<Window>> m_windows;
private:
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(const wpi::Twine& typeName, WindowManager* manager)
: IniSaverBase{typeName}, m_manager{manager} {}
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
private:
WindowManager* m_manager;
};
IniSaver m_iniSaver;
};
} // namespace glass

View File

@@ -0,0 +1,32 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "glass/Model.h"
namespace glass {
class DataSource;
class AccelerometerModel : public Model {
public:
virtual DataSource* GetXData() = 0;
virtual DataSource* GetYData() = 0;
virtual DataSource* GetZData() = 0;
virtual int GetRange() = 0;
virtual void SetX(double val) = 0;
virtual void SetY(double val) = 0;
virtual void SetZ(double val) = 0;
virtual void SetRange(int val) = 0;
};
void DisplayAccelerometerDevice(AccelerometerModel* model);
} // namespace glass

View File

@@ -0,0 +1,36 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class AnalogGyroModel : public Model {
public:
virtual DataSource* GetAngleData() = 0;
virtual DataSource* GetRateData() = 0;
virtual void SetAngle(double val) = 0;
virtual void SetRate(double val) = 0;
};
class AnalogGyrosModel : public Model {
public:
virtual void ForEachAnalogGyro(
wpi::function_ref<void(AnalogGyroModel& model, int index)> func) = 0;
};
void DisplayAnalogGyroDevice(AnalogGyroModel* model, int index);
void DisplayAnalogGyrosDevice(AnalogGyrosModel* model);
} // namespace glass

View File

@@ -0,0 +1,39 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class AnalogInputModel : public Model {
public:
virtual bool IsGyro() const = 0;
virtual const char* GetSimDevice() const = 0;
virtual DataSource* GetVoltageData() = 0;
virtual void SetVoltage(double val) = 0;
};
class AnalogInputsModel : public Model {
public:
virtual void ForEachAnalogInput(
wpi::function_ref<void(AnalogInputModel& model, int index)> func) = 0;
};
void DisplayAnalogInput(AnalogInputModel* model, int index);
void DisplayAnalogInputs(AnalogInputsModel* model,
wpi::StringRef noneMsg = "No analog inputs");
} // namespace glass

View File

@@ -0,0 +1,33 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class AnalogOutputModel : public Model {
public:
virtual DataSource* GetVoltageData() = 0;
virtual void SetVoltage(double val) = 0;
};
class AnalogOutputsModel : public Model {
public:
virtual void ForEachAnalogOutput(
wpi::function_ref<void(AnalogOutputModel& model, int index)> func) = 0;
};
void DisplayAnalogOutputsDevice(AnalogOutputsModel* model);
} // namespace glass

View File

@@ -0,0 +1,65 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class EncoderModel;
class DataSource;
class DPWMModel : public Model {
public:
virtual const char* GetSimDevice() const = 0;
virtual DataSource* GetValueData() = 0;
virtual void SetValue(double val) = 0;
};
class DutyCycleModel : public Model {
public:
virtual const char* GetSimDevice() const = 0;
virtual DataSource* GetValueData() = 0;
virtual void SetValue(double val) = 0;
};
class DIOModel : public Model {
public:
virtual const char* GetName() const = 0;
virtual const char* GetSimDevice() const = 0;
virtual DPWMModel* GetDPWM() = 0;
virtual DutyCycleModel* GetDutyCycle() = 0;
virtual EncoderModel* GetEncoder() = 0;
virtual bool IsInput() const = 0;
virtual DataSource* GetValueData() = 0;
virtual void SetValue(bool val) = 0;
};
class DIOsModel : public Model {
public:
virtual void ForEachDIO(
wpi::function_ref<void(DIOModel& model, int index)> func) = 0;
};
void DisplayDIO(DIOModel* model, int index, bool outputsEnabled);
void DisplayDIOs(DIOsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg = "No Digital I/O");
} // namespace glass

View File

@@ -0,0 +1,59 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class EncoderModel : public Model {
public:
virtual void SetName(const wpi::Twine& name);
virtual const char* GetSimDevice() const = 0;
virtual int GetChannelA() const = 0;
virtual int GetChannelB() const = 0;
virtual DataSource* GetDistancePerPulseData() = 0;
virtual DataSource* GetCountData() = 0;
virtual DataSource* GetPeriodData() = 0;
virtual DataSource* GetDirectionData() = 0;
virtual DataSource* GetDistanceData() = 0;
virtual DataSource* GetRateData() = 0;
virtual double GetMaxPeriod() = 0;
virtual bool GetReverseDirection() = 0;
virtual void SetDistancePerPulse(double val) = 0;
virtual void SetCount(int val) = 0;
virtual void SetPeriod(double val) = 0;
virtual void SetDirection(bool val) = 0;
virtual void SetDistance(double val) = 0;
virtual void SetRate(double val) = 0;
virtual void SetMaxPeriod(double val) = 0;
virtual void SetReverseDirection(bool val) = 0;
};
class EncodersModel : public Model {
public:
virtual void ForEachEncoder(
wpi::function_ref<void(EncoderModel& model, int index)> func) = 0;
};
void DisplayEncoder(EncoderModel* model);
void DisplayEncoders(EncodersModel* model,
wpi::StringRef noneMsg = "No encoders");
} // namespace glass

View File

@@ -0,0 +1,47 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/ArrayRef.h>
#include <wpi/STLExtras.h>
#include "glass/Model.h"
namespace wpi {
template <typename T>
class SmallVectorImpl;
} // namespace wpi
namespace glass {
class LEDDisplayModel : public glass::Model {
public:
struct Data {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t padding;
};
virtual bool IsRunning() = 0;
virtual wpi::ArrayRef<Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
};
class LEDDisplaysModel : public glass::Model {
public:
virtual size_t GetNumLEDDisplays() = 0;
virtual void ForEachLEDDisplay(
wpi::function_ref<void(LEDDisplayModel& model, int index)> func) = 0;
};
void DisplayLEDDisplay(LEDDisplayModel* model, int index);
void DisplayLEDDisplays(LEDDisplaysModel* model);
} // namespace glass

View File

@@ -0,0 +1,62 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class CompressorModel : public Model {
public:
virtual DataSource* GetRunningData() = 0;
virtual DataSource* GetEnabledData() = 0;
virtual DataSource* GetPressureSwitchData() = 0;
virtual DataSource* GetCurrentData() = 0;
virtual void SetRunning(bool val) = 0;
virtual void SetEnabled(bool val) = 0;
virtual void SetPressureSwitch(bool val) = 0;
virtual void SetCurrent(double val) = 0;
};
class SolenoidModel : public Model {
public:
virtual DataSource* GetOutputData() = 0;
virtual void SetOutput(bool val) = 0;
};
class PCMModel : public Model {
public:
virtual CompressorModel* GetCompressor() = 0;
virtual void ForEachSolenoid(
wpi::function_ref<void(SolenoidModel& model, int index)> func) = 0;
};
class PCMsModel : public Model {
public:
virtual void ForEachPCM(
wpi::function_ref<void(PCMModel& model, int index)> func) = 0;
};
bool DisplayPCMSolenoids(PCMModel* model, int index, bool outputsEnabled);
void DisplayPCMsSolenoids(PCMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg = "No solenoids");
void DisplayCompressorDevice(PCMModel* model, int index, bool outputsEnabled);
void DisplayCompressorDevice(CompressorModel* model, int index,
bool outputsEnabled);
void DisplayCompressorsDevice(PCMsModel* model, bool outputsEnabled);
} // namespace glass

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class PDPModel : public Model {
public:
virtual int GetNumChannels() const = 0;
virtual DataSource* GetTemperatureData() = 0;
virtual DataSource* GetVoltageData() = 0;
virtual DataSource* GetCurrentData(int channel) = 0;
virtual void SetTemperature(double val) = 0;
virtual void SetVoltage(double val) = 0;
virtual void SetCurrent(int channel, double val) = 0;
};
class PDPsModel : public Model {
public:
virtual void ForEachPDP(
wpi::function_ref<void(PDPModel& model, int index)> func) = 0;
};
void DisplayPDP(PDPModel* model, int index);
void DisplayPDPs(PDPsModel* model, wpi::StringRef noneMsg = "No PDPs");
} // namespace glass

View File

@@ -0,0 +1,39 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class PWMModel : public Model {
public:
// returns -1 if not an addressable LED
virtual int GetAddressableLED() const = 0;
virtual DataSource* GetSpeedData() = 0;
virtual void SetSpeed(double val) = 0;
};
class PWMsModel : public Model {
public:
virtual void ForEachPWM(
wpi::function_ref<void(PWMModel& model, int index)> func) = 0;
};
void DisplayPWM(PWMModel* model, int index, bool outputsEnabled);
void DisplayPWMs(PWMsModel* model, bool outputsEnabled,
wpi::StringRef noneMsg = "No PWM outputs");
} // namespace glass

View File

@@ -0,0 +1,38 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
class RelayModel : public Model {
public:
virtual DataSource* GetForwardData() = 0;
virtual DataSource* GetReverseData() = 0;
virtual void SetForward(bool val) = 0;
virtual void SetReverse(bool val) = 0;
};
class RelaysModel : public Model {
public:
virtual void ForEachRelay(
wpi::function_ref<void(RelayModel& model, int index)> func) = 0;
};
void DisplayRelay(RelayModel* model, int index, bool outputsEnabled);
void DisplayRelays(RelaysModel* model, bool outputsEnabled,
wpi::StringRef noneMsg = "No relays");
} // namespace glass

View File

@@ -0,0 +1,46 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "glass/Model.h"
namespace glass {
class DataSource;
class RoboRioRailModel : public Model {
public:
virtual DataSource* GetVoltageData() = 0;
virtual DataSource* GetCurrentData() = 0;
virtual DataSource* GetActiveData() = 0;
virtual DataSource* GetFaultsData() = 0;
virtual void SetVoltage(double val) = 0;
virtual void SetCurrent(double val) = 0;
virtual void SetActive(bool val) = 0;
virtual void SetFaults(int val) = 0;
};
class RoboRioModel : public Model {
public:
virtual RoboRioRailModel* GetUser6VRail() = 0;
virtual RoboRioRailModel* GetUser5VRail() = 0;
virtual RoboRioRailModel* GetUser3V3Rail() = 0;
virtual DataSource* GetUserButton() = 0;
virtual DataSource* GetVInVoltageData() = 0;
virtual DataSource* GetVInCurrentData() = 0;
virtual void SetUserButton(bool val) = 0;
virtual void SetVInVoltage(double val) = 0;
virtual void SetVInCurrent(double val) = 0;
};
void DisplayRoboRio(RoboRioModel* model);
} // namespace glass

View File

@@ -0,0 +1,143 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <utility>
#include <vector>
#include <imgui.h>
#include <wpi/FunctionExtras.h>
#include "glass/Model.h"
namespace glass {
class DataSource;
/**
* Model for device tree.
*/
class DeviceTreeModel : public Model {
public:
using DisplayFunc = wpi::unique_function<void(Model*)>;
/**
* Add a display to the device tree.
*
* @param model Model to keep updated (may be nullptr)
* @param display Display function
*/
void Add(std::unique_ptr<Model> model, DisplayFunc display) {
m_displays.emplace_back(model.get(), std::move(display));
m_ownedModels.emplace_back(std::move(model));
}
void Add(Model* model, DisplayFunc display) {
m_displays.emplace_back(model, std::move(display));
}
void Update() override;
bool Exists() override;
void Display();
private:
std::vector<std::pair<Model*, DisplayFunc>> m_displays;
std::vector<std::unique_ptr<Model>> m_ownedModels;
};
/**
* Hides device on tree.
*
* @param id device name
*/
void HideDevice(const char* id);
/**
* Wraps CollapsingHeader() to provide both hiding functionality and open
* persistence. As with the ImGui function, returns true if the tree node
* is visible and expanded. If returns true, call EndDevice() to finish
* the block.
*
* @param id label
* @param flags ImGuiTreeNodeFlags flags
* @return True if expanded
*/
bool BeginDevice(const char* id, ImGuiTreeNodeFlags flags = 0);
/**
* Finish a device block started with BeginDevice().
*/
void EndDevice();
/**
* Displays device value.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param source data source for drag source (may be nullptr)
* @return True if value was modified by the user
*/
bool DeviceBoolean(const char* name, bool readonly, bool* value,
const DataSource* source = nullptr);
/**
* Displays device value.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param source data source for drag source (may be nullptr)
* @return True if value was modified by the user
*/
bool DeviceDouble(const char* name, bool readonly, double* value,
const DataSource* source = nullptr);
/**
* Displays device value.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param options options array
* @param numOptions size of options array
* @param source data source for drag source (may be nullptr)
* @return True if value was modified by the user
*/
bool DeviceEnum(const char* name, bool readonly, int* value,
const char** options, int32_t numOptions,
const DataSource* source = nullptr);
/**
* Displays device value.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param source data source for drag source (may be nullptr)
* @return True if value was modified by the user
*/
bool DeviceInt(const char* name, bool readonly, int32_t* value,
const DataSource* source = nullptr);
/**
* Displays device value.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param source data source for drag source (may be nullptr)
* @return True if value was modified by the user
*/
bool DeviceLong(const char* name, bool readonly, int64_t* value,
const DataSource* source = nullptr);
} // namespace glass

View File

@@ -0,0 +1,56 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace wpi {
template <typename T>
class SmallVectorImpl;
} // namespace wpi
namespace glass {
class DataSource;
class FMSModel : public Model {
public:
virtual DataSource* GetFmsAttachedData() = 0;
virtual DataSource* GetDsAttachedData() = 0;
virtual DataSource* GetAllianceStationIdData() = 0;
virtual DataSource* GetMatchTimeData() = 0;
virtual DataSource* GetEStopData() = 0;
virtual DataSource* GetEnabledData() = 0;
virtual DataSource* GetTestData() = 0;
virtual DataSource* GetAutonomousData() = 0;
virtual wpi::StringRef GetGameSpecificMessage(
wpi::SmallVectorImpl<char>& buf) = 0;
virtual void SetFmsAttached(bool val) = 0;
virtual void SetDsAttached(bool val) = 0;
virtual void SetAllianceStationId(int val) = 0;
virtual void SetMatchTime(double val) = 0;
virtual void SetEStop(bool val) = 0;
virtual void SetEnabled(bool val) = 0;
virtual void SetTest(bool val) = 0;
virtual void SetAutonomous(bool val) = 0;
virtual void SetGameSpecificMessage(const char* val) = 0;
};
/**
* Displays FMS view.
*
* @param matchTimeEnabled If not null, a checkbox is displayed for
* "enable match time" linked to this value
*/
void DisplayFMS(FMSModel* model, bool* matchTimeEnabled = nullptr);
void DisplayFMSReadOnly(FMSModel* model);
} // namespace glass

View File

@@ -0,0 +1,58 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <imgui.h>
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
#include "glass/View.h"
namespace glass {
class DataSource;
class FieldObjectModel : public Model {
public:
virtual DataSource* GetXData() = 0;
virtual DataSource* GetYData() = 0;
virtual DataSource* GetRotationData() = 0;
virtual void SetPose(double x, double y, double rot) = 0;
virtual void SetPosition(double x, double y) = 0;
virtual void SetRotation(double rot) = 0;
};
class FieldObjectGroupModel : public Model {
public:
virtual void ForEachFieldObject(
wpi::function_ref<void(FieldObjectModel& model)> func) = 0;
};
class Field2DModel : public Model {
public:
virtual void ForEachFieldObjectGroup(
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
func) = 0;
};
void DisplayField2D(Field2DModel* model, const ImVec2& contentSize);
void DisplayField2DSettings(Field2DModel* model);
class Field2DView : public View {
public:
explicit Field2DView(Field2DModel* model) : m_model{model} {}
void Display() override;
private:
Field2DModel* m_model;
};
} // namespace glass

View File

@@ -0,0 +1,67 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "glass/WindowManager.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
class PlotProvider : private WindowManager {
public:
explicit PlotProvider(const wpi::Twine& iniName);
~PlotProvider() override;
void GlobalInit() override;
/**
* Pauses or unpauses all plots.
*
* @param paused true to pause, false to unpause
*/
void SetPaused(bool paused) { m_paused = paused; }
/**
* Returns true if all plots are paused.
*/
bool IsPaused() { return m_paused; }
/**
* Resets time on all plots such that 0 = time when this function is called.
* Also clears the plot data.
*/
void ResetTime();
uint64_t GetStartTime() const { return m_startTime; }
void DisplayMenu() override;
private:
void DisplayWindows() override;
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(const wpi::Twine& typeName, PlotProvider* provider,
bool forSeries);
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
private:
PlotProvider* m_provider;
bool m_forSeries;
};
IniSaver m_plotSaver;
IniSaver m_seriesSaver;
uint64_t m_startTime = 0;
bool m_paused = false;
};
} // namespace glass

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <wpi/ArrayRef.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
namespace glass {
class StringChooserModel : public Model {
public:
virtual const std::string& GetDefault() = 0;
virtual const std::string& GetSelected() = 0;
virtual const std::string& GetActive() = 0;
virtual const std::vector<std::string>& GetOptions() = 0;
virtual void SetDefault(wpi::StringRef val) = 0;
virtual void SetSelected(wpi::StringRef val) = 0;
virtual void SetActive(wpi::StringRef val) = 0;
virtual void SetOptions(wpi::ArrayRef<std::string> val) = 0;
};
void DisplayStringChooser(StringChooserModel* model);
} // namespace glass

View File

@@ -9,9 +9,9 @@
#include <imgui.h>
namespace halsimgui {
namespace glass {
class GuiDataSource;
class DataSource;
/**
* DrawLEDs() configuration for 2D arrays.
@@ -81,9 +81,19 @@ void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
* if 0, defaults to 1/3 of font size
* @param config 2D array configuration
*/
void DrawLEDSources(const int* values, GuiDataSource** sources, int numValues,
void DrawLEDSources(const int* values, DataSource** sources, int numValues,
int cols, const ImU32* colors, float size = 0.0f,
float spacing = 0.0f,
const LEDConfig& config = LEDConfig{});
} // namespace halsimgui
/**
* Delete button (X in circle), based on ImGui::CloseButton().
*/
bool DeleteButton(ImGuiID id, const ImVec2& pos);
/**
* Create a small overlapping delete button for collapsing headers.
*/
bool HeaderDeleteButton(const char* label);
} // namespace glass

View File

@@ -8,16 +8,19 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/DenseMap.h>
#include <wpi/Twine.h>
namespace halsimgui {
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaver {
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(const char* typeName) : m_typeName(typeName) {}
void Initialize();
explicit IniSaver(const wpi::Twine& typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
// pass through useful functions to map
Info& operator[](int index) { return m_map[index]; }
@@ -31,17 +34,13 @@ class IniSaver {
auto find(int index) const { return m_map.find(index); }
private:
static void* ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name);
static void ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr);
static void WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf);
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
const char* m_typeName;
wpi::DenseMap<int, Info> m_map;
};
} // namespace halsimgui
} // namespace glass
#include "IniSaver.inl"

View File

@@ -0,0 +1,40 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/StringRef.h>
namespace glass {
template <typename Info>
void* IniSaver<Info>::IniReadOpen(const char* name) {
int num;
if (wpi::StringRef{name}.getAsInteger(10, num)) return nullptr;
return &m_map[num];
}
template <typename Info>
void IniSaver<Info>::IniReadLine(void* entry, const char* lineStr) {
auto element = static_cast<Info*>(entry);
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
element->ReadIni(name, value);
}
template <typename Info>
void IniSaver<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& it : m_map) {
out_buf->appendf("[%s][%d]\n", GetTypeName(), it.first);
it.second.WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -0,0 +1,49 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <imgui.h>
#include <wpi/Twine.h>
namespace glass {
class IniSaverBase;
class IniSaverBackend {
public:
virtual ~IniSaverBackend() = default;
virtual void Register(IniSaverBase* iniSaver) = 0;
virtual void Unregister(IniSaverBase* iniSaver) = 0;
};
class IniSaverBase {
public:
explicit IniSaverBase(const wpi::Twine& typeName,
IniSaverBackend* backend = nullptr);
virtual ~IniSaverBase();
void Initialize() { m_backend->Register(this); }
const char* GetTypeName() const { return m_typeName.c_str(); }
IniSaverBackend* GetBackend() const { return m_backend; }
IniSaverBase(const IniSaverBase&) = delete;
IniSaverBase& operator=(const IniSaverBase&) = delete;
virtual void* IniReadOpen(const char* name) = 0;
virtual void IniReadLine(void* entry, const char* lineStr) = 0;
virtual void IniWriteAll(ImGuiTextBuffer* out_buf) = 0;
private:
std::string m_typeName;
IniSaverBackend* m_backend;
};
} // namespace glass

View File

@@ -9,14 +9,16 @@
#include <imgui.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
namespace halsimgui {
namespace glass {
class NameInfo {
public:
NameInfo() { m_name[0] = '\0'; }
bool HasName() const { return m_name[0] != '\0'; }
void SetName(const wpi::Twine& name);
const char* GetName() const { return m_name; }
void GetName(char* buf, size_t size, const char* defaultName) const;
void GetName(char* buf, size_t size, const char* defaultName,
@@ -55,4 +57,10 @@ class OpenInfo {
bool m_open = false;
};
} // namespace halsimgui
class NameOpenInfo : public NameInfo, public OpenInfo {
public:
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
};
} // namespace glass

View File

@@ -7,22 +7,35 @@
#pragma once
#include <utility>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/StringMap.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
namespace halsimgui {
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaverString {
class IniSaverString : public IniSaverBase {
public:
explicit IniSaverString(const char* typeName) : m_typeName(typeName) {}
void Initialize();
explicit IniSaverString(const wpi::Twine& typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
// pass through useful functions to map
Info& operator[](wpi::StringRef key) { return m_map[key]; }
template <typename... ArgsTy>
auto try_emplace(wpi::StringRef key, ArgsTy&&... args) {
return m_map.try_emplace(key, std::forward<ArgsTy>(args)...);
}
void erase(typename wpi::StringMap<Info>::iterator it) { m_map.erase(it); }
auto erase(wpi::StringRef key) { return m_map.erase(key); }
auto begin() { return m_map.begin(); }
auto end() { return m_map.end(); }
auto find(wpi::StringRef key) { return m_map.find(key); }
@@ -31,18 +44,16 @@ class IniSaverString {
auto end() const { return m_map.end(); }
auto find(wpi::StringRef key) const { return m_map.find(key); }
private:
static void* ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name);
static void ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr);
static void WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf);
bool empty() const { return m_map.empty(); }
private:
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
const char* m_typeName;
wpi::StringMap<Info> m_map;
};
} // namespace halsimgui
} // namespace glass
#include "IniSaverString.inl"

View File

@@ -0,0 +1,38 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/StringRef.h>
namespace glass {
template <typename Info>
void* IniSaverString<Info>::IniReadOpen(const char* name) {
return &m_map[name];
}
template <typename Info>
void IniSaverString<Info>::IniReadLine(void* entry, const char* lineStr) {
auto element = static_cast<Info*>(entry);
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
element->ReadIni(name, value);
}
template <typename Info>
void IniSaverString<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& it : m_map) {
out_buf->appendf("[%s][%s]\n", GetTypeName(), it.getKey().data());
it.second.WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -10,27 +10,25 @@
#include <vector>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/Twine.h>
namespace halsimgui {
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaverVector : public std::vector<Info> {
class IniSaverVector : public std::vector<Info>, public IniSaverBase {
public:
explicit IniSaverVector(const char* typeName) : m_typeName(typeName) {}
void Initialize();
explicit IniSaverVector(const wpi::Twine& typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
private:
static void* ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name);
static void ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr);
static void WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf);
const char* m_typeName;
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
};
} // namespace halsimgui
} // namespace glass
#include "IniSaverVector.inl"

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <wpi/StringRef.h>
namespace glass {
template <typename Info>
void* IniSaverVector<Info>::IniReadOpen(const char* name) {
unsigned int num;
if (wpi::StringRef{name}.getAsInteger(10, num)) return nullptr;
if (num >= this->size()) this->resize(num + 1);
return &(*this)[num];
}
template <typename Info>
void IniSaverVector<Info>::IniReadLine(void* entry, const char* lineStr) {
auto element = static_cast<Info*>(entry);
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
element->ReadIni(name, value);
}
template <typename Info>
void IniSaverVector<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (size_t i = 0; i < this->size(); ++i) {
out_buf->appendf("[%s][%d]\n", GetTypeName(), static_cast<int>(i));
(*this)[i].WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -0,0 +1,46 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTDigitalInput.h"
#include <wpi/Twine.h>
using namespace glass;
NTDigitalInputModel::NTDigitalInputModel(wpi::StringRef path)
: NTDigitalInputModel{nt::GetDefaultInstance(), path} {}
NTDigitalInputModel::NTDigitalInputModel(NT_Inst inst, wpi::StringRef path)
: m_nt{inst},
m_value{m_nt.GetEntry(path + "/Value")},
m_name{m_nt.GetEntry(path + "/.name")},
m_valueData{"NT_DIn:" + path},
m_nameValue{path.rsplit('/').second} {
m_nt.AddListener(m_value);
m_nt.AddListener(m_name);
m_valueData.SetDigital(true);
Update();
}
void NTDigitalInputModel::Update() {
for (auto&& event : m_nt.PollListener()) {
if (event.entry == m_value) {
if (event.value && event.value->IsBoolean()) {
m_valueData.SetValue(event.value->GetBoolean());
}
} else if (event.entry == m_name) {
if (event.value && event.value->IsString()) {
m_nameValue = event.value->GetString();
}
}
}
}
bool NTDigitalInputModel::Exists() {
return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
}

View File

@@ -0,0 +1,55 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTDigitalOutput.h"
#include <wpi/Twine.h>
using namespace glass;
NTDigitalOutputModel::NTDigitalOutputModel(wpi::StringRef path)
: NTDigitalOutputModel{nt::GetDefaultInstance(), path} {}
NTDigitalOutputModel::NTDigitalOutputModel(NT_Inst inst, wpi::StringRef path)
: m_nt{inst},
m_value{m_nt.GetEntry(path + "/Value")},
m_name{m_nt.GetEntry(path + "/.name")},
m_controllable{m_nt.GetEntry(path + "/.controllable")},
m_valueData{"NT_DOut:" + path} {
m_nt.AddListener(m_value);
m_nt.AddListener(m_name);
m_nt.AddListener(m_controllable);
m_valueData.SetDigital(true);
Update();
}
void NTDigitalOutputModel::SetValue(bool val) {
nt::SetEntryValue(m_value, nt::Value::MakeBoolean(val));
}
void NTDigitalOutputModel::Update() {
for (auto&& event : m_nt.PollListener()) {
if (event.entry == m_value) {
if (event.value && event.value->IsBoolean()) {
m_valueData.SetValue(event.value->GetBoolean());
}
} else if (event.entry == m_name) {
if (event.value && event.value->IsString()) {
m_nameValue = event.value->GetString();
}
} else if (event.entry == m_controllable) {
if (event.value && event.value->IsBoolean()) {
m_controllableValue = event.value->GetBoolean();
}
}
}
}
bool NTDigitalOutputModel::Exists() {
return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
}

View File

@@ -0,0 +1,93 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTFMS.h"
#include <stdint.h>
#include <wpi/Twine.h>
#include <wpi/timestamp.h>
using namespace glass;
NTFMSModel::NTFMSModel(wpi::StringRef path)
: NTFMSModel{nt::GetDefaultInstance(), path} {}
NTFMSModel::NTFMSModel(NT_Inst inst, wpi::StringRef path)
: m_nt{inst},
m_gameSpecificMessage{m_nt.GetEntry(path + "/GameSpecificMessage")},
m_alliance{m_nt.GetEntry(path + "/IsRedAlliance")},
m_station{m_nt.GetEntry(path + "/StationNumber")},
m_controlWord{m_nt.GetEntry(path + "/FMSControlData")},
m_fmsAttached{"NT_FMS:FMSAttached:" + path},
m_dsAttached{"NT_FMS:DSAttached:" + path},
m_allianceStationId{"NT_FMS:AllianceStationID:" + path},
m_estop{"NT_FMS:EStop:" + path},
m_enabled{"NT_FMS:RobotEnabled:" + path},
m_test{"NT_FMS:TestMode:" + path},
m_autonomous{"NT_FMS:AutonomousMode:" + path} {
m_nt.AddListener(m_alliance);
m_nt.AddListener(m_station);
m_nt.AddListener(m_controlWord);
m_fmsAttached.SetDigital(true);
m_dsAttached.SetDigital(true);
m_estop.SetDigital(true);
m_enabled.SetDigital(true);
m_test.SetDigital(true);
m_autonomous.SetDigital(true);
Update();
}
wpi::StringRef NTFMSModel::GetGameSpecificMessage(
wpi::SmallVectorImpl<char>& buf) {
buf.clear();
auto value = nt::GetEntryValue(m_gameSpecificMessage);
if (value && value->IsString()) {
auto str = value->GetString();
buf.append(str.begin(), str.end());
}
return wpi::StringRef{buf.data(), buf.size()};
}
void NTFMSModel::Update() {
for (auto&& event : m_nt.PollListener()) {
if (event.entry == m_alliance) {
if (event.value && event.value->IsBoolean()) {
int allianceStationId = m_allianceStationId.GetValue();
allianceStationId %= 3;
// true if red
allianceStationId += 3 * (event.value->GetBoolean() ? 0 : 1);
m_allianceStationId.SetValue(allianceStationId);
}
} else if (event.entry == m_station) {
if (event.value && event.value->IsDouble()) {
int allianceStationId = m_allianceStationId.GetValue();
bool isRed = (allianceStationId < 3);
// the NT value is 1-indexed
m_allianceStationId.SetValue(event.value->GetDouble() - 1 +
3 * (isRed ? 0 : 1));
}
} else if (event.entry == m_controlWord) {
if (event.value && event.value->IsDouble()) {
uint32_t controlWord = event.value->GetDouble();
// See HAL_ControlWord definition
auto time = wpi::Now();
m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, time);
m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, time);
m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, time);
m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, time);
m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, time);
m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, time);
}
}
}
}
bool NTFMSModel::Exists() {
return m_nt.IsConnected() && nt::GetEntryType(m_controlWord) != NT_UNASSIGNED;
}

View File

@@ -0,0 +1,236 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTField2D.h"
#include <algorithm>
#include <ntcore_cpp.h>
#include <wpi/SmallVector.h>
#include "glass/DataSource.h"
using namespace glass;
class NTField2DModel::GroupModel : public FieldObjectGroupModel {
public:
GroupModel(wpi::StringRef name, NT_Entry entry)
: m_name{name}, m_entry{entry} {}
wpi::StringRef GetName() const { return m_name; }
NT_Entry GetEntry() const { return m_entry; }
void NTUpdate(const nt::Value& value);
void Update() override {
if (auto value = nt::GetEntryValue(m_entry)) NTUpdate(*value);
}
bool Exists() override { return nt::GetEntryType(m_entry) != NT_UNASSIGNED; }
bool IsReadOnly() override { return false; }
void ForEachFieldObject(
wpi::function_ref<void(FieldObjectModel& model)> func) override;
private:
std::string m_name;
NT_Entry m_entry;
// keep count of objects rather than resizing vector, as there is a fair
// amount of overhead associated with the latter (DataSource record keeping)
size_t m_count = 0;
class ObjectModel;
std::vector<std::unique_ptr<ObjectModel>> m_objects;
};
class NTField2DModel::GroupModel::ObjectModel : public FieldObjectModel {
public:
ObjectModel(wpi::StringRef name, NT_Entry entry, int index)
: m_entry{entry},
m_index{index},
m_x{name + "[" + wpi::Twine{index} + "]/x"},
m_y{name + "[" + wpi::Twine{index} + "]/y"},
m_rot{name + "[" + wpi::Twine{index} + "]/rot"} {}
void SetExists(bool exists) { m_exists = exists; }
void Update() override {}
bool Exists() override { return m_exists; }
bool IsReadOnly() override { return false; }
DataSource* GetXData() override { return &m_x; }
DataSource* GetYData() override { return &m_y; }
DataSource* GetRotationData() override { return &m_rot; }
void SetPose(double x, double y, double rot) override;
void SetPosition(double x, double y) override;
void SetRotation(double rot) override;
private:
void SetPoseImpl(double x, double y, double rot, bool setX, bool setY,
bool setRot);
NT_Entry m_entry;
int m_index;
bool m_exists = true;
public:
DataSource m_x;
DataSource m_y;
DataSource m_rot;
};
void NTField2DModel::GroupModel::NTUpdate(const nt::Value& value) {
if (!value.IsDoubleArray()) {
m_count = 0;
return;
}
auto arr = value.GetDoubleArray();
// must be triples
if ((arr.size() % 3) != 0) {
m_count = 0;
return;
}
m_count = arr.size() / 3;
if (m_count > m_objects.size()) {
m_objects.reserve(m_count);
for (size_t i = m_objects.size(); i < m_count; ++i)
m_objects.emplace_back(std::make_unique<ObjectModel>(m_name, m_entry, i));
}
if (m_count < m_objects.size()) {
for (size_t i = m_count; i < m_objects.size(); ++i)
m_objects[i]->SetExists(false);
}
for (size_t i = 0; i < m_count; ++i) {
auto& obj = m_objects[i];
obj->SetExists(true);
obj->m_x.SetValue(arr[i * 3], value.last_change());
obj->m_y.SetValue(arr[i * 3 + 1], value.last_change());
obj->m_rot.SetValue(arr[i * 3 + 2], value.last_change());
}
}
void NTField2DModel::GroupModel::ForEachFieldObject(
wpi::function_ref<void(FieldObjectModel& model)> func) {
for (size_t i = 0; i < m_count; ++i) {
func(*m_objects[i]);
}
}
void NTField2DModel::GroupModel::ObjectModel::SetPose(double x, double y,
double rot) {
SetPoseImpl(x, y, rot, true, true, true);
}
void NTField2DModel::GroupModel::ObjectModel::SetPosition(double x, double y) {
SetPoseImpl(x, y, 0, true, true, false);
}
void NTField2DModel::GroupModel::ObjectModel::SetRotation(double rot) {
SetPoseImpl(0, 0, rot, false, false, true);
}
void NTField2DModel::GroupModel::ObjectModel::SetPoseImpl(double x, double y,
double rot, bool setX,
bool setY,
bool setRot) {
// get from NT, validate type and size
auto value = nt::GetEntryValue(m_entry);
if (!value || !value->IsDoubleArray()) return;
auto origArr = value->GetDoubleArray();
if (origArr.size() < static_cast<size_t>((m_index + 1) * 3)) return;
// copy existing array
wpi::SmallVector<double, 8> arr;
arr.reserve(origArr.size());
for (auto&& elem : origArr) arr.emplace_back(elem);
// update value
if (setX) arr[m_index * 3 + 0] = x;
if (setY) arr[m_index * 3 + 1] = y;
if (setRot) arr[m_index * 3 + 2] = rot;
// set back to NT
nt::SetEntryValue(m_entry, nt::Value::MakeDoubleArray(arr));
}
NTField2DModel::NTField2DModel(wpi::StringRef path)
: NTField2DModel{nt::GetDefaultInstance(), path} {}
NTField2DModel::NTField2DModel(NT_Inst inst, wpi::StringRef path)
: m_nt{inst},
m_path{(path + "/").str()},
m_name{m_nt.GetEntry(path + "/.name")} {
m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
}
NTField2DModel::~NTField2DModel() {}
void NTField2DModel::Update() {
for (auto&& event : m_nt.PollListener()) {
// .name
if (event.entry == m_name) {
if (event.value && event.value->IsString()) {
m_nameValue = event.value->GetString();
}
continue;
}
// common case: update of existing entry; search by entry
if (event.flags & NT_NOTIFY_UPDATE) {
auto it = std::find_if(
m_groups.begin(), m_groups.end(),
[&](const auto& e) { return e->GetEntry() == event.entry; });
if (it != m_groups.end()) {
(*it)->NTUpdate(*event.value);
continue;
}
}
// handle create/delete
if (wpi::StringRef{event.name}.startswith(m_path)) {
auto name = wpi::StringRef{event.name}.drop_front(m_path.size());
if (name.empty() || name[0] == '.') continue;
auto it = std::lower_bound(m_groups.begin(), m_groups.end(), name,
[](const auto& e, wpi::StringRef name) {
return e->GetName() < name;
});
bool match = (it != m_groups.end() && (*it)->GetName() == name);
if (event.flags & NT_NOTIFY_DELETE) {
if (match) m_groups.erase(it);
continue;
} else if (event.flags & NT_NOTIFY_NEW) {
if (!match)
it = m_groups.emplace(
it, std::make_unique<GroupModel>(event.name, event.entry));
} else if (!match) {
continue;
}
if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
(*it)->NTUpdate(*event.value);
}
}
}
}
bool NTField2DModel::Exists() {
return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
}
bool NTField2DModel::IsReadOnly() { return false; }
void NTField2DModel::ForEachFieldObjectGroup(
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
func) {
for (auto&& group : m_groups) {
if (group->Exists())
func(*group, wpi::StringRef{group->GetName()}.drop_front(m_path.size()));
}
}

View File

@@ -0,0 +1,64 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTStringChooser.h"
using namespace glass;
NTStringChooserModel::NTStringChooserModel(wpi::StringRef path)
: NTStringChooserModel{nt::GetDefaultInstance(), path} {}
NTStringChooserModel::NTStringChooserModel(NT_Inst inst, wpi::StringRef path)
: m_nt{inst},
m_default{m_nt.GetEntry(path + "/default")},
m_selected{m_nt.GetEntry(path + "/selected")},
m_active{m_nt.GetEntry(path + "/active")},
m_options{m_nt.GetEntry(path + "/options")} {
m_nt.AddListener(m_default);
m_nt.AddListener(m_selected);
m_nt.AddListener(m_active);
m_nt.AddListener(m_options);
Update();
}
void NTStringChooserModel::SetDefault(wpi::StringRef val) {
nt::SetEntryValue(m_default, nt::Value::MakeString(val));
}
void NTStringChooserModel::SetSelected(wpi::StringRef val) {
nt::SetEntryValue(m_selected, nt::Value::MakeString(val));
}
void NTStringChooserModel::SetActive(wpi::StringRef val) {
nt::SetEntryValue(m_active, nt::Value::MakeString(val));
}
void NTStringChooserModel::SetOptions(wpi::ArrayRef<std::string> val) {
nt::SetEntryValue(m_options, nt::Value::MakeStringArray(val));
}
void NTStringChooserModel::Update() {
for (auto&& event : m_nt.PollListener()) {
if (event.entry == m_default && event.value && event.value->IsString()) {
m_defaultValue = event.value->GetString();
} else if (event.entry == m_selected && event.value &&
event.value->IsString()) {
m_selectedValue = event.value->GetString();
} else if (event.entry == m_active && event.value &&
event.value->IsString()) {
m_activeValue = event.value->GetString();
} else if (event.entry == m_options && event.value &&
event.value->IsStringArray()) {
m_optionsValue = event.value->GetStringArray();
}
}
}
bool NTStringChooserModel::Exists() {
return m_nt.IsConnected() && nt::GetEntryType(m_options) != NT_UNASSIGNED;
}

View File

@@ -0,0 +1,610 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NetworkTables.h"
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <vector>
#include <imgui.h>
#include <networktables/NetworkTableValue.h>
#include <ntcore_cpp.h>
#include <wpi/ArrayRef.h>
#include <wpi/Format.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
#include "glass/Context.h"
#include "glass/DataSource.h"
using namespace glass;
static std::string BooleanArrayToString(wpi::ArrayRef<int> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto v : in) {
if (!first) os << ',';
first = false;
if (v)
os << "true";
else
os << "false";
}
os << ']';
return rv;
}
static std::string DoubleArrayToString(wpi::ArrayRef<double> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto v : in) {
if (!first) os << ',';
first = false;
os << wpi::format("%.6f", v);
}
os << ']';
return rv;
}
static std::string StringArrayToString(wpi::ArrayRef<std::string> in) {
std::string rv;
wpi::raw_string_ostream os{rv};
os << '[';
bool first = true;
for (auto&& v : in) {
if (!first) os << ',';
first = false;
os << '"';
os.write_escaped(v);
os << '"';
}
os << ']';
return rv;
}
NetworkTablesModel::NetworkTablesModel()
: NetworkTablesModel{nt::GetDefaultInstance()} {}
NetworkTablesModel::NetworkTablesModel(NT_Inst inst)
: m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {
nt::AddPolledEntryListener(m_poller, "",
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
NT_NOTIFY_FLAGS | NT_NOTIFY_IMMEDIATE);
}
NetworkTablesModel::~NetworkTablesModel() {
nt::DestroyEntryListenerPoller(m_poller);
}
NetworkTablesModel::Entry::Entry(nt::EntryNotification&& event)
: entry{event.entry},
name{std::move(event.name)},
value{std::move(event.value)},
flags{nt::GetEntryFlags(event.entry)} {
UpdateValue();
}
void NetworkTablesModel::Entry::UpdateValue() {
switch (value->type()) {
case NT_BOOLEAN:
if (!source)
source = std::make_unique<DataSource>(wpi::Twine{"NT:"} + name);
source->SetValue(value->GetBoolean() ? 1 : 0);
source->SetDigital(true);
break;
case NT_DOUBLE:
if (!source)
source = std::make_unique<DataSource>(wpi::Twine{"NT:"} + name);
source->SetValue(value->GetDouble());
source->SetDigital(false);
break;
case NT_BOOLEAN_ARRAY:
valueStr = BooleanArrayToString(value->GetBooleanArray());
break;
case NT_DOUBLE_ARRAY:
valueStr = DoubleArrayToString(value->GetDoubleArray());
break;
case NT_STRING_ARRAY:
valueStr = StringArrayToString(value->GetStringArray());
break;
default:
break;
}
}
void NetworkTablesModel::Update() {
bool timedOut = false;
bool updateTree = false;
for (auto&& event : nt::PollEntryListener(m_poller, 0, &timedOut)) {
auto& entry = m_entries[event.entry];
if (event.flags & NT_NOTIFY_NEW) {
if (!entry) {
entry = std::make_unique<Entry>(std::move(event));
m_sortedEntries.emplace_back(entry.get());
updateTree = true;
}
}
if (!entry) continue;
if (event.flags & NT_NOTIFY_DELETE) {
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
entry.get());
// will be removed completely below
if (it != m_sortedEntries.end()) *it = nullptr;
m_entries.erase(event.entry);
updateTree = true;
continue;
}
if (event.flags & NT_NOTIFY_UPDATE) {
entry->value = std::move(event.value);
entry->UpdateValue();
}
if (event.flags & NT_NOTIFY_FLAGS) {
entry->flags = nt::GetEntryFlags(event.entry);
}
}
// shortcut common case (updates)
if (!updateTree) return;
// remove deleted entries
m_sortedEntries.erase(
std::remove(m_sortedEntries.begin(), m_sortedEntries.end(), nullptr),
m_sortedEntries.end());
// sort by name
std::sort(m_sortedEntries.begin(), m_sortedEntries.end(),
[](const auto& a, const auto& b) { return a->name < b->name; });
// rebuild tree
m_root.clear();
wpi::SmallVector<wpi::StringRef, 16> parts;
for (auto& entry : m_sortedEntries) {
parts.clear();
wpi::StringRef{entry->name}.split(parts, '/', -1, false);
// get to leaf
auto nodes = &m_root;
for (auto part : wpi::ArrayRef(parts.begin(), parts.end()).drop_back()) {
auto it =
std::find_if(nodes->begin(), nodes->end(),
[&](const auto& node) { return node.name == part; });
if (it == nodes->end()) {
nodes->emplace_back(part);
// path is from the beginning of the string to the end of the current
// part; this works because part is a reference to the internals of
// entry->name
nodes->back().path.assign(entry->name.data(),
part.end() - entry->name.data());
it = nodes->end() - 1;
}
nodes = &it->children;
}
auto it = std::find_if(nodes->begin(), nodes->end(), [&](const auto& node) {
return node.name == parts.back();
});
if (it == nodes->end()) {
nodes->emplace_back(parts.back());
// no need to set path, as it's identical to entry->name
it = nodes->end() - 1;
}
it->entry = entry;
}
}
bool NetworkTablesModel::Exists() { return nt::IsConnected(m_inst); }
static std::shared_ptr<nt::Value> StringToBooleanArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeBooleanArray(
std::initializer_list<bool>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
wpi::SmallVector<int, 16> out;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
if (val.equals_lower("true")) {
out.emplace_back(1);
} else if (val.equals_lower("false")) {
out.emplace_back(0);
} else {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
}
return nt::NetworkTableValue::MakeBooleanArray(out);
}
static std::shared_ptr<nt::Value> StringToDoubleArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeBooleanArray(
std::initializer_list<bool>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
wpi::SmallVector<double, 16> out;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
wpi::SmallString<32> valStr = val;
double d;
if (std::sscanf(valStr.c_str(), "%lf", &d) == 1) {
out.emplace_back(d);
} else {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
}
return nt::NetworkTableValue::MakeDoubleArray(out);
}
static int fromxdigit(char ch) {
if (ch >= 'a' && ch <= 'f')
return (ch - 'a' + 10);
else if (ch >= 'A' && ch <= 'F')
return (ch - 'A' + 10);
else
return ch - '0';
}
static wpi::StringRef UnescapeString(wpi::StringRef source,
wpi::SmallVectorImpl<char>& buf) {
assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
buf.clear();
buf.reserve(source.size() - 2);
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
if (*s != '\\') {
buf.push_back(*s);
continue;
}
switch (*++s) {
case 't':
buf.push_back('\t');
break;
case 'n':
buf.push_back('\n');
break;
case 'x': {
if (!isxdigit(*(s + 1))) {
buf.push_back('x'); // treat it like a unknown escape
break;
}
int ch = fromxdigit(*++s);
if (std::isxdigit(*(s + 1))) {
ch <<= 4;
ch |= fromxdigit(*++s);
}
buf.push_back(static_cast<char>(ch));
break;
}
default:
buf.push_back(*s);
break;
}
}
return wpi::StringRef{buf.data(), buf.size()};
}
static std::shared_ptr<nt::Value> StringToStringArray(wpi::StringRef in) {
in = in.trim();
if (in.empty())
return nt::NetworkTableValue::MakeStringArray(
std::initializer_list<std::string>{});
if (in.front() == '[') in = in.drop_front();
if (in.back() == ']') in = in.drop_back();
in = in.trim();
wpi::SmallVector<wpi::StringRef, 16> inSplit;
std::vector<std::string> out;
wpi::SmallString<32> buf;
in.split(inSplit, ',', -1, false);
for (auto val : inSplit) {
val = val.trim();
if (val.empty()) continue;
if (val.front() != '"' || val.back() != '"') {
wpi::errs() << "GUI: NetworkTables: Could not understand value '" << val
<< "'\n";
return nullptr;
}
out.emplace_back(UnescapeString(val, buf));
}
return nt::NetworkTableValue::MakeStringArray(std::move(out));
}
static void EmitEntryValueReadonly(NetworkTablesModel::Entry& entry) {
auto& val = entry.value;
if (!val) return;
switch (val->type()) {
case NT_BOOLEAN:
ImGui::LabelText("boolean", "%s", val->GetBoolean() ? "true" : "false");
break;
case NT_DOUBLE:
ImGui::LabelText("double", "%.6f", val->GetDouble());
break;
case NT_STRING: {
// GetString() comes from a std::string, so it's null terminated
ImGui::LabelText("string", "%s", val->GetString().data());
break;
}
case NT_BOOLEAN_ARRAY:
ImGui::LabelText("boolean[]", "%s", entry.valueStr.c_str());
break;
case NT_DOUBLE_ARRAY:
ImGui::LabelText("double[]", "%s", entry.valueStr.c_str());
break;
case NT_STRING_ARRAY:
ImGui::LabelText("string[]", "%s", entry.valueStr.c_str());
break;
case NT_RAW:
ImGui::LabelText("raw", "[...]");
break;
case NT_RPC:
ImGui::LabelText("rpc", "[...]");
break;
default:
ImGui::LabelText("other", "?");
break;
}
}
static constexpr size_t kTextBufferSize = 4096;
static char* GetTextBuffer(wpi::StringRef in) {
static char textBuffer[kTextBufferSize];
size_t len = (std::min)(in.size(), kTextBufferSize - 1);
std::memcpy(textBuffer, in.data(), len);
textBuffer[len] = '\0';
return textBuffer;
}
static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry) {
auto& val = entry.value;
if (!val) return;
ImGui::PushID(entry.name.c_str());
switch (val->type()) {
case NT_BOOLEAN: {
static const char* boolOptions[] = {"false", "true"};
int v = val->GetBoolean() ? 1 : 0;
if (ImGui::Combo("boolean", &v, boolOptions, 2))
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeBoolean(v));
break;
}
case NT_DOUBLE: {
double v = val->GetDouble();
if (ImGui::InputDouble("double", &v, 0, 0, "%.6f",
ImGuiInputTextFlags_EnterReturnsTrue))
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeDouble(v));
break;
}
case NT_STRING: {
char* v = GetTextBuffer(val->GetString());
if (ImGui::InputText("string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue))
nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeString(v));
break;
}
case NT_BOOLEAN_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText("boolean[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToBooleanArray(v))
nt::SetEntryValue(entry.entry, std::move(outv));
}
break;
}
case NT_DOUBLE_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText("double[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToDoubleArray(v))
nt::SetEntryValue(entry.entry, std::move(outv));
}
break;
}
case NT_STRING_ARRAY: {
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText("string[]", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (auto outv = StringToStringArray(v))
nt::SetEntryValue(entry.entry, std::move(outv));
}
break;
}
case NT_RAW:
ImGui::LabelText("raw", "[...]");
break;
case NT_RPC:
ImGui::LabelText("rpc", "[...]");
break;
default:
ImGui::LabelText("other", "?");
break;
}
ImGui::PopID();
}
static void EmitEntry(NetworkTablesModel::Entry& entry, const char* name,
NetworkTablesFlags flags) {
if (entry.source) {
ImGui::Selectable(name);
entry.source->EmitDrag();
} else {
ImGui::Text("%s", name);
}
ImGui::NextColumn();
if (flags & NetworkTablesFlags_ReadOnly)
EmitEntryValueReadonly(entry);
else
EmitEntryValueEditable(entry);
ImGui::NextColumn();
if (flags & NetworkTablesFlags_ShowFlags) {
if ((entry.flags & NT_PERSISTENT) != 0)
ImGui::Text("Persistent");
else if (entry.flags != 0)
ImGui::Text("%02x", entry.flags);
ImGui::NextColumn();
}
if (flags & NetworkTablesFlags_ShowTimestamp) {
if (entry.value)
ImGui::Text("%" PRIu64, entry.value->last_change());
else
ImGui::TextUnformatted("");
ImGui::NextColumn();
}
ImGui::Separator();
}
static void EmitTree(const std::vector<NetworkTablesModel::TreeNode>& tree,
NetworkTablesFlags flags) {
for (auto&& node : tree) {
if (node.entry) {
EmitEntry(*node.entry, node.name.c_str(), flags);
}
if (!node.children.empty()) {
bool open =
TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
ImGui::NextColumn();
ImGui::NextColumn();
if (flags & NetworkTablesFlags_ShowFlags) ImGui::NextColumn();
if (flags & NetworkTablesFlags_ShowTimestamp) ImGui::NextColumn();
ImGui::Separator();
if (open) {
EmitTree(node.children, flags);
TreePop();
}
}
}
}
void glass::DisplayNetworkTables(NetworkTablesModel* model,
NetworkTablesFlags flags) {
auto inst = model->GetInstance();
if (flags & NetworkTablesFlags_ShowConnections) {
if (CollapsingHeader("Connections")) {
ImGui::Columns(4, "connections");
ImGui::Text("Id");
ImGui::NextColumn();
ImGui::Text("Address");
ImGui::NextColumn();
ImGui::Text("Updated");
ImGui::NextColumn();
ImGui::Text("Proto");
ImGui::NextColumn();
ImGui::Separator();
for (auto&& i : nt::GetConnections(inst)) {
ImGui::Text("%s", i.remote_id.c_str());
ImGui::NextColumn();
ImGui::Text("%s", i.remote_ip.c_str());
ImGui::NextColumn();
ImGui::Text("%llu",
static_cast<unsigned long long>( // NOLINT(runtime/int)
i.last_update));
ImGui::NextColumn();
ImGui::Text("%d.%d", i.protocol_version >> 8,
i.protocol_version & 0xff);
ImGui::NextColumn();
}
ImGui::Columns();
}
if (!CollapsingHeader("Values", ImGuiTreeNodeFlags_DefaultOpen)) return;
}
const bool showFlags = (flags & NetworkTablesFlags_ShowFlags);
const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
static bool first = true;
ImGui::Columns(2 + (showFlags ? 1 : 0) + (showTimestamp ? 1 : 0), "values");
if (first) ImGui::SetColumnWidth(-1, 0.5f * ImGui::GetWindowWidth());
ImGui::Text("Name");
ImGui::NextColumn();
ImGui::Text("Value");
ImGui::NextColumn();
if (showFlags) {
if (first) ImGui::SetColumnWidth(-1, 12 * ImGui::GetFontSize());
ImGui::Text("Flags");
ImGui::NextColumn();
}
if (showTimestamp) {
ImGui::Text("Changed");
ImGui::NextColumn();
}
ImGui::Separator();
first = false;
if (flags & NetworkTablesFlags_TreeView) {
EmitTree(model->GetTreeRoot(), flags);
} else {
for (auto entry : model->GetEntries()) {
EmitEntry(*entry, entry->name.c_str(), flags);
}
}
ImGui::Columns();
}
void NetworkTablesView::Display() {
if (ImGui::BeginPopupContextItem()) {
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);
ImGui::MenuItem("Tree View", "", pTreeView);
ImGui::MenuItem("Show Connections", "", pShowConnections);
ImGui::MenuItem("Show Flags", "", pShowFlags);
ImGui::MenuItem("Show Timestamp", "", pShowTimestamp);
m_flags &=
~(NetworkTablesFlags_TreeView | NetworkTablesFlags_ShowConnections |
NetworkTablesFlags_ShowFlags | NetworkTablesFlags_ShowTimestamp);
m_flags |= (*pTreeView ? NetworkTablesFlags_TreeView : 0) |
(*pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
(*pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
(*pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0);
ImGui::EndPopup();
}
DisplayNetworkTables(m_model, m_flags);
}

View File

@@ -0,0 +1,22 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NetworkTablesHelper.h"
using namespace glass;
NetworkTablesHelper::NetworkTablesHelper(NT_Inst inst)
: m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {}
NetworkTablesHelper::~NetworkTablesHelper() {
nt::DestroyEntryListenerPoller(m_poller);
}
bool NetworkTablesHelper::IsConnected() const {
return nt::GetNetworkMode(m_inst) == NT_NET_MODE_SERVER ||
nt::IsConnected(m_inst);
}

View File

@@ -0,0 +1,177 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NetworkTablesProvider.h"
#include <algorithm>
#include <ntcore_cpp.h>
#include <wpi/SmallString.h>
#include <wpigui.h>
using namespace glass;
NetworkTablesProvider::NetworkTablesProvider(const wpi::Twine& iniName)
: NetworkTablesProvider{iniName, nt::GetDefaultInstance()} {}
NetworkTablesProvider::NetworkTablesProvider(const wpi::Twine& iniName,
NT_Inst inst)
: Provider{iniName + "Window"}, m_nt{inst}, m_typeCache{iniName} {
m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
NT_NOTIFY_IMMEDIATE);
}
void NetworkTablesProvider::GlobalInit() {
Provider::GlobalInit();
wpi::gui::AddInit([this] { m_typeCache.Initialize(); });
}
void NetworkTablesProvider::DisplayMenu() {
wpi::SmallVector<wpi::StringRef, 6> path;
wpi::SmallString<64> name;
for (auto&& entry : m_viewEntries) {
path.clear();
wpi::StringRef{entry->name}.split(path, '/', -1, false);
bool fullDepth = true;
int depth = 0;
for (; depth < (static_cast<int>(path.size()) - 1); ++depth) {
name = path[depth];
if (!ImGui::BeginMenu(name.c_str())) {
fullDepth = false;
break;
}
}
if (fullDepth) {
bool visible = entry->window && entry->window->IsVisible();
bool wasVisible = visible;
// FIXME: enabled?
// data is the last item, so is guaranteed to be null-terminated
ImGui::MenuItem(path.back().data(), nullptr, &visible, true);
if (!wasVisible && visible) {
Show(entry.get(), entry->window);
} else if (wasVisible && !visible && entry->window) {
entry->window->SetVisible(false);
}
}
for (; depth > 0; --depth) ImGui::EndMenu();
}
}
void NetworkTablesProvider::Update() {
Provider::Update();
// add/remove entries from NT changes
for (auto&& event : m_nt.PollListener()) {
// look for .type fields
wpi::StringRef eventName{event.name};
if (!eventName.endswith("/.type") || !event.value ||
!event.value->IsString())
continue;
auto tableName = eventName.drop_back(6);
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(event.value->GetString());
if (builderIt == m_typeMap.end()) continue;
if (event.flags & NT_NOTIFY_DELETE) {
auto it = std::find_if(
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
return static_cast<Entry*>(elem->modelEntry)->typeEntry ==
event.entry;
});
if (it != m_viewEntries.end()) {
m_viewEntries.erase(it);
}
} else if (event.flags & NT_NOTIFY_NEW) {
GetOrCreateView(builderIt->second, event.entry, tableName);
// cache the type
m_typeCache[tableName].SetName(event.value->GetString());
}
}
// check for visible windows that need displays (typically this is due to
// file loading)
for (auto&& window : m_windows) {
if (!window->IsVisible() || window->HasView()) continue;
auto id = window->GetId();
auto typeIt = m_typeCache.find(id);
if (typeIt == m_typeCache.end()) continue;
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(typeIt->second.GetName());
if (builderIt == m_typeMap.end()) continue;
auto entry = GetOrCreateView(
builderIt->second, nt::GetEntry(m_nt.GetInstance(), id + "/.type"), id);
if (entry) Show(entry, window.get());
}
}
void NetworkTablesProvider::Register(wpi::StringRef typeName,
CreateModelFunc createModel,
CreateViewFunc createView) {
m_typeMap[typeName] = Builder{std::move(createModel), std::move(createView)};
}
void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
// if there's already a window, just show it
if (entry->window) {
entry->window->SetVisible(true);
return;
}
// get or create model
if (!entry->modelEntry->model)
entry->modelEntry->model =
entry->modelEntry->createModel(m_nt.GetInstance(), entry->name.c_str());
if (!entry->modelEntry->model) return;
// the window might exist and we're just not associated to it yet
if (!window) window = GetOrAddWindow(entry->name, true);
if (!window) return;
entry->window = window;
// create view
auto view = entry->createView(window, entry->modelEntry->model.get(),
entry->name.c_str());
if (!view) return;
window->SetView(std::move(view));
entry->window->SetVisible(true);
}
NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
const Builder& builder, NT_Entry typeEntry, wpi::StringRef name) {
// get view entry if it already exists
auto viewIt = FindViewEntry(name);
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
// make sure typeEntry is set in model
static_cast<Entry*>((*viewIt)->modelEntry)->typeEntry = typeEntry;
return viewIt->get();
}
// get or create model entry
auto modelIt = FindModelEntry(name);
if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
static_cast<Entry*>(modelIt->get())->typeEntry = typeEntry;
} else {
modelIt = m_modelEntries.emplace(
modelIt, std::make_unique<Entry>(typeEntry, name, builder));
}
// create new view entry
viewIt = m_viewEntries.emplace(
viewIt,
std::make_unique<ViewEntry>(
name, modelIt->get(), [](Model*, const char*) { return true; },
builder.createView));
return viewIt->get();
}

View File

@@ -0,0 +1,73 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "glass/networktables/NTDigitalInput.h"
#include "glass/networktables/NTDigitalOutput.h"
#include "glass/networktables/NTFMS.h"
#include "glass/networktables/NTField2D.h"
#include "glass/networktables/NTStringChooser.h"
#include "glass/networktables/NetworkTablesProvider.h"
using namespace glass;
void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
provider.Register(
NTFMSModel::kType,
[](NT_Inst inst, const char* path) {
return std::make_unique<NTFMSModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
return MakeFunctionView(
[=] { DisplayFMS(static_cast<FMSModel*>(model)); });
});
provider.Register(
NTDigitalInputModel::kType,
[](NT_Inst inst, const char* path) {
return std::make_unique<NTDigitalInputModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
return MakeFunctionView([=] {
DisplayDIO(static_cast<NTDigitalInputModel*>(model), 0, true);
});
});
provider.Register(
NTDigitalOutputModel::kType,
[](NT_Inst inst, const char* path) {
return std::make_unique<NTDigitalOutputModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
return MakeFunctionView([=] {
DisplayDIO(static_cast<NTDigitalOutputModel*>(model), 0, true);
});
});
provider.Register(
NTField2DModel::kType,
[](NT_Inst inst, const char* path) {
return std::make_unique<NTField2DModel>(inst, path);
},
[=](Window* win, Model* model, const char* path) {
win->SetDefaultPos(200, 200);
win->SetDefaultSize(400, 200);
win->SetPadding(0, 0);
return std::make_unique<Field2DView>(
static_cast<NTField2DModel*>(model));
});
provider.Register(
NTStringChooserModel::kType,
[](NT_Inst inst, const char* path) {
return std::make_unique<NTStringChooserModel>(inst, path);
},
[](Window* win, Model* model, const char*) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
return MakeFunctionView([=] {
DisplayStringChooser(static_cast<NTStringChooserModel*>(model));
});
});
}

View File

@@ -0,0 +1,56 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <ntcore_cpp.h>
#include <wpi/StringRef.h>
#include "glass/DataSource.h"
#include "glass/hardware/DIO.h"
#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
class NTDigitalInputModel : public DIOModel {
public:
static constexpr const char* kType = "Digital Input";
// path is to the table containing ".type", excluding the trailing /
explicit NTDigitalInputModel(wpi::StringRef path);
NTDigitalInputModel(NT_Inst inst, wpi::StringRef path);
const char* GetName() const override { return m_nameValue.c_str(); }
const char* GetSimDevice() const override { return nullptr; }
DPWMModel* GetDPWM() override { return nullptr; }
DutyCycleModel* GetDutyCycle() override { return nullptr; }
EncoderModel* GetEncoder() override { return nullptr; }
bool IsInput() const override { return true; }
DataSource* GetValueData() override { return &m_valueData; }
void SetValue(bool val) override {}
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return true; }
private:
NetworkTablesHelper m_nt;
NT_Entry m_value;
NT_Entry m_name;
DataSource m_valueData;
std::string m_nameValue;
};
} // namespace glass

View File

@@ -0,0 +1,58 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <ntcore_cpp.h>
#include <wpi/StringRef.h>
#include "glass/DataSource.h"
#include "glass/hardware/DIO.h"
#include "glass/networktables/NetworkTablesHelper.h"
namespace glass {
class NTDigitalOutputModel : public DIOModel {
public:
static constexpr const char* kType = "Digital Output";
// path is to the table containing ".type", excluding the trailing /
explicit NTDigitalOutputModel(wpi::StringRef path);
NTDigitalOutputModel(NT_Inst inst, wpi::StringRef path);
const char* GetName() const override { return m_nameValue.c_str(); }
const char* GetSimDevice() const override { return nullptr; }
DPWMModel* GetDPWM() override { return nullptr; }
DutyCycleModel* GetDutyCycle() override { return nullptr; }
EncoderModel* GetEncoder() override { return nullptr; }
bool IsInput() const override { return true; }
DataSource* GetValueData() override { return &m_valueData; }
void SetValue(bool val) override;
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return !m_controllableValue; }
private:
NetworkTablesHelper m_nt;
NT_Entry m_value;
NT_Entry m_name;
NT_Entry m_controllable;
DataSource m_valueData;
std::string m_nameValue;
bool m_controllableValue = false;
};
} // namespace glass

View File

@@ -0,0 +1,72 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <ntcore_cpp.h>
#include <wpi/StringRef.h>
#include "glass/DataSource.h"
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/FMS.h"
namespace glass {
class NTFMSModel : public FMSModel {
public:
static constexpr const char* kType = "FMSInfo";
// path is to the table containing ".type", excluding the trailing /
explicit NTFMSModel(wpi::StringRef path);
NTFMSModel(NT_Inst inst, wpi::StringRef path);
DataSource* GetFmsAttachedData() override { return &m_fmsAttached; }
DataSource* GetDsAttachedData() override { return &m_dsAttached; }
DataSource* GetAllianceStationIdData() override {
return &m_allianceStationId;
}
// NT does not provide match time
DataSource* GetMatchTimeData() override { return nullptr; }
DataSource* GetEStopData() override { return &m_estop; }
DataSource* GetEnabledData() override { return &m_enabled; }
DataSource* GetTestData() override { return &m_test; }
DataSource* GetAutonomousData() override { return &m_autonomous; }
wpi::StringRef GetGameSpecificMessage(
wpi::SmallVectorImpl<char>& buf) override;
// NT is read-only (it's continually set by robot code)
void SetFmsAttached(bool val) override {}
void SetDsAttached(bool val) override {}
void SetAllianceStationId(int val) override {}
void SetMatchTime(double val) override {}
void SetEStop(bool val) override {}
void SetEnabled(bool val) override {}
void SetTest(bool val) override {}
void SetAutonomous(bool val) override {}
void SetGameSpecificMessage(const char* val) override {}
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return true; }
private:
NetworkTablesHelper m_nt;
NT_Entry m_gameSpecificMessage;
NT_Entry m_alliance;
NT_Entry m_station;
NT_Entry m_controlWord;
DataSource m_fmsAttached;
DataSource m_dsAttached;
DataSource m_allianceStationId;
DataSource m_estop;
DataSource m_enabled;
DataSource m_test;
DataSource m_autonomous;
};
} // namespace glass

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <ntcore_cpp.h>
#include <wpi/StringRef.h>
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Field2D.h"
namespace glass {
class NTField2DModel : public Field2DModel {
public:
static constexpr const char* kType = "Field2d";
// path is to the table containing ".type", excluding the trailing /
explicit NTField2DModel(wpi::StringRef path);
NTField2DModel(NT_Inst inst, wpi::StringRef path);
~NTField2DModel() override;
const char* GetPath() const { return m_path.c_str(); }
const char* GetName() const { return m_nameValue.c_str(); }
void Update() override;
bool Exists() override;
bool IsReadOnly() override;
void ForEachFieldObjectGroup(
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
func) override;
private:
NetworkTablesHelper m_nt;
std::string m_path;
NT_Entry m_name;
std::string m_nameValue;
class GroupModel;
std::vector<std::unique_ptr<GroupModel>> m_groups;
};
} // namespace glass

View File

@@ -0,0 +1,57 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <ntcore_cpp.h>
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/StringChooser.h"
namespace glass {
class NTStringChooserModel : public StringChooserModel {
public:
static constexpr const char* kType = "String Chooser";
// path is to the table containing ".type", excluding the trailing /
explicit NTStringChooserModel(wpi::StringRef path);
NTStringChooserModel(NT_Inst inst, wpi::StringRef path);
const std::string& GetDefault() override { return m_defaultValue; }
const std::string& GetSelected() override { return m_selectedValue; }
const std::string& GetActive() override { return m_activeValue; }
const std::vector<std::string>& GetOptions() override {
return m_optionsValue;
}
void SetDefault(wpi::StringRef val) override;
void SetSelected(wpi::StringRef val) override;
void SetActive(wpi::StringRef val) override;
void SetOptions(wpi::ArrayRef<std::string> val) override;
void Update() override;
bool Exists() override;
bool IsReadOnly() override { return false; }
private:
NetworkTablesHelper m_nt;
NT_Entry m_default;
NT_Entry m_selected;
NT_Entry m_active;
NT_Entry m_options;
std::string m_defaultValue;
std::string m_selectedValue;
std::string m_activeValue;
std::vector<std::string> m_optionsValue;
};
} // namespace glass

View File

@@ -0,0 +1,123 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <ntcore_cpp.h>
#include <wpi/DenseMap.h>
#include <wpi/STLExtras.h>
#include <wpi/StringRef.h>
#include "glass/Model.h"
#include "glass/View.h"
namespace glass {
class DataSource;
class NetworkTablesModel : public Model {
public:
struct Entry {
explicit Entry(nt::EntryNotification&& event);
void UpdateValue();
/** Entry handle. */
NT_Entry entry;
/** Entry name. */
std::string name;
/** The value. */
std::shared_ptr<nt::Value> value;
/** Flags. */
unsigned int flags = 0;
/** String representation of the value (for arrays / complex values). */
std::string valueStr;
/** Data source (for numeric values). */
std::unique_ptr<DataSource> source;
};
struct TreeNode {
explicit TreeNode(wpi::StringRef name) : name{name} {}
/** Short name (e.g. of just this node) */
std::string name;
/**
* Full path if entry is null (otherwise use entry->name),
* excluding trailing /
*/
std::string path;
/** Null if no value at this node */
Entry* entry = nullptr;
/** Children of node, sorted by name */
std::vector<TreeNode> children;
};
NetworkTablesModel();
explicit NetworkTablesModel(NT_Inst inst);
~NetworkTablesModel() override;
void Update() override;
bool Exists() override;
NT_Inst GetInstance() { return m_inst; }
const std::vector<Entry*>& GetEntries() { return m_sortedEntries; }
const std::vector<TreeNode>& GetTreeRoot() { return m_root; }
private:
NT_Inst m_inst;
NT_EntryListenerPoller m_poller;
wpi::DenseMap<NT_Entry, std::unique_ptr<Entry>> m_entries;
// sorted by name
std::vector<Entry*> m_sortedEntries;
std::vector<TreeNode> m_root;
};
using NetworkTablesFlags = int;
enum NetworkTablesFlags_ {
NetworkTablesFlags_TreeView = 1 << 0,
NetworkTablesFlags_ReadOnly = 1 << 1,
NetworkTablesFlags_ShowConnections = 1 << 2,
NetworkTablesFlags_ShowFlags = 1 << 3,
NetworkTablesFlags_ShowTimestamp = 1 << 4,
NetworkTablesFlags_Default = 0x1D // all except readonly
};
void DisplayNetworkTables(
NetworkTablesModel* model,
NetworkTablesFlags flags = NetworkTablesFlags_Default);
class NetworkTablesView : public View {
public:
explicit NetworkTablesView(
NetworkTablesModel* model,
NetworkTablesFlags defaultFlags = NetworkTablesFlags_Default)
: m_model{model}, m_defaultFlags{defaultFlags}, m_flags{defaultFlags} {}
void Display() override;
private:
NetworkTablesModel* m_model;
NetworkTablesFlags m_defaultFlags;
NetworkTablesFlags m_flags;
};
} // namespace glass

View File

@@ -0,0 +1,58 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <vector>
#include <ntcore_cpp.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
namespace glass {
class NetworkTablesHelper {
public:
explicit NetworkTablesHelper(NT_Inst inst);
~NetworkTablesHelper();
NetworkTablesHelper(const NetworkTablesHelper&) = delete;
NetworkTablesHelper& operator=(const NetworkTablesHelper&) = delete;
NT_Inst GetInstance() const { return m_inst; }
NT_EntryListenerPoller GetPoller() const { return m_poller; }
NT_Entry GetEntry(const wpi::Twine& name) const {
return nt::GetEntry(m_inst, name);
}
static constexpr int kDefaultListenerFlags =
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE;
NT_EntryListener AddListener(NT_Entry entry,
unsigned int flags = kDefaultListenerFlags) {
return nt::AddPolledEntryListener(m_poller, entry, flags);
}
NT_EntryListener AddListener(const wpi::Twine& prefix,
unsigned int flags = kDefaultListenerFlags) {
return nt::AddPolledEntryListener(m_poller, prefix, flags);
}
std::vector<nt::EntryNotification> PollListener() {
bool timedOut = false;
return nt::PollEntryListener(m_poller, 0, &timedOut);
}
bool IsConnected() const;
private:
NT_Inst m_inst;
NT_EntryListenerPoller m_poller;
};
} // namespace glass

View File

@@ -0,0 +1,118 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <ntcore_cpp.h>
#include <wpi/StringMap.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include "glass/Model.h"
#include "glass/Provider.h"
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/IniSaverString.h"
namespace glass {
class Window;
namespace detail {
struct NTProviderFunctions {
using Exists = std::function<bool(NT_Inst inst, const char* path)>;
using CreateModel =
std::function<std::unique_ptr<Model>(NT_Inst inst, const char* path)>;
using ViewExists = std::function<bool(Model*, const char* path)>;
using CreateView =
std::function<std::unique_ptr<View>(Window*, Model*, const char* path)>;
};
} // namespace detail
/**
* A provider for NetworkTables (SmartDashboard style) models and views.
*/
class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
public:
using Provider::CreateModelFunc;
using Provider::CreateViewFunc;
explicit NetworkTablesProvider(const wpi::Twine& iniName);
NetworkTablesProvider(const wpi::Twine& iniName, NT_Inst inst);
/**
* Get the NetworkTables instance being used for this provider.
*
* @return NetworkTables instance
*/
NT_Inst GetInstance() const { return m_nt.GetInstance(); }
/**
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
void GlobalInit() override;
/**
* Displays menu contents as a tree of available NetworkTables views.
*/
void DisplayMenu() override;
/**
* Registers a NetworkTables model and view.
*
* @param typeName SmartDashboard .type value to match
* @param createModel functor to create model
* @param createView functor to create view
*/
void Register(wpi::StringRef typeName, CreateModelFunc createModel,
CreateViewFunc createView);
using WindowManager::AddWindow;
private:
void Update() override;
NetworkTablesHelper m_nt;
// cached mapping from table name to type string
IniSaverString<NameInfo> m_typeCache;
struct Builder {
CreateModelFunc createModel;
CreateViewFunc createView;
};
// mapping from .type string to model/view creators
wpi::StringMap<Builder> m_typeMap;
struct Entry : public ModelEntry {
Entry(NT_Entry typeEntry, wpi::StringRef name, const Builder& builder)
: ModelEntry{name, [](NT_Inst, const char*) { return true; },
builder.createModel},
typeEntry{typeEntry} {}
NT_Entry typeEntry;
};
void Show(ViewEntry* entry, Window* window) override;
ViewEntry* GetOrCreateView(const Builder& builder, NT_Entry typeEntry,
wpi::StringRef name);
};
/**
* Add "standard" set of NetworkTables models/views.
*
* @param provider NetworkTables provider
*/
void AddStandardNetworkTablesViews(NetworkTablesProvider& provider);
} // namespace glass

View File

@@ -28,6 +28,7 @@ include 'wpilibcIntegrationTests'
include 'wpilibjExamples'
include 'wpilibjIntegrationTests'
include 'wpilibj'
include 'glass'
include 'simulation:gz_msgs'
include 'simulation:frc_gazebo_plugins'
include 'simulation:halsim_gazebo'

View File

@@ -10,7 +10,7 @@ wpilib_target_warnings(halsim_gui)
set_target_properties(halsim_gui PROPERTIES DEBUG_POSTFIX "d")
wpilib_link_macos_gui(halsim_gui)
target_link_libraries(halsim_gui PUBLIC hal ntcore wpimath PRIVATE wpigui)
target_link_libraries(halsim_gui PUBLIC hal wpimath PRIVATE libglassnt libglass)
target_include_directories(halsim_gui PRIVATE src/main/native/include)

View File

@@ -24,6 +24,8 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra
binaries {
all {
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':glass', library: 'glassnt', linkage: 'static'
lib project: ':glass', library: 'glass', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {

View File

@@ -1,75 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "AccelerometerGui.h"
#include <cstdio>
#include <memory>
#include <hal/Value.h>
#include <hal/simulation/AccelerometerData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerX, "X Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerY, "Y Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerZ, "Z Accel");
} // namespace
static std::unique_ptr<AccelerometerXSource> gAccelXSource;
static std::unique_ptr<AccelerometerYSource> gAccelYSource;
static std::unique_ptr<AccelerometerZSource> gAccelZSource;
static void UpdateAccelSources() {
if (!HALSIM_GetAccelerometerActive(0)) return;
if (!gAccelXSource) gAccelXSource = std::make_unique<AccelerometerXSource>(0);
if (!gAccelYSource) gAccelYSource = std::make_unique<AccelerometerYSource>(0);
if (!gAccelZSource) gAccelZSource = std::make_unique<AccelerometerZSource>(0);
}
static void DisplayAccelerometers() {
if (!HALSIM_GetAccelerometerActive(0)) return;
if (SimDeviceGui::StartDevice("BuiltInAccel")) {
HAL_Value value;
// Range
value = HAL_MakeEnum(HALSIM_GetAccelerometerRange(0));
static const char* rangeOptions[] = {"2G", "4G", "8G"};
SimDeviceGui::DisplayValue("Range", true, &value, rangeOptions, 3);
// X Accel
value = HAL_MakeDouble(gAccelXSource->GetValue());
if (SimDeviceGui::DisplayValueSource("X Accel", false, &value,
gAccelXSource.get()))
HALSIM_SetAccelerometerX(0, value.data.v_double);
// Y Accel
value = HAL_MakeDouble(gAccelYSource->GetValue());
if (SimDeviceGui::DisplayValueSource("Y Accel", false, &value,
gAccelYSource.get()))
HALSIM_SetAccelerometerY(0, value.data.v_double);
// Z Accel
value = HAL_MakeDouble(gAccelZSource->GetValue());
if (SimDeviceGui::DisplayValueSource("Z Accel", false, &value,
gAccelZSource.get()))
HALSIM_SetAccelerometerZ(0, value.data.v_double);
SimDeviceGui::FinishDevice();
}
}
void AccelerometerGui::Initialize() {
HALSimGui::AddExecute(UpdateAccelSources);
SimDeviceGui::Add(DisplayAccelerometers);
}

View File

@@ -0,0 +1,67 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "AccelerometerSimGui.h"
#include <glass/hardware/Accelerometer.h>
#include <glass/other/DeviceTree.h>
#include <memory>
#include <hal/Value.h>
#include <hal/simulation/AccelerometerData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace glass;
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerX, "X Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerY, "Y Accel");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AccelerometerZ, "Z Accel");
class AccelerometerSimModel : public glass::AccelerometerModel {
public:
explicit AccelerometerSimModel(int32_t index)
: m_index{index}, m_xData{m_index}, m_yData{m_index}, m_zData{m_index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAccelerometerActive(m_index); }
glass::DataSource* GetXData() override { return &m_xData; }
glass::DataSource* GetYData() override { return &m_yData; }
glass::DataSource* GetZData() override { return &m_zData; }
int GetRange() override { return HALSIM_GetAccelerometerRange(m_index); }
void SetX(double val) override { HALSIM_SetAccelerometerX(m_index, val); }
void SetY(double val) override { HALSIM_SetAccelerometerY(m_index, val); }
void SetZ(double val) override { HALSIM_SetAccelerometerZ(m_index, val); }
void SetRange(int val) override {
HALSIM_SetAccelerometerRange(m_index,
static_cast<HAL_AccelerometerRange>(val));
}
private:
int32_t m_index;
AccelerometerXSource m_xData;
AccelerometerYSource m_yData;
AccelerometerZSource m_zData;
};
} // namespace
void AccelerometerSimGui::Initialize() {
SimDeviceGui::GetDeviceTree().Add(
std::make_unique<AccelerometerSimModel>(0), [](glass::Model* model) {
glass::DisplayAccelerometerDevice(
static_cast<AccelerometerSimModel*>(model));
});
}

View File

@@ -0,0 +1,17 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace halsimgui {
class AccelerometerSimGui {
public:
static void Initialize();
};
} // namespace halsimgui

View File

@@ -7,120 +7,103 @@
#include "AddressableLEDGui.h"
#include <glass/hardware/LEDDisplay.h>
#include <hal/Ports.h>
#include <hal/simulation/AddressableLEDData.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/SmallVector.h>
#include <wpi/StringRef.h>
#include "ExtraGuiWidgets.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
struct LEDDisplayInfo {
int numColumns = 10;
LEDConfig config;
class AddressableLEDModel : public glass::LEDDisplayModel {
public:
explicit AddressableLEDModel(int32_t index) : m_index{index} {}
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
void Update() override {}
bool Exists() override {
return HALSIM_GetAddressableLEDInitialized(m_index);
}
bool IsRunning() override { return HALSIM_GetAddressableLEDRunning(m_index); }
wpi::ArrayRef<Data> GetData(wpi::SmallVectorImpl<Data>&) override {
size_t length = HALSIM_GetAddressableLEDData(m_index, m_data);
return {reinterpret_cast<Data*>(m_data), length};
}
private:
int32_t m_index;
HAL_AddressableLEDData m_data[HAL_kAddressableLEDMaxLength];
};
class AddressableLEDsModel : public glass::LEDDisplaysModel {
public:
AddressableLEDsModel() : m_models(HAL_GetNumAddressableLEDs()) {}
void Update() override;
bool Exists() override;
size_t GetNumLEDDisplays() override { return m_models.size(); }
void ForEachLEDDisplay(
wpi::function_ref<void(glass::LEDDisplayModel& model, int index)> func)
override;
private:
std::vector<std::unique_ptr<AddressableLEDModel>> m_models;
};
} // namespace
static IniSaver<LEDDisplayInfo> gDisplaySettings{"AddressableLED"};
bool LEDDisplayInfo::ReadIni(wpi::StringRef name, wpi::StringRef value) {
if (name == "columns") {
int num;
if (value.getAsInteger(10, num)) return true;
numColumns = num;
} else if (name == "serpentine") {
int num;
if (value.getAsInteger(10, num)) return true;
config.serpentine = num != 0;
} else if (name == "order") {
int num;
if (value.getAsInteger(10, num)) return true;
config.order = static_cast<LEDConfig::Order>(num);
} else if (name == "start") {
int num;
if (value.getAsInteger(10, num)) return true;
config.start = static_cast<LEDConfig::Start>(num);
} else {
return false;
}
return true;
}
void LEDDisplayInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("columns=%d\nserpentine=%d\norder=%d\nstart=%d\n", numColumns,
config.serpentine ? 1 : 0, static_cast<int>(config.order),
static_cast<int>(config.start));
}
static void DisplayAddressableLEDs() {
bool hasAny = false;
static const int numLED = HAL_GetNumAddressableLEDs();
for (int i = 0; i < numLED; ++i) {
if (!HALSIM_GetAddressableLEDInitialized(i)) continue;
hasAny = true;
if (numLED > 1) ImGui::Text("LEDs[%d]", i);
static HAL_AddressableLEDData data[HAL_kAddressableLEDMaxLength];
int length = HALSIM_GetAddressableLEDData(i, data);
bool running = HALSIM_GetAddressableLEDRunning(i);
auto& info = gDisplaySettings[i];
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", &info.numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
int val = info.config.order;
if (ImGui::Combo("Order", &val, options, 2))
info.config.order = static_cast<LEDConfig::Order>(val);
}
{
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
"Lower Right"};
int val = info.config.start;
if (ImGui::Combo("Start", &val, options, 4))
info.config.start = static_cast<LEDConfig::Start>(val);
}
ImGui::Checkbox("Serpentine", &info.config.serpentine);
if (info.numColumns < 1) info.numColumns = 1;
ImGui::PopItemWidth();
// show as LED indicators
static int values[HAL_kAddressableLEDMaxLength];
static ImU32 colors[HAL_kAddressableLEDMaxLength];
if (!running) {
colors[0] = IM_COL32(128, 128, 128, 255);
for (int j = 0; j < length; ++j) values[j] = -1;
} else {
for (int j = 0; j < length; ++j) {
values[j] = j + 1;
colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
void AddressableLEDsModel::Update() {
for (int i = 0; i < static_cast<int>(m_models.size()); ++i) {
auto& model = m_models[i];
if (HALSIM_GetAddressableLEDInitialized(i)) {
if (!model) {
model = std::make_unique<AddressableLEDModel>(i);
}
if (model) model->Update();
} else {
model.reset();
}
DrawLEDs(values, length, info.numColumns, colors, 0, 0, info.config);
}
if (!hasAny) ImGui::Text("No addressable LEDs");
}
bool AddressableLEDsModel::Exists() {
for (auto&& model : m_models) {
if (model && model->Exists()) return true;
}
return false;
}
void AddressableLEDsModel::ForEachLEDDisplay(
wpi::function_ref<void(glass::LEDDisplayModel& model, int index)> func) {
for (int i = 0; i < static_cast<int>(m_models.size()); ++i) {
if (m_models[i]) func(*m_models[i], i);
}
}
static bool AddressableLEDsExists() {
static const int numLED = HAL_GetNumAddressableLEDs();
for (int i = 0; i < numLED; ++i) {
if (HALSIM_GetAddressableLEDInitialized(i)) return true;
}
return false;
}
void AddressableLEDGui::Initialize() {
gDisplaySettings.Initialize();
HALSimGui::AddWindow("Addressable LEDs", DisplayAddressableLEDs,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetWindowVisibility("Addressable LEDs", HALSimGui::kHide);
HALSimGui::SetDefaultWindowPos("Addressable LEDs", 290, 100);
HALSimGui::halProvider.Register(
"Addressable LEDs", [] { return AddressableLEDsExists(); },
[] { return std::make_unique<AddressableLEDsModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(290, 100);
return glass::MakeFunctionView([=] {
glass::DisplayLEDDisplays(static_cast<AddressableLEDsModel*>(model));
});
});
}

View File

@@ -1,80 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "AnalogGyroGui.h"
#include <cstdio>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/AnalogGyroData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroAngle, "AGyro Angle");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroRate, "AGyro Rate");
struct AnalogGyroSource {
explicit AnalogGyroSource(int32_t index) : angle{index}, rate{index} {}
AnalogGyroAngleSource angle;
AnalogGyroRateSource rate;
};
} // namespace
static std::vector<std::unique_ptr<AnalogGyroSource>> gAnalogGyroSources;
static void UpdateAnalogGyroSources() {
for (int i = 0, iend = gAnalogGyroSources.size(); i < iend; ++i) {
auto& source = gAnalogGyroSources[i];
if (HALSIM_GetAnalogGyroInitialized(i)) {
if (!source) {
source = std::make_unique<AnalogGyroSource>(i);
}
} else {
source.reset();
}
}
}
static void DisplayAnalogGyros() {
for (int i = 0, iend = gAnalogGyroSources.size(); i < iend; ++i) {
if (auto source = gAnalogGyroSources[i].get()) {
char name[32];
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", i);
if (SimDeviceGui::StartDevice(name)) {
HAL_Value value;
// angle
value = HAL_MakeDouble(source->angle.GetValue());
if (SimDeviceGui::DisplayValueSource("Angle", false, &value,
&source->angle))
HALSIM_SetAnalogGyroAngle(i, value.data.v_double);
// rate
value = HAL_MakeDouble(source->rate.GetValue());
if (SimDeviceGui::DisplayValueSource("Rate", false, &value,
&source->rate))
HALSIM_SetAnalogGyroRate(i, value.data.v_double);
SimDeviceGui::FinishDevice();
}
}
}
}
void AnalogGyroGui::Initialize() {
gAnalogGyroSources.resize(HAL_GetNumAccumulators());
HALSimGui::AddExecute(UpdateAnalogGyroSources);
SimDeviceGui::Add(DisplayAnalogGyros);
}

View File

@@ -0,0 +1,101 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "AnalogGyroSimGui.h"
#include <glass/hardware/AnalogGyro.h>
#include <glass/other/DeviceTree.h>
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/Value.h>
#include <hal/simulation/AnalogGyroData.h>
#include "HALDataSource.h"
#include "HALSimGui.h"
#include "SimDeviceGui.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroAngle, "AGyro Angle");
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogGyroRate, "AGyro Rate");
class AnalogGyroSimModel : public glass::AnalogGyroModel {
public:
explicit AnalogGyroSimModel(int32_t index)
: m_index{index}, m_angle{index}, m_rate{index} {}
void Update() override {}
bool Exists() override { return HALSIM_GetAnalogGyroInitialized(m_index); }
glass::DataSource* GetAngleData() override { return &m_angle; }
glass::DataSource* GetRateData() override { return &m_rate; }
void SetAngle(double val) override {
HALSIM_SetAnalogGyroAngle(m_index, val);
}
void SetRate(double val) override { HALSIM_SetAnalogGyroRate(m_index, val); }
private:
int32_t m_index;
AnalogGyroAngleSource m_angle;
AnalogGyroRateSource m_rate;
};
class AnalogGyrosSimModel : public glass::AnalogGyrosModel {
public:
AnalogGyrosSimModel() : m_models(HAL_GetNumAccumulators()) {}
void Update() override;
bool Exists() override { return true; }
void ForEachAnalogGyro(
wpi::function_ref<void(glass::AnalogGyroModel& model, int index)> func)
override;
private:
// indexed by channel
std::vector<std::unique_ptr<AnalogGyroSimModel>> m_models;
};
} // namespace
void AnalogGyrosSimModel::Update() {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
auto& model = m_models[i];
if (HALSIM_GetAnalogGyroInitialized(i)) {
if (!model) {
model = std::make_unique<AnalogGyroSimModel>(i);
}
} else {
model.reset();
}
}
}
void AnalogGyrosSimModel::ForEachAnalogGyro(
wpi::function_ref<void(glass::AnalogGyroModel& model, int index)> func) {
for (int32_t i = 0, iend = static_cast<int32_t>(m_models.size()); i < iend;
++i) {
if (auto model = m_models[i].get()) {
func(*model, i);
}
}
}
void AnalogGyroSimGui::Initialize() {
SimDeviceGui::GetDeviceTree().Add(
std::make_unique<AnalogGyrosSimModel>(), [](glass::Model* model) {
glass::DisplayAnalogGyrosDevice(
static_cast<AnalogGyrosSimModel*>(model));
});
}

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -9,7 +9,7 @@
namespace halsimgui {
class AccelerometerGui {
class AnalogGyroSimGui {
public:
static void Initialize();
};

View File

@@ -1,102 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "AnalogInputGui.h"
#include <memory>
#include <vector>
#include <hal/Ports.h>
#include <hal/simulation/AnalogGyroData.h>
#include <hal/simulation/AnalogInData.h>
#include <hal/simulation/SimDeviceData.h>
#include <imgui.h>
#include "GuiDataSource.h"
#include "HALSimGui.h"
#include "IniSaver.h"
#include "IniSaverInfo.h"
using namespace halsimgui;
namespace {
HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(AnalogInVoltage, "AIn");
} // namespace
// indexed by channel
static IniSaver<NameInfo> gAnalogInputs{"AnalogInput"};
static std::vector<std::unique_ptr<AnalogInVoltageSource>> gAnalogInputSources;
static void UpdateAnalogInputSources() {
for (int i = 0, iend = gAnalogInputSources.size(); i < iend; ++i) {
auto& source = gAnalogInputSources[i];
if (HALSIM_GetAnalogInInitialized(i)) {
if (!source) {
source = std::make_unique<AnalogInVoltageSource>(i);
source->SetName(gAnalogInputs[i].GetName());
}
} else {
source.reset();
}
}
}
static void DisplayAnalogInputs() {
ImGui::Text("(Use Ctrl+Click to edit value)");
bool hasInputs = false;
static const int numAccum = HAL_GetNumAccumulators();
bool first = true;
for (int i = 0, iend = gAnalogInputSources.size(); i < iend; ++i) {
if (auto source = gAnalogInputSources[i].get()) {
ImGui::PushID(i);
hasInputs = true;
if (!first) {
ImGui::Spacing();
ImGui::Spacing();
} else {
first = false;
}
auto& info = gAnalogInputs[i];
// build label
char label[128];
info.GetLabel(label, sizeof(label), "In", i);
if (i < numAccum && HALSIM_GetAnalogGyroInitialized(i)) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "AnalogGyro[%d]", i);
ImGui::PopStyleColor();
} else if (auto simDevice = HALSIM_GetAnalogInSimDevice(i)) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
ImGui::LabelText(label, "%s", HALSIM_GetSimDeviceName(simDevice));
ImGui::PopStyleColor();
} else {
float val = source->GetValue();
if (source->SliderFloat(label, &val, 0.0, 5.0))
HALSIM_SetAnalogInVoltage(i, val);
}
// context menu to change name
if (info.PopupEditName(i)) {
source->SetName(info.GetName());
}
ImGui::PopID();
}
}
if (!hasInputs) ImGui::Text("No analog inputs");
}
void AnalogInputGui::Initialize() {
gAnalogInputs.Initialize();
gAnalogInputSources.resize(HAL_GetNumAnalogInputs());
HALSimGui::AddExecute(UpdateAnalogInputSources);
HALSimGui::AddWindow("Analog Inputs", DisplayAnalogInputs,
ImGuiWindowFlags_AlwaysAutoResize);
HALSimGui::SetDefaultWindowPos("Analog Inputs", 640, 20);
}

View File

@@ -1,17 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace halsimgui {
class AnalogInputGui {
public:
static void Initialize();
};
} // namespace halsimgui

Some files were not shown because too many files have changed in this diff Show More