mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[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:
@@ -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
69
glass/CMakeLists.txt
Normal 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
30
glass/Info.plist
Normal 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
131
glass/build.gradle
Normal 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
55
glass/publish.gradle
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
glass/src/app/native/cpp/NetworkTablesSettings.cpp
Normal file
71
glass/src/app/native/cpp/NetworkTablesSettings.cpp
Normal 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;
|
||||
}
|
||||
35
glass/src/app/native/cpp/NetworkTablesSettings.h
Normal file
35
glass/src/app/native/cpp/NetworkTablesSettings.h
Normal 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;
|
||||
};
|
||||
137
glass/src/app/native/cpp/main.cpp
Normal file
137
glass/src/app/native/cpp/main.cpp
Normal 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();
|
||||
}
|
||||
422
glass/src/lib/native/cpp/Context.cpp
Normal file
422
glass/src/lib/native/cpp/Context.cpp
Normal 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;
|
||||
}
|
||||
142
glass/src/lib/native/cpp/DataSource.cpp
Normal file
142
glass/src/lib/native/cpp/DataSource.cpp
Normal 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;
|
||||
}
|
||||
52
glass/src/lib/native/cpp/MainMenuBar.cpp
Normal file
52
glass/src/lib/native/cpp/MainMenuBar.cpp
Normal 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();
|
||||
}
|
||||
@@ -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; }
|
||||
30
glass/src/lib/native/cpp/View.cpp
Normal file
30
glass/src/lib/native/cpp/View.cpp
Normal 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() {}
|
||||
102
glass/src/lib/native/cpp/Window.cpp
Normal file
102
glass/src/lib/native/cpp/Window.cpp
Normal 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);
|
||||
}
|
||||
107
glass/src/lib/native/cpp/WindowManager.cpp
Normal file
107
glass/src/lib/native/cpp/WindowManager.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
51
glass/src/lib/native/cpp/hardware/Accelerometer.cpp
Normal file
51
glass/src/lib/native/cpp/hardware/Accelerometer.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
41
glass/src/lib/native/cpp/hardware/AnalogGyro.cpp
Normal file
41
glass/src/lib/native/cpp/hardware/AnalogGyro.cpp
Normal 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); });
|
||||
}
|
||||
66
glass/src/lib/native/cpp/hardware/AnalogInput.cpp
Normal file
66
glass/src/lib/native/cpp/hardware/AnalogInput.cpp
Normal 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());
|
||||
}
|
||||
47
glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
Normal file
47
glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
118
glass/src/lib/native/cpp/hardware/DIO.cpp
Normal file
118
glass/src/lib/native/cpp/hardware/DIO.cpp
Normal 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());
|
||||
}
|
||||
165
glass/src/lib/native/cpp/hardware/Encoder.cpp
Normal file
165
glass/src/lib/native/cpp/hardware/Encoder.cpp
Normal 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());
|
||||
}
|
||||
91
glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
Normal file
91
glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
Normal 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");
|
||||
}
|
||||
150
glass/src/lib/native/cpp/hardware/PCM.cpp
Normal file
150
glass/src/lib/native/cpp/hardware/PCM.cpp
Normal 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);
|
||||
});
|
||||
}
|
||||
92
glass/src/lib/native/cpp/hardware/PDP.cpp
Normal file
92
glass/src/lib/native/cpp/hardware/PDP.cpp
Normal 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());
|
||||
}
|
||||
62
glass/src/lib/native/cpp/hardware/PWM.cpp
Normal file
62
glass/src/lib/native/cpp/hardware/PWM.cpp
Normal 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());
|
||||
}
|
||||
74
glass/src/lib/native/cpp/hardware/Relay.cpp
Normal file
74
glass/src/lib/native/cpp/hardware/Relay.cpp
Normal 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());
|
||||
}
|
||||
87
glass/src/lib/native/cpp/hardware/RoboRio.cpp
Normal file
87
glass/src/lib/native/cpp/hardware/RoboRio.cpp
Normal 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();
|
||||
}
|
||||
161
glass/src/lib/native/cpp/other/DeviceTree.cpp
Normal file
161
glass/src/lib/native/cpp/other/DeviceTree.cpp
Normal 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);
|
||||
}
|
||||
140
glass/src/lib/native/cpp/other/FMS.cpp
Normal file
140
glass/src/lib/native/cpp/other/FMS.cpp
Normal 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();
|
||||
}
|
||||
617
glass/src/lib/native/cpp/other/Field2D.cpp
Normal file
617
glass/src/lib/native/cpp/other/Field2D.cpp
Normal 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());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
44
glass/src/lib/native/cpp/other/StringChooser.cpp
Normal file
44
glass/src/lib/native/cpp/other/StringChooser.cpp
Normal 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();
|
||||
}
|
||||
@@ -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
|
||||
64
glass/src/lib/native/cpp/support/IniSaverBase.cpp
Normal file
64
glass/src/lib/native/cpp/support/IniSaverBase.cpp
Normal 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); }
|
||||
@@ -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);
|
||||
}
|
||||
147
glass/src/lib/native/include/glass/Context.h
Normal file
147
glass/src/lib/native/include/glass/Context.h
Normal 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
|
||||
49
glass/src/lib/native/include/glass/ContextInternal.h
Normal file
49
glass/src/lib/native/include/glass/ContextInternal.h
Normal 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
|
||||
86
glass/src/lib/native/include/glass/DataSource.h
Normal file
86
glass/src/lib/native/include/glass/DataSource.h
Normal 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
|
||||
51
glass/src/lib/native/include/glass/MainMenuBar.h
Normal file
51
glass/src/lib/native/include/glass/MainMenuBar.h
Normal 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
|
||||
@@ -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
|
||||
171
glass/src/lib/native/include/glass/Provider.h
Normal file
171
glass/src/lib/native/include/glass/Provider.h
Normal 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"
|
||||
90
glass/src/lib/native/include/glass/Provider.inc
Normal file
90
glass/src/lib/native/include/glass/Provider.inc
Normal 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
|
||||
51
glass/src/lib/native/include/glass/View.h
Normal file
51
glass/src/lib/native/include/glass/View.h
Normal 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
|
||||
134
glass/src/lib/native/include/glass/Window.h
Normal file
134
glass/src/lib/native/include/glass/Window.h
Normal 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
|
||||
141
glass/src/lib/native/include/glass/WindowManager.h
Normal file
141
glass/src/lib/native/include/glass/WindowManager.h
Normal 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
|
||||
32
glass/src/lib/native/include/glass/hardware/Accelerometer.h
Normal file
32
glass/src/lib/native/include/glass/hardware/Accelerometer.h
Normal 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
|
||||
36
glass/src/lib/native/include/glass/hardware/AnalogGyro.h
Normal file
36
glass/src/lib/native/include/glass/hardware/AnalogGyro.h
Normal 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
|
||||
39
glass/src/lib/native/include/glass/hardware/AnalogInput.h
Normal file
39
glass/src/lib/native/include/glass/hardware/AnalogInput.h
Normal 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
|
||||
33
glass/src/lib/native/include/glass/hardware/AnalogOutput.h
Normal file
33
glass/src/lib/native/include/glass/hardware/AnalogOutput.h
Normal 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
|
||||
65
glass/src/lib/native/include/glass/hardware/DIO.h
Normal file
65
glass/src/lib/native/include/glass/hardware/DIO.h
Normal 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
|
||||
59
glass/src/lib/native/include/glass/hardware/Encoder.h
Normal file
59
glass/src/lib/native/include/glass/hardware/Encoder.h
Normal 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
|
||||
47
glass/src/lib/native/include/glass/hardware/LEDDisplay.h
Normal file
47
glass/src/lib/native/include/glass/hardware/LEDDisplay.h
Normal 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
|
||||
62
glass/src/lib/native/include/glass/hardware/PCM.h
Normal file
62
glass/src/lib/native/include/glass/hardware/PCM.h
Normal 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
|
||||
41
glass/src/lib/native/include/glass/hardware/PDP.h
Normal file
41
glass/src/lib/native/include/glass/hardware/PDP.h
Normal 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
|
||||
39
glass/src/lib/native/include/glass/hardware/PWM.h
Normal file
39
glass/src/lib/native/include/glass/hardware/PWM.h
Normal 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
|
||||
38
glass/src/lib/native/include/glass/hardware/Relay.h
Normal file
38
glass/src/lib/native/include/glass/hardware/Relay.h
Normal 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
|
||||
46
glass/src/lib/native/include/glass/hardware/RoboRio.h
Normal file
46
glass/src/lib/native/include/glass/hardware/RoboRio.h
Normal 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
|
||||
143
glass/src/lib/native/include/glass/other/DeviceTree.h
Normal file
143
glass/src/lib/native/include/glass/other/DeviceTree.h
Normal 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
|
||||
56
glass/src/lib/native/include/glass/other/FMS.h
Normal file
56
glass/src/lib/native/include/glass/other/FMS.h
Normal 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
|
||||
58
glass/src/lib/native/include/glass/other/Field2D.h
Normal file
58
glass/src/lib/native/include/glass/other/Field2D.h
Normal 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
|
||||
67
glass/src/lib/native/include/glass/other/Plot.h
Normal file
67
glass/src/lib/native/include/glass/other/Plot.h
Normal 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
|
||||
35
glass/src/lib/native/include/glass/other/StringChooser.h
Normal file
35
glass/src/lib/native/include/glass/other/StringChooser.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
40
glass/src/lib/native/include/glass/support/IniSaver.inl
Normal file
40
glass/src/lib/native/include/glass/support/IniSaver.inl
Normal 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
|
||||
49
glass/src/lib/native/include/glass/support/IniSaverBase.h
Normal file
49
glass/src/lib/native/include/glass/support/IniSaverBase.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
46
glass/src/libnt/native/cpp/NTDigitalInput.cpp
Normal file
46
glass/src/libnt/native/cpp/NTDigitalInput.cpp
Normal 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;
|
||||
}
|
||||
55
glass/src/libnt/native/cpp/NTDigitalOutput.cpp
Normal file
55
glass/src/libnt/native/cpp/NTDigitalOutput.cpp
Normal 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;
|
||||
}
|
||||
93
glass/src/libnt/native/cpp/NTFMS.cpp
Normal file
93
glass/src/libnt/native/cpp/NTFMS.cpp
Normal 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;
|
||||
}
|
||||
236
glass/src/libnt/native/cpp/NTField2D.cpp
Normal file
236
glass/src/libnt/native/cpp/NTField2D.cpp
Normal 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()));
|
||||
}
|
||||
}
|
||||
64
glass/src/libnt/native/cpp/NTStringChooser.cpp
Normal file
64
glass/src/libnt/native/cpp/NTStringChooser.cpp
Normal 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;
|
||||
}
|
||||
610
glass/src/libnt/native/cpp/NetworkTables.cpp
Normal file
610
glass/src/libnt/native/cpp/NetworkTables.cpp
Normal 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);
|
||||
}
|
||||
22
glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
Normal file
22
glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
Normal 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);
|
||||
}
|
||||
177
glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
Normal file
177
glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
Normal 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();
|
||||
}
|
||||
73
glass/src/libnt/native/cpp/StandardNetworkTables.cpp
Normal file
73
glass/src/libnt/native/cpp/StandardNetworkTables.cpp
Normal 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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
72
glass/src/libnt/native/include/glass/networktables/NTFMS.h
Normal file
72
glass/src/libnt/native/include/glass/networktables/NTFMS.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
101
simulation/halsim_gui/src/main/native/cpp/AnalogGyroSimGui.cpp
Normal file
101
simulation/halsim_gui/src/main/native/cpp/AnalogGyroSimGui.cpp
Normal 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));
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user