Compare commits

...

24 Commits

Author SHA1 Message Date
Peter Johnson
ae208d2b17 [wpiutil] StringExtras: Add substr() (#3742)
Unlike std::string and std::string_view, this substr() allows a start
greater than the length of the string, in which case an empty string
is returned.  This matches llvm::StringRef behavior.
2021-11-27 21:31:40 -08:00
Thad House
6f51cb3b98 [wpiutil] MulticastResolver: make event manual reset, change to multiple read (#3736) 2021-11-27 11:16:24 -08:00
Peter Johnson
f6159ee1a2 [glass] Fix Drive widget handling of negative rotation (#3739)
This would crash in debug mode due to an imgui assertion in PathArcTo.
2021-11-27 10:58:45 -08:00
Thad House
7f401ae895 [build] Update NI libraries to 2022.2.3 (#3738) 2021-11-27 09:43:07 -08:00
Peter Johnson
0587b7043a [glass] Use JSON files for storage instead of imgui ini
Storage is now nested.

Separate "roots" can be configured which save to separate files.
In particular, this is used to save wpigui and ImGui window position
to a -window.json file.

ImGui's ini (for window position) is mapped to JSON.

You can optionally specify a directory to load from on the command line.
If one isn't provided, it uses the global system directory.
Any changes made are automatically saved here.

Workspace | Open: select directory, the current layout is replaced with that
workspace, and future auto-saves also switch to that location. The main
window size/location is not changed, only the contents.

Workspace | Save As: select directory, the current layout is saved there,
and future auto-saves also switch to that location.

Workspace | Reset: window locations are preserved, but all other settings
are reset to default (including e.g. removing plot windows). This will also
end up clearing the current save file. as with load, the main window
size/location is not changed.

Workspace | Save As Global: "save as" to the global system location

Notably, the main window size/location is only loaded at startup, but is
auto-saved as part of the current workspace.
2021-11-27 00:12:13 -08:00
Peter Johnson
0bbf51d566 [wpigui] Change maximized to bool 2021-11-27 00:12:13 -08:00
Peter Johnson
92c6eae6b0 [wpigui] PFD: Add explicit to constructors 2021-11-27 00:12:13 -08:00
Peter Johnson
141354cd79 [wpigui] Add hooks for custom load/save settings
Add GetPlatformSaveFileDir().
2021-11-27 00:12:13 -08:00
Thad House
f6e9fc7d71 [wpiutil] Handle multicast service collision on linux (#3734) 2021-11-26 23:20:54 -08:00
Peter Johnson
d8418be7d1 [glass, outlineviewer] Return 0 from WinMain (#3735)
While this is implicit in main(), WinMain() requires an explicit return.
2021-11-26 23:19:45 -08:00
Thad House
82066946e5 [wpiutil] Add mDNS resolver and announcer (#3733) 2021-11-25 22:08:26 -08:00
Thad House
4b1defc8d8 [wpilib] Remove automatic PD type from module type enum (#3732)
Using automatic type doesn't work with any module number, so the API was confusing.
2021-11-23 23:03:45 -08:00
Oblarg
da90c1cd2c [wpilib] Add bang-bang controller (#3676)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2021-11-23 20:34:46 -08:00
Thad House
3aa54fa027 [wpilib] Add new counter implementations (#2447) 2021-11-23 20:33:36 -08:00
Thad House
b156db400d [hal, wpilib] Incorporate pneumatic control type into wpilibc/j (#3728) 2021-11-23 20:32:02 -08:00
sciencewhiz
9aba2b7583 [oldCommands] Add wrappers for WPILib objects to work with old PID Controller (#3710) 2021-11-23 20:30:30 -08:00
Jan-Felix Abellera
a9931223f0 [hal] Add REV PH faults (#3729) 2021-11-22 21:15:32 -08:00
Tyler Veness
aacf9442e4 [wpimath] Fix units typo in LinearSystemId source comment (#3730) 2021-11-22 21:14:38 -08:00
Tyler Veness
7db10ecf00 [wpilibc] Make SPI destructor virtual since SPI contains virtual functions (#3727) 2021-11-20 11:21:02 -08:00
Tyler Veness
a0a5b2aea5 [wpimath] Upgrade to EJML 0.41 (#3726) 2021-11-20 01:02:37 -08:00
Jan-Felix Abellera
eb835598a4 [hal] Add HAL functions for compressor config modes on REV PH (#3724) 2021-11-20 01:02:23 -08:00
Tyler Veness
f0ab6df5b6 [wpimath] Upgrade to Drake v0.36.0 (#3722) 2021-11-16 14:37:29 -08:00
sciencewhiz
075144faa3 [docs] Parse files without extensions with Doxygen (#3721)
Fixes inclusion of wpi::numbers and some Eigen files
2021-11-16 11:22:34 -08:00
Thad House
32468a40cb [hal] Remove use of getDmaDescriptor from autospi (#3717)
It’s not necessary, as the index equals the channel.
2021-11-13 08:55:21 -08:00
214 changed files with 9685 additions and 2826 deletions

View File

@@ -6,11 +6,17 @@ FATAL: In-source builds are not allowed.
")
endif()
if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
set(CMAKE_SYSTEM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
endif()
project(allwpilib)
cmake_minimum_required(VERSION 3.3.0)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
message(STATUS "Platform version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
INCLUDE(CPack)

View File

@@ -450,9 +450,9 @@ Instance::Instance() {
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
return;
} else if (wpi::starts_with(relativeKey, "Property/")) {
propName = relativeKey.substr(9);
propName = wpi::substr(relativeKey, 9);
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
propName = relativeKey.substr(12);
propName = wpi::substr(relativeKey, 12);
} else {
return; // ignore
}

View File

@@ -208,7 +208,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
if (wpi::trim(key) == "boundary") {
value = wpi::trim(wpi::trim(value), '"'); // value may be quoted
if (wpi::starts_with(value, "--")) {
value = value.substr(2);
value = wpi::substr(value, 2);
}
boundary.append(value.begin(), value.end());
}

View File

@@ -4,6 +4,7 @@
#include "JpegUtil.h"
#include <wpi/StringExtras.h>
#include <wpi/raw_istream.h>
namespace cs {
@@ -64,7 +65,7 @@ bool GetJpegSize(std::string_view data, int* width, int* height) {
return false;
}
data = data.substr(2); // Get to the first block
data = wpi::substr(data, 2); // Get to the first block
for (;;) {
if (data.size() < 4) {
return false; // EOF
@@ -89,7 +90,7 @@ bool GetJpegSize(std::string_view data, int* width, int* height) {
return true;
}
// Go to the next block
data = data.substr(bytes[2] * 256 + bytes[3] + 2);
data = wpi::substr(data, bytes[2] * 256 + bytes[3] + 2);
}
}
@@ -102,7 +103,7 @@ bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
*locSOF = *size;
// Search until SOS for DHT tag
sdata = sdata.substr(2); // Get to the first block
sdata = wpi::substr(sdata, 2); // Get to the first block
for (;;) {
if (sdata.size() < 4) {
return false; // EOF
@@ -121,7 +122,7 @@ bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
*locSOF = sdata.data() - data; // SOF
}
// Go to the next block
sdata = sdata.substr(bytes[2] * 256 + bytes[3] + 2);
sdata = wpi::substr(sdata, bytes[2] * 256 + bytes[3] + 2);
}
// Only add DHT if we also found SOF (insertion point)

View File

@@ -797,14 +797,14 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
// compatibility, others are for Axis camera compatibility.
if ((pos = req.find("POST /stream")) != std::string_view::npos) {
kind = kStream;
parameters = req.substr(req.find('?', pos + 12)).substr(1);
parameters = wpi::substr(wpi::substr(req, req.find('?', pos + 12)), 1);
} else if ((pos = req.find("GET /?action=stream")) !=
std::string_view::npos) {
kind = kStream;
parameters = req.substr(req.find('&', pos + 19)).substr(1);
parameters = wpi::substr(wpi::substr(req, req.find('&', pos + 19)), 1);
} else if ((pos = req.find("GET /stream.mjpg")) != std::string_view::npos) {
kind = kStream;
parameters = req.substr(req.find('?', pos + 16)).substr(1);
parameters = wpi::substr(wpi::substr(req, req.find('?', pos + 16)), 1);
} else if (req.find("GET /settings") != std::string_view::npos &&
req.find(".json") != std::string_view::npos) {
kind = kGetSettings;
@@ -820,7 +820,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
} else if ((pos = req.find("GET /?action=command")) !=
std::string_view::npos) {
kind = kCommand;
parameters = req.substr(req.find('&', pos + 20)).substr(1);
parameters = wpi::substr(wpi::substr(req, req.find('&', pos + 20)), 1);
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
kind = kRootPage;
} else {
@@ -833,7 +833,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
pos = parameters.find_first_not_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
"-=&1234567890%./");
parameters = parameters.substr(0, pos);
parameters = wpi::substr(parameters, 0, pos);
SDEBUG("command parameters: \"{}\"", parameters);
// Read the rest of the HTTP request.

View File

@@ -107,7 +107,7 @@ static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
static bool IsPercentageProperty(std::string_view name) {
if (wpi::starts_with(name, "raw_")) {
name = name.substr(4);
name = wpi::substr(name, 4);
}
return name == "brightness" || name == "contrast" || name == "saturation" ||
name == "hue" || name == "sharpness" || name == "gain" ||
@@ -181,13 +181,13 @@ static bool GetVendorProduct(int dev, int* vendor, int* product) {
}
std::string_view readStr{readBuf};
if (auto v = wpi::parse_integer<int>(
readStr.substr(readStr.find('v')).substr(1, 4), 16)) {
wpi::substr(wpi::substr(readStr, readStr.find('v')), 1, 4), 16)) {
*vendor = v.value();
} else {
return false;
}
if (auto v = wpi::parse_integer<int>(
readStr.substr(readStr.find('p')).substr(1, 4), 16)) {
wpi::substr(wpi::substr(readStr, readStr.find('p')), 1, 4), 16)) {
*product = v.value();
} else {
return false;
@@ -236,8 +236,8 @@ static bool GetDescriptionIoctl(const char* cpath, std::string* desc) {
std::optional<int> vendor;
std::optional<int> product;
if (wpi::starts_with(card, "UVC Camera (") &&
(vendor = wpi::parse_integer<int>(card.substr(12, 4), 16)) &&
(product = wpi::parse_integer<int>(card.substr(17, 4), 16))) {
(vendor = wpi::parse_integer<int>(wpi::substr(card, 12, 4), 16)) &&
(product = wpi::parse_integer<int>(wpi::substr(card, 17, 4), 16))) {
std::string card2 = GetUsbNameFromId(vendor.value(), product.value());
if (!card2.empty()) {
*desc = std::move(card2);
@@ -283,7 +283,7 @@ static int GetDeviceNum(const char* cpath) {
if (!wpi::starts_with(fn, "video")) {
return -1;
}
if (auto dev = wpi::parse_integer<int>(fn.substr(5), 10)) {
if (auto dev = wpi::parse_integer<int>(wpi::substr(fn, 5), 10)) {
return dev.value();
}
return -1;
@@ -1635,7 +1635,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
}
unsigned int dev = 0;
if (auto v = wpi::parse_integer<unsigned int>(fname.substr(5), 10)) {
if (auto v =
wpi::parse_integer<unsigned int>(wpi::substr(fname, 5), 10)) {
dev = v.value();
} else {
continue;
@@ -1686,7 +1687,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
std::string fname = fs::path{target}.filename();
std::optional<unsigned int> dev;
if (wpi::starts_with(fname, "video") &&
(dev = wpi::parse_integer<unsigned int>(fname.substr(5), 10)) &&
(dev = wpi::parse_integer<unsigned int>(wpi::substr(fname, 5),
10)) &&
dev.value() < retval.size()) {
retval[dev.value()].otherPaths.emplace_back(path.str());
}

View File

@@ -6,6 +6,7 @@
#include <fmt/format.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include "UsbUtil.h"
@@ -93,7 +94,7 @@ static int GetStringCtrlIoctl(int fd, int id, int maximum, std::string* value) {
static int SetStringCtrlIoctl(int fd, int id, int maximum,
std::string_view value) {
wpi::SmallString<64> str{value.substr(0, maximum)};
wpi::SmallString<64> str{wpi::substr(value, 0, maximum)};
struct v4l2_ext_control ctrl;
struct v4l2_ext_controls ctrls;

View File

@@ -49,7 +49,7 @@ static std::string GetUsbNameFromFile(int vendor, int product) {
// look for vendor at start of line
if (wpi::starts_with(line, vendorStr)) {
foundVendor = true;
buf += wpi::trim(line.substr(5));
buf += wpi::trim(wpi::substr(line, 5));
buf += ' ';
continue;
}
@@ -62,8 +62,8 @@ static std::string GetUsbNameFromFile(int vendor, int product) {
}
// look for product
if (wpi::starts_with(line.substr(1), productStr)) {
buf += wpi::trim(line.substr(6));
if (wpi::starts_with(wpi::substr(line, 1), productStr)) {
buf += wpi::trim(wpi::substr(line, 6));
return buf;
}
}

View File

@@ -269,7 +269,7 @@ void UsbCameraImpl::DeviceDisconnect() {
static bool IsPercentageProperty(std::string_view name) {
if (wpi::starts_with(name, "raw_"))
name = name.substr(4);
name = wpi::substr(name, 4);
return name == "Brightness" || name == "Contrast" || name == "Saturation" ||
name == "Hue" || name == "Sharpness" || name == "Gain" ||
name == "Exposure";

View File

@@ -125,9 +125,10 @@ doxygen {
}
case_sense_names false
extension_mapping 'inc=C++'
extension_mapping 'inc=C++', 'no_extension=C++'
extract_all true
extract_static true
file_patterns '*'
full_path_names true
generate_html true
generate_latex false

View File

@@ -11,7 +11,9 @@
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/MainMenuBar.h"
#include "glass/Model.h"
#include "glass/Storage.h"
#include "glass/View.h"
#include "glass/networktables/NetworkTables.h"
#include "glass/networktables/NetworkTablesProvider.h"
@@ -39,9 +41,12 @@ static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
static glass::LogData gNetworkTablesLog;
static glass::Window* gNetworkTablesWindow;
static glass::Window* gNetworkTablesSettingsWindow;
static glass::Window* gNetworkTablesLogWindow;
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
static glass::MainMenuBar gMainMenu;
static bool gAbout = false;
static void NtInitialize() {
// update window title when connection status changes
@@ -84,48 +89,65 @@ static void NtInitialize() {
}
});
gNetworkTablesLogWindow = gNtProvider->AddWindow(
"NetworkTables Log",
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Log"),
"NetworkTables Log", glass::Window::kHide);
gNetworkTablesLogWindow->SetView(
std::make_unique<glass::LogView>(&gNetworkTablesLog));
if (gNetworkTablesLogWindow) {
gNetworkTablesLogWindow->SetDefaultPos(250, 615);
gNetworkTablesLogWindow->SetDefaultSize(600, 130);
gNetworkTablesLogWindow->SetVisible(false);
gNetworkTablesLogWindow->DisableRenamePopup();
}
gNetworkTablesLogWindow->SetDefaultPos(250, 615);
gNetworkTablesLogWindow->SetDefaultSize(600, 130);
gNetworkTablesLogWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesLogWindow->Display(); });
// NetworkTables table window
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
gNetworkTablesWindow = gNtProvider->AddWindow(
"NetworkTables",
gNetworkTablesWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables");
gNetworkTablesWindow->SetView(
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
if (gNetworkTablesWindow) {
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
}
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
// NetworkTables settings window
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>();
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
"NetworkTables Settings", [] { gNetworkTablesSettings->Display(); });
if (gNetworkTablesSettingsWindow) {
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
gNetworkTablesSettingsWindow->DisableRenamePopup();
}
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Settings"),
"NetworkTables Settings");
gNetworkTablesSettingsWindow->SetView(
glass::MakeFunctionView([] { gNetworkTablesSettings->Display(); }));
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
gNetworkTablesSettingsWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesSettingsWindow->Display(); });
gui::AddWindowScaler([](float scale) {
// scale default window positions
gNetworkTablesLogWindow->ScaleDefault(scale);
gNetworkTablesWindow->ScaleDefault(scale);
gNetworkTablesSettingsWindow->ScaleDefault(scale);
});
}
#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
int argc = __argc;
char** argv = __argv;
#else
int main() {
int main(int argc, char** argv) {
#endif
std::string_view saveDir;
if (argc == 2) {
saveDir = argv[1];
}
gui::CreateContext();
glass::CreateContext();
@@ -137,20 +159,24 @@ int main() {
gui::AddIcon(glass::GetResource_glass_256_png());
gui::AddIcon(glass::GetResource_glass_512_png());
gPlotProvider = std::make_unique<glass::PlotProvider>("Plot");
gNtProvider = std::make_unique<glass::NetworkTablesProvider>("NTProvider");
gPlotProvider = std::make_unique<glass::PlotProvider>(
glass::GetStorageRoot().GetChild("Plots"));
gNtProvider = std::make_unique<glass::NetworkTablesProvider>(
glass::GetStorageRoot().GetChild("NetworkTables"));
gui::ConfigurePlatformSaveFile("glass.ini");
glass::SetStorageName("glass");
glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
: saveDir);
gPlotProvider->GlobalInit();
gui::AddInit([] { glass::ResetTime(); });
gNtProvider->GlobalInit();
gui::AddInit(NtInitialize);
NtInitialize();
glass::AddStandardNetworkTablesViews(*gNtProvider);
gui::AddLateExecute([] {
ImGui::BeginMainMenuBar();
gui::EmitViewMenu();
gui::AddLateExecute([] { gMainMenu.Display(); });
gMainMenu.AddMainMenu([] {
if (ImGui::BeginMenu("View")) {
if (ImGui::MenuItem("Reset Time")) {
glass::ResetTime();
@@ -181,23 +207,25 @@ int main() {
ImGui::EndMenu();
}
bool about = false;
if (ImGui::BeginMenu("Info")) {
if (ImGui::MenuItem("About")) {
about = true;
gAbout = true;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
});
if (about) {
gui::AddLateExecute([] {
if (gAbout) {
ImGui::OpenPopup("About");
about = false;
gAbout = false;
}
if (ImGui::BeginPopupModal("About")) {
ImGui::Text("Glass: A different kind of dashboard");
ImGui::Separator();
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
@@ -208,11 +236,15 @@ int main() {
gui::Initialize("Glass - DISCONNECTED", 1024, 768);
gui::Main();
gNetworkTablesSettingsWindow.reset();
gNetworkTablesLogWindow.reset();
gNetworkTablesWindow.reset();
gNetworkTablesModel.reset();
gNetworkTablesSettings.reset();
gNtProvider.reset();
gPlotProvider.reset();
glass::DestroyContext();
gui::DestroyContext();
return 0;
}

View File

@@ -7,13 +7,21 @@
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <filesystem>
#include <fmt/format.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <wpi/StringExtras.h>
#include <wpi/fs.h>
#include <wpi/json.h>
#include <wpi/json_serializer.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpi/timestamp.h>
#include <wpigui.h>
#include <wpigui_internal.h>
#include "glass/ContextInternal.h"
@@ -21,172 +29,292 @@ using namespace glass;
Context* glass::gContext;
static bool ConvertInt(Storage::Value* value) {
value->type = Storage::Value::kInt;
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
value->intVal = val.value();
return true;
static void WorkspaceResetImpl() {
// call reset functions
for (auto&& reset : gContext->workspaceReset) {
if (reset) {
reset();
}
}
return false;
// clear storage
for (auto&& root : gContext->storageRoots) {
root.second->Clear();
}
// ImGui reset
ImGui::ClearIniSettings();
}
static bool ConvertInt64(Storage::Value* value) {
value->type = Storage::Value::kInt64;
if (auto val = wpi::parse_integer<int64_t>(value->stringVal, 10)) {
value->int64Val = val.value();
return true;
static void WorkspaceInit() {
for (auto&& init : gContext->workspaceInit) {
if (init) {
init();
}
}
for (auto&& root : gContext->storageRoots) {
root.getValue()->Apply();
}
return false;
}
static bool ConvertBool(Storage::Value* value) {
value->type = Storage::Value::kBool;
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
value->intVal = (val.value() != 0);
return true;
static bool JsonToWindow(const wpi::json& jfile, const char* filename) {
if (!jfile.is_object()) {
ImGui::LogText("%s top level is not object", filename);
return false;
}
return false;
// loop over JSON and generate ini format
std::string iniStr;
wpi::raw_string_ostream ini{iniStr};
for (auto&& jsection : jfile.items()) {
if (!jsection.value().is_object()) {
ImGui::LogText("%s section %s is not object", filename,
jsection.key().c_str());
return false;
}
for (auto&& jsubsection : jsection.value().items()) {
if (!jsubsection.value().is_object()) {
ImGui::LogText("%s section %s subsection %s is not object", filename,
jsection.key().c_str(), jsubsection.key().c_str());
return false;
}
ini << '[' << jsection.key() << "][" << jsubsection.key() << "]\n";
for (auto&& jkv : jsubsection.value().items()) {
try {
auto& value = jkv.value().get_ref<const std::string&>();
ini << jkv.key() << '=' << value << "\n";
} catch (wpi::json::exception&) {
ImGui::LogText("%s section %s subsection %s value %s is not string",
filename, jsection.key().c_str(),
jsubsection.key().c_str(), jkv.key().c_str());
return false;
}
}
ini << '\n';
}
}
ini.flush();
ImGui::LoadIniSettingsFromMemory(iniStr.data(), iniStr.size());
return true;
}
static bool ConvertFloat(Storage::Value* value) {
value->type = Storage::Value::kFloat;
if (auto val = wpi::parse_float<float>(value->stringVal)) {
value->floatVal = val.value();
return true;
}
return false;
}
static bool ConvertDouble(Storage::Value* value) {
value->type = Storage::Value::kDouble;
if (auto val = wpi::parse_float<double>(value->stringVal)) {
value->doubleVal = val.value();
return true;
}
return false;
}
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::split(line, '=');
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));
static bool LoadWindowStorageImpl(const std::string& filename) {
std::error_code ec;
wpi::raw_fd_istream is{filename, ec};
if (ec) {
ImGui::LogText("error opening %s: %s", filename.c_str(),
ec.message().c_str());
return false;
} 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;
try {
return JsonToWindow(wpi::json::parse(is), filename.c_str());
} catch (wpi::json::parse_error& e) {
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
return false;
}
}
}
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);
static bool LoadStorageRootImpl(Context* ctx, const std::string& filename,
std::string_view rootName) {
std::error_code ec;
wpi::raw_fd_istream is{filename, ec};
if (ec) {
ImGui::LogText("error opening %s: %s", filename.c_str(),
ec.message().c_str());
return false;
} else {
auto& storage = ctx->storageRoots[rootName];
bool createdStorage = false;
if (!storage) {
storage = std::make_unique<Storage>();
createdStorage = true;
}
try {
storage->FromJson(wpi::json::parse(is), filename.c_str());
} catch (wpi::json::parse_error& e) {
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
if (createdStorage) {
ctx->storageRoots.erase(rootName);
}
return false;
}
}
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
return a->getKey() < b->getKey();
});
return true;
}
for (auto&& entryIt : sorted) {
auto& entry = *entryIt;
out_buf->append("[GlassStorage][");
out_buf->append(entry.first().data(),
entry.first().data() + entry.first().size());
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;
static bool LoadStorageImpl(Context* ctx, std::string_view dir,
std::string_view name) {
WorkspaceResetImpl();
bool rv = true;
for (auto&& root : ctx->storageRoots) {
std::string filename;
auto rootName = root.getKey();
if (rootName.empty()) {
filename = (fs::path{dir} / fmt::format("{}.json", name)).string();
} else {
filename =
(fs::path{dir} / fmt::format("{}-{}.json", name, rootName)).string();
}
if (!LoadStorageRootImpl(ctx, filename, rootName)) {
rv = false;
}
}
WorkspaceInit();
return rv;
}
static wpi::json WindowToJson() {
size_t iniLen;
const char* iniData = ImGui::SaveIniSettingsToMemory(&iniLen);
std::string_view ini{iniData, iniLen};
// parse the ini data and build JSON
// JSON format:
// {
// "Section": {
// "Subsection": {
// "Key": "Value" // all values are saved as strings
// }
// }
// }
wpi::json out = wpi::json::object();
wpi::json* curSection = nullptr;
while (!ini.empty()) {
std::string_view line;
std::tie(line, ini) = wpi::split(ini, '\n');
line = wpi::trim(line);
if (line.empty()) {
continue;
}
if (line[0] == '[') {
// new section
auto [section, subsection] = wpi::split(line, ']');
section = wpi::drop_front(section); // drop '['; ']' was dropped by split
subsection = wpi::drop_back(wpi::drop_front(subsection)); // drop []
auto& jsection = out[section];
if (jsection.is_null()) {
jsection = wpi::json::object();
}
curSection = &jsection[subsection];
if (curSection->is_null()) {
*curSection = wpi::json::object();
}
} else {
// value
if (!curSection) {
continue; // shouldn't happen, but just in case
}
auto [name, value] = wpi::split(line, '=');
(*curSection)[name] = value;
}
}
return out;
}
bool SaveWindowStorageImpl(const std::string& filename) {
std::error_code ec;
wpi::raw_fd_ostream os{filename, ec};
if (ec) {
ImGui::LogText("error opening %s: %s", filename.c_str(),
ec.message().c_str());
return false;
}
WindowToJson().dump(os, 2);
os << '\n';
return true;
}
static bool SaveStorageRootImpl(Context* ctx, const std::string& filename,
const Storage& storage) {
std::error_code ec;
wpi::raw_fd_ostream os{filename, ec};
if (ec) {
ImGui::LogText("error opening %s: %s", filename.c_str(),
ec.message().c_str());
return false;
}
storage.ToJson().dump(os, 2);
os << '\n';
return true;
}
static bool SaveStorageImpl(Context* ctx, std::string_view dir,
std::string_view name, bool exiting) {
fs::path dirPath{dir};
std::error_code ec;
fs::create_directories(dirPath, ec);
if (ec) {
return false;
}
// handle erasing save files on exit if requested
if (exiting && wpi::gui::gContext->resetOnExit) {
fs::remove(dirPath / fmt::format("{}-window.json", name), ec);
for (auto&& root : ctx->storageRoots) {
auto rootName = root.getKey();
if (rootName.empty()) {
fs::remove(dirPath / fmt::format("{}.json", name), ec);
} else {
fs::remove(dirPath / fmt::format("{}-{}.json", name, rootName), ec);
}
}
out_buf->append("\n");
}
bool rv = SaveWindowStorageImpl(
(dirPath / fmt::format("{}-window.json", name)).string());
for (auto&& root : ctx->storageRoots) {
auto rootName = root.getKey();
std::string filename;
if (rootName.empty()) {
filename = (dirPath / fmt::format("{}.json", name)).string();
} else {
filename = (dirPath / fmt::format("{}-{}.json", name, rootName)).string();
}
if (!SaveStorageRootImpl(ctx, filename, *root.getValue())) {
rv = false;
}
}
return rv;
}
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);
Context::Context()
: sourceNameStorage{storageRoots.insert({"", std::make_unique<Storage>()})
.first->getValue()
->GetChild("sourceNames")} {
storageStack.emplace_back(storageRoots[""].get());
ctx->sources.Initialize();
});
// override ImGui ini saving
wpi::gui::ConfigureCustomSaveSettings(
[this] { LoadStorageImpl(this, storageLoadDir, storageName); },
[this] {
LoadWindowStorageImpl((fs::path{storageLoadDir} /
fmt::format("{}-window.json", storageName))
.string());
},
[this](bool exiting) {
SaveStorageImpl(this, storageAutoSaveDir, storageName, exiting);
});
}
static void Shutdown(Context* ctx) {}
Context::~Context() {
wpi::gui::ConfigureCustomSaveSettings(nullptr, nullptr, nullptr);
}
Context* glass::CreateContext() {
Context* ctx = new Context;
if (!gContext) {
SetCurrentContext(ctx);
}
Initialize(ctx);
return ctx;
}
@@ -194,7 +322,6 @@ void glass::DestroyContext(Context* ctx) {
if (!ctx) {
ctx = gContext;
}
Shutdown(ctx);
if (gContext == ctx) {
SetCurrentContext(nullptr);
}
@@ -217,215 +344,167 @@ uint64_t glass::GetZeroTime() {
return gContext->zeroTime;
}
Storage::Value& Storage::GetValue(std::string_view 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()];
void glass::WorkspaceReset() {
WorkspaceResetImpl();
WorkspaceInit();
}
void glass::AddWorkspaceInit(std::function<void()> init) {
if (init) {
gContext->workspaceInit.emplace_back(std::move(init));
}
}
#define DEFUN(CapsName, LowerName, CType) \
CType Storage::Get##CapsName(std::string_view 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(std::string_view 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(std::string_view 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(std::string_view key,
std::string_view defaultVal) const {
auto it = std::find(m_keys.begin(), m_keys.end(), key);
if (it == m_keys.end()) {
return std::string{defaultVal};
}
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return value.stringVal;
}
void Storage::SetString(std::string_view key, std::string_view 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;
void glass::AddWorkspaceReset(std::function<void()> reset) {
if (reset) {
gContext->workspaceReset.emplace_back(std::move(reset));
}
}
std::string* Storage::GetStringRef(std::string_view key,
std::string_view 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;
void glass::SetStorageName(std::string_view name) {
gContext->storageName = name;
}
void glass::SetStorageDir(std::string_view dir) {
if (dir.empty()) {
gContext->storageLoadDir = ".";
gContext->storageAutoSaveDir = ".";
} else {
Value& value = *m_values[it - m_keys.begin()];
value.type = Value::kString;
return &value.stringVal;
gContext->storageLoadDir = dir;
gContext->storageAutoSaveDir = dir;
gContext->isPlatformSaveDir = (dir == wpi::gui::GetPlatformSaveFileDir());
}
}
std::string glass::GetStorageDir() {
return gContext->storageAutoSaveDir;
}
bool glass::LoadStorage(std::string_view dir) {
SaveStorage();
SetStorageDir(dir);
LoadWindowStorageImpl((fs::path{gContext->storageLoadDir} /
fmt::format("{}-window.json", gContext->storageName))
.string());
return LoadStorageImpl(gContext, dir, gContext->storageName);
}
bool glass::SaveStorage() {
return SaveStorageImpl(gContext, gContext->storageAutoSaveDir,
gContext->storageName, false);
}
bool glass::SaveStorage(std::string_view dir) {
return SaveStorageImpl(gContext, dir, gContext->storageName, false);
}
Storage& glass::GetCurStorageRoot() {
return *gContext->storageStack.front();
}
Storage& glass::GetStorageRoot(std::string_view rootName) {
auto& storage = gContext->storageRoots[rootName];
if (!storage) {
storage = std::make_unique<Storage>();
}
return *storage;
}
void glass::ResetStorageStack(std::string_view rootName) {
if (gContext->storageStack.size() != 1) {
ImGui::LogText("resetting non-empty storage stack");
}
gContext->storageStack.clear();
gContext->storageStack.emplace_back(&GetStorageRoot(rootName));
}
Storage& glass::GetStorage() {
auto& storage = gContext->storage[gContext->curId];
if (!storage) {
storage = std::make_unique<Storage>();
}
return *storage;
return *gContext->storageStack.back();
}
Storage& glass::GetStorage(std::string_view id) {
auto& storage = gContext->storage[id];
if (!storage) {
storage = std::make_unique<Storage>();
}
return *storage;
void glass::PushStorageStack(std::string_view label_id) {
gContext->storageStack.emplace_back(
&gContext->storageStack.back()->GetChild(label_id));
}
static void PushIDStack(std::string_view label_id) {
gContext->idStack.emplace_back(gContext->curId.size());
auto [label, id] = wpi::split(label_id, "###");
// if no ###id, use label as id
if (id.empty()) {
id = label;
}
if (!gContext->curId.empty()) {
gContext->curId += "###";
}
gContext->curId += id;
void glass::PushStorageStack(Storage& storage) {
gContext->storageStack.emplace_back(&storage);
}
static void PopIDStack() {
gContext->curId.resize(gContext->idStack.back());
gContext->idStack.pop_back();
void glass::PopStorageStack() {
if (gContext->storageStack.size() <= 1) {
ImGui::LogText("attempted to pop empty storage stack, mismatch push/pop?");
return; // ignore
}
gContext->storageStack.pop_back();
}
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
PushIDStack(name);
PushStorageStack(name);
return ImGui::Begin(name, p_open, flags);
}
void glass::End() {
ImGui::End();
PopIDStack();
PopStorageStack();
}
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
ImGuiWindowFlags flags) {
PushIDStack(str_id);
PushStorageStack(str_id);
return ImGui::BeginChild(str_id, size, border, flags);
}
void glass::EndChild() {
ImGui::EndChild();
PopIDStack();
PopStorageStack();
}
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
wpi::SmallString<64> openKey;
auto [name, id] = wpi::split(label, "###");
// if no ###id, use name as id
if (id.empty()) {
id = name;
}
openKey = id;
openKey += "###open";
bool* open = GetStorage().GetBoolRef(openKey.str());
*open = ImGui::CollapsingHeader(
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
return *open;
bool& open = GetStorage().GetChild(label).GetBool(
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
ImGui::SetNextItemOpen(open);
open = ImGui::CollapsingHeader(label, flags);
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();
PushStorageStack(label);
bool& open = GetStorage().GetBool(
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
ImGui::SetNextItemOpen(open);
open = ImGui::TreeNodeEx(label, flags);
if (!open) {
PopStorageStack();
}
return *open;
return open;
}
void glass::TreePop() {
ImGui::TreePop();
PopIDStack();
PopStorageStack();
}
void glass::PushID(const char* str_id) {
PushIDStack(str_id);
PushStorageStack(str_id);
ImGui::PushID(str_id);
}
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
PushIDStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
PushStorageStack(std::string_view(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);
PushStorageStack(buf);
ImGui::PushID(int_id);
}
void glass::PopID() {
ImGui::PopID();
PopIDStack();
PopStorageStack();
}
bool glass::PopupEditName(const char* label, std::string* name) {

View File

@@ -12,13 +12,9 @@ using namespace glass;
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
DataSource::DataSource(std::string_view id) : m_id{id} {
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;
}
DataSource::DataSource(std::string_view id)
: m_id{id}, m_name{gContext->sourceNameStorage.GetString(m_id)} {
gContext->sources.try_emplace(m_id, this);
sourceCreated(m_id.c_str(), this);
}
@@ -32,43 +28,7 @@ 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(std::string_view 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);
gContext->sources.erase(m_id);
}
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
@@ -82,7 +42,7 @@ void DataSource::LabelText(const char* label, const char* fmt, ...) const {
void DataSource::LabelTextV(const char* label, const char* fmt,
va_list args) const {
ImGui::PushID(label);
ImGui::LabelTextV("##input", fmt, args);
ImGui::LabelTextV("##input", fmt, args); // NOLINT
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Selectable(label);
ImGui::PopID();
@@ -141,7 +101,7 @@ void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
if (ImGui::BeginDragDropSource(flags)) {
auto self = this;
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self)); // NOLINT
const char* name = GetName();
const char* name = GetName().c_str();
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
ImGui::EndDragDropSource();
}
@@ -152,5 +112,5 @@ DataSource* DataSource::Find(std::string_view id) {
if (it == gContext->sources.end()) {
return nullptr;
}
return it->getValue().source;
return it->getValue();
}

View File

@@ -6,8 +6,12 @@
#include <cstdio>
#include <imgui.h>
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/ContextInternal.h"
using namespace glass;
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
@@ -25,6 +29,8 @@ void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
void MainMenuBar::Display() {
ImGui::BeginMainMenuBar();
WorkspaceMenu();
if (!m_optionMenus.empty()) {
if (ImGui::BeginMenu("Options")) {
for (auto&& menu : m_optionMenus) {
@@ -55,3 +61,46 @@ void MainMenuBar::Display() {
#endif
ImGui::EndMainMenuBar();
}
void MainMenuBar::WorkspaceMenu() {
if (ImGui::BeginMenu("Workspace")) {
if (ImGui::MenuItem("Open...")) {
m_openFolder =
std::make_unique<pfd::select_folder>("Choose folder to open");
}
if (ImGui::MenuItem("Save As...")) {
m_saveFolder = std::make_unique<pfd::select_folder>("Choose save folder");
}
if (ImGui::MenuItem("Save As Global", nullptr, false,
!gContext->isPlatformSaveDir)) {
SetStorageDir(wpi::gui::GetPlatformSaveFileDir());
SaveStorage();
}
ImGui::Separator();
if (ImGui::MenuItem("Reset")) {
WorkspaceReset();
}
ImGui::Separator();
if (ImGui::MenuItem("Exit")) {
wpi::gui::Exit();
}
ImGui::EndMenu();
}
if (m_openFolder && m_openFolder->ready(0)) {
auto result = m_openFolder->result();
if (!result.empty()) {
LoadStorage(result);
}
m_openFolder.reset();
}
if (m_saveFolder && m_saveFolder->ready(0)) {
auto result = m_saveFolder->result();
if (!result.empty()) {
SetStorageDir(result);
SaveStorage(result);
}
m_saveFolder.reset();
}
}

View File

@@ -0,0 +1,688 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/Storage.h"
#include <type_traits>
#include <imgui.h>
#include <wpi/StringExtras.h>
#include <wpi/json.h>
using namespace glass;
template <typename To>
bool ConvertFromString(To* out, std::string_view str) {
if constexpr (std::is_same_v<To, bool>) {
if (str == "true") {
*out = true;
} else if (str == "false") {
*out = false;
} else if (auto val = wpi::parse_integer<int>(str, 10)) {
*out = val.value() != 0;
} else {
return false;
}
} else if constexpr (std::is_floating_point_v<To>) {
if (auto val = wpi::parse_float<To>(str)) {
*out = val.value();
} else {
return false;
}
} else {
if (auto val = wpi::parse_integer<To>(str, 10)) {
*out = val.value();
} else {
return false;
}
}
return true;
}
#define CONVERT(CapsName, LowerName, CType) \
static bool Convert##CapsName(Storage::Value* value) { \
switch (value->type) { \
case Storage::Value::kBool: \
value->LowerName##Val = value->boolVal; \
value->LowerName##Default = value->boolDefault; \
break; \
case Storage::Value::kDouble: \
value->LowerName##Val = value->doubleVal; \
value->LowerName##Default = value->doubleDefault; \
break; \
case Storage::Value::kFloat: \
value->LowerName##Val = value->floatVal; \
value->LowerName##Default = value->floatDefault; \
break; \
case Storage::Value::kInt: \
value->LowerName##Val = value->intVal; \
value->LowerName##Default = value->intDefault; \
break; \
case Storage::Value::kInt64: \
value->LowerName##Val = value->int64Val; \
value->LowerName##Default = value->int64Default; \
break; \
case Storage::Value::kString: \
if (!ConvertFromString(&value->LowerName##Val, value->stringVal)) { \
return false; \
} \
if (!ConvertFromString(&value->LowerName##Default, \
value->stringDefault)) { \
return false; \
} \
break; \
default: \
return false; \
} \
value->type = Storage::Value::k##CapsName; \
return true; \
}
CONVERT(Int, int, int)
CONVERT(Int64, int64, int64_t)
CONVERT(Float, float, float)
CONVERT(Double, double, double)
CONVERT(Bool, bool, bool)
static inline bool ConvertString(Storage::Value* value) {
return false;
}
// Arrays can only come from JSON, so we only have to worry about conversions
// between the various number types, not bool or string
template <typename From, typename To>
static void ConvertArray(std::vector<To>** outPtr, std::vector<From>** inPtr) {
if (*inPtr) {
std::vector<To>* tmp;
tmp = new std::vector<To>{(*inPtr)->begin(), (*inPtr)->end()};
delete *inPtr;
*outPtr = tmp;
} else {
*outPtr = nullptr;
}
}
#define CONVERT_ARRAY(CapsName, LowerName) \
static bool Convert##CapsName##Array(Storage::Value* value) { \
switch (value->type) { \
case Storage::Value::kDoubleArray: \
ConvertArray(&value->LowerName##Array, &value->doubleArray); \
ConvertArray(&value->LowerName##ArrayDefault, \
&value->doubleArrayDefault); \
break; \
case Storage::Value::kFloatArray: \
ConvertArray(&value->LowerName##Array, &value->floatArray); \
ConvertArray(&value->LowerName##ArrayDefault, \
&value->floatArrayDefault); \
break; \
case Storage::Value::kIntArray: \
ConvertArray(&value->LowerName##Array, &value->intArray); \
ConvertArray(&value->LowerName##ArrayDefault, \
&value->intArrayDefault); \
break; \
case Storage::Value::kInt64Array: \
ConvertArray(&value->LowerName##Array, &value->int64Array); \
ConvertArray(&value->LowerName##ArrayDefault, \
&value->int64ArrayDefault); \
break; \
default: \
return false; \
} \
value->type = Storage::Value::k##CapsName##Array; \
return true; \
}
CONVERT_ARRAY(Int, int)
CONVERT_ARRAY(Int64, int64)
CONVERT_ARRAY(Float, float)
CONVERT_ARRAY(Double, double)
static inline bool ConvertBoolArray(Storage::Value* value) {
return false;
}
static inline bool ConvertStringArray(Storage::Value* value) {
return false;
}
void Storage::Value::Reset(Type newType) {
switch (type) {
case kChild:
delete child;
break;
case kIntArray:
delete intArray;
delete intArrayDefault;
break;
case kInt64Array:
delete int64Array;
delete int64ArrayDefault;
break;
case kBoolArray:
delete boolArray;
delete boolArrayDefault;
break;
case kFloatArray:
delete floatArray;
delete floatArrayDefault;
break;
case kDoubleArray:
delete doubleArray;
delete doubleArrayDefault;
break;
case kStringArray:
delete stringArray;
delete stringArrayDefault;
break;
case kChildArray:
delete childArray;
break;
default:
break;
}
type = newType;
}
Storage::Value* Storage::FindValue(std::string_view key) {
auto it = m_values.find(key);
if (it == m_values.end()) {
return nullptr;
}
return it->second.get();
}
Storage::Value& Storage::GetValue(std::string_view key) {
auto& val = m_values[key];
if (!val) {
val = std::make_unique<Value>();
}
return *val;
}
#define DEFUN(CapsName, LowerName, CType, CParamType, ArrCType) \
CType Storage::Read##CapsName(std::string_view key, CParamType defaultVal) \
const { \
auto it = m_values.find(key); \
if (it == m_values.end()) { \
return CType{defaultVal}; \
} \
Value& value = *it->second; \
if (value.type != Value::k##CapsName) { \
if (!Convert##CapsName(&value)) { \
value.Reset(Value::k##CapsName); \
value.LowerName##Val = defaultVal; \
value.LowerName##Default = defaultVal; \
value.hasDefault = true; \
} \
} \
return value.LowerName##Val; \
} \
\
void Storage::Set##CapsName(std::string_view key, CParamType val) { \
auto& valuePtr = m_values[key]; \
if (!valuePtr) { \
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
} else { \
valuePtr->Reset(Value::k##CapsName); \
} \
valuePtr->LowerName##Val = val; \
valuePtr->LowerName##Default = {}; \
} \
\
CType& Storage::Get##CapsName(std::string_view key, CParamType defaultVal) { \
auto& valuePtr = m_values[key]; \
bool setValue = false; \
if (!valuePtr) { \
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
setValue = true; \
} else if (valuePtr->type != Value::k##CapsName) { \
if (!Convert##CapsName(valuePtr.get())) { \
valuePtr->Reset(Value::k##CapsName); \
setValue = true; \
} \
} \
if (setValue) { \
valuePtr->LowerName##Val = defaultVal; \
} \
if (!valuePtr->hasDefault) { \
valuePtr->LowerName##Default = defaultVal; \
valuePtr->hasDefault = true; \
} \
return valuePtr->LowerName##Val; \
} \
\
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
auto& valuePtr = m_values[key]; \
bool setValue = false; \
if (!valuePtr) { \
valuePtr = std::make_unique<Value>(Value::k##CapsName##Array); \
setValue = true; \
} else if (valuePtr->type != Value::k##CapsName##Array) { \
if (!Convert##CapsName##Array(valuePtr.get())) { \
valuePtr->Reset(Value::k##CapsName##Array); \
setValue = true; \
} \
} \
if (setValue) { \
valuePtr->LowerName##Array = \
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
} \
if (!valuePtr->hasDefault) { \
if (defaultVal.empty()) { \
valuePtr->LowerName##ArrayDefault = nullptr; \
} else { \
valuePtr->LowerName##ArrayDefault = \
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
} \
valuePtr->hasDefault = true; \
} \
assert(valuePtr->LowerName##Array); \
return *valuePtr->LowerName##Array; \
}
DEFUN(Int, int, int, int, int)
DEFUN(Int64, int64, int64_t, int64_t, int64_t)
DEFUN(Bool, bool, bool, bool, int)
DEFUN(Float, float, float, float, float)
DEFUN(Double, double, double, double, double)
DEFUN(String, string, std::string, std::string_view, std::string)
Storage& Storage::GetChild(std::string_view label_id) {
auto [label, id] = wpi::split(label_id, "###");
if (id.empty()) {
id = label;
}
auto& childPtr = m_values[id];
if (!childPtr) {
childPtr = std::make_unique<Value>();
}
if (childPtr->type != Value::kChild) {
childPtr->type = Value::kChild;
childPtr->child = new Storage;
}
return *childPtr->child;
}
std::vector<std::unique_ptr<Storage>>& Storage::GetChildArray(
std::string_view key) {
auto& valuePtr = m_values[key];
if (!valuePtr) {
valuePtr = std::make_unique<Value>(Value::kChildArray);
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
} else if (valuePtr->type != Value::kChildArray) {
valuePtr->Reset(Value::kChildArray);
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
}
return *valuePtr->childArray;
}
std::unique_ptr<Storage::Value> Storage::Erase(std::string_view key) {
auto it = m_values.find(key);
if (it != m_values.end()) {
auto rv = std::move(it->getValue());
m_values.erase(it);
return rv;
}
return nullptr;
}
void Storage::EraseChildren() {
for (auto&& kv : m_values) {
if (kv.getValue()->type == Value::kChild) {
m_values.remove(&kv);
}
}
}
static bool JsonArrayToStorage(Storage::Value* valuePtr, const wpi::json& jarr,
const char* filename) {
auto& arr = jarr.get_ref<const wpi::json::array_t&>();
if (arr.empty()) {
ImGui::LogText("empty array in %s, ignoring", filename);
return false;
}
// guess array type from first element
switch (arr[0].type()) {
case wpi::json::value_t::boolean:
if (valuePtr->type != Storage::Value::kBoolArray) {
valuePtr->Reset(Storage::Value::kBoolArray);
valuePtr->boolArray = new std::vector<int>();
valuePtr->boolArrayDefault = nullptr;
}
break;
case wpi::json::value_t::number_float:
if (valuePtr->type != Storage::Value::kDoubleArray) {
valuePtr->Reset(Storage::Value::kDoubleArray);
valuePtr->doubleArray = new std::vector<double>();
valuePtr->doubleArrayDefault = nullptr;
}
break;
case wpi::json::value_t::number_integer:
case wpi::json::value_t::number_unsigned:
if (valuePtr->type != Storage::Value::kInt64Array) {
valuePtr->Reset(Storage::Value::kInt64Array);
valuePtr->int64Array = new std::vector<int64_t>();
valuePtr->int64ArrayDefault = nullptr;
}
break;
case wpi::json::value_t::string:
if (valuePtr->type != Storage::Value::kStringArray) {
valuePtr->Reset(Storage::Value::kStringArray);
valuePtr->stringArray = new std::vector<std::string>();
valuePtr->stringArrayDefault = nullptr;
}
break;
case wpi::json::value_t::object:
if (valuePtr->type != Storage::Value::kChildArray) {
valuePtr->Reset(Storage::Value::kChildArray);
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
}
break;
case wpi::json::value_t::array:
ImGui::LogText("nested array in %s, ignoring", filename);
return false;
default:
ImGui::LogText("null value in %s, ignoring", filename);
return false;
}
// loop over array to store elements
for (auto jvalue : arr) {
switch (jvalue.type()) {
case wpi::json::value_t::boolean:
if (valuePtr->type == Storage::Value::kBoolArray) {
valuePtr->boolArray->push_back(jvalue.get<bool>());
} else {
goto error;
}
break;
case wpi::json::value_t::number_float:
if (valuePtr->type == Storage::Value::kDoubleArray) {
valuePtr->doubleArray->push_back(jvalue.get<double>());
} else {
goto error;
}
break;
case wpi::json::value_t::number_integer:
if (valuePtr->type == Storage::Value::kInt64Array) {
valuePtr->int64Array->push_back(jvalue.get<int64_t>());
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
valuePtr->doubleArray->push_back(jvalue.get<int64_t>());
} else {
goto error;
}
break;
case wpi::json::value_t::number_unsigned:
if (valuePtr->type == Storage::Value::kInt64Array) {
valuePtr->int64Array->push_back(jvalue.get<uint64_t>());
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
valuePtr->doubleArray->push_back(jvalue.get<uint64_t>());
} else {
goto error;
}
break;
case wpi::json::value_t::string:
if (valuePtr->type == Storage::Value::kStringArray) {
valuePtr->stringArray->emplace_back(
jvalue.get_ref<const std::string&>());
} else {
goto error;
}
break;
case wpi::json::value_t::object:
if (valuePtr->type == Storage::Value::kChildArray) {
valuePtr->childArray->emplace_back(std::make_unique<Storage>());
valuePtr->childArray->back()->FromJson(jvalue, filename);
} else {
goto error;
}
break;
case wpi::json::value_t::array:
ImGui::LogText("nested array in %s, ignoring", filename);
return false;
default:
ImGui::LogText("null value in %s, ignoring", filename);
return false;
}
}
return true;
error:
ImGui::LogText("array with variant types in %s, ignoring", filename);
return false;
}
bool Storage::FromJson(const wpi::json& json, const char* filename) {
if (m_fromJson) {
return m_fromJson(json, filename);
}
if (!json.is_object()) {
ImGui::LogText("non-object in %s", filename);
return false;
}
for (auto&& jkv : json.items()) {
auto& valuePtr = m_values[jkv.key()];
bool created = false;
if (!valuePtr) {
valuePtr = std::make_unique<Value>();
created = true;
}
auto& jvalue = jkv.value();
switch (jvalue.type()) {
case wpi::json::value_t::boolean:
valuePtr->Reset(Value::kBool);
valuePtr->boolVal = jvalue.get<bool>();
break;
case wpi::json::value_t::number_float:
valuePtr->Reset(Value::kDouble);
valuePtr->doubleVal = jvalue.get<double>();
break;
case wpi::json::value_t::number_integer:
valuePtr->Reset(Value::kInt64);
valuePtr->int64Val = jvalue.get<int64_t>();
break;
case wpi::json::value_t::number_unsigned:
valuePtr->Reset(Value::kInt64);
valuePtr->int64Val = jvalue.get<uint64_t>();
break;
case wpi::json::value_t::string:
valuePtr->Reset(Value::kString);
valuePtr->stringVal = jvalue.get_ref<const std::string&>();
break;
case wpi::json::value_t::object:
if (valuePtr->type != Value::kChild) {
valuePtr->Reset(Value::kChild);
valuePtr->child = new Storage;
}
valuePtr->child->FromJson(jvalue, filename); // recurse
break;
case wpi::json::value_t::array:
if (!JsonArrayToStorage(valuePtr.get(), jvalue, filename)) {
if (created) {
m_values.erase(jkv.key());
}
}
break;
default:
ImGui::LogText("null value in %s, ignoring", filename);
if (created) {
m_values.erase(jkv.key());
}
break;
}
}
return true;
}
template <typename T>
static wpi::json StorageToJsonArray(const std::vector<T>& arr) {
wpi::json jarr = wpi::json::array();
for (auto&& v : arr) {
jarr.emplace_back(v);
}
return jarr;
}
template <>
wpi::json StorageToJsonArray<std::unique_ptr<Storage>>(
const std::vector<std::unique_ptr<Storage>>& arr) {
wpi::json jarr = wpi::json::array();
for (auto&& v : arr) {
jarr.emplace_back(v->ToJson());
}
// remove any trailing empty items
while (!jarr.empty() && jarr.back().empty()) {
jarr.get_ref<wpi::json::array_t&>().pop_back();
}
return jarr;
}
wpi::json Storage::ToJson() const {
if (m_toJson) {
return m_toJson();
}
wpi::json j = wpi::json::object();
for (auto&& kv : m_values) {
wpi::json jelem;
auto& value = *kv.getValue();
switch (value.type) {
#define CASE(CapsName, LowerName) \
case Value::k##CapsName: \
if (value.hasDefault && \
value.LowerName##Val == value.LowerName##Default) { \
continue; \
} \
jelem = value.LowerName##Val; \
break; \
case Value::k##CapsName##Array: \
if (value.hasDefault && \
((!value.LowerName##ArrayDefault && \
value.LowerName##Array->empty()) || \
(value.LowerName##ArrayDefault && \
*value.LowerName##Array == *value.LowerName##ArrayDefault))) { \
continue; \
} \
jelem = StorageToJsonArray(*value.LowerName##Array); \
break;
CASE(Int, int)
CASE(Int64, int64)
CASE(Bool, bool)
CASE(Float, float)
CASE(Double, double)
CASE(String, string)
case Value::kChild:
jelem = value.child->ToJson(); // recurse
if (jelem.empty()) {
continue;
}
break;
case Value::kChildArray:
jelem = StorageToJsonArray(*value.childArray);
if (jelem.empty()) {
continue;
}
break;
default:
continue;
}
j.emplace(kv.getKey(), std::move(jelem));
}
return j;
}
void Storage::Clear() {
if (m_clear) {
return m_clear();
}
ClearValues();
}
void Storage::ClearValues() {
for (auto&& kv : m_values) {
auto& value = *kv.getValue();
switch (value.type) {
case Value::kInt:
value.intVal = value.intDefault;
break;
case Value::kInt64:
value.int64Val = value.int64Default;
break;
case Value::kBool:
value.boolVal = value.boolDefault;
break;
case Value::kFloat:
value.floatVal = value.floatDefault;
break;
case Value::kDouble:
value.doubleVal = value.doubleDefault;
break;
case Value::kString:
value.stringVal = value.stringDefault;
break;
case Value::kIntArray:
*value.intArray = *value.intArrayDefault;
break;
case Value::kInt64Array:
*value.int64Array = *value.int64ArrayDefault;
break;
case Value::kBoolArray:
*value.boolArray = *value.boolArrayDefault;
break;
case Value::kFloatArray:
*value.floatArray = *value.floatArrayDefault;
break;
case Value::kDoubleArray:
*value.doubleArray = *value.doubleArrayDefault;
break;
case Value::kStringArray:
*value.stringArray = *value.stringArrayDefault;
break;
case Value::kChild:
value.child->Clear();
break;
case Value::kChildArray:
for (auto&& child : *value.childArray) {
child->Clear();
}
break;
default:
break;
}
}
}
void Storage::Apply() {
if (m_apply) {
return m_apply();
}
ApplyChildren();
}
void Storage::ApplyChildren() {
for (auto&& kv : m_values) {
auto& value = *kv.getValue();
switch (value.type) {
case Value::kChild:
value.child->Apply();
break;
case Value::kChildArray:
for (auto&& child : *value.childArray) {
child->Apply();
}
break;
default:
break;
}
}
}

View File

@@ -8,23 +8,28 @@
#include <wpi/StringExtras.h>
#include "glass/Context.h"
#include "glass/Storage.h"
using namespace glass;
Window::Window(Storage& storage, std::string_view id,
Visibility defaultVisibility)
: m_id{id},
m_name{storage.GetString("name")},
m_defaultName{id},
m_visible{storage.GetBool("visible", defaultVisibility != kHide)},
m_enabled{storage.GetBool("enabled", defaultVisibility != kDisabled)},
m_defaultVisible{storage.GetValue("visible").boolDefault},
m_defaultEnabled{storage.GetValue("enabled").boolDefault} {}
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;
}
m_visible = visibility != kHide;
m_enabled = visibility != kDisabled;
}
void Window::SetDefaultVisibility(Visibility visibility) {
m_defaultVisible = visibility != kHide;
m_defaultEnabled = visibility != kDisabled;
}
void Window::Display() {
@@ -85,27 +90,3 @@ void Window::ScaleDefault(float scale) {
m_size.y *= scale;
}
}
void Window::IniReadLine(const char* line) {
auto [name, value] = wpi::split(line, '=');
name = wpi::trim(name);
value = wpi::trim(value);
if (name == "name") {
m_name = value;
} else if (name == "visible") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_visible = num.value();
}
} else if (name == "enabled") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_enabled = num.value();
}
}
}
void Window::IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf) {
out_buf->appendf("[%s][%s]\nname=%s\nvisible=%d\nenabled=%d\n\n", typeName,
m_id.c_str(), m_name.c_str(), m_visible ? 1 : 0,
m_enabled ? 1 : 0);
}

View File

@@ -10,30 +10,23 @@
#include <fmt/format.h>
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/Storage.h"
using namespace glass;
WindowManager::WindowManager(std::string_view 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);
}
WindowManager::WindowManager(Storage& storage) : m_storage{storage} {
storage.SetCustomApply([this] {
for (auto&& childIt : m_storage.GetChildren()) {
GetOrAddWindow(childIt.key(), true);
}
});
}
Window* WindowManager::AddWindow(std::string_view id,
wpi::unique_function<void()> display) {
auto win = GetOrAddWindow(id, false);
wpi::unique_function<void()> display,
Window::Visibility defaultVisibility) {
auto win = GetOrAddWindow(id, false, defaultVisibility);
if (!win) {
return nullptr;
}
@@ -46,8 +39,9 @@ Window* WindowManager::AddWindow(std::string_view id,
}
Window* WindowManager::AddWindow(std::string_view id,
std::unique_ptr<View> view) {
auto win = GetOrAddWindow(id, false);
std::unique_ptr<View> view,
Window::Visibility defaultVisibility) {
auto win = GetOrAddWindow(id, false, defaultVisibility);
if (!win) {
return nullptr;
}
@@ -59,7 +53,8 @@ Window* WindowManager::AddWindow(std::string_view id,
return win;
}
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk,
Window::Visibility defaultVisibility) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
@@ -72,7 +67,11 @@ Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
return it->get();
}
// insert before (keeps sort)
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
return m_windows
.emplace(it, std::make_unique<Window>(
m_storage.GetChild(id).GetChild("window"), id,
defaultVisibility))
->get();
}
Window* WindowManager::GetWindow(std::string_view id) {
@@ -86,8 +85,12 @@ Window* WindowManager::GetWindow(std::string_view id) {
return it->get();
}
void WindowManager::RemoveWindow(size_t index) {
m_storage.Erase(m_windows[index]->GetId());
m_windows.erase(m_windows.begin() + index);
}
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) {
@@ -104,7 +107,9 @@ void WindowManager::DisplayMenu() {
}
void WindowManager::DisplayWindows() {
PushStorageStack(m_storage);
for (auto&& window : m_windows) {
window->Display();
}
PopStorageStack();
}

View File

@@ -8,6 +8,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
@@ -18,10 +19,10 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
}
// build label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
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);
}
@@ -42,8 +43,8 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
}
// context menu to change name
if (PopupEditName("name", name)) {
voltageData->SetName(name->c_str());
if (PopupEditName("name", &name)) {
voltageData->SetName(name);
}
}

View File

@@ -6,6 +6,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
#include "glass/other/DeviceTree.h"
using namespace glass;
@@ -26,10 +27,10 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
PushID(i);
// build label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
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);
}
@@ -37,9 +38,9 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
double value = analogOutData->GetValue();
DeviceDouble(label, true, &value, analogOutData);
if (PopupEditName("name", name)) {
if (PopupEditName("name", &name)) {
if (analogOutData) {
analogOutData->SetName(name->c_str());
analogOutData->SetName(name);
}
}
PopID();

View File

@@ -8,7 +8,7 @@
#include "glass/DataSource.h"
#include "glass/hardware/Encoder.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/NameSetting.h"
using namespace glass;
@@ -28,17 +28,18 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
bool exists = model->Exists();
auto& info = dioData->GetNameInfo();
NameSetting dioName{dioData->GetName()};
char label[128];
if (exists && dpwmData) {
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
NameSetting{dpwmData->GetName()}.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);
dioName.GetLabel(label, sizeof(label), " In", index);
if (auto simDevice = encoder->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
@@ -48,7 +49,8 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
ImGui::PopStyleColor();
}
} else if (exists && dutyCycleData) {
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
NameSetting{dutyCycleData->GetName()}.GetLabel(label, sizeof(label), "Dty",
index);
if (auto simDevice = dutyCycle->GetSimDevice()) {
LabelSimDevice(label, simDevice);
} else {
@@ -60,10 +62,10 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
} else {
const char* name = model->GetName();
if (name[0] != '\0') {
info.GetLabel(label, sizeof(label), name);
dioName.GetLabel(label, sizeof(label), name);
} else {
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
index);
dioName.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
index);
}
if (auto simDevice = model->GetSimDevice()) {
LabelSimDevice(label, simDevice);
@@ -87,12 +89,12 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
}
}
}
if (info.PopupEditName(index)) {
if (dioName.PopupEditName(index)) {
if (dpwmData) {
dpwmData->SetName(info.GetName());
dpwmData->SetName(dioName.GetName());
}
if (dutyCycleData) {
dutyCycleData->SetName(info.GetName());
dutyCycleData->SetName(dioName.GetName());
}
}
}

View File

@@ -9,6 +9,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
@@ -66,10 +67,10 @@ void glass::DisplayEncoder(EncoderModel* model) {
int chB = model->GetChannelB();
// build header label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
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);
@@ -79,8 +80,8 @@ void glass::DisplayEncoder(EncoderModel* model) {
bool open = CollapsingHeader(label);
// context menu to change name
if (PopupEditName("name", name)) {
model->SetName(name->c_str());
if (PopupEditName("name", &name)) {
model->SetName(name);
}
if (!open) {

View File

@@ -7,6 +7,7 @@
#include <wpi/SmallVector.h>
#include "glass/Context.h"
#include "glass/Storage.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
@@ -25,27 +26,27 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
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);
int& numColumns = storage.GetInt("columns", 10);
bool& serpentine = storage.GetBool("serpentine", false);
int& order = storage.GetInt("order", LEDConfig::RowMajor);
int& start = storage.GetInt("start", LEDConfig::UpperLeft);
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::LabelText("Length", "%d", length);
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
ImGui::InputInt("Columns", numColumns);
ImGui::InputInt("Columns", &numColumns);
{
static const char* options[] = {"Row Major", "Column Major"};
ImGui::Combo("Order", order, options, 2);
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::Combo("Start", &start, options, 4);
}
ImGui::Checkbox("Serpentine", serpentine);
if (*numColumns < 1) {
*numColumns = 1;
ImGui::Checkbox("Serpentine", &serpentine);
if (numColumns < 1) {
numColumns = 1;
}
ImGui::PopItemWidth();
@@ -74,12 +75,12 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
}
LEDConfig config;
config.serpentine = *serpentine;
config.order = static_cast<LEDConfig::Order>(*order);
config.start = static_cast<LEDConfig::Start>(*start);
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);
DrawLEDs(iData->values.data(), length, numColumns, iData->colors.data(), 0, 0,
config);
}
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {

View File

@@ -12,9 +12,10 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
#include "glass/other/DeviceTree.h"
#include "glass/support/ExtraGuiWidgets.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/NameSetting.h"
using namespace glass;
@@ -42,10 +43,10 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
}
// build header label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
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);
}
@@ -53,7 +54,7 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
// header
bool open = CollapsingHeader(label);
PopupEditName("name", name);
PopupEditName("name", &name);
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
@@ -68,11 +69,11 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
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);
char label[64];
NameSetting name{data->GetName()};
name.GetLabel(label, sizeof(label), "Solenoid", j);
data->LabelText(label, "%s", channels[j] == 1 ? "On" : "Off");
name.PopupEditName(j);
PopID();
}
});

View File

@@ -8,6 +8,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
@@ -18,10 +19,10 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
}
// build label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
if (!name->empty()) {
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
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);
}
@@ -35,8 +36,8 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
float val = outputsEnabled ? data->GetValue() : 0;
data->LabelText(label, "%0.3f", val);
}
if (PopupEditName("name", name)) {
data->SetName(name->c_str());
if (PopupEditName("name", &name)) {
data->SetName(name);
}
}

View File

@@ -11,7 +11,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/NameSetting.h"
using namespace glass;
@@ -19,16 +19,16 @@ static float DisplayChannel(PowerDistributionModel& pdp, int channel) {
float width = 0;
if (auto currentData = pdp.GetCurrentData(channel)) {
ImGui::PushID(channel);
auto& leftInfo = currentData->GetNameInfo();
NameSetting leftName{currentData->GetName()};
char name[64];
leftInfo.GetLabel(name, sizeof(name), "", channel);
leftName.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);
leftName.PopupEditName(channel);
ImGui::PopID();
}
return width;

View File

@@ -8,6 +8,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
@@ -31,20 +32,20 @@ void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
}
}
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
ImGui::PushID("name");
if (!name->empty()) {
ImGui::Text("%s [%d]", name->c_str(), index);
if (!name.empty()) {
ImGui::Text("%s [%d]", name.c_str(), index);
} else {
ImGui::Text("Relay[%d]", index);
}
ImGui::PopID();
if (PopupEditName("name", name)) {
if (PopupEditName("name", &name)) {
if (forwardData) {
forwardData->SetName(name->c_str());
forwardData->SetName(name);
}
if (reverseData) {
reverseData->SetName(name->c_str());
reverseData->SetName(name);
}
}
ImGui::SameLine();

View File

@@ -51,13 +51,13 @@ bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
PushID(id);
// build label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
std::snprintf(label, sizeof(label), "%s###name",
name->empty() ? id : name->c_str());
name.empty() ? id : name.c_str());
bool open = CollapsingHeader(label, flags);
PopupEditName("name", name);
PopupEditName("name", &name);
if (!open) {
PopID();

View File

@@ -90,11 +90,20 @@ void glass::DisplayDrive(DriveModel* m) {
double a1 = 0.0;
double a2 = wpi::numbers::pi / 2 * rotation;
draw->PathArcTo(center, radius, a1, a2, 20);
draw->PathStroke(color, false);
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
a2 + wpi::numbers::pi, 20);
draw->PathStroke(color, false);
// PathArcTo requires a_min <= a_max, and rotation can be negative
if (a1 > a2) {
draw->PathArcTo(center, radius, a2, a1, 20);
draw->PathStroke(color, false);
draw->PathArcTo(center, radius, a2 + wpi::numbers::pi,
a1 + wpi::numbers::pi, 20);
draw->PathStroke(color, false);
} else {
draw->PathArcTo(center, radius, a1, a2, 20);
draw->PathStroke(color, false);
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
a2 + wpi::numbers::pi, 20);
draw->PathStroke(color, false);
}
double adder = rotation < 0 ? wpi::numbers::pi : 0;

View File

@@ -32,6 +32,9 @@
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/Storage.h"
#include "glass/support/ColorSetting.h"
#include "glass/support/EnumSetting.h"
using namespace glass;
@@ -114,12 +117,14 @@ struct DisplayOptions {
static constexpr Style kDefaultStyle = kBoxImage;
static constexpr float kDefaultWeight = 4.0f;
static constexpr float kDefaultColorFloat[] = {255, 0, 0, 255};
static constexpr ImU32 kDefaultColor = IM_COL32(255, 0, 0, 255);
static constexpr auto kDefaultWidth = 0.6858_m;
static constexpr auto kDefaultLength = 0.8204_m;
static constexpr bool kDefaultArrows = true;
static constexpr int kDefaultArrowSize = 50;
static constexpr float kDefaultArrowWeight = 4.0f;
static constexpr float kDefaultArrowColorFloat[] = {0, 255, 0, 255};
static constexpr ImU32 kDefaultArrowColor = IM_COL32(0, 255, 0, 255);
static constexpr bool kDefaultSelectable = true;
@@ -180,7 +185,7 @@ class PoseFrameData {
class ObjectInfo {
public:
ObjectInfo();
explicit ObjectInfo(Storage& storage);
DisplayOptions GetDisplayOptions() const;
void DisplaySettings();
@@ -191,26 +196,26 @@ class ObjectInfo {
private:
void Reset();
bool LoadImageImpl(const char* fn);
bool LoadImageImpl(const std::string& fn);
std::unique_ptr<pfd::open_file> m_fileOpener;
// in meters
float* m_pWidth;
float* m_pLength;
float& m_width;
float& m_length;
int* m_pStyle; // DisplayOptions::Style
float* m_pWeight;
int* m_pColor;
EnumSetting m_style; // DisplayOptions::Style
float& m_weight;
ColorSetting m_color;
bool* m_pArrows;
int* m_pArrowSize;
float* m_pArrowWeight;
int* m_pArrowColor;
bool& m_arrows;
int& m_arrowSize;
float& m_arrowWeight;
ColorSetting m_arrowColor;
bool* m_pSelectable;
bool& m_selectable;
std::string* m_pFilename;
std::string& m_filename;
gui::Texture m_texture;
};
@@ -219,7 +224,7 @@ class FieldInfo {
static constexpr auto kDefaultWidth = 15.98_m;
static constexpr auto kDefaultHeight = 8.21_m;
FieldInfo();
explicit FieldInfo(Storage& storage);
void DisplaySettings();
@@ -231,25 +236,25 @@ class FieldInfo {
private:
void Reset();
bool LoadImageImpl(const char* fn);
bool LoadImageImpl(const std::string& fn);
void LoadJson(std::string_view jsonfile);
std::unique_ptr<pfd::open_file> m_fileOpener;
std::string* m_pFilename;
std::string& m_filename;
gui::Texture m_texture;
// in meters
float* m_pWidth;
float* m_pHeight;
float& m_width;
float& m_height;
// in image pixels
int m_imageWidth;
int m_imageHeight;
int* m_pTop;
int* m_pLeft;
int* m_pBottom;
int* m_pRight;
int& m_top;
int& m_left;
int& m_bottom;
int& m_right;
};
} // namespace
@@ -334,16 +339,14 @@ static bool InputPose(frc::Pose2d* pose) {
return changed;
}
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.to<float>());
m_pHeight = storage.GetFloatRef("height", kDefaultHeight.to<float>());
}
FieldInfo::FieldInfo(Storage& storage)
: m_filename{storage.GetString("image")},
m_width{storage.GetFloat("width", kDefaultWidth.to<float>())},
m_height{storage.GetFloat("height", kDefaultHeight.to<float>())},
m_top{storage.GetInt("top", 0)},
m_left{storage.GetInt("left", 0)},
m_bottom{storage.GetInt("bottom", -1)},
m_right{storage.GetInt("right", -1)} {}
void FieldInfo::DisplaySettings() {
if (ImGui::Button("Choose image...")) {
@@ -357,23 +360,23 @@ void FieldInfo::DisplaySettings() {
if (ImGui::Button("Reset image")) {
Reset();
}
InputFloatLength("Field Width", m_pWidth);
InputFloatLength("Field Height", m_pHeight);
// ImGui::InputInt("Field Top", m_pTop);
// ImGui::InputInt("Field Left", m_pLeft);
// ImGui::InputInt("Field Right", m_pRight);
// ImGui::InputInt("Field Bottom", m_pBottom);
InputFloatLength("Field Width", &m_width);
InputFloatLength("Field Height", &m_height);
// ImGui::InputInt("Field Top", &m_top);
// ImGui::InputInt("Field Left", &m_left);
// ImGui::InputInt("Field Right", &m_right);
// ImGui::InputInt("Field Bottom", &m_bottom);
}
void FieldInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
m_filename.clear();
m_imageWidth = 0;
m_imageHeight = 0;
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
m_top = 0;
m_left = 0;
m_bottom = -1;
m_right = -1;
}
void FieldInfo::LoadImage() {
@@ -384,17 +387,17 @@ void FieldInfo::LoadImage() {
LoadJson(result[0]);
} else {
LoadImageImpl(result[0].c_str());
*m_pTop = 0;
*m_pLeft = 0;
*m_pBottom = -1;
*m_pRight = -1;
m_top = 0;
m_left = 0;
m_bottom = -1;
m_right = -1;
}
}
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) {
m_pFilename->clear();
if (!m_texture && !m_filename.empty()) {
if (!LoadImageImpl(m_filename)) {
m_filename.clear();
}
}
}
@@ -478,18 +481,18 @@ void FieldInfo::LoadJson(std::string_view jsonfile) {
}
// save to field info
*m_pFilename = pathname;
*m_pTop = top;
*m_pLeft = left;
*m_pBottom = bottom;
*m_pRight = right;
*m_pWidth = width;
*m_pHeight = height;
m_filename = pathname;
m_top = top;
m_left = left;
m_bottom = bottom;
m_right = right;
m_width = width;
m_height = height;
}
bool FieldInfo::LoadImageImpl(const char* fn) {
bool FieldInfo::LoadImageImpl(const std::string& fn) {
fmt::print("GUI: loading field image '{}'\n", fn);
auto texture = gui::Texture::CreateFromFile(fn);
auto texture = gui::Texture::CreateFromFile(fn.c_str());
if (!texture) {
std::puts("GUI: could not read field image");
return false;
@@ -497,7 +500,7 @@ bool FieldInfo::LoadImageImpl(const char* fn) {
m_texture = std::move(texture);
m_imageWidth = m_texture.GetWidth();
m_imageHeight = m_texture.GetHeight();
*m_pFilename = fn;
m_filename = fn;
return true;
}
@@ -512,19 +515,19 @@ FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
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;
if (m_bottom > 0 && m_right > 0) {
min.x += m_left * (max.x - min.x) / m_imageWidth;
min.y += m_top * (max.y - min.y) / m_imageHeight;
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
}
// draw the field "active area" as a yellow boundary box
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
gui::MaxFit(&min, &max, m_width, m_height);
ffd.min = min;
ffd.max = max;
ffd.scale = (max.x - min.x) / *m_pWidth;
ffd.scale = (max.x - min.x) / m_width;
return ffd;
}
@@ -537,48 +540,47 @@ void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
}
ObjectInfo::ObjectInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
m_pWidth =
storage.GetFloatRef("width", DisplayOptions::kDefaultWidth.to<float>());
m_pLength =
storage.GetFloatRef("length", DisplayOptions::kDefaultLength.to<float>());
m_pStyle = storage.GetIntRef("style", DisplayOptions::kDefaultStyle);
m_pWeight = storage.GetFloatRef("weight", DisplayOptions::kDefaultWeight);
m_pColor = storage.GetIntRef("color", DisplayOptions::kDefaultColor);
m_pArrows = storage.GetBoolRef("arrows", DisplayOptions::kDefaultArrows);
m_pArrowSize =
storage.GetIntRef("arrowSize", DisplayOptions::kDefaultArrowSize);
m_pArrowWeight =
storage.GetFloatRef("arrowWeight", DisplayOptions::kDefaultArrowWeight);
m_pArrowColor =
storage.GetIntRef("arrowColor", DisplayOptions::kDefaultArrowColor);
m_pSelectable =
storage.GetBoolRef("selectable", DisplayOptions::kDefaultSelectable);
}
ObjectInfo::ObjectInfo(Storage& storage)
: m_width{storage.GetFloat("width",
DisplayOptions::kDefaultWidth.to<float>())},
m_length{storage.GetFloat("length",
DisplayOptions::kDefaultLength.to<float>())},
m_style{storage.GetString("style"),
DisplayOptions::kDefaultStyle,
{"Box/Image", "Line", "Line (Closed)", "Track"}},
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
m_color{
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
m_arrows{storage.GetBool("arrows", DisplayOptions::kDefaultArrows)},
m_arrowSize{
storage.GetInt("arrowSize", DisplayOptions::kDefaultArrowSize)},
m_arrowWeight{
storage.GetFloat("arrowWeight", DisplayOptions::kDefaultArrowWeight)},
m_arrowColor{storage.GetFloatArray(
"arrowColor", DisplayOptions::kDefaultArrowColorFloat)},
m_selectable{
storage.GetBool("selectable", DisplayOptions::kDefaultSelectable)},
m_filename{storage.GetString("image")} {}
DisplayOptions ObjectInfo::GetDisplayOptions() const {
DisplayOptions rv{m_texture};
rv.style = static_cast<DisplayOptions::Style>(*m_pStyle);
rv.weight = *m_pWeight;
rv.color = *m_pColor;
rv.width = units::meter_t{*m_pWidth};
rv.length = units::meter_t{*m_pLength};
rv.arrows = *m_pArrows;
rv.arrowSize = *m_pArrowSize;
rv.arrowWeight = *m_pArrowWeight;
rv.arrowColor = *m_pArrowColor;
rv.selectable = *m_pSelectable;
rv.style = static_cast<DisplayOptions::Style>(m_style.GetValue());
rv.weight = m_weight;
rv.color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
rv.width = units::meter_t{m_width};
rv.length = units::meter_t{m_length};
rv.arrows = m_arrows;
rv.arrowSize = m_arrowSize;
rv.arrowWeight = m_arrowWeight;
rv.arrowColor = ImGui::ColorConvertFloat4ToU32(m_arrowColor.GetColor());
rv.selectable = m_selectable;
return rv;
}
void ObjectInfo::DisplaySettings() {
static const char* styleChoices[] = {"Box/Image", "Line", "Line (Closed)",
"Track"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::Combo("Style", m_pStyle, styleChoices, IM_ARRAYSIZE(styleChoices));
switch (*m_pStyle) {
m_style.Combo("Style");
switch (m_style.GetValue()) {
case DisplayOptions::kBoxImage:
if (ImGui::Button("Choose image...")) {
m_fileOpener = std::make_unique<pfd::open_file>(
@@ -591,35 +593,27 @@ void ObjectInfo::DisplaySettings() {
if (ImGui::Button("Reset image")) {
Reset();
}
InputFloatLength("Width", m_pWidth);
InputFloatLength("Length", m_pLength);
InputFloatLength("Width", &m_width);
InputFloatLength("Length", &m_length);
break;
case DisplayOptions::kTrack:
InputFloatLength("Width", m_pWidth);
InputFloatLength("Width", &m_width);
break;
default:
break;
}
ImGui::InputFloat("Line Weight", m_pWeight);
ImColor col(*m_pColor);
if (ImGui::ColorEdit3("Line Color", &col.Value.x,
ImGuiColorEditFlags_NoInputs)) {
*m_pColor = col;
}
ImGui::Checkbox("Arrows", m_pArrows);
if (*m_pArrows) {
ImGui::SliderInt("Arrow Size", m_pArrowSize, 0, 100, "%d%%",
ImGui::InputFloat("Line Weight", &m_weight);
m_color.ColorEdit3("Line Color", ImGuiColorEditFlags_NoInputs);
ImGui::Checkbox("Arrows", &m_arrows);
if (m_arrows) {
ImGui::SliderInt("Arrow Size", &m_arrowSize, 0, 100, "%d%%",
ImGuiSliderFlags_AlwaysClamp);
ImGui::InputFloat("Arrow Weight", m_pArrowWeight);
ImColor col(*m_pArrowColor);
if (ImGui::ColorEdit3("Arrow Color", &col.Value.x,
ImGuiColorEditFlags_NoInputs)) {
*m_pArrowColor = col;
}
ImGui::InputFloat("Arrow Weight", &m_arrowWeight);
m_arrowColor.ColorEdit3("Arrow Color", ImGuiColorEditFlags_NoInputs);
}
ImGui::Checkbox("Selectable", m_pSelectable);
ImGui::Checkbox("Selectable", &m_selectable);
}
void ObjectInfo::DrawLine(ImDrawList* drawList,
@@ -629,10 +623,12 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
}
if (points.size() == 1) {
drawList->AddCircleFilled(points.front(), *m_pWeight, *m_pWeight);
drawList->AddCircleFilled(points.front(), m_weight, m_weight);
return;
}
ImU32 color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
// PolyLine doesn't handle acute angles well; workaround from
// https://github.com/ocornut/imgui/issues/3366
size_t i = 0;
@@ -651,18 +647,18 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
++nlin;
}
drawList->AddPolyline(&points[i], nlin, *m_pColor, false, *m_pWeight);
drawList->AddPolyline(&points[i], nlin, color, false, m_weight);
i += nlin - 1;
}
if (points.size() > 2 && *m_pStyle == DisplayOptions::kLineClosed) {
drawList->AddLine(points.back(), points.front(), *m_pColor, *m_pWeight);
if (points.size() > 2 && m_style.GetValue() == DisplayOptions::kLineClosed) {
drawList->AddLine(points.back(), points.front(), color, m_weight);
}
}
void ObjectInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
m_filename.clear();
}
void ObjectInfo::LoadImage() {
@@ -673,22 +669,22 @@ void ObjectInfo::LoadImage() {
}
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) {
m_pFilename->clear();
if (!m_texture && !m_filename.empty()) {
if (!LoadImageImpl(m_filename)) {
m_filename.clear();
}
}
}
bool ObjectInfo::LoadImageImpl(const char* fn) {
bool ObjectInfo::LoadImageImpl(const std::string& fn) {
fmt::print("GUI: loading object image '{}'\n", fn);
auto texture = gui::Texture::CreateFromFile(fn);
auto texture = gui::Texture::CreateFromFile(fn.c_str());
if (!texture) {
std::fputs("GUI: could not read object image\n", stderr);
return false;
}
m_texture = std::move(texture);
*m_pFilename = fn;
m_filename = fn;
return true;
}
@@ -857,15 +853,16 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
storage.SetData(std::make_shared<FieldInfo>(storage));
field = storage.GetData<FieldInfo>();
}
static const char* unitNames[] = {"meters", "feet", "inches"};
int* pDisplayUnits = GetStorage().GetIntRef("units", kDisplayMeters);
EnumSetting displayUnits{GetStorage().GetString("units"),
kDisplayMeters,
{"meters", "feet", "inches"}};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::Combo("Units", pDisplayUnits, unitNames, IM_ARRAYSIZE(unitNames));
gDisplayUnits = static_cast<DisplayUnits>(*pDisplayUnits);
displayUnits.Combo("Units");
gDisplayUnits = static_cast<DisplayUnits>(displayUnits.GetValue());
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
if (ImGui::CollapsingHeader("Field")) {
@@ -881,7 +878,7 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
PushID(name);
auto& objRef = field->m_objects[name];
if (!objRef) {
objRef = std::make_unique<ObjectInfo>();
objRef = std::make_unique<ObjectInfo>(GetStorage());
}
auto obj = objRef.get();
@@ -1025,7 +1022,7 @@ void FieldDisplay::DisplayObject(FieldObjectModel& model,
PushID(name);
auto& objRef = m_field->m_objects[name];
if (!objRef) {
objRef = std::make_unique<ObjectInfo>();
objRef = std::make_unique<ObjectInfo>(GetStorage());
}
auto obj = objRef.get();
obj->LoadImage();
@@ -1205,7 +1202,7 @@ void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
auto& storage = GetStorage();
auto field = storage.GetData<FieldInfo>();
if (!field) {
storage.SetData(std::make_shared<FieldInfo>());
storage.SetData(std::make_shared<FieldInfo>(storage));
field = storage.GetData<FieldInfo>();
}

View File

@@ -27,6 +27,7 @@
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/Storage.h"
using namespace glass;
@@ -61,7 +62,7 @@ struct FrameData {
class BackgroundInfo {
public:
BackgroundInfo();
explicit BackgroundInfo(Storage& storage);
void DisplaySettings();
@@ -72,11 +73,11 @@ class BackgroundInfo {
private:
void Reset();
bool LoadImageImpl(const char* fn);
bool LoadImageImpl(const std::string& fn);
std::unique_ptr<pfd::open_file> m_fileOpener;
std::string* m_pFilename;
std::string& m_filename;
gui::Texture m_texture;
// in image pixels
@@ -86,10 +87,8 @@ class BackgroundInfo {
} // namespace
BackgroundInfo::BackgroundInfo() {
auto& storage = GetStorage();
m_pFilename = storage.GetStringRef("image");
}
BackgroundInfo::BackgroundInfo(Storage& storage)
: m_filename{storage.GetString("image")} {}
void BackgroundInfo::DisplaySettings() {
if (ImGui::Button("Choose image...")) {
@@ -106,7 +105,7 @@ void BackgroundInfo::DisplaySettings() {
void BackgroundInfo::Reset() {
m_texture = gui::Texture{};
m_pFilename->clear();
m_filename.clear();
m_imageWidth = 0;
m_imageHeight = 0;
}
@@ -119,16 +118,16 @@ void BackgroundInfo::LoadImage() {
}
m_fileOpener.reset();
}
if (!m_texture && !m_pFilename->empty()) {
if (!LoadImageImpl(m_pFilename->c_str())) {
m_pFilename->clear();
if (!m_texture && !m_filename.empty()) {
if (!LoadImageImpl(m_filename)) {
m_filename.clear();
}
}
}
bool BackgroundInfo::LoadImageImpl(const char* fn) {
bool BackgroundInfo::LoadImageImpl(const std::string& fn) {
fmt::print("GUI: loading background image '{}'\n", fn);
auto texture = gui::Texture::CreateFromFile(fn);
auto texture = gui::Texture::CreateFromFile(fn.c_str());
if (!texture) {
std::puts("GUI: could not read background image");
return false;
@@ -136,7 +135,7 @@ bool BackgroundInfo::LoadImageImpl(const char* fn) {
m_texture = std::move(texture);
m_imageWidth = m_texture.GetWidth();
m_imageHeight = m_texture.GetHeight();
*m_pFilename = fn;
m_filename = fn;
return true;
}
@@ -175,7 +174,7 @@ void glass::DisplayMechanism2DSettings(Mechanism2DModel* model) {
auto& storage = GetStorage();
auto bg = storage.GetData<BackgroundInfo>();
if (!bg) {
storage.SetData(std::make_shared<BackgroundInfo>());
storage.SetData(std::make_shared<BackgroundInfo>(storage));
bg = storage.GetData<BackgroundInfo>();
}
bg->DisplaySettings();
@@ -208,7 +207,7 @@ void glass::DisplayMechanism2D(Mechanism2DModel* model,
auto& storage = GetStorage();
auto bg = storage.GetData<BackgroundInfo>();
if (!bg) {
storage.SetData(std::make_shared<BackgroundInfo>());
storage.SetData(std::make_shared<BackgroundInfo>(storage));
bg = storage.GetData<BackgroundInfo>();
}

View File

@@ -19,10 +19,8 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <implot.h>
#include <wpigui.h>
#include <wpi/Signal.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
@@ -31,6 +29,9 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
#include "glass/support/ColorSetting.h"
#include "glass/support/EnumSetting.h"
#include "glass/support/ExtraGuiWidgets.h"
using namespace glass;
@@ -45,9 +46,11 @@ struct PlotSeriesRef {
};
class PlotSeries {
explicit PlotSeries(Storage& storage, int yAxis = 0);
public:
explicit PlotSeries(std::string_view id);
explicit PlotSeries(DataSource* source, int yAxis = 0);
PlotSeries(Storage& storage, std::string_view id);
PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
const std::string& GetId() const { return m_id; }
@@ -56,9 +59,6 @@ class PlotSeries {
void SetSource(DataSource* source);
DataSource* GetSource() const { return m_source; }
bool ReadIni(std::string_view name, std::string_view value);
void WriteIni(ImGuiTextBuffer* out);
enum Action { kNone, kMoveUp, kMoveDown, kDelete };
Action EmitPlot(PlotView& view, double now, size_t i, size_t plotIndex);
void EmitSettings(size_t i);
@@ -69,10 +69,12 @@ class PlotSeries {
int GetYAxis() const { return m_yAxis; }
void SetYAxis(int yAxis) { m_yAxis = yAxis; }
void SetColor(const ImVec4& color) { m_color.SetColor(color); }
private:
bool IsDigital() const {
return m_digital == kDigital ||
(m_digital == kAuto && m_source && m_source->IsDigital());
return m_digital.GetValue() == kDigital ||
(m_digital.GetValue() == kAuto && m_source && m_source->IsDigital());
}
void AppendValue(double value, uint64_t time);
@@ -80,19 +82,20 @@ class PlotSeries {
DataSource* m_source = nullptr;
wpi::sig::ScopedConnection m_sourceCreatedConn;
wpi::sig::ScopedConnection m_newValueConn;
std::string m_id;
std::string& m_id;
// user settings
std::string m_name;
int m_yAxis = 0;
ImVec4 m_color = IMPLOT_AUTO_COL;
int m_marker = 0;
float m_weight = IMPLOT_AUTO;
std::string& m_name;
int& m_yAxis;
static constexpr float kDefaultColor[4] = {0.0, 0.0, 0.0, IMPLOT_AUTO};
ColorSetting m_color;
EnumSetting m_marker;
float& m_weight;
enum Digital { kAuto, kDigital, kAnalog };
int m_digital = 0;
int m_digitalBitHeight = 8;
int m_digitalBitGap = 4;
EnumSetting m_digital;
int& m_digitalBitHeight;
int& m_digitalBitGap;
// value storage
static constexpr int kMaxSize = 2000;
@@ -104,10 +107,7 @@ class PlotSeries {
class Plot {
public:
Plot();
bool ReadIni(std::string_view name, std::string_view value);
void WriteIni(ImGuiTextBuffer* out);
explicit Plot(Storage& storage);
void DragDropTarget(PlotView& view, size_t i, bool inPlot);
void EmitPlot(PlotView& view, double now, bool paused, size_t i);
@@ -116,6 +116,7 @@ class Plot {
const std::string& GetName() const { return m_name; }
std::vector<std::unique_ptr<PlotSeries>> m_series;
std::vector<std::unique_ptr<Storage>>& m_seriesStorage;
// Returns base height; does not include actual plot height if auto-sized.
int GetAutoBaseHeight(bool* isAuto, size_t i);
@@ -129,30 +130,35 @@ class Plot {
private:
void EmitSettingsLimits(int axis);
std::string m_name;
bool m_visible = true;
bool m_showPause = true;
unsigned int m_plotFlags = ImPlotFlags_None;
bool m_lockPrevX = false;
bool m_paused = false;
float m_viewTime = 10;
bool m_autoHeight = true;
int m_height = 300;
std::string& m_name;
bool& m_visible;
bool& m_showPause;
bool& m_lockPrevX;
bool& m_legend;
bool& m_yAxis2;
bool& m_yAxis3;
float& m_viewTime;
bool& m_autoHeight;
int& m_height;
struct PlotRange {
double min = 0;
double max = 1;
bool lockMin = false;
bool lockMax = false;
explicit PlotRange(Storage& storage);
std::string& label;
double& min;
double& max;
bool& lockMin;
bool& lockMax;
bool apply = false;
};
std::string m_axisLabel[3];
PlotRange m_axisRange[3];
std::vector<PlotRange> m_axis;
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
};
class PlotView : public View {
public:
explicit PlotView(PlotProvider* provider) : m_provider{provider} {}
PlotView(PlotProvider* provider, Storage& storage);
void Display() override;
@@ -163,12 +169,30 @@ class PlotView : public View {
size_t toSeriesIndex, int yAxis = -1);
PlotProvider* m_provider;
std::vector<std::unique_ptr<Storage>>& m_plotsStorage;
std::vector<std::unique_ptr<Plot>> m_plots;
};
} // namespace
PlotSeries::PlotSeries(std::string_view id) : m_id(id) {
PlotSeries::PlotSeries(Storage& storage, int yAxis)
: m_id{storage.GetString("id")},
m_name{storage.GetString("name")},
m_yAxis{storage.GetInt("yAxis", yAxis)},
m_color{storage.GetFloatArray("color", kDefaultColor)},
m_marker{storage.GetString("marker"),
0,
{"None", "Circle", "Square", "Diamond", "Up", "Down", "Left",
"Right", "Cross", "Plus", "Asterisk"}},
m_weight{storage.GetFloat("weight", IMPLOT_AUTO)},
m_digital{
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
: PlotSeries{storage, 0} {
m_id = id;
if (DataSource* source = DataSource::Find(id)) {
SetSource(source);
return;
@@ -176,7 +200,8 @@ PlotSeries::PlotSeries(std::string_view id) : m_id(id) {
CheckSource();
}
PlotSeries::PlotSeries(DataSource* source, int yAxis) : m_yAxis(yAxis) {
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
: PlotSeries{storage, yAxis} {
SetSource(source);
m_id = source->GetId();
}
@@ -245,66 +270,14 @@ void PlotSeries::AppendValue(double value, uint64_t timeUs) {
}
}
bool PlotSeries::ReadIni(std::string_view name, std::string_view value) {
if (name == "name") {
m_name = value;
return true;
}
if (name == "yAxis") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_yAxis = num.value();
}
return true;
} else if (name == "color") {
if (auto num = wpi::parse_integer<unsigned int>(value, 10)) {
m_color = ImColor(num.value());
}
return true;
} else if (name == "marker") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_marker = num.value();
}
return true;
} else if (name == "weight") {
if (auto num = wpi::parse_float<float>(value)) {
m_weight = num.value();
}
return true;
} else if (name == "digital") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_digital = num.value();
}
return true;
} else if (name == "digitalBitHeight") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_digitalBitHeight = num.value();
}
return true;
} else if (name == "digitalBitGap") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_digitalBitGap = num.value();
}
return true;
}
return false;
}
void PlotSeries::WriteIni(ImGuiTextBuffer* out) {
out->appendf(
"name=%s\nyAxis=%d\ncolor=%u\nmarker=%d\nweight=%f\ndigital=%d\n"
"digitalBitHeight=%d\ndigitalBitGap=%d\n",
m_name.c_str(), m_yAxis, static_cast<ImU32>(ImColor(m_color)), m_marker,
m_weight, m_digital, m_digitalBitHeight, m_digitalBitGap);
}
const char* PlotSeries::GetName() const {
if (!m_name.empty()) {
return m_name.c_str();
}
if (m_newValueConn.connected()) {
auto sourceName = m_source->GetName();
if (sourceName[0] != '\0') {
return sourceName;
auto& sourceName = m_source->GetName();
if (!sourceName.empty()) {
return sourceName.c_str();
}
}
return m_id.c_str();
@@ -346,10 +319,10 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
return ImPlotPoint{point->x - d->zeroTime, point->y};
};
if (m_color.w == IMPLOT_AUTO_COL.w) {
m_color = ImPlot::GetColormapColor(i);
if (m_color.GetColorFloat()[3] == IMPLOT_AUTO) {
SetColor(ImPlot::GetColormapColor(i));
}
ImPlot::SetNextLineStyle(m_color, m_weight);
ImPlot::SetNextLineStyle(m_color.GetColor(), m_weight);
if (IsDigital()) {
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
@@ -358,7 +331,7 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
ImPlot::PopStyleVar();
} else {
ImPlot::SetPlotYAxis(m_yAxis);
ImPlot::SetNextMarkerStyle(m_marker - 1);
ImPlot::SetNextMarkerStyle(m_marker.GetValue() - 1);
ImPlot::PlotLineG(label, getter, &getterData, size + 1);
}
@@ -413,10 +386,10 @@ void PlotSeries::EmitDragDropPayload(PlotView& view, size_t i,
void PlotSeries::EmitSettings(size_t i) {
// Line color
{
ImGui::ColorEdit3("Color", &m_color.x, ImGuiColorEditFlags_NoInputs);
m_color.ColorEdit3("Color", ImGuiColorEditFlags_NoInputs);
ImGui::SameLine();
if (ImGui::Button("Default")) {
m_color = ImPlot::GetColormapColor(i);
SetColor(ImPlot::GetColormapColor(i));
}
}
@@ -428,10 +401,8 @@ void PlotSeries::EmitSettings(size_t i) {
// Digital
{
static const char* const options[] = {"Auto", "Digital", "Analog"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
ImGui::Combo("Digital", &m_digital, options,
sizeof(options) / sizeof(options[0]));
m_digital.Combo("Digital");
}
if (IsDigital()) {
@@ -456,135 +427,44 @@ void PlotSeries::EmitSettings(size_t i) {
// Marker
{
static const char* const options[] = {
"None", "Circle", "Square", "Diamond", "Up", "Down",
"Left", "Right", "Cross", "Plus", "Asterisk"};
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
ImGui::Combo("Marker", &m_marker, options,
sizeof(options) / sizeof(options[0]));
m_marker.Combo("Marker");
}
}
}
Plot::Plot() {
for (int i = 0; i < 3; ++i) {
m_axisRange[i] = PlotRange{};
}
}
Plot::PlotRange::PlotRange(Storage& storage)
: label{storage.GetString("label")},
min{storage.GetDouble("min", 0)},
max{storage.GetDouble("max", 1)},
lockMin{storage.GetBool("lockMin", false)},
lockMax{storage.GetBool("lockMax", false)} {}
bool Plot::ReadIni(std::string_view name, std::string_view value) {
if (name == "name") {
m_name = value;
return true;
} else if (name == "visible") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_visible = num.value() != 0;
}
return true;
} else if (name == "showPause") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_showPause = num.value() != 0;
}
return true;
} else if (name == "lockPrevX") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_lockPrevX = num.value() != 0;
}
return true;
} else if (name == "legend") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
if (num.value() == 0) {
m_plotFlags |= ImPlotFlags_NoLegend;
} else {
m_plotFlags &= ~ImPlotFlags_NoLegend;
}
}
return true;
} else if (name == "yaxis2") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
if (num.value() == 0) {
m_plotFlags &= ~ImPlotFlags_YAxis2;
} else {
m_plotFlags |= ImPlotFlags_YAxis2;
}
}
return true;
} else if (name == "yaxis3") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
if (num.value() == 0) {
m_plotFlags &= ~ImPlotFlags_YAxis3;
} else {
m_plotFlags |= ImPlotFlags_YAxis3;
}
}
return true;
} else if (name == "viewTime") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_viewTime = num.value() / 1000.0;
}
return true;
} else if (name == "autoHeight") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_autoHeight = num.value() != 0;
}
return true;
} else if (name == "height") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_height = num.value();
}
return true;
} else if (wpi::starts_with(name, 'y')) {
auto [yAxisStr, yName] = wpi::split(name, '_');
int yAxis =
wpi::parse_integer<int>(wpi::drop_front(yAxisStr), 10).value_or(-1);
if (yAxis < 0 || yAxis > 3) {
return false;
}
if (yName == "min") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_axisRange[yAxis].min = num.value() / 1000.0;
}
return true;
} else if (yName == "max") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_axisRange[yAxis].max = num.value() / 1000.0;
}
return true;
} else if (yName == "lockMin") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_axisRange[yAxis].lockMin = num.value() != 0;
}
return true;
} else if (yName == "lockMax") {
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_axisRange[yAxis].lockMax = num.value() != 0;
}
return true;
} else if (yName == "label") {
m_axisLabel[yAxis] = value;
return true;
Plot::Plot(Storage& storage)
: m_seriesStorage{storage.GetChildArray("series")},
m_name{storage.GetString("name")},
m_visible{storage.GetBool("visible", true)},
m_showPause{storage.GetBool("showPause", true)},
m_lockPrevX{storage.GetBool("lockPrevX", false)},
m_legend{storage.GetBool("legend", true)},
m_yAxis2{storage.GetBool("yaxis2", false)},
m_yAxis3{storage.GetBool("yaxis3", false)},
m_viewTime{storage.GetFloat("viewTime", 10)},
m_autoHeight{storage.GetBool("autoHeight", true)},
m_height{storage.GetInt("height", 300)} {
auto& axesStorage = storage.GetChildArray("axis");
axesStorage.resize(3);
for (auto&& axisStorage : axesStorage) {
if (!axisStorage) {
axisStorage = std::make_unique<Storage>();
}
m_axis.emplace_back(*axisStorage);
}
return false;
}
void Plot::WriteIni(ImGuiTextBuffer* out) {
out->appendf(
"name=%s\nvisible=%d\nshowPause=%d\nlockPrevX=%d\nlegend=%d\n"
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nautoHeight=%d\nheight=%d\n",
m_name.c_str(), m_visible ? 1 : 0, m_showPause ? 1 : 0,
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_NoLegend) ? 0 : 1,
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
static_cast<int>(m_viewTime * 1000), m_autoHeight ? 1 : 0, m_height);
for (int i = 0; i < 3; ++i) {
out->appendf(
"y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n"
"y%d_label=%s\n",
i, static_cast<int>(m_axisRange[i].min * 1000), i,
static_cast<int>(m_axisRange[i].max * 1000), i,
m_axisRange[i].lockMin ? 1 : 0, i, m_axisRange[i].lockMax ? 1 : 0, i,
m_axisLabel[i].c_str());
// loop over series
for (auto&& v : m_seriesStorage) {
m_series.emplace_back(
std::make_unique<PlotSeries>(*v, v->ReadString("id")));
}
}
@@ -612,8 +492,9 @@ void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
(yAxis == -1 || elem->GetYAxis() == yAxis);
});
if (it == m_series.end()) {
m_series.emplace_back(
std::make_unique<PlotSeries>(source, yAxis == -1 ? 0 : yAxis));
m_seriesStorage.emplace_back(std::make_unique<Storage>());
m_series.emplace_back(std::make_unique<PlotSeries>(
*m_seriesStorage.back(), source, yAxis == -1 ? 0 : yAxis));
}
} else if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("PlotSeries")) {
@@ -658,38 +539,45 @@ void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
ImPlotAxisFlags_NoGridLines};
for (int i = 0; i < 3; ++i) {
ImPlot::SetNextPlotLimitsY(
m_axisRange[i].min, m_axisRange[i].max,
m_axisRange[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
m_axisRange[i].apply = false;
if (m_axisRange[i].lockMin) {
m_axis[i].min, m_axis[i].max,
m_axis[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
m_axis[i].apply = false;
if (m_axis[i].lockMin) {
yFlags[i] |= ImPlotAxisFlags_LockMin;
}
if (m_axisRange[i].lockMax) {
if (m_axis[i].lockMax) {
yFlags[i] |= ImPlotAxisFlags_LockMax;
}
}
ImPlotFlags plotFlags = (m_legend ? 0 : ImPlotFlags_NoLegend) |
(m_yAxis2 ? ImPlotFlags_YAxis2 : 0) |
(m_yAxis3 ? ImPlotFlags_YAxis3 : 0);
if (ImPlot::BeginPlot(
label, nullptr,
m_axisLabel[0].empty() ? nullptr : m_axisLabel[0].c_str(),
ImVec2(-1, m_height), m_plotFlags, ImPlotAxisFlags_None, yFlags[0],
m_axis[0].label.empty() ? nullptr : m_axis[0].label.c_str(),
ImVec2(-1, m_height), plotFlags, ImPlotAxisFlags_None, yFlags[0],
yFlags[1], yFlags[2],
m_axisLabel[1].empty() ? nullptr : m_axisLabel[1].c_str(),
m_axisLabel[2].empty() ? nullptr : m_axisLabel[2].c_str())) {
m_axis[1].label.empty() ? nullptr : m_axis[1].label.c_str(),
m_axis[2].label.empty() ? nullptr : m_axis[2].label.c_str())) {
for (size_t j = 0; j < m_series.size(); ++j) {
ImGui::PushID(j);
switch (m_series[j]->EmitPlot(view, now, j, i)) {
case PlotSeries::kMoveUp:
if (j > 0) {
std::swap(m_seriesStorage[j - 1], m_seriesStorage[j]);
std::swap(m_series[j - 1], m_series[j]);
}
break;
case PlotSeries::kMoveDown:
if (j < (m_series.size() - 1)) {
std::swap(m_seriesStorage[j], m_seriesStorage[j + 1]);
std::swap(m_series[j], m_series[j + 1]);
}
break;
case PlotSeries::kDelete:
m_seriesStorage.erase(m_seriesStorage.begin() + j);
m_series.erase(m_series.begin() + j);
break;
default:
@@ -708,22 +596,22 @@ void Plot::EmitSettingsLimits(int axis) {
ImGui::PushID(axis);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10);
ImGui::InputText("Label", &m_axisLabel[axis]);
ImGui::InputText("Label", &m_axis[axis].label);
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
ImGui::InputDouble("Min", &m_axis[axis].min, 0, 0, "%.3f");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
ImGui::InputDouble("Max", &m_axisRange[axis].max, 0, 0, "%.3f");
ImGui::InputDouble("Max", &m_axis[axis].max, 0, 0, "%.3f");
ImGui::SameLine();
if (ImGui::Button("Apply")) {
m_axisRange[axis].apply = true;
m_axis[axis].apply = true;
}
ImGui::TextUnformatted("Lock Axis");
ImGui::SameLine();
ImGui::Checkbox("Min##minlock", &m_axisRange[axis].lockMin);
ImGui::Checkbox("Min##minlock", &m_axis[axis].lockMin);
ImGui::SameLine();
ImGui::Checkbox("Max##maxlock", &m_axisRange[axis].lockMax);
ImGui::Checkbox("Max##maxlock", &m_axis[axis].lockMax);
ImGui::PopID();
ImGui::Unindent();
@@ -734,18 +622,18 @@ void Plot::EmitSettings(size_t i) {
ImGui::InputText("##editname", &m_name);
ImGui::Checkbox("Visible", &m_visible);
ImGui::Checkbox("Show Pause Button", &m_showPause);
ImGui::CheckboxFlags("Hide Legend", &m_plotFlags, ImPlotFlags_NoLegend);
ImGui::Checkbox("Show Legend", &m_legend);
if (i != 0) {
ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
}
ImGui::TextUnformatted("Primary Y-Axis");
EmitSettingsLimits(0);
ImGui::CheckboxFlags("2nd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis2);
if ((m_plotFlags & ImPlotFlags_YAxis2) != 0) {
ImGui::Checkbox("2nd Y-Axis", &m_yAxis2);
if (m_yAxis2) {
EmitSettingsLimits(1);
}
ImGui::CheckboxFlags("3rd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis3);
if ((m_plotFlags & ImPlotFlags_YAxis3) != 0) {
ImGui::Checkbox("3rd Y-Axis", &m_yAxis3);
if (m_yAxis3) {
EmitSettingsLimits(2);
}
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
@@ -778,10 +666,20 @@ int Plot::GetAutoBaseHeight(bool* isAuto, size_t i) {
return height;
}
PlotView::PlotView(PlotProvider* provider, Storage& storage)
: m_provider{provider}, m_plotsStorage{storage.GetChildArray("plots")} {
// loop over plots
for (auto&& v : m_plotsStorage) {
// create plot
m_plots.emplace_back(std::make_unique<Plot>(*v));
}
}
void PlotView::Display() {
if (ImGui::BeginPopupContextItem()) {
if (ImGui::Button("Add plot")) {
m_plots.emplace_back(std::make_unique<Plot>());
m_plotsStorage.emplace_back(std::make_unique<Storage>());
m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
}
for (size_t i = 0; i < m_plots.size(); ++i) {
@@ -813,6 +711,7 @@ void PlotView::Display() {
if (open) {
if (ImGui::Button("Move Up")) {
if (i > 0) {
std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]);
std::swap(m_plots[i - 1], plot);
}
}
@@ -820,12 +719,14 @@ void PlotView::Display() {
ImGui::SameLine();
if (ImGui::Button("Move Down")) {
if (i < (m_plots.size() - 1)) {
std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]);
std::swap(plot, m_plots[i + 1]);
}
}
ImGui::SameLine();
if (ImGui::Button("Delete")) {
m_plotsStorage.erase(m_plotsStorage.begin() + i);
m_plots.erase(m_plots.begin() + i);
ImGui::PopID();
continue;
@@ -842,7 +743,8 @@ void PlotView::Display() {
if (m_plots.empty()) {
if (ImGui::Button("Add plot")) {
m_plots.emplace_back(std::make_unique<Plot>());
m_plotsStorage.emplace_back(std::make_unique<Storage>());
m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
}
// Make "add plot" button a DND target for Plot
@@ -889,10 +791,21 @@ void PlotView::MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex) {
if (fromIndex == toIndex) {
return;
}
auto st = std::move(m_plotsStorage[fromIndex]);
m_plotsStorage.insert(m_plotsStorage.begin() + toIndex, std::move(st));
m_plotsStorage.erase(m_plotsStorage.begin() + fromIndex +
(fromIndex > toIndex ? 1 : 0));
auto val = std::move(m_plots[fromIndex]);
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
m_plots.erase(m_plots.begin() + fromIndex + (fromIndex > toIndex ? 1 : 0));
} else {
auto st = std::move(fromView->m_plotsStorage[fromIndex]);
m_plotsStorage.insert(m_plotsStorage.begin() + toIndex, std::move(st));
fromView->m_plotsStorage.erase(fromView->m_plotsStorage.begin() +
fromIndex);
auto val = std::move(fromView->m_plots[fromIndex]);
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
fromView->m_plots.erase(fromView->m_plots.begin() + fromIndex);
@@ -905,6 +818,13 @@ void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
if (fromView == this && fromPlotIndex == toPlotIndex) {
// need to handle this specially as the index of the old location changes
if (fromSeriesIndex != toSeriesIndex) {
auto& seriesStorage = m_plots[fromPlotIndex]->m_seriesStorage;
auto st = std::move(seriesStorage[fromSeriesIndex]);
seriesStorage.insert(seriesStorage.begin() + toSeriesIndex,
std::move(st));
seriesStorage.erase(seriesStorage.begin() + fromSeriesIndex +
(fromSeriesIndex > toSeriesIndex ? 1 : 0));
auto& plotSeries = m_plots[fromPlotIndex]->m_series;
auto val = std::move(plotSeries[fromSeriesIndex]);
// only set Y-axis if actually set
@@ -920,34 +840,53 @@ void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
auto& toPlot = *m_plots[toPlotIndex];
// always set Y-axis if moving plots
fromPlot.m_series[fromSeriesIndex]->SetYAxis(yAxis == -1 ? 0 : yAxis);
toPlot.m_seriesStorage.insert(
toPlot.m_seriesStorage.begin() + toSeriesIndex,
std::move(fromPlot.m_seriesStorage[fromSeriesIndex]));
fromPlot.m_seriesStorage.erase(fromPlot.m_seriesStorage.begin() +
fromSeriesIndex);
toPlot.m_series.insert(toPlot.m_series.begin() + toSeriesIndex,
std::move(fromPlot.m_series[fromSeriesIndex]));
fromPlot.m_series.erase(fromPlot.m_series.begin() + fromSeriesIndex);
}
}
PlotProvider::PlotProvider(std::string_view iniName)
: WindowManager{fmt::format("{}Window", iniName)},
m_plotSaver{iniName, this, false},
m_seriesSaver{fmt::format("{}Series", iniName), this, true} {}
PlotProvider::PlotProvider(Storage& storage) : WindowManager{storage} {
storage.SetCustomApply([this] {
// loop over windows
for (auto&& windowkv : m_storage.GetChildren()) {
// get or create window
auto win = GetOrAddWindow(windowkv.key(), true);
if (!win) {
continue;
}
PlotProvider::~PlotProvider() = default;
void PlotProvider::GlobalInit() {
WindowManager::GlobalInit();
wpi::gui::AddInit([this] {
m_plotSaver.Initialize();
m_seriesSaver.Initialize();
// get or create view
auto view = static_cast<PlotView*>(win->GetView());
if (!view) {
win->SetView(std::make_unique<PlotView>(this, windowkv.value()));
view = static_cast<PlotView*>(win->GetView());
}
}
});
storage.SetCustomClear([this] {
EraseWindows();
m_storage.EraseChildren();
});
}
PlotProvider::~PlotProvider() = default;
void PlotProvider::DisplayMenu() {
// use index-based loop due to possible RemoveWindow call
for (size_t i = 0; i < m_windows.size(); ++i) {
m_windows[i]->DisplayMenuItem();
// provide method to destroy the plot window
if (ImGui::BeginPopupContextItem()) {
if (ImGui::Selectable("Destroy Plot Window")) {
m_windows.erase(m_windows.begin() + i);
RemoveWindow(i);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
@@ -971,105 +910,9 @@ void PlotProvider::DisplayMenu() {
break;
}
}
if (auto win = AddWindow(id, std::make_unique<PlotView>(this))) {
if (auto win = AddWindow(
id, std::make_unique<PlotView>(this, m_storage.GetChild(id)))) {
win->SetDefaultSize(700, 400);
}
}
}
void PlotProvider::DisplayWindows() {
// create views if not already created
for (auto&& window : m_windows) {
if (!window->HasView()) {
window->SetView(std::make_unique<PlotView>(this));
}
}
WindowManager::DisplayWindows();
}
PlotProvider::IniSaver::IniSaver(std::string_view typeName,
PlotProvider* provider, bool forSeries)
: IniSaverBase{typeName}, m_provider{provider}, m_forSeries{forSeries} {}
void* PlotProvider::IniSaver::IniReadOpen(const char* name) {
auto [viewId, plotNumStr] = wpi::split(name, '#');
std::string_view seriesId;
if (m_forSeries) {
std::tie(plotNumStr, seriesId) = wpi::split(plotNumStr, '#');
if (seriesId.empty()) {
return nullptr;
}
}
unsigned int plotNum;
if (auto plotNumOpt = wpi::parse_integer<unsigned int>(plotNumStr, 10)) {
plotNum = plotNumOpt.value();
} else {
return nullptr;
}
// get or create window
auto win = m_provider->GetOrAddWindow(viewId, true);
if (!win) {
return nullptr;
}
// get or create view
auto view = static_cast<PlotView*>(win->GetView());
if (!view) {
win->SetView(std::make_unique<PlotView>(m_provider));
view = static_cast<PlotView*>(win->GetView());
}
// get or create plot
if (view->m_plots.size() <= plotNum) {
view->m_plots.resize(plotNum + 1);
}
auto& plot = view->m_plots[plotNum];
if (!plot) {
plot = std::make_unique<Plot>();
}
// early exit for plot data
if (!m_forSeries) {
return plot.get();
}
// get or create series
return plot->m_series.emplace_back(std::make_unique<PlotSeries>(seriesId))
.get();
}
void PlotProvider::IniSaver::IniReadLine(void* entry, const char* line) {
auto [name, value] = wpi::split(line, '=');
name = wpi::trim(name);
value = wpi::trim(value);
if (m_forSeries) {
static_cast<PlotSeries*>(entry)->ReadIni(name, value);
} else {
static_cast<Plot*>(entry)->ReadIni(name, value);
}
}
void PlotProvider::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& win : m_provider->m_windows) {
auto view = static_cast<PlotView*>(win->GetView());
auto id = win->GetId();
for (size_t i = 0; i < view->m_plots.size(); ++i) {
if (m_forSeries) {
// Loop over series
for (auto&& series : view->m_plots[i]->m_series) {
out_buf->appendf("[%s][%s#%d#%s]\n", GetTypeName(), id.data(),
static_cast<int>(i), series->GetId().c_str());
series->WriteIni(out_buf);
out_buf->append("\n");
}
} else {
// Just the plot
out_buf->appendf("[%s][%s#%d]\n", GetTypeName(), id.data(),
static_cast<int>(i));
view->m_plots[i]->WriteIni(out_buf);
out_buf->append("\n");
}
}
}
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/support/ColorSetting.h"
using namespace glass;
ColorSetting::ColorSetting(std::vector<float>& color) : m_color{color} {
m_color.resize(4);
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/support/EnumSetting.h"
#include <imgui.h>
using namespace glass;
EnumSetting::EnumSetting(std::string& str, int defaultValue,
std::initializer_list<const char*> choices)
: m_str{str}, m_choices{choices}, m_value{defaultValue} {
// override default value if str is one of the choices
int i = 0;
for (auto choice : choices) {
if (str == choice) {
m_value = i;
break;
}
++i;
}
}
void EnumSetting::SetValue(int value) {
m_value = value;
m_str = m_choices[m_value];
}
bool EnumSetting::Combo(const char* label, int numOptions,
int popup_max_height_in_items) {
if (ImGui::Combo(
label, &m_value, m_choices.data(),
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
popup_max_height_in_items)) {
m_str = m_choices[m_value]; // update stored string
return true;
}
return false;
}

View File

@@ -1,62 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "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(std::string_view typeName, IniSaverBackend* backend)
: m_typeName(typeName), m_backend{backend ? backend : GetSaverInstance()} {}
IniSaverBase::~IniSaverBase() {
m_backend->Unregister(this);
}

View File

@@ -1,168 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/support/IniSaverInfo.h"
#include <cstdio>
#include <cstring>
#include <imgui_internal.h>
#include <wpi/StringExtras.h>
using namespace glass;
void NameInfo::SetName(std::string_view name) {
size_t len = (std::min)(name.size(), sizeof(m_name) - 1);
std::memcpy(m_name, name.data(), len);
m_name[len] = '\0';
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s", m_name);
} else {
std::snprintf(buf, size, "%s", defaultName);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]", m_name, index);
} else {
std::snprintf(buf, size, "%s[%d]", defaultName, index);
}
}
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]", m_name, index, index2);
} else {
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s###Name%s", m_name, defaultName);
} else {
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d]###Name%d", m_name, index, index);
} else {
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
}
}
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (m_name[0] != '\0') {
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name, index, index2,
index);
} else {
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
index);
}
}
bool NameInfo::ReadIni(std::string_view name, std::string_view value) {
if (name != "name") {
return false;
}
size_t len = (std::min)(value.size(), sizeof(m_name) - 1);
std::memcpy(m_name, value.data(), len);
m_name[len] = '\0';
return true;
}
void NameInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("name=%s\n", m_name);
}
void NameInfo::PushEditNameId(int index) {
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
ImGui::PushID(id);
}
void NameInfo::PushEditNameId(const char* name) {
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
ImGui::PushID(id);
}
bool NameInfo::PopupEditName(int index) {
bool rv = false;
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::PopupEditName(const char* name) {
bool rv = false;
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameInfo::InputTextName(const char* label_id, ImGuiInputTextFlags flags) {
return ImGui::InputText(label_id, m_name, sizeof(m_name), flags);
}
bool OpenInfo::ReadIni(std::string_view name, std::string_view value) {
if (name != "open") {
return false;
}
if (auto num = wpi::parse_integer<int>(value, 10)) {
m_open = num.value();
}
return true;
}
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
out->appendf("open=%d\n", m_open ? 1 : 0);
}
bool NameOpenInfo::ReadIni(std::string_view name, std::string_view value) {
if (NameInfo::ReadIni(name, value)) {
return true;
}
if (OpenInfo::ReadIni(name, value)) {
return true;
}
return false;
}
void NameOpenInfo::WriteIni(ImGuiTextBuffer* out) {
NameInfo::WriteIni(out);
OpenInfo::WriteIni(out);
}

View File

@@ -0,0 +1,123 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/support/NameSetting.h"
#include <cstdio>
#include <cstring>
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <wpi/StringExtras.h>
using namespace glass;
void NameSetting::GetName(char* buf, size_t size,
const char* defaultName) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s", m_name.c_str());
} else {
std::snprintf(buf, size, "%s", defaultName);
}
}
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
int index) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s [%d]", m_name.c_str(), index);
} else {
std::snprintf(buf, size, "%s[%d]", defaultName, index);
}
}
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s [%d,%d]", m_name.c_str(), index, index2);
} else {
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
}
}
void NameSetting::GetLabel(char* buf, size_t size,
const char* defaultName) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s###Name%s", m_name.c_str(), defaultName);
} else {
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
}
}
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
int index) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s [%d]###Name%d", m_name.c_str(), index, index);
} else {
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
}
}
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
int index, int index2) const {
if (!m_name.empty()) {
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name.c_str(), index,
index2, index);
} else {
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
index);
}
}
void NameSetting::PushEditNameId(int index) {
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
ImGui::PushID(id);
}
void NameSetting::PushEditNameId(const char* name) {
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
ImGui::PushID(id);
}
bool NameSetting::PopupEditName(int index) {
bool rv = false;
char id[64];
std::snprintf(id, sizeof(id), "Name%d", index);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameSetting::PopupEditName(const char* name) {
bool rv = false;
char id[128];
std::snprintf(id, sizeof(id), "Name%s", name);
if (ImGui::BeginPopupContextItem(id)) {
ImGui::Text("Edit name:");
if (InputTextName("##edit")) {
rv = true;
}
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return rv;
}
bool NameSetting::InputTextName(const char* label_id,
ImGuiInputTextFlags flags) {
return ImGui::InputText(label_id, &m_name, flags);
}

View File

@@ -4,17 +4,16 @@
#pragma once
#include <memory>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <imgui.h>
namespace glass {
struct Context;
class Context;
class Storage;
Context* CreateContext();
void DestroyContext(Context* ctx = nullptr);
@@ -32,86 +31,135 @@ void ResetTime();
uint64_t GetZeroTime();
/**
* 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).
* Resets the workspace (all storage except window storage).
* Operates effectively like calling LoadStorage() on a path with no existing
* storage files. Note this will result in auto-saving of the reset state to
* storage.
*/
class Storage {
public:
struct Value {
Value() = default;
explicit Value(std::string_view str) : stringVal{str} {}
void WorkspaceReset();
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;
};
/**
* Adds function to be called during workspace (storage) initialization/load.
* This should set up any initial default state, restore stored
* settings/windows, etc. This will be called after the storage is initialized.
* This must be called prior to WorkspaceInit() for proper automatic startup
* loading.
*
* @param init initialization function
*/
void AddWorkspaceInit(std::function<void()> init);
int GetInt(std::string_view key, int defaultVal = 0) const;
int64_t GetInt64(std::string_view key, int64_t defaultVal = 0) const;
bool GetBool(std::string_view key, bool defaultVal = false) const;
float GetFloat(std::string_view key, float defaultVal = 0.0f) const;
double GetDouble(std::string_view key, double defaultVal = 0.0) const;
std::string GetString(std::string_view key,
std::string_view defaultVal = {}) const;
/**
* Adds function to be called during workspace (storage) reset. This should
* bring back the state to startup state (e.g. remove any storage references,
* destroy windows, etc). This will be called prior to the storage being
* destroyed.
*
* @param reset reset function
*/
void AddWorkspaceReset(std::function<void()> reset);
void SetInt(std::string_view key, int val);
void SetInt64(std::string_view key, int64_t val);
void SetBool(std::string_view key, bool val);
void SetFloat(std::string_view key, float val);
void SetDouble(std::string_view key, double val);
void SetString(std::string_view key, std::string_view val);
/**
* Sets storage load and auto-save name.
* Call this prior to calling wpi::gui::Initialize() for automatic startup
* loading.
*
* @param name base name, suffix will be generated
*/
void SetStorageName(std::string_view name);
int* GetIntRef(std::string_view key, int defaultVal = 0);
int64_t* GetInt64Ref(std::string_view key, int64_t defaultVal = 0);
bool* GetBoolRef(std::string_view key, bool defaultVal = false);
float* GetFloatRef(std::string_view key, float defaultVal = 0.0f);
double* GetDoubleRef(std::string_view key, double defaultVal = 0.0);
std::string* GetStringRef(std::string_view key,
std::string_view defaultVal = {});
/**
* Sets storage load and auto-save directory. For more customized behavior, set
* Context::storageLoadPath and Context::storageAutoSavePath directly.
* Call this prior to calling wpi::gui::Initialize() for automatic startup
* loading.
*
* @param dir path to directory
*/
void SetStorageDir(std::string_view dir);
Value& GetValue(std::string_view key);
/**
* Gets storage auto-save directory.
*
* @return Path to directory
*/
std::string GetStorageDir();
void SetData(std::shared_ptr<void>&& data) { m_data = std::move(data); }
/**
* Explicitly load storage. Set Context::storageLoadDir prior to calling
* wpi::gui::Initialize() for automatic startup loading.
*
* Non-empty root names are not loaded unless GetStorageRoot() is called during
* initialization (or before this function is called).
*
* @param dir path to directory
*/
bool LoadStorage(std::string_view dir);
template <typename T>
T* GetData() const {
return static_cast<T*>(m_data.get());
}
/**
* Save storage to automatic on-change save location.
*/
bool SaveStorage();
Storage() = default;
Storage(const Storage&) = delete;
Storage& operator=(const Storage&) = delete;
/**
* Explicitly save storage. Set Context::storageAutoSaveDir prior to calling
* wpi::gui::Initialize() for automatic on-change saving.
*
* @param dir path to directory
*/
bool SaveStorage(std::string_view dir);
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;
}
/**
* Gets the storage root for the current ID stack (e.g. the last call to
* ResetStorageStack).
*
* @return Storage object
*/
Storage& GetCurStorageRoot();
private:
mutable std::vector<std::string> m_keys;
mutable std::vector<std::unique_ptr<Value>> m_values;
std::shared_ptr<void> m_data;
};
/**
* Gets an arbitrary storage root.
*
* Non-empty root names are saved but not loaded unless GetStorageRoot()
* is called during initialization (or before LoadStorage is called).
*
* @param rootName root name
* @return Storage object
*/
Storage& GetStorageRoot(std::string_view rootName = {});
/**
* Resets storage stack. Should only be called at top level.
*
* @param rootName root name
*/
void ResetStorageStack(std::string_view rootName = {});
/**
* Gets the storage object for the current point in the ID stack.
*
* @return Storage object
*/
Storage& GetStorage();
Storage& GetStorage(std::string_view id);
/**
* Pushes label/ID onto the storage stack, without pushing the imgui ID stack.
*
* @param label_id label or label###id
*/
void PushStorageStack(std::string_view label_id);
/**
* Pushes specific storage onto the storage stack.
*
* @param storage storage
*/
void PushStorageStack(Storage& storage);
/**
* Pops storage stack, without popping the imgui ID stack.
*/
void PopStorageStack();
bool Begin(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0);

View File

@@ -6,42 +6,40 @@
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#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"
#include "glass/Storage.h"
namespace glass {
class DataSource;
class DataSourceName {
class Context {
public:
DataSourceName() = default;
explicit DataSourceName(DataSource* source) : source{source} {}
Context();
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
~Context();
bool ReadIni(std::string_view name_, std::string_view 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;
std::vector<std::function<void()>> workspaceInit;
std::vector<std::function<void()>> workspaceReset;
std::string storageLoadDir = ".";
std::string storageAutoSaveDir = ".";
std::string storageName = "imgui";
wpi::SmallVector<Storage*, 32> storageStack;
wpi::StringMap<std::unique_ptr<Storage>> storageRoots;
wpi::StringMap<bool> deviceHidden;
IniSaverString<DataSourceName> sources{"Data Sources"};
wpi::StringMap<DataSource*> sources;
Storage& sourceNameStorage;
uint64_t zeroTime = 0;
bool isPlatformSaveDir = false;
};
extern Context* gContext;

View File

@@ -16,8 +16,6 @@
namespace glass {
class NameInfo;
/**
* A data source for numeric/boolean data.
*/
@@ -33,15 +31,9 @@ class DataSource {
const char* GetId() const { return m_id.c_str(); }
void SetName(std::string_view 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 SetName(std::string_view name) { m_name = name; }
std::string& GetName() { return m_name; }
const std::string& GetName() const { return m_name; }
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
@@ -53,8 +45,9 @@ class DataSource {
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;
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
void LabelTextV(const char* label, const char* fmt, va_list args) const
IM_FMTLIST(3);
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,
@@ -74,7 +67,7 @@ class DataSource {
private:
std::string m_id;
NameInfo* m_name;
std::string& m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};

View File

@@ -4,13 +4,14 @@
#pragma once
#include <portable-file-dialogs.h>
#include <functional>
#include <memory>
#include <vector>
namespace glass {
class WindowManager;
/**
* GUI main menu bar.
*/
@@ -21,6 +22,11 @@ class MainMenuBar {
*/
void Display();
/**
* Displays workspace menu. Called by Display().
*/
void WorkspaceMenu();
/**
* Adds to GUI's main menu bar. The menu function is called from within a
* ImGui::BeginMainMenuBar()/EndMainMenuBar() block. Usually it's only
@@ -43,6 +49,8 @@ class MainMenuBar {
private:
std::vector<std::function<void()>> m_optionMenus;
std::vector<std::function<void()>> m_menus;
std::unique_ptr<pfd::select_folder> m_openFolder;
std::unique_ptr<pfd::select_folder> m_saveFolder;
};
} // namespace glass

View File

@@ -19,6 +19,8 @@
namespace glass {
class Storage;
namespace detail {
struct ProviderFunctions {
using Exists = std::function<bool()>;
@@ -49,9 +51,9 @@ class Provider : public WindowManager {
/**
* Constructor.
*
* @param iniName Group name to use in ini file
* @param storage Storage
*/
explicit Provider(std::string_view iniName) : WindowManager{iniName} {}
explicit Provider(Storage& storage) : WindowManager{storage} {}
Provider(const Provider&) = delete;
Provider& operator=(const Provider&) = delete;
@@ -133,6 +135,7 @@ class Provider : public WindowManager {
ModelEntry* modelEntry;
ViewExistsFunc exists;
CreateViewFunc createView;
bool showDefault = false;
Window* window = nullptr;
};

View File

@@ -26,7 +26,7 @@ void Provider<Functions>::ShowDefault(std::string_view name) {
if (it == m_viewEntries.end() || (*it)->name != name) {
return;
}
this->Show(it->get(), (*it)->window);
(*it)->showDefault = true;
}
template <typename Functions>

View File

@@ -0,0 +1,293 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/StringMap.h>
#include <wpi/iterator_range.h>
#include <wpi/span.h>
namespace wpi {
class json;
} // namespace wpi
namespace glass {
namespace detail {
template <typename IteratorType>
class ChildIterator;
} // namespace detail
/**
* Storage provides both persistent and non-persistent nested 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 {
enum Type {
kNone,
kInt,
kInt64,
kBool,
kFloat,
kDouble,
kString,
kChild,
kIntArray,
kInt64Array,
kBoolArray,
kFloatArray,
kDoubleArray,
kStringArray,
kChildArray
};
Value() = default;
explicit Value(Type type) : type{type} {}
Value(const Value&) = delete;
Value& operator=(const Value&) = delete;
~Value() { Reset(kNone); }
Type type = kNone;
union {
int intVal;
int64_t int64Val;
bool boolVal;
float floatVal;
double doubleVal;
Storage* child;
std::vector<int>* intArray;
std::vector<int64_t>* int64Array;
std::vector<int>* boolArray;
std::vector<float>* floatArray;
std::vector<double>* doubleArray;
std::vector<std::string>* stringArray;
std::vector<std::unique_ptr<Storage>>* childArray;
};
std::string stringVal;
union {
int intDefault;
int64_t int64Default;
bool boolDefault;
float floatDefault;
double doubleDefault;
// pointers may be nullptr to indicate empty
std::vector<int>* intArrayDefault;
std::vector<int64_t>* int64ArrayDefault;
std::vector<int>* boolArrayDefault;
std::vector<float>* floatArrayDefault;
std::vector<double>* doubleArrayDefault;
std::vector<std::string>* stringArrayDefault;
};
std::string stringDefault;
bool hasDefault = false;
void Reset(Type newType);
};
using ValueMap = wpi::StringMap<std::unique_ptr<Value>>;
template <typename Iterator>
using ChildIterator = detail::ChildIterator<Iterator>;
// The "Read" functions don't create or overwrite the value
int ReadInt(std::string_view key, int defaultVal = 0) const;
int64_t ReadInt64(std::string_view key, int64_t defaultVal = 0) const;
bool ReadBool(std::string_view key, bool defaultVal = false) const;
float ReadFloat(std::string_view key, float defaultVal = 0.0f) const;
double ReadDouble(std::string_view key, double defaultVal = 0.0) const;
std::string ReadString(std::string_view key,
std::string_view defaultVal = {}) const;
void SetInt(std::string_view key, int val);
void SetInt64(std::string_view key, int64_t val);
void SetBool(std::string_view key, bool val);
void SetFloat(std::string_view key, float val);
void SetDouble(std::string_view key, double val);
void SetString(std::string_view key, std::string_view val);
// The "Get" functions create or override the current value type.
// If the value is not set, it is set to the provided default.
int& GetInt(std::string_view key, int defaultVal = 0);
int64_t& GetInt64(std::string_view key, int64_t defaultVal = 0);
bool& GetBool(std::string_view key, bool defaultVal = false);
float& GetFloat(std::string_view key, float defaultVal = 0.0f);
double& GetDouble(std::string_view key, double defaultVal = 0.0);
std::string& GetString(std::string_view key,
std::string_view defaultVal = {});
std::vector<int>& GetIntArray(std::string_view key,
wpi::span<const int> defaultVal = {});
std::vector<int64_t>& GetInt64Array(std::string_view key,
wpi::span<const int64_t> defaultVal = {});
std::vector<int>& GetBoolArray(std::string_view key,
wpi::span<const int> defaultVal = {});
std::vector<float>& GetFloatArray(std::string_view key,
wpi::span<const float> defaultVal = {});
std::vector<double>& GetDoubleArray(std::string_view key,
wpi::span<const double> defaultVal = {});
std::vector<std::string>& GetStringArray(
std::string_view key, wpi::span<const std::string> defaultVal = {});
std::vector<std::unique_ptr<Storage>>& GetChildArray(std::string_view key);
Value* FindValue(std::string_view key);
Value& GetValue(std::string_view key);
Storage& GetChild(std::string_view label_id);
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;
void Insert(std::string_view key, std::unique_ptr<Value> value) {
m_values[key] = std::move(value);
}
std::unique_ptr<Value> Erase(std::string_view key);
void EraseAll() { m_values.clear(); }
ValueMap& GetValues() { return m_values; }
const ValueMap& GetValues() const { return m_values; }
wpi::iterator_range<ChildIterator<ValueMap::iterator>> GetChildren();
/**
* Erases all children (at top level).
*/
void EraseChildren();
bool FromJson(const wpi::json& json, const char* filename);
wpi::json ToJson() const;
/**
* Clear settings (set to default). Calls custom clear function (if set),
* otherwise calls ClearValues().
*/
void Clear();
/**
* Clear values (and values of children) only (set to default). Does not
* call custom clear function.
*/
void ClearValues();
/**
* Apply settings (called after all settings have been loaded). Calls
* custom apply function (if set), otherwise calls ApplyChildren().
*/
void Apply();
/**
* Apply settings to children. Does not call custom apply function.
*/
void ApplyChildren();
/**
* Sets custom JSON handlers (replaces FromJson and ToJson).
*
* @param fromJson replacement for FromJson()
* @param toJson replacement for ToJson()
*/
void SetCustomJson(
std::function<bool(const wpi::json& json, const char* filename)> fromJson,
std::function<wpi::json()> toJson) {
m_fromJson = std::move(fromJson);
m_toJson = std::move(toJson);
}
void SetCustomClear(std::function<void()> clear) {
m_clear = std::move(clear);
}
void SetCustomApply(std::function<void()> apply) {
m_apply = std::move(apply);
}
private:
mutable ValueMap m_values;
std::shared_ptr<void> m_data;
std::function<bool(const wpi::json& json, const char* filename)> m_fromJson;
std::function<wpi::json()> m_toJson;
std::function<void()> m_clear;
std::function<void()> m_apply;
};
namespace detail {
/// proxy class for the GetChildren() function
template <typename IteratorType>
class ChildIterator {
private:
/// the iterator
IteratorType anchor;
IteratorType end;
public:
ChildIterator(IteratorType it, IteratorType end) noexcept
: anchor(it), end(end) {
while (anchor != end &&
anchor->getValue()->type != Storage::Value::kChild) {
++anchor;
}
}
/// dereference operator (needed for range-based for)
ChildIterator& operator*() { return *this; }
/// increment operator (needed for range-based for)
ChildIterator& operator++() {
++anchor;
while (anchor != end &&
anchor->getValue()->type != Storage::Value::kChild) {
++anchor;
}
return *this;
}
/// inequality operator (needed for range-based for)
bool operator!=(const ChildIterator& o) const noexcept {
return anchor != o.anchor;
}
/// return key of the iterator
std::string_view key() const { return anchor->getKey(); }
/// return value of the iterator
Storage& value() const { return *anchor->getValue()->child; }
};
} // namespace detail
inline auto Storage::GetChildren()
-> wpi::iterator_range<ChildIterator<ValueMap::iterator>> {
return {{m_values.begin(), m_values.end()}, {m_values.end(), m_values.end()}};
}
} // namespace glass

View File

@@ -15,19 +15,21 @@
namespace glass {
class Storage;
/**
* Managed window information.
* A Window owns the View that displays the window's contents.
*/
class Window {
public:
Window() = default;
explicit Window(std::string_view id) : m_id{id}, m_defaultName{id} {}
enum Visibility { kHide = 0, kShow, kDisabled };
Window(Storage& storage, std::string_view id,
Visibility defaultVisibility = kShow);
std::string_view 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); }
@@ -59,6 +61,13 @@ class Window {
*/
void SetVisibility(Visibility visibility);
/**
* Sets default visibility of window.
*
* @param visibility 0=hide, 1=show, 2=disabled (force-hide)
*/
void SetDefaultVisibility(Visibility visibility);
/**
* Sets default position of window.
*
@@ -109,17 +118,16 @@ class Window {
*/
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::string& m_name;
std::string m_defaultName;
std::unique_ptr<View> m_view;
ImGuiWindowFlags m_flags = 0;
bool m_visible = true;
bool m_enabled = true;
bool& m_visible;
bool& m_enabled;
bool& m_defaultVisible;
bool& m_defaultEnabled;
bool m_renamePopupEnabled = true;
ImGuiCond m_posCond = 0;
ImGuiCond m_sizeCond = 0;

View File

@@ -4,20 +4,18 @@
#pragma once
#include <functional>
#include <memory>
#include <string_view>
#include <type_traits>
#include <vector>
#include <imgui.h>
#include <wpi/FunctionExtras.h>
#include "glass/Window.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
class Storage;
/**
* Window manager.
*
@@ -31,9 +29,9 @@ class WindowManager {
/**
* Constructor.
*
* @param iniName Group name to use in ini file
* @param storage Storage for window information
*/
explicit WindowManager(std::string_view iniName);
explicit WindowManager(Storage& storage);
virtual ~WindowManager() = default;
WindowManager(const WindowManager&) = delete;
@@ -65,8 +63,10 @@ class WindowManager {
*
* @param id unique identifier of the window (title bar)
* @param display window contents display function
* @param defaultVisibility default window visibility
*/
Window* AddWindow(std::string_view id, wpi::unique_function<void()> display);
Window* AddWindow(std::string_view id, wpi::unique_function<void()> display,
Window::Visibility defaultVisibility = Window::kShow);
/**
* Adds window to GUI. The view's display function is called from within a
@@ -82,9 +82,11 @@ class WindowManager {
*
* @param id unique identifier of the window (title bar)
* @param view view object
* @param defaultVisibility default window visibility
* @return Window, or nullptr on duplicate window
*/
Window* AddWindow(std::string_view id, std::unique_ptr<View> view);
Window* AddWindow(std::string_view id, std::unique_ptr<View> view,
Window::Visibility defaultVisibility = Window::kShow);
/**
* Adds window to GUI. A View must be assigned to the returned Window
@@ -99,9 +101,12 @@ class WindowManager {
* every frame in the gui::AddExecute() function.
*
* @param id unique identifier of the window (default title bar)
* @param duplicateOk if false, warn on duplicates
* @param defaultVisibility default window visibility
* @return Window, or nullptr on duplicate window
*/
Window* GetOrAddWindow(std::string_view id, bool duplicateOk = false);
Window* GetOrAddWindow(std::string_view id, bool duplicateOk = false,
Window::Visibility defaultVisibility = Window::kShow);
/**
* Gets existing window. If none exists, returns nullptr.
@@ -111,27 +116,26 @@ class WindowManager {
*/
Window* GetWindow(std::string_view id);
/**
* Erases all windows.
*/
void EraseWindows() { m_windows.clear(); }
protected:
virtual void DisplayWindows();
/**
* Removes existing window (by index)
*
* @param index index of window in m_windows
*/
void RemoveWindow(size_t index);
// kept sorted by id
std::vector<std::unique_ptr<Window>> m_windows;
Storage& m_storage;
private:
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(std::string_view 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;
void DisplayWindows();
};
} // namespace glass

View File

@@ -4,19 +4,16 @@
#pragma once
#include <string_view>
#include "glass/WindowManager.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
class PlotProvider : private WindowManager {
public:
explicit PlotProvider(std::string_view iniName);
explicit PlotProvider(Storage& storage);
~PlotProvider() override;
void GlobalInit() override;
using WindowManager::GlobalInit;
/**
* Pauses or unpauses all plots.
@@ -33,24 +30,6 @@ class PlotProvider : private WindowManager {
void DisplayMenu() override;
private:
void DisplayWindows() override;
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(std::string_view 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;
bool m_paused = false;
};

View File

@@ -0,0 +1,53 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <vector>
#include <imgui.h>
namespace glass {
class ColorSetting {
public:
explicit ColorSetting(std::vector<float>& color);
ImVec4 GetColor() const {
return {m_color[0], m_color[1], m_color[2], m_color[3]};
}
float* GetColorFloat() { return m_color.data(); }
const float* GetColorFloat() const { return m_color.data(); }
void SetColor(const ImVec4& color) {
m_color[0] = color.x;
m_color[1] = color.y;
m_color[2] = color.z;
m_color[3] = color.w;
}
// updates internal value, returns true on change
bool ColorEdit3(const char* label, ImGuiColorEditFlags flags = 0) {
return ImGui::ColorEdit3(label, m_color.data(), flags);
}
bool ColorEdit4(const char* label, ImGuiColorEditFlags flags = 0) {
return ImGui::ColorEdit4(label, m_color.data(), flags);
}
bool ColorPicker3(const char* label, ImGuiColorEditFlags flags = 0) {
return ImGui::ColorPicker3(label, m_color.data(), flags);
}
bool ColorPicker4(const char* label, ImGuiColorEditFlags flags = 0,
const float* ref_col = nullptr) {
return ImGui::ColorPicker4(label, m_color.data(), flags, ref_col);
}
private:
std::vector<float>& m_color;
};
} // namespace glass

View File

@@ -0,0 +1,31 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string>
#include <wpi/SmallVector.h>
namespace glass {
class EnumSetting {
public:
EnumSetting(std::string& str, int defaultValue,
std::initializer_list<const char*> choices);
int GetValue() const { return m_value; }
void SetValue(int value);
// updates internal value, returns true on change
bool Combo(const char* label, int numOptions = -1,
int popup_max_height_in_items = -1);
private:
std::string& m_str;
wpi::SmallVector<const char*, 8> m_choices;
int m_value;
};
} // namespace glass

View File

@@ -1,44 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <imgui.h>
#include <wpi/DenseMap.h>
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(std::string_view typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
// pass through useful functions to map
Info& operator[](int index) { return m_map[index]; }
auto begin() { return m_map.begin(); }
auto end() { return m_map.end(); }
auto find(int index) { return m_map.find(index); }
auto begin() const { return m_map.begin(); }
auto end() const { return m_map.end(); }
auto find(int index) const { return m_map.find(index); }
private:
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
wpi::DenseMap<int, Info> m_map;
};
} // namespace glass
#include "IniSaver.inc"

View File

@@ -1,40 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <wpi/StringExtras.h>
#include "glass/support/IniSaver.h"
namespace glass {
template <typename Info>
void* IniSaver<Info>::IniReadOpen(const char* name) {
if (auto num = wpi::parse_integer<int>(name, 10)) {
return &m_map[num.value()];
} else {
return nullptr;
}
}
template <typename Info>
void IniSaver<Info>::IniReadLine(void* entry, const char* line) {
auto element = static_cast<Info*>(entry);
auto [name, value] = wpi::split(line, '=');
element->ReadIni(wpi::trim(name), wpi::trim(value));
}
template <typename Info>
void IniSaver<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& it : m_map) {
out_buf->appendf("[%s][%d]\n", GetTypeName(), it.first);
it.second.WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -1,46 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string>
#include <string_view>
#include <imgui.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(std::string_view typeName,
IniSaverBackend* backend = nullptr);
virtual ~IniSaverBase();
void Initialize() { m_backend->Register(this); }
const char* GetTypeName() const { return m_typeName.c_str(); }
IniSaverBackend* GetBackend() const { return m_backend; }
IniSaverBase(const IniSaverBase&) = delete;
IniSaverBase& operator=(const IniSaverBase&) = delete;
virtual void* IniReadOpen(const char* name) = 0;
virtual void IniReadLine(void* entry, const char* lineStr) = 0;
virtual void IniWriteAll(ImGuiTextBuffer* out_buf) = 0;
private:
std::string m_typeName;
IniSaverBackend* m_backend;
};
} // namespace glass

View File

@@ -1,55 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <utility>
#include <imgui.h>
#include <wpi/StringMap.h>
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaverString : public IniSaverBase {
public:
explicit IniSaverString(std::string_view typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
// pass through useful functions to map
Info& operator[](std::string_view key) { return m_map[key]; }
template <typename... ArgsTy>
auto try_emplace(std::string_view 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(std::string_view key) { return m_map.erase(key); }
auto begin() { return m_map.begin(); }
auto end() { return m_map.end(); }
auto find(std::string_view key) { return m_map.find(key); }
auto begin() const { return m_map.begin(); }
auto end() const { return m_map.end(); }
auto find(std::string_view key) const { return m_map.find(key); }
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;
wpi::StringMap<Info> m_map;
};
} // namespace glass
#include "IniSaverString.inc"

View File

@@ -1,36 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <wpi/StringExtras.h>
#include "glass/support/IniSaverString.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* line) {
auto element = static_cast<Info*>(entry);
auto [name, value] = wpi::split(line, '=');
element->ReadIni(wpi::trim(name), wpi::trim(value));
}
template <typename Info>
void IniSaverString<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (auto&& it : m_map) {
out_buf->appendf("[%s][%s]\n", GetTypeName(), it.getKey().data());
it.second.WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -1,31 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <vector>
#include <imgui.h>
#include "glass/support/IniSaverBase.h"
namespace glass {
template <typename Info>
class IniSaverVector : public std::vector<Info>, public IniSaverBase {
public:
explicit IniSaverVector(std::string_view typeName,
IniSaverBackend* backend = nullptr)
: IniSaverBase(typeName, backend) {}
private:
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
};
} // namespace glass
#include "IniSaverVector.inc"

View File

@@ -1,43 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <wpi/StringExtras.h>
#include "glass/support/IniSaverVector.h"
namespace glass {
template <typename Info>
void* IniSaverVector<Info>::IniReadOpen(const char* name) {
if (auto num = wpi::parse_integer<unsigned int>(name, 10)) {
if (num.value() >= this->size()) {
this->resize(num.value() + 1);
}
return &(*this)[num.value()];
} else {
return nullptr;
}
}
template <typename Info>
void IniSaverVector<Info>::IniReadLine(void* entry, const char* line) {
auto element = static_cast<Info*>(entry);
auto [name, value] = wpi::split(line, '=');
element->ReadIni(wpi::trim(name), wpi::trim(value));
}
template <typename Info>
void IniSaverVector<Info>::IniWriteAll(ImGuiTextBuffer* out_buf) {
for (size_t i = 0; i < this->size(); ++i) {
out_buf->appendf("[%s][%d]\n", GetTypeName(), static_cast<int>(i));
(*this)[i].WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace glass

View File

@@ -4,19 +4,21 @@
#pragma once
#include <string>
#include <string_view>
#include <imgui.h>
namespace glass {
class NameInfo {
class NameSetting {
public:
NameInfo() { m_name[0] = '\0'; }
explicit NameSetting(std::string& str) : m_name{str} {}
bool HasName() const { return m_name[0] != '\0'; }
void SetName(std::string_view name);
const char* GetName() const { return m_name; }
bool HasName() const { return !m_name.empty(); }
void SetName(std::string_view name) { m_name = name; }
std::string& GetName() { return m_name; }
const std::string& 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,
int index) const;
@@ -28,8 +30,6 @@ class NameInfo {
void GetLabel(char* buf, size_t size, const char* defaultName, int index,
int index2) const;
bool ReadIni(std::string_view name, std::string_view value);
void WriteIni(ImGuiTextBuffer* out);
void PushEditNameId(int index);
void PushEditNameId(const char* name);
bool PopupEditName(int index);
@@ -37,27 +37,7 @@ class NameInfo {
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
private:
char m_name[64];
};
class OpenInfo {
public:
OpenInfo() = default;
explicit OpenInfo(bool open) : m_open(open) {}
bool IsOpen() const { return m_open; }
void SetOpen(bool open) { m_open = open; }
bool ReadIni(std::string_view name, std::string_view value);
void WriteIni(ImGuiTextBuffer* out);
private:
bool m_open = false;
};
class NameOpenInfo : public NameInfo, public OpenInfo {
public:
bool ReadIni(std::string_view name, std::string_view value);
void WriteIni(ImGuiTextBuffer* out);
std::string& m_name;
};
} // namespace glass

View File

@@ -25,6 +25,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/Storage.h"
using namespace glass;
@@ -731,15 +732,15 @@ void glass::DisplayNetworkTables(NetworkTablesModel* model,
void NetworkTablesFlagsSettings::Update() {
if (!m_pTreeView) {
auto& storage = GetStorage();
m_pTreeView = storage.GetBoolRef(
"tree", m_defaultFlags & NetworkTablesFlags_TreeView);
m_pShowConnections = storage.GetBoolRef(
m_pTreeView =
&storage.GetBool("tree", m_defaultFlags & NetworkTablesFlags_TreeView);
m_pShowConnections = &storage.GetBool(
"connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
m_pShowFlags = storage.GetBoolRef(
m_pShowFlags = &storage.GetBool(
"flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
m_pShowTimestamp = storage.GetBoolRef(
m_pShowTimestamp = &storage.GetBool(
"timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
m_pCreateNoncanonicalKeys = storage.GetBoolRef(
m_pCreateNoncanonicalKeys = &storage.GetBool(
"createNonCanonical",
m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
}

View File

@@ -12,23 +12,53 @@
#include <wpi/StringExtras.h>
#include <wpigui.h>
#include "glass/Storage.h"
using namespace glass;
NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName)
: NetworkTablesProvider{iniName, nt::GetDefaultInstance()} {}
NetworkTablesProvider::NetworkTablesProvider(Storage& storage)
: NetworkTablesProvider{storage, nt::GetDefaultInstance()} {}
NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName,
NT_Inst inst)
: Provider{fmt::format("{}Window", iniName)},
NetworkTablesProvider::NetworkTablesProvider(Storage& storage, NT_Inst inst)
: Provider{storage.GetChild("windows")},
m_nt{inst},
m_typeCache{iniName} {
m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
NT_NOTIFY_IMMEDIATE);
}
m_typeCache{storage.GetChild("types")} {
storage.SetCustomApply([this] {
m_listener =
m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
NT_NOTIFY_DELETE | NT_NOTIFY_IMMEDIATE);
for (auto&& childIt : m_storage.GetChildren()) {
auto id = childIt.key();
auto typePtr = m_typeCache.FindValue(id);
if (!typePtr || typePtr->type != Storage::Value::kString) {
continue;
}
void NetworkTablesProvider::GlobalInit() {
Provider::GlobalInit();
wpi::gui::AddInit([this] { m_typeCache.Initialize(); });
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(typePtr->stringVal);
if (builderIt == m_typeMap.end()) {
continue;
}
auto entry = GetOrCreateView(
builderIt->second,
nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id);
if (entry) {
Show(entry, nullptr);
}
}
});
storage.SetCustomClear([this, &storage] {
nt::RemoveEntryListener(m_listener);
m_listener = 0;
for (auto&& modelEntry : m_modelEntries) {
modelEntry->model.reset();
}
m_viewEntries.clear();
m_windows.clear();
m_typeCache.EraseAll();
storage.ClearValues();
});
}
void NetworkTablesProvider::DisplayMenu() {
@@ -98,33 +128,7 @@ void NetworkTablesProvider::Update() {
} 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(), fmt::format("{}/.type", id)), id);
if (entry) {
Show(entry, window.get());
m_typeCache.SetString(tableName, event.value->GetString());
}
}
}
@@ -153,7 +157,7 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
// the window might exist and we're just not associated to it yet
if (!window) {
window = GetOrAddWindow(entry->name, true);
window = GetOrAddWindow(entry->name, true, Window::kHide);
}
if (!window) {
return;

View File

@@ -15,6 +15,7 @@
#include <wpi/StringExtras.h>
#include "glass/Context.h"
#include "glass/Storage.h"
using namespace glass;
@@ -81,15 +82,12 @@ void NetworkTablesSettings::Thread::Main() {
}
}
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
const char* storageName) {
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");
m_pDsClient = storage.GetBoolRef("dsClient", true);
NetworkTablesSettings::NetworkTablesSettings(Storage& storage, NT_Inst inst)
: m_mode{storage.GetString("mode"), 0, {"Disabled", "Client", "Server"}},
m_iniName{storage.GetString("iniName", "networktables.ini")},
m_serverTeam{storage.GetString("serverTeam")},
m_listenAddress{storage.GetString("listenAddress")},
m_dsClient{storage.GetBool("dsClient", true)} {
m_thread.Start(inst);
}
@@ -102,25 +100,24 @@ void NetworkTablesSettings::Update() {
// do actual operation on thread
auto thr = m_thread.GetThread();
thr->m_restart = true;
thr->m_mode = *m_pMode;
thr->m_iniName = *m_pIniName;
thr->m_serverTeam = *m_pServerTeam;
thr->m_listenAddress = *m_pListenAddress;
thr->m_dsClient = *m_pDsClient;
thr->m_mode = m_mode.GetValue();
thr->m_iniName = m_iniName;
thr->m_serverTeam = m_serverTeam;
thr->m_listenAddress = m_listenAddress;
thr->m_dsClient = m_dsClient;
thr->m_cond.notify_one();
}
bool NetworkTablesSettings::Display() {
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
ImGui::Combo("Mode", m_pMode, modeOptions, m_serverOption ? 3 : 2);
switch (*m_pMode) {
m_mode.Combo("Mode", m_serverOption ? 3 : 2);
switch (m_mode.GetValue()) {
case 1:
ImGui::InputText("Team/IP", m_pServerTeam);
ImGui::Checkbox("Get Address from DS", m_pDsClient);
ImGui::InputText("Team/IP", &m_serverTeam);
ImGui::Checkbox("Get Address from DS", &m_dsClient);
break;
case 2:
ImGui::InputText("Listen Address", m_pListenAddress);
ImGui::InputText("ini Filename", m_pIniName);
ImGui::InputText("Listen Address", &m_listenAddress);
ImGui::InputText("ini Filename", &m_iniName);
break;
default:
break;

View File

@@ -9,14 +9,13 @@
#include <string_view>
#include <vector>
#include <ntcore_c.h>
#include <ntcore_cpp.h>
#include <wpi/StringMap.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 {
@@ -41,8 +40,8 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
using Provider::CreateModelFunc;
using Provider::CreateViewFunc;
explicit NetworkTablesProvider(std::string_view iniName);
NetworkTablesProvider(std::string_view iniName, NT_Inst inst);
explicit NetworkTablesProvider(Storage& storage);
NetworkTablesProvider(Storage& storage, NT_Inst inst);
/**
* Get the NetworkTables instance being used for this provider.
@@ -55,7 +54,7 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
* Perform global initialization. This should be called prior to
* wpi::gui::Initialize().
*/
void GlobalInit() override;
void GlobalInit() override { Provider::GlobalInit(); }
/**
* Displays menu contents as a tree of available NetworkTables views.
@@ -72,15 +71,14 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
void Register(std::string_view typeName, CreateModelFunc createModel,
CreateViewFunc createView);
using WindowManager::AddWindow;
private:
void Update() override;
NetworkTablesHelper m_nt;
NT_EntryListener m_listener{0};
// cached mapping from table name to type string
IniSaverString<NameInfo> m_typeCache;
Storage& m_typeCache;
struct Builder {
CreateModelFunc createModel;

View File

@@ -9,6 +9,8 @@
#include <ntcore_cpp.h>
#include <wpi/SafeThread.h>
#include "glass/support/EnumSetting.h"
namespace wpi {
template <typename T>
class SmallVectorImpl;
@@ -16,11 +18,12 @@ class SmallVectorImpl;
namespace glass {
class Storage;
class NetworkTablesSettings {
public:
explicit NetworkTablesSettings(
NT_Inst inst = nt::GetDefaultInstance(),
const char* storageName = "NetworkTables Settings");
explicit NetworkTablesSettings(Storage& storage,
NT_Inst inst = nt::GetDefaultInstance());
/**
* Enables or disables the server option. Default is enabled.
@@ -33,11 +36,11 @@ class NetworkTablesSettings {
private:
bool m_restart = true;
bool m_serverOption = true;
int* m_pMode;
std::string* m_pIniName;
std::string* m_pServerTeam;
std::string* m_pListenAddress;
bool* m_pDsClient;
EnumSetting m_mode;
std::string& m_iniName;
std::string& m_serverTeam;
std::string& m_listenAddress;
bool& m_dsClient;
class Thread : public wpi::SafeThread {
public:

View File

@@ -7,6 +7,11 @@ package edu.wpi.first.hal;
import java.nio.IntBuffer;
public class CounterJNI extends JNIWrapper {
public static final int TWO_PULSE = 0;
public static final int SEMI_PERIOD = 1;
public static final int PULSE_LENGTH = 2;
public static final int EXTERNAL_DIRECTION = 3;
public static native int initializeCounter(int mode, IntBuffer index);
public static native void freeCounter(int counterHandle);

View File

@@ -55,4 +55,6 @@ public class JNIWrapper {
loader.loadLibrary();
libraryLoaded = true;
}
public static void suppressUnused(Object object) {}
}

View File

@@ -6,6 +6,11 @@ package edu.wpi.first.hal;
@SuppressWarnings("AbbreviationAsWordInName")
public class REVPHJNI extends JNIWrapper {
public static final int COMPRESSOR_CONFIG_TYPE_DISABLED = 0;
public static final int COMPRESSOR_CONFIG_TYPE_DIGITAL = 1;
public static final int COMPRESSOR_CONFIG_TYPE_ANALOG = 2;
public static final int COMPRESSOR_CONFIG_TYPE_HYBRID = 3;
public static native int initialize(int module);
public static native void free(int handle);
@@ -14,9 +19,24 @@ public class REVPHJNI extends JNIWrapper {
public static native boolean getCompressor(int handle);
public static native void setClosedLoopControl(int handle, boolean enabled);
public static native void setCompressorConfig(
int handle,
double minAnalogVoltage,
double maxAnalogVoltage,
boolean forceDisable,
boolean useDigital);
public static native boolean getClosedLoopControl(int handle);
public static native void setClosedLoopControlDisabled(int handle);
public static native void setClosedLoopControlDigital(int handle);
public static native void setClosedLoopControlAnalog(
int handle, double minAnalogVoltage, double maxAnalogVoltage);
public static native void setClosedLoopControlHybrid(
int handle, double minAnalogVoltage, double maxAnalogVoltage);
public static native int getCompressorConfig(int handle);
public static native boolean getPressureSwitch(int handle);

View File

@@ -35,14 +35,14 @@ public class REVPHDataJNI extends JNIWrapper {
public static native void setCompressorOn(int index, boolean compressorOn);
public static native int registerClosedLoopEnabledCallback(
public static native int registerCompressorConfigTypeCallback(
int index, NotifyCallback callback, boolean initialNotify);
public static native void cancelClosedLoopEnabledCallback(int index, int uid);
public static native void cancelCompressorConfigTypeCallback(int index, int uid);
public static native boolean getClosedLoopEnabled(int index);
public static native int getCompressorConfigType(int index);
public static native void setClosedLoopEnabled(int index, boolean closeLoopEnabled);
public static native void setCompressorConfigType(int index, int configType);
public static native int registerPressureSwitchCallback(
int index, NotifyCallback callback, boolean initialNotify);

View File

@@ -41,6 +41,8 @@ static constexpr uint32_t PH_SET_ALL_FRAME_API =
APIFromExtId(PH_SET_ALL_FRAME_ID);
static constexpr uint32_t PH_PULSE_ONCE_FRAME_API =
APIFromExtId(PH_PULSE_ONCE_FRAME_ID);
static constexpr uint32_t PH_COMPRESSOR_CONFIG_API =
APIFromExtId(PH_COMPRESSOR_CONFIG_FRAME_ID);
static constexpr uint32_t PH_STATUS0_FRAME_API =
APIFromExtId(PH_STATUS0_FRAME_ID);
static constexpr uint32_t PH_STATUS1_FRAME_API =
@@ -256,14 +258,86 @@ HAL_Bool HAL_GetREVPHCompressor(HAL_REVPHHandle handle, int32_t* status) {
return status0.compressor_on;
}
void HAL_SetREVPHClosedLoopControl(HAL_REVPHHandle handle, HAL_Bool enabled,
int32_t* status) {
// TODO
void HAL_SetREVPHCompressorConfig(HAL_REVPHHandle handle,
const HAL_REVPHCompressorConfig* config,
int32_t* status) {
auto ph = REVPHHandles->Get(handle);
if (ph == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
PH_compressor_config_t frameData;
frameData.minimum_tank_pressure =
PH_compressor_config_minimum_tank_pressure_encode(
config->minAnalogVoltage);
frameData.maximum_tank_pressure =
PH_compressor_config_maximum_tank_pressure_encode(
config->maxAnalogVoltage);
frameData.force_disable = config->forceDisable;
frameData.use_digital = config->useDigital;
uint8_t packedData[PH_COMPRESSOR_CONFIG_LENGTH] = {0};
PH_compressor_config_pack(packedData, &frameData,
PH_COMPRESSOR_CONFIG_LENGTH);
HAL_WriteCANPacket(ph->hcan, packedData, PH_COMPRESSOR_CONFIG_LENGTH,
PH_COMPRESSOR_CONFIG_API, status);
}
HAL_Bool HAL_GetREVPHClosedLoopControl(HAL_REVPHHandle handle,
int32_t* status) {
return false; // TODO
void HAL_SetREVPHClosedLoopControlDisabled(HAL_REVPHHandle handle,
int32_t* status) {
HAL_REVPHCompressorConfig config = {0, 0, 0, 0};
config.forceDisable = true;
HAL_SetREVPHCompressorConfig(handle, &config, status);
}
void HAL_SetREVPHClosedLoopControlDigital(HAL_REVPHHandle handle,
int32_t* status) {
HAL_REVPHCompressorConfig config = {0, 0, 0, 0};
config.useDigital = true;
HAL_SetREVPHCompressorConfig(handle, &config, status);
}
void HAL_SetREVPHClosedLoopControlAnalog(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status) {
HAL_REVPHCompressorConfig config = {0, 0, 0, 0};
config.minAnalogVoltage = minAnalogVoltage;
config.maxAnalogVoltage = maxAnalogVoltage;
HAL_SetREVPHCompressorConfig(handle, &config, status);
}
void HAL_SetREVPHClosedLoopControlHybrid(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status) {
HAL_REVPHCompressorConfig config = {0, 0, 0, 0};
config.minAnalogVoltage = minAnalogVoltage;
config.maxAnalogVoltage = maxAnalogVoltage;
config.useDigital = true;
HAL_SetREVPHCompressorConfig(handle, &config, status);
}
HAL_REVPHCompressorConfigType HAL_GetREVPHCompressorConfig(
HAL_REVPHHandle handle, int32_t* status) {
auto ph = REVPHHandles->Get(handle);
if (ph == nullptr) {
*status = HAL_HANDLE_ERROR;
return HAL_REVPHCompressorConfigType_kDisabled;
}
PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status);
if (*status != 0) {
return HAL_REVPHCompressorConfigType_kDisabled;
}
return static_cast<HAL_REVPHCompressorConfigType>(status0.compressor_config);
}
HAL_Bool HAL_GetREVPHPressureSwitch(HAL_REVPHHandle handle, int32_t* status) {
@@ -478,3 +552,59 @@ void HAL_FireREVPHOneShot(HAL_REVPHHandle handle, int32_t index, int32_t durMs,
HAL_WriteCANPacket(ph->hcan, packedData, PH_PULSE_ONCE_LENGTH,
PH_PULSE_ONCE_FRAME_API, status);
}
HAL_REVPHFaults HAL_GetREVPHFaults(HAL_REVPHHandle handle, int32_t* status) {
HAL_REVPHFaults faults = {};
auto ph = REVPHHandles->Get(handle);
if (ph == nullptr) {
*status = HAL_HANDLE_ERROR;
return faults;
}
PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status);
faults.channel0Fault = status0.channel_0_fault;
faults.channel1Fault = status0.channel_1_fault;
faults.channel2Fault = status0.channel_2_fault;
faults.channel3Fault = status0.channel_3_fault;
faults.channel4Fault = status0.channel_4_fault;
faults.channel5Fault = status0.channel_5_fault;
faults.channel6Fault = status0.channel_6_fault;
faults.channel7Fault = status0.channel_7_fault;
faults.channel8Fault = status0.channel_8_fault;
faults.channel9Fault = status0.channel_9_fault;
faults.channel10Fault = status0.channel_10_fault;
faults.channel11Fault = status0.channel_11_fault;
faults.channel12Fault = status0.channel_12_fault;
faults.channel13Fault = status0.channel_13_fault;
faults.channel14Fault = status0.channel_14_fault;
faults.channel15Fault = status0.channel_15_fault;
faults.compressorOverCurrent = status0.compressor_oc;
faults.compressorOpen = status0.compressor_open;
faults.solenoidOverCurrent = status0.solenoid_oc;
faults.brownout = status0.brownout;
faults.canWarning = status0.can_warning;
faults.hardwareFault = status0.hardware_fault;
return faults;
}
HAL_REVPHStickyFaults HAL_GetREVPHStickyFaults(HAL_REVPHHandle handle,
int32_t* status) {
HAL_REVPHStickyFaults stickyFaults = {};
auto ph = REVPHHandles->Get(handle);
if (ph == nullptr) {
*status = HAL_HANDLE_ERROR;
return stickyFaults;
}
PH_status1_t status1 = HAL_REV_ReadPHStatus1(ph->hcan, status);
stickyFaults.compressorOverCurrent = status1.sticky_compressor_over_current;
stickyFaults.compressorOpen = status1.sticky_compressor_not_present;
stickyFaults.solenoidOverCurrent = status1.sticky_solenoid_over_current;
stickyFaults.brownout = status1.sticky_brownout;
stickyFaults.canWarning = status1.sticky_can_warning;
stickyFaults.canBusOff = status1.sticky_can_bus_off;
stickyFaults.hasReset = status1.sticky_has_reset;
return stickyFaults;
}

View File

@@ -496,9 +496,8 @@ void HAL_InitSPIAuto(HAL_SPIPort port, int32_t bufferSize, int32_t* status) {
}
// configure DMA
tDMAChannelDescriptor desc;
spiSystem->getSystemInterface()->getDmaDescriptor(g_SpiAutoData_index, &desc);
spiAutoDMA = std::make_unique<tDMAManager>(desc.channel, bufferSize, status);
spiAutoDMA =
std::make_unique<tDMAManager>(g_SpiAutoData_index, bufferSize, status);
}
void HAL_FreeSPIAuto(HAL_SPIPort port, int32_t* status) {

View File

@@ -15,7 +15,8 @@ void HALSIM_ResetREVPHData(int32_t index) {}
HAL_SIMDATAVALUE_STUB_CAPI_CHANNEL(HAL_Bool, HALSIM, REVPHSolenoidOutput, false)
DEFINE_CAPI(HAL_Bool, Initialized, false)
DEFINE_CAPI(HAL_Bool, CompressorOn, false)
DEFINE_CAPI(HAL_Bool, ClosedLoopEnabled, false)
DEFINE_CAPI(HAL_REVPHCompressorConfigType, CompressorConfigType,
HAL_REVPHCompressorConfigType_kDisabled)
DEFINE_CAPI(HAL_Bool, PressureSwitch, false)
DEFINE_CAPI(double, CompressorCurrent, 0)

View File

@@ -80,6 +80,106 @@ static inline uint16_t unpack_right_shift_u16(
return (uint16_t)((uint16_t)(value & mask) >> shift);
}
int PH_compressor_config_pack(
uint8_t *dst_p,
const struct PH_compressor_config_t *src_p,
size_t size)
{
if (size < 5u) {
return (-EINVAL);
}
memset(&dst_p[0], 0, 5);
dst_p[0] |= pack_left_shift_u16(src_p->minimum_tank_pressure, 0u, 0xffu);
dst_p[1] |= pack_right_shift_u16(src_p->minimum_tank_pressure, 8u, 0xffu);
dst_p[2] |= pack_left_shift_u16(src_p->maximum_tank_pressure, 0u, 0xffu);
dst_p[3] |= pack_right_shift_u16(src_p->maximum_tank_pressure, 8u, 0xffu);
dst_p[4] |= pack_left_shift_u8(src_p->force_disable, 0u, 0x01u);
dst_p[4] |= pack_left_shift_u8(src_p->use_digital, 1u, 0x02u);
return (5);
}
int PH_compressor_config_unpack(
struct PH_compressor_config_t *dst_p,
const uint8_t *src_p,
size_t size)
{
if (size < 5u) {
return (-EINVAL);
}
dst_p->minimum_tank_pressure = unpack_right_shift_u16(src_p[0], 0u, 0xffu);
dst_p->minimum_tank_pressure |= unpack_left_shift_u16(src_p[1], 8u, 0xffu);
dst_p->maximum_tank_pressure = unpack_right_shift_u16(src_p[2], 0u, 0xffu);
dst_p->maximum_tank_pressure |= unpack_left_shift_u16(src_p[3], 8u, 0xffu);
dst_p->force_disable = unpack_right_shift_u8(src_p[4], 0u, 0x01u);
dst_p->use_digital = unpack_right_shift_u8(src_p[4], 1u, 0x02u);
return (0);
}
uint16_t PH_compressor_config_minimum_tank_pressure_encode(double value)
{
return (uint16_t)(value / 0.001);
}
double PH_compressor_config_minimum_tank_pressure_decode(uint16_t value)
{
return ((double)value * 0.001);
}
bool PH_compressor_config_minimum_tank_pressure_is_in_range(uint16_t value)
{
return (value <= 5000u);
}
uint16_t PH_compressor_config_maximum_tank_pressure_encode(double value)
{
return (uint16_t)(value / 0.001);
}
double PH_compressor_config_maximum_tank_pressure_decode(uint16_t value)
{
return ((double)value * 0.001);
}
bool PH_compressor_config_maximum_tank_pressure_is_in_range(uint16_t value)
{
return (value <= 5000u);
}
uint8_t PH_compressor_config_force_disable_encode(double value)
{
return (uint8_t)(value);
}
double PH_compressor_config_force_disable_decode(uint8_t value)
{
return ((double)value);
}
bool PH_compressor_config_force_disable_is_in_range(uint8_t value)
{
return (value <= 1u);
}
uint8_t PH_compressor_config_use_digital_encode(double value)
{
return (uint8_t)(value);
}
double PH_compressor_config_use_digital_decode(uint8_t value)
{
return ((double)value);
}
bool PH_compressor_config_use_digital_is_in_range(uint8_t value)
{
return (value <= 1u);
}
int PH_set_all_pack(
uint8_t *dst_p,
const struct PH_set_all_t *src_p,
@@ -755,6 +855,8 @@ int PH_status0_pack(
dst_p[6] |= pack_left_shift_u8(src_p->channel_15_fault, 7u, 0x80u);
dst_p[7] |= pack_left_shift_u8(src_p->compressor_on, 0u, 0x01u);
dst_p[7] |= pack_left_shift_u8(src_p->system_enabled, 1u, 0x02u);
dst_p[7] |= pack_left_shift_u8(src_p->robo_rio_present, 2u, 0x04u);
dst_p[7] |= pack_left_shift_u8(src_p->compressor_config, 3u, 0x18u);
return (8);
}
@@ -811,6 +913,8 @@ int PH_status0_unpack(
dst_p->channel_15_fault = unpack_right_shift_u8(src_p[6], 7u, 0x80u);
dst_p->compressor_on = unpack_right_shift_u8(src_p[7], 0u, 0x01u);
dst_p->system_enabled = unpack_right_shift_u8(src_p[7], 1u, 0x02u);
dst_p->robo_rio_present = unpack_right_shift_u8(src_p[7], 2u, 0x04u);
dst_p->compressor_config = unpack_right_shift_u8(src_p[7], 3u, 0x18u);
return (0);
}
@@ -1464,6 +1568,36 @@ bool PH_status0_system_enabled_is_in_range(uint8_t value)
return (value <= 1u);
}
uint8_t PH_status0_robo_rio_present_encode(double value)
{
return (uint8_t)(value);
}
double PH_status0_robo_rio_present_decode(uint8_t value)
{
return ((double)value);
}
bool PH_status0_robo_rio_present_is_in_range(uint8_t value)
{
return (value <= 1u);
}
uint8_t PH_status0_compressor_config_encode(double value)
{
return (uint8_t)(value);
}
double PH_status0_compressor_config_decode(uint8_t value)
{
return ((double)value);
}
bool PH_status0_compressor_config_is_in_range(uint8_t value)
{
return (value <= 3u);
}
int PH_status1_pack(
uint8_t *dst_p,
const struct PH_status1_t *src_p,
@@ -1718,3 +1852,27 @@ bool PH_status1_sticky_has_reset_is_in_range(uint8_t value)
{
return (value <= 1u);
}
int PH_clear_faults_pack(
uint8_t *dst_p,
const struct PH_clear_faults_t *src_p,
size_t size)
{
(void)dst_p;
(void)src_p;
(void)size;
return (0);
}
int PH_clear_faults_unpack(
struct PH_clear_faults_t *dst_p,
const uint8_t *src_p,
size_t size)
{
(void)dst_p;
(void)src_p;
(void)size;
return (0);
}

View File

@@ -44,22 +44,28 @@ extern "C" {
#endif
/* Frame ids. */
#define PH_COMPRESSOR_CONFIG_FRAME_ID (0x9050840u)
#define PH_SET_ALL_FRAME_ID (0x9050c00u)
#define PH_PULSE_ONCE_FRAME_ID (0x9050c40u)
#define PH_STATUS0_FRAME_ID (0x9051800u)
#define PH_STATUS1_FRAME_ID (0x9051840u)
#define PH_CLEAR_FAULTS_FRAME_ID (0x9051b80u)
/* Frame lengths in bytes. */
#define PH_COMPRESSOR_CONFIG_LENGTH (5u)
#define PH_SET_ALL_LENGTH (4u)
#define PH_PULSE_ONCE_LENGTH (4u)
#define PH_STATUS0_LENGTH (8u)
#define PH_STATUS1_LENGTH (8u)
#define PH_CLEAR_FAULTS_LENGTH (0u)
/* Extended or standard frame types. */
#define PH_COMPRESSOR_CONFIG_IS_EXTENDED (1)
#define PH_SET_ALL_IS_EXTENDED (1)
#define PH_PULSE_ONCE_IS_EXTENDED (1)
#define PH_STATUS0_IS_EXTENDED (1)
#define PH_STATUS1_IS_EXTENDED (1)
#define PH_CLEAR_FAULTS_IS_EXTENDED (1)
/* Frame cycle times in milliseconds. */
@@ -67,6 +73,43 @@ extern "C" {
/* Signal choices. */
/**
* Signals in message Compressor_Config.
*
* Configures compressor to use digitial/analog sensors
*
* All signal values are as on the CAN bus.
*/
struct PH_compressor_config_t {
/**
* Range: 0..5000 (0..5 V)
* Scale: 0.001
* Offset: 0
*/
uint16_t minimum_tank_pressure : 16;
/**
* Range: 0..5000 (0..5 V)
* Scale: 0.001
* Offset: 0
*/
uint16_t maximum_tank_pressure : 16;
/**
* Range: 0..1 (0..1 -)
* Scale: 1
* Offset: 0
*/
uint8_t force_disable : 1;
/**
* Range: 0..1 (0..1 -)
* Scale: 1
* Offset: 0
*/
uint8_t use_digital : 1;
};
/**
* Signals in message SetAll.
*
@@ -624,6 +667,20 @@ struct PH_status0_t {
* Offset: 0
*/
uint8_t system_enabled : 1;
/**
* Range: 0..1 (0..1 -)
* Scale: 1
* Offset: 0
*/
uint8_t robo_rio_present : 1;
/**
* Range: 0..3 (0..3 -)
* Scale: 1
* Offset: 0
*/
uint8_t compressor_config : 2;
};
/**
@@ -726,6 +783,156 @@ struct PH_status1_t {
uint8_t sticky_has_reset : 1;
};
/**
* Signals in message ClearFaults.
*
* Clear sticky faults on the device
*
* All signal values are as on the CAN bus.
*/
struct PH_clear_faults_t {
/**
* Dummy signal in empty message.
*/
uint8_t dummy;
};
/**
* Pack message Compressor_Config.
*
* @param[out] dst_p Buffer to pack the message into.
* @param[in] src_p Data to pack.
* @param[in] size Size of dst_p.
*
* @return Size of packed data, or negative error code.
*/
int PH_compressor_config_pack(
uint8_t *dst_p,
const struct PH_compressor_config_t *src_p,
size_t size);
/**
* Unpack message Compressor_Config.
*
* @param[out] dst_p Object to unpack the message into.
* @param[in] src_p Message to unpack.
* @param[in] size Size of src_p.
*
* @return zero(0) or negative error code.
*/
int PH_compressor_config_unpack(
struct PH_compressor_config_t *dst_p,
const uint8_t *src_p,
size_t size);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint16_t PH_compressor_config_minimum_tank_pressure_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_compressor_config_minimum_tank_pressure_decode(uint16_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_compressor_config_minimum_tank_pressure_is_in_range(uint16_t value);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint16_t PH_compressor_config_maximum_tank_pressure_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_compressor_config_maximum_tank_pressure_decode(uint16_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_compressor_config_maximum_tank_pressure_is_in_range(uint16_t value);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint8_t PH_compressor_config_force_disable_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_compressor_config_force_disable_decode(uint8_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_compressor_config_force_disable_is_in_range(uint8_t value);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint8_t PH_compressor_config_use_digital_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_compressor_config_use_digital_decode(uint8_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_compressor_config_use_digital_is_in_range(uint8_t value);
/**
* Pack message SetAll.
*
@@ -2862,6 +3069,60 @@ double PH_status0_system_enabled_decode(uint8_t value);
*/
bool PH_status0_system_enabled_is_in_range(uint8_t value);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint8_t PH_status0_robo_rio_present_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_status0_robo_rio_present_decode(uint8_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_status0_robo_rio_present_is_in_range(uint8_t value);
/**
* Encode given signal by applying scaling and offset.
*
* @param[in] value Signal to encode.
*
* @return Encoded signal.
*/
uint8_t PH_status0_compressor_config_encode(double value);
/**
* Decode given signal by applying scaling and offset.
*
* @param[in] value Signal to decode.
*
* @return Decoded signal.
*/
double PH_status0_compressor_config_decode(uint8_t value);
/**
* Check that given signal is in allowed range.
*
* @param[in] value Signal to check.
*
* @return true if in range, false otherwise.
*/
bool PH_status0_compressor_config_is_in_range(uint8_t value);
/**
* Pack message Status1.
*
@@ -3241,6 +3502,34 @@ double PH_status1_sticky_has_reset_decode(uint8_t value);
*/
bool PH_status1_sticky_has_reset_is_in_range(uint8_t value);
/**
* Pack message ClearFaults.
*
* @param[out] dst_p Buffer to pack the message into.
* @param[in] src_p Data to pack.
* @param[in] size Size of dst_p.
*
* @return Size of packed data, or negative error code.
*/
int PH_clear_faults_pack(
uint8_t *dst_p,
const struct PH_clear_faults_t *src_p,
size_t size);
/**
* Unpack message ClearFaults.
*
* @param[out] dst_p Object to unpack the message into.
* @param[in] src_p Message to unpack.
* @param[in] size Size of src_p.
*
* @return zero(0) or negative error code.
*/
int PH_clear_faults_unpack(
struct PH_clear_faults_t *dst_p,
const uint8_t *src_p,
size_t size);
#ifdef __cplusplus
}

View File

@@ -11,6 +11,18 @@
#include "hal/Counter.h"
#include "hal/Errors.h"
static_assert(HAL_Counter_Mode::HAL_Counter_kTwoPulse ==
edu_wpi_first_hal_CounterJNI_TWO_PULSE);
static_assert(HAL_Counter_Mode::HAL_Counter_kSemiperiod ==
edu_wpi_first_hal_CounterJNI_SEMI_PERIOD);
static_assert(HAL_Counter_Mode::HAL_Counter_kPulseLength ==
edu_wpi_first_hal_CounterJNI_PULSE_LENGTH);
static_assert(HAL_Counter_Mode::HAL_Counter_kExternalDirection ==
edu_wpi_first_hal_CounterJNI_EXTERNAL_DIRECTION);
using namespace hal;
extern "C" {

View File

@@ -12,6 +12,19 @@
#include "hal/REVPH.h"
#include "hal/handles/HandlesInternal.h"
static_assert(
edu_wpi_first_hal_REVPHJNI_COMPRESSOR_CONFIG_TYPE_DISABLED ==
HAL_REVPHCompressorConfigType::HAL_REVPHCompressorConfigType_kDisabled);
static_assert(
edu_wpi_first_hal_REVPHJNI_COMPRESSOR_CONFIG_TYPE_DIGITAL ==
HAL_REVPHCompressorConfigType::HAL_REVPHCompressorConfigType_kDigital);
static_assert(
edu_wpi_first_hal_REVPHJNI_COMPRESSOR_CONFIG_TYPE_ANALOG ==
HAL_REVPHCompressorConfigType::HAL_REVPHCompressorConfigType_kAnalog);
static_assert(
edu_wpi_first_hal_REVPHJNI_COMPRESSOR_CONFIG_TYPE_HYBRID ==
HAL_REVPHCompressorConfigType::HAL_REVPHCompressorConfigType_kHybrid);
using namespace hal;
extern "C" {
@@ -73,31 +86,97 @@ Java_edu_wpi_first_hal_REVPHJNI_getCompressor
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: setClosedLoopControl
* Signature: (IZ)V
* Method: setCompressorConfig
* Signature: (IDDZZ)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_REVPHJNI_setClosedLoopControl
(JNIEnv* env, jclass, jint handle, jboolean enabled)
Java_edu_wpi_first_hal_REVPHJNI_setCompressorConfig
(JNIEnv* env, jclass, jint handle, jdouble minAnalogVoltage,
jdouble maxAnalogVoltage, jboolean forceDisable, jboolean useDigital)
{
int32_t status = 0;
HAL_SetREVPHClosedLoopControl(handle, enabled, &status);
HAL_REVPHCompressorConfig config;
config.minAnalogVoltage = minAnalogVoltage;
config.maxAnalogVoltage = maxAnalogVoltage;
config.useDigital = useDigital;
config.forceDisable = forceDisable;
HAL_SetREVPHCompressorConfig(handle, &config, &status);
CheckStatus(env, status, false);
}
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: getClosedLoopControl
* Signature: (I)Z
* Method: setClosedLoopControlDisabled
* Signature: (I)V
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_REVPHJNI_getClosedLoopControl
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_REVPHJNI_setClosedLoopControlDisabled
(JNIEnv* env, jclass, jint handle)
{
int32_t status = 0;
auto result = HAL_GetREVPHClosedLoopControl(handle, &status);
HAL_SetREVPHClosedLoopControlDisabled(handle, &status);
CheckStatus(env, status, false);
return result;
}
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: setClosedLoopControlDigital
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_REVPHJNI_setClosedLoopControlDigital
(JNIEnv* env, jclass, jint handle)
{
int32_t status = 0;
HAL_SetREVPHClosedLoopControlDigital(handle, &status);
CheckStatus(env, status, false);
}
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: setClosedLoopControlAnalog
* Signature: (IDD)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_REVPHJNI_setClosedLoopControlAnalog
(JNIEnv* env, jclass, jint handle, jdouble minAnalogVoltage,
jdouble maxAnalogVoltage)
{
int32_t status = 0;
HAL_SetREVPHClosedLoopControlAnalog(handle, minAnalogVoltage,
maxAnalogVoltage, &status);
CheckStatus(env, status, false);
}
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: setClosedLoopControlHybrid
* Signature: (IDD)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_REVPHJNI_setClosedLoopControlHybrid
(JNIEnv* env, jclass, jint handle, jdouble minAnalogVoltage,
jdouble maxAnalogVoltage)
{
int32_t status = 0;
HAL_SetREVPHClosedLoopControlHybrid(handle, minAnalogVoltage,
maxAnalogVoltage, &status);
CheckStatus(env, status, false);
}
/*
* Class: edu_wpi_first_hal_REVPHJNI
* Method: getCompressorConfig
* Signature: (I)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_REVPHJNI_getCompressorConfig
(JNIEnv* env, jclass, jint handle)
{
int32_t status = 0;
auto config = HAL_GetREVPHCompressorConfig(handle, &status);
CheckStatus(env, status, false);
return static_cast<jint>(config);
}
/*

View File

@@ -166,52 +166,54 @@ Java_edu_wpi_first_hal_simulation_REVPHDataJNI_setCompressorOn
/*
* Class: edu_wpi_first_hal_simulation_REVPHDataJNI
* Method: registerClosedLoopEnabledCallback
* Method: registerCompressorConfigTypeCallback
* Signature: (ILjava/lang/Object;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_registerClosedLoopEnabledCallback
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_registerCompressorConfigTypeCallback
(JNIEnv* env, jclass, jint index, jobject callback, jboolean initialNotify)
{
return sim::AllocateCallback(env, index, callback, initialNotify,
&HALSIM_RegisterREVPHClosedLoopEnabledCallback);
return sim::AllocateCallback(
env, index, callback, initialNotify,
&HALSIM_RegisterREVPHCompressorConfigTypeCallback);
}
/*
* Class: edu_wpi_first_hal_simulation_REVPHDataJNI
* Method: cancelClosedLoopEnabledCallback
* Method: cancelCompressorConfigTypeCallback
* Signature: (II)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_cancelClosedLoopEnabledCallback
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_cancelCompressorConfigTypeCallback
(JNIEnv* env, jclass, jint index, jint handle)
{
return sim::FreeCallback(env, handle, index,
&HALSIM_CancelREVPHClosedLoopEnabledCallback);
&HALSIM_CancelREVPHCompressorConfigTypeCallback);
}
/*
* Class: edu_wpi_first_hal_simulation_REVPHDataJNI
* Method: getClosedLoopEnabled
* Signature: (I)Z
* Method: getCompressorConfigType
* Signature: (I)I
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_getClosedLoopEnabled
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_getCompressorConfigType
(JNIEnv*, jclass, jint index)
{
return HALSIM_GetREVPHClosedLoopEnabled(index);
return static_cast<jint>(HALSIM_GetREVPHCompressorConfigType(index));
}
/*
* Class: edu_wpi_first_hal_simulation_REVPHDataJNI
* Method: setClosedLoopEnabled
* Signature: (IZ)V
* Method: setCompressorConfigType
* Signature: (II)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_setClosedLoopEnabled
(JNIEnv*, jclass, jint index, jboolean value)
Java_edu_wpi_first_hal_simulation_REVPHDataJNI_setCompressorConfigType
(JNIEnv*, jclass, jint index, jint value)
{
HALSIM_SetREVPHClosedLoopEnabled(index, value);
HALSIM_SetREVPHCompressorConfigType(
index, static_cast<HAL_REVPHCompressorConfigType>(value));
}
/*

View File

@@ -14,6 +14,67 @@
* @{
*/
/**
* The compressor configuration type
*/
HAL_ENUM(HAL_REVPHCompressorConfigType){
HAL_REVPHCompressorConfigType_kDisabled = 0,
HAL_REVPHCompressorConfigType_kDigital = 1,
HAL_REVPHCompressorConfigType_kAnalog = 2,
HAL_REVPHCompressorConfigType_kHybrid = 3,
};
/**
* Storage for compressor config
*/
struct HAL_REVPHCompressorConfig {
double minAnalogVoltage;
double maxAnalogVoltage;
HAL_Bool forceDisable;
HAL_Bool useDigital;
};
/**
* Storage for REV PH Faults
*/
struct HAL_REVPHFaults {
uint32_t channel0Fault : 1;
uint32_t channel1Fault : 1;
uint32_t channel2Fault : 1;
uint32_t channel3Fault : 1;
uint32_t channel4Fault : 1;
uint32_t channel5Fault : 1;
uint32_t channel6Fault : 1;
uint32_t channel7Fault : 1;
uint32_t channel8Fault : 1;
uint32_t channel9Fault : 1;
uint32_t channel10Fault : 1;
uint32_t channel11Fault : 1;
uint32_t channel12Fault : 1;
uint32_t channel13Fault : 1;
uint32_t channel14Fault : 1;
uint32_t channel15Fault : 1;
uint32_t compressorOverCurrent : 1;
uint32_t compressorOpen : 1;
uint32_t solenoidOverCurrent : 1;
uint32_t brownout : 1;
uint32_t canWarning : 1;
uint32_t hardwareFault : 1;
};
/**
* Storage for REV PH Sticky Faults
*/
struct HAL_REVPHStickyFaults {
uint32_t compressorOverCurrent : 1;
uint32_t compressorOpen : 1;
uint32_t solenoidOverCurrent : 1;
uint32_t brownout : 1;
uint32_t canWarning : 1;
uint32_t canBusOff : 1;
uint32_t hasReset : 1;
};
#ifdef __cplusplus
extern "C" {
#endif
@@ -28,9 +89,23 @@ HAL_Bool HAL_CheckREVPHSolenoidChannel(int32_t channel);
HAL_Bool HAL_CheckREVPHModuleNumber(int32_t module);
HAL_Bool HAL_GetREVPHCompressor(HAL_REVPHHandle handle, int32_t* status);
void HAL_SetREVPHClosedLoopControl(HAL_REVPHHandle handle, HAL_Bool enabled,
int32_t* status);
HAL_Bool HAL_GetREVPHClosedLoopControl(HAL_REVPHHandle handle, int32_t* status);
void HAL_SetREVPHCompressorConfig(HAL_REVPHHandle handle,
const HAL_REVPHCompressorConfig* config,
int32_t* status);
void HAL_SetREVPHClosedLoopControlDisabled(HAL_REVPHHandle handle,
int32_t* status);
void HAL_SetREVPHClosedLoopControlDigital(HAL_REVPHHandle handle,
int32_t* status);
void HAL_SetREVPHClosedLoopControlAnalog(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status);
void HAL_SetREVPHClosedLoopControlHybrid(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status);
HAL_REVPHCompressorConfigType HAL_GetREVPHCompressorConfig(
HAL_REVPHHandle handle, int32_t* status);
HAL_Bool HAL_GetREVPHPressureSwitch(HAL_REVPHHandle handle, int32_t* status);
double HAL_GetREVPHCompressorCurrent(HAL_REVPHHandle handle, int32_t* status);
double HAL_GetREVPHAnalogPressure(HAL_REVPHHandle handle, int32_t channel,
@@ -43,6 +118,11 @@ void HAL_SetREVPHSolenoids(HAL_REVPHHandle handle, int32_t mask, int32_t values,
void HAL_FireREVPHOneShot(HAL_REVPHHandle handle, int32_t index, int32_t durMs,
int32_t* status);
HAL_REVPHFaults HAL_GetREVPHFaults(HAL_REVPHHandle handle, int32_t* status);
HAL_REVPHStickyFaults HAL_GetREVPHStickyFaults(HAL_REVPHHandle handle,
int32_t* status);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -4,6 +4,7 @@
#pragma once
#include "hal/REVPH.h"
#include "hal/Types.h"
#include "hal/simulation/NotifyListener.h"
@@ -39,13 +40,14 @@ void HALSIM_CancelREVPHCompressorOnCallback(int32_t index, int32_t uid);
HAL_Bool HALSIM_GetREVPHCompressorOn(int32_t index);
void HALSIM_SetREVPHCompressorOn(int32_t index, HAL_Bool compressorOn);
int32_t HALSIM_RegisterREVPHClosedLoopEnabledCallback(
int32_t HALSIM_RegisterREVPHCompressorConfigTypeCallback(
int32_t index, HAL_NotifyCallback callback, void* param,
HAL_Bool initialNotify);
void HALSIM_CancelREVPHClosedLoopEnabledCallback(int32_t index, int32_t uid);
HAL_Bool HALSIM_GetREVPHClosedLoopEnabled(int32_t index);
void HALSIM_SetREVPHClosedLoopEnabled(int32_t index,
HAL_Bool closedLoopEnabled);
void HALSIM_CancelREVPHCompressorConfigTypeCallback(int32_t index, int32_t uid);
HAL_REVPHCompressorConfigType HALSIM_GetREVPHCompressorConfigType(
int32_t index);
void HALSIM_SetREVPHCompressorConfigType(
int32_t index, HAL_REVPHCompressorConfigType configType);
int32_t HALSIM_RegisterREVPHPressureSwitchCallback(int32_t index,
HAL_NotifyCallback callback,

View File

@@ -64,7 +64,8 @@ HAL_REVPHHandle HAL_InitializeREVPH(int32_t module,
SimREVPHData[module].initialized = true;
// Enable closed loop
SimREVPHData[module].closedLoopEnabled = true;
SimREVPHData[module].compressorConfigType =
HAL_REVPHCompressorConfigType_kDigital;
return handle;
}
@@ -97,26 +98,73 @@ HAL_Bool HAL_GetREVPHCompressor(HAL_REVPHHandle handle, int32_t* status) {
return SimREVPHData[pcm->module].compressorOn;
}
void HAL_SetREVPHClosedLoopControl(HAL_REVPHHandle handle, HAL_Bool enabled,
int32_t* status) {
void HAL_SetREVPHCompressorConfig(HAL_REVPHHandle handle,
const HAL_REVPHCompressorConfig* config,
int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
SimREVPHData[pcm->module].closedLoopEnabled = enabled;
// TODO
// SimREVPHData[pcm->module].compressorConfigType = config.
}
HAL_Bool HAL_GetREVPHClosedLoopControl(HAL_REVPHHandle handle,
int32_t* status) {
void HAL_SetREVPHClosedLoopControlDisabled(HAL_REVPHHandle handle,
int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return false;
return;
}
SimREVPHData[pcm->module].compressorConfigType =
HAL_REVPHCompressorConfigType_kDisabled;
}
return SimREVPHData[pcm->module].closedLoopEnabled;
void HAL_SetREVPHClosedLoopControlDigital(HAL_REVPHHandle handle,
int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
SimREVPHData[pcm->module].compressorConfigType =
HAL_REVPHCompressorConfigType_kDigital;
}
void HAL_SetREVPHClosedLoopControlAnalog(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
SimREVPHData[pcm->module].compressorConfigType =
HAL_REVPHCompressorConfigType_kAnalog;
}
void HAL_SetREVPHClosedLoopControlHybrid(HAL_REVPHHandle handle,
double minAnalogVoltage,
double maxAnalogVoltage,
int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return;
}
SimREVPHData[pcm->module].compressorConfigType =
HAL_REVPHCompressorConfigType_kHybrid;
}
HAL_REVPHCompressorConfigType HAL_GetREVPHCompressorConfig(
HAL_REVPHHandle handle, int32_t* status) {
auto pcm = pcmHandles->Get(handle);
if (pcm == nullptr) {
*status = HAL_HANDLE_ERROR;
return HAL_REVPHCompressorConfigType_kDisabled;
}
return SimREVPHData[pcm->module].compressorConfigType;
}
HAL_Bool HAL_GetREVPHPressureSwitch(HAL_REVPHHandle handle, int32_t* status) {

View File

@@ -21,7 +21,7 @@ void REVPHData::ResetData() {
}
initialized.Reset(false);
compressorOn.Reset(false);
closedLoopEnabled.Reset(true);
compressorConfigType.Reset(HAL_REVPHCompressorConfigType_kDisabled);
pressureSwitch.Reset(false);
compressorCurrent.Reset(0.0);
}
@@ -39,7 +39,8 @@ HAL_SIMDATAVALUE_DEFINE_CAPI_CHANNEL(HAL_Bool, HALSIM, REVPHSolenoidOutput,
SimREVPHData, solenoidOutput)
DEFINE_CAPI(HAL_Bool, Initialized, initialized)
DEFINE_CAPI(HAL_Bool, CompressorOn, compressorOn)
DEFINE_CAPI(HAL_Bool, ClosedLoopEnabled, closedLoopEnabled)
DEFINE_CAPI(HAL_REVPHCompressorConfigType, CompressorConfigType,
compressorConfigType)
DEFINE_CAPI(HAL_Bool, PressureSwitch, pressureSwitch)
DEFINE_CAPI(double, CompressorCurrent, compressorCurrent)
@@ -69,7 +70,7 @@ void HALSIM_RegisterREVPHAllNonSolenoidCallbacks(int32_t index,
HAL_Bool initialNotify) {
REGISTER(initialized);
REGISTER(compressorOn);
REGISTER(closedLoopEnabled);
REGISTER(compressorConfigType);
REGISTER(pressureSwitch);
REGISTER(compressorCurrent);
}

View File

@@ -13,7 +13,7 @@ class REVPHData {
HAL_SIMDATAVALUE_DEFINE_NAME(Initialized)
HAL_SIMDATAVALUE_DEFINE_NAME(SolenoidOutput)
HAL_SIMDATAVALUE_DEFINE_NAME(CompressorOn)
HAL_SIMDATAVALUE_DEFINE_NAME(ClosedLoopEnabled)
HAL_SIMDATAVALUE_DEFINE_NAME(CompressorConfigType)
HAL_SIMDATAVALUE_DEFINE_NAME(PressureSwitch)
HAL_SIMDATAVALUE_DEFINE_NAME(CompressorCurrent)
@@ -22,6 +22,11 @@ class REVPHData {
return false;
}
static inline HAL_Value MakeCompressorConfigTypeValue(
HAL_REVPHCompressorConfigType value) {
return HAL_MakeEnum(value);
}
public:
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetInitializedName> initialized{
false};
@@ -30,8 +35,10 @@ class REVPHData {
solenoidOutput[kNumREVPHChannels];
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetCompressorOnName> compressorOn{
false};
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetClosedLoopEnabledName>
closedLoopEnabled{true};
SimDataValue<HAL_REVPHCompressorConfigType, MakeCompressorConfigTypeValue,
GetCompressorConfigTypeName>
compressorConfigType{HAL_REVPHCompressorConfigType::
HAL_REVPHCompressorConfigType_kDisabled};
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetPressureSwitchName> pressureSwitch{
false};
SimDataValue<double, HAL_MakeDouble, GetCompressorCurrentName>

View File

@@ -87,7 +87,7 @@ static std::pair<std::string_view, std::string_view> ReadStringToken(
break;
}
}
return {wpi::slice(source, 0, pos), source.substr(pos)};
return {wpi::slice(source, 0, pos), wpi::substr(source, pos)};
}
static int fromxdigit(char ch) {

View File

@@ -22,7 +22,7 @@ std::string_view NetworkTable::BasenameKey(std::string_view key) {
if (slash == std::string_view::npos) {
return key;
}
return key.substr(slash + 1);
return wpi::substr(key, slash + 1);
}
std::string NetworkTable::NormalizeKey(std::string_view key,
@@ -105,7 +105,7 @@ NT_EntryListener NetworkTable::AddEntryListener(TableEntryListener listener,
return nt::AddEntryListener(
m_inst, fmt::format("{}/", m_path),
[=](const EntryNotification& event) {
auto relative_key = std::string_view{event.name}.substr(prefix_len);
auto relative_key = wpi::substr(event.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
return;
}
@@ -124,8 +124,8 @@ NT_EntryListener NetworkTable::AddEntryListener(std::string_view key,
entry.GetHandle(),
[=](const EntryNotification& event) {
listener(const_cast<NetworkTable*>(this),
std::string_view{event.name}.substr(prefix_len), entry,
event.value, event.flags);
wpi::substr(event.name, prefix_len), entry, event.value,
event.flags);
},
flags);
}
@@ -149,12 +149,12 @@ NT_EntryListener NetworkTable::AddSubTableListener(TableListener listener,
NT_EntryListener id = nt::AddEntryListener(
m_inst, fmt::format("{}/", m_path),
[=](const EntryNotification& event) {
auto relative_key = std::string_view{event.name}.substr(prefix_len);
auto relative_key = wpi::substr(event.name, prefix_len);
auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
if (end_sub_table == std::string_view::npos) {
return;
}
auto sub_table_key = relative_key.substr(0, end_sub_table);
auto sub_table_key = wpi::substr(relative_key, 0, end_sub_table);
if (notified_tables->find(sub_table_key) == notified_tables->end()) {
return;
}
@@ -196,7 +196,7 @@ std::vector<std::string> NetworkTable::GetKeys(int types) const {
auto infos = GetEntryInfo(m_inst, fmt::format("{}/", m_path), types);
std::scoped_lock lock(m_mutex);
for (auto& info : infos) {
auto relative_key = std::string_view{info.name}.substr(prefix_len);
auto relative_key = wpi::substr(info.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
continue;
}
@@ -210,12 +210,12 @@ std::vector<std::string> NetworkTable::GetSubTables() const {
std::vector<std::string> keys;
size_t prefix_len = m_path.size() + 1;
for (auto& entry : GetEntryInfo(m_inst, fmt::format("{}/", m_path), 0)) {
auto relative_key = std::string_view{entry.name}.substr(prefix_len);
auto relative_key = wpi::substr(entry.name, prefix_len);
size_t end_subtable = relative_key.find(PATH_SEPARATOR_CHAR);
if (end_subtable == std::string_view::npos) {
continue;
}
keys.emplace_back(relative_key.substr(0, end_subtable));
keys.emplace_back(wpi::substr(relative_key, 0, end_subtable));
}
return keys;
}

View File

@@ -7,6 +7,8 @@
#include <string>
#include <string_view>
#include <wpi/StringExtras.h>
#include "TestPrinters.h"
#include "WireEncoder.h"
#include "gtest/gtest.h"
@@ -84,7 +86,7 @@ TEST_F(WireEncoderTest, Write8) {
e.Write8(0x101u); // should be truncated
e.Write8(0u);
ASSERT_EQ(3u, e.size() - off);
ASSERT_EQ("\x05\x01\x00"sv, std::string_view(e.data(), e.size()).substr(off));
ASSERT_EQ("\x05\x01\x00"sv, wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, Write16) {
@@ -99,7 +101,7 @@ TEST_F(WireEncoderTest, Write16) {
e.Write16(0u);
ASSERT_EQ(8u, e.size() - off);
ASSERT_EQ("\x00\x05\x00\x01\x45\x67\x00\x00"sv,
std::string_view(e.data(), e.size()).substr(off));
wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, Write32) {
@@ -117,7 +119,7 @@ TEST_F(WireEncoderTest, Write32) {
ASSERT_EQ(std::string_view("\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\xab\xcd"
"\x12\x34\x56\x78\x00\x00\x00\x00",
20),
std::string_view(e.data(), e.size()).substr(off));
wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, WriteDouble) {
@@ -140,7 +142,7 @@ TEST_F(WireEncoderTest, WriteDouble) {
"\x00\x10\x00\x00\x00\x00\x00\x00"
"\x7f\xef\xff\xff\xff\xff\xff\xff",
40),
std::string_view(e.data(), e.size()).substr(off));
wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, WriteUleb128) {
@@ -153,8 +155,7 @@ TEST_F(WireEncoderTest, WriteUleb128) {
e.WriteUleb128(0x7ful);
e.WriteUleb128(0x80ul);
ASSERT_EQ(4u, e.size() - off);
ASSERT_EQ("\x00\x7f\x80\x01"sv,
std::string_view(e.data(), e.size()).substr(off));
ASSERT_EQ("\x00\x7f\x80\x01"sv, wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, WriteType) {
@@ -174,7 +175,7 @@ TEST_F(WireEncoderTest, WriteType) {
ASSERT_EQ(nullptr, e.error());
ASSERT_EQ(8u, e.size() - off);
ASSERT_EQ("\x00\x01\x02\x03\x10\x11\x12\x20"sv,
std::string_view(e.data(), e.size()).substr(off));
wpi::substr({e.data(), e.size()}, off));
}
TEST_F(WireEncoderTest, WriteTypeError) {

View File

@@ -11,7 +11,9 @@
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/MainMenuBar.h"
#include "glass/Model.h"
#include "glass/Storage.h"
#include "glass/networktables/NetworkTables.h"
#include "glass/networktables/NetworkTablesSettings.h"
#include "glass/other/Log.h"
@@ -34,6 +36,7 @@ static std::unique_ptr<glass::NetworkTablesModel> gModel;
static std::unique_ptr<glass::NetworkTablesSettings> gSettings;
static glass::LogData gLog;
static glass::NetworkTablesFlagsSettings gFlagsSettings;
static glass::MainMenuBar gMainMenu;
static void NtInitialize() {
// update window title when connection status changes
@@ -87,7 +90,8 @@ static void NtInitialize() {
gui::AddEarlyExecute([] { gModel->Update(); });
// NetworkTables settings window
gSettings = std::make_unique<glass::NetworkTablesSettings>();
gSettings = std::make_unique<glass::NetworkTablesSettings>(
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
gui::AddEarlyExecute([] { gSettings->Update(); });
}
@@ -114,6 +118,7 @@ static void DisplayGui() {
// main menu
ImGui::BeginMenuBar();
gMainMenu.WorkspaceMenu();
gui::EmitViewMenu();
if (ImGui::BeginMenu("View")) {
gFlagsSettings.DisplayMenu();
@@ -179,6 +184,8 @@ static void DisplayGui() {
ImGui::Text("OutlineViewer");
ImGui::Separator();
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
@@ -194,9 +201,16 @@ static void DisplayGui() {
#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
int argc = __argc;
char** argv = __argv;
#else
int main() {
int main(int argc, char** argv) {
#endif
std::string_view saveDir;
if (argc == 2) {
saveDir = argv[1];
}
gui::CreateContext();
glass::CreateContext();
@@ -208,7 +222,10 @@ int main() {
gui::AddIcon(ov::GetResource_ov_256_png());
gui::AddIcon(ov::GetResource_ov_512_png());
gui::ConfigurePlatformSaveFile("outlineviewer.ini");
glass::SetStorageName("outlineviewer");
glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
: saveDir);
gui::AddInit(NtInitialize);
gui::AddLateExecute(DisplayGui);
@@ -221,4 +238,6 @@ int main() {
glass::DestroyContext();
gui::DestroyContext();
return 0;
}

View File

@@ -10,7 +10,7 @@ nativeUtils {
wpi {
configureDependencies {
wpiVersion = "-1"
niLibVersion = "2022.2.2"
niLibVersion = "2022.2.3"
opencvVersion = "4.5.2-1"
googleTestVersion = "1.9.0-5-437e100-1"
imguiVersion = "1.82-1"

View File

@@ -100,7 +100,7 @@ static bool AddressableLEDsExists() {
}
void AddressableLEDGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Addressable LEDs", [] { return AddressableLEDsExists(); },
[] { return std::make_unique<AddressableLEDsModel>(); },
[](glass::Window* win, glass::Model* model) {

View File

@@ -109,7 +109,7 @@ static bool AnalogInputsAnyInitialized() {
}
void AnalogInputSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Analog Inputs", AnalogInputsAnyInitialized,
[] { return std::make_unique<AnalogInputsSimModel>(); },
[](glass::Window* win, glass::Model* model) {

View File

@@ -232,14 +232,14 @@ static bool DIOAnyInitialized() {
}
void DIOSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"DIO", DIOAnyInitialized, [] { return std::make_unique<DIOsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
win->SetDefaultPos(470, 20);
return glass::MakeFunctionView([=] {
glass::DisplayDIOs(static_cast<DIOsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
HALSimGui::halProvider->AreOutputsEnabled());
});
});
}

View File

@@ -6,13 +6,14 @@
#include <glass/WindowManager.h>
#include <memory>
#include <string_view>
namespace halsimgui {
class DSManager : public glass::WindowManager {
public:
explicit DSManager(std::string_view iniName) : WindowManager{iniName} {}
explicit DSManager(glass::Storage& storage) : WindowManager{storage} {}
void DisplayMenu() override;
};
@@ -22,7 +23,7 @@ class DriverStationGui {
static void GlobalInit();
static void SetDSSocketExtension(void* data);
static DSManager dsManager;
static std::unique_ptr<DSManager> dsManager;
};
} // namespace halsimgui

View File

@@ -246,7 +246,7 @@ static bool EncodersAnyInitialized() {
}
void EncoderSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Encoders", EncodersAnyInitialized,
[] { return std::make_unique<EncodersSimModel>(); },
[](glass::Window* win, glass::Model* model) {
@@ -258,7 +258,7 @@ void EncoderSimGui::Initialize() {
}
glass::EncodersModel& EncoderSimGui::GetEncodersModel() {
static auto model = HALSimGui::halProvider.GetModel("Encoders");
static auto model = HALSimGui::halProvider->GetModel("Encoders");
assert(model);
return *static_cast<glass::EncodersModel*>(model);
}

View File

@@ -5,6 +5,7 @@
#include "HALProvider.h"
#include <glass/Model.h>
#include <glass/Storage.h>
#include <algorithm>
#include <string>
@@ -15,6 +16,32 @@ using namespace halsimgui;
static bool gDisableOutputsOnDSDisable = true;
HALProvider::HALProvider(glass::Storage& storage) : Provider{storage} {
storage.SetCustomApply([this] {
for (auto&& childIt : m_storage.GetChildren()) {
auto it = FindViewEntry(childIt.key());
if (it != m_viewEntries.end() && (*it)->name == childIt.key()) {
Show(it->get(), nullptr);
}
}
for (auto&& entry : m_viewEntries) {
if (entry->showDefault) {
Show(entry.get(), entry->window);
if (entry->window) {
entry->window->SetDefaultVisibility(glass::Window::kShow);
}
}
}
});
storage.SetCustomClear([this, &storage] {
for (auto&& entry : m_viewEntries) {
entry->window = nullptr;
}
m_windows.clear();
storage.ClearValues();
});
}
bool HALProvider::AreOutputsDisabled() {
return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
}
@@ -28,34 +55,17 @@ void HALProvider::DisplayMenu() {
bool visible = viewEntry->window && viewEntry->window->IsVisible();
bool wasVisible = visible;
bool exists = viewEntry->modelEntry->exists();
ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible,
visible || exists);
if (!wasVisible && visible) {
Show(viewEntry.get(), viewEntry->window);
} else if (wasVisible && !visible && viewEntry->window) {
viewEntry->window->SetVisible(false);
if (ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible,
visible || exists)) {
if (!wasVisible && visible) {
Show(viewEntry.get(), viewEntry->window);
} else if (wasVisible && !visible && viewEntry->window) {
viewEntry->window->SetVisible(false);
}
}
}
}
void HALProvider::Update() {
Provider::Update();
// 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 it = FindViewEntry(id);
if (it == m_viewEntries.end() || (*it)->name != id) {
continue;
}
Show(it->get(), window.get());
}
}
glass::Model* HALProvider::GetModel(std::string_view name) {
auto it = FindModelEntry(name);
if (it == m_modelEntries.end() || (*it)->name != name) {
@@ -87,7 +97,7 @@ void HALProvider::Show(ViewEntry* entry, glass::Window* window) {
// the window might exist and we're just not associated to it yet
if (!window) {
window = GetOrAddWindow(entry->name, true);
window = GetOrAddWindow(entry->name, true, glass::Window::kHide);
}
if (!window) {
return;

View File

@@ -4,22 +4,31 @@
#include "HALSimGui.h"
#include <glass/Context.h>
#include <glass/Storage.h>
#include <imgui.h>
#include <wpigui.h>
using namespace halsimgui;
glass::MainMenuBar HALSimGui::mainMenu;
glass::WindowManager HALSimGui::manager{"SimWindow"};
HALProvider HALSimGui::halProvider{"HALProvider"};
glass::NetworkTablesProvider HALSimGui::ntProvider{"NTProvider"};
std::unique_ptr<glass::WindowManager> HALSimGui::manager;
std::unique_ptr<HALProvider> HALSimGui::halProvider;
std::unique_ptr<glass::NetworkTablesProvider> HALSimGui::ntProvider;
void HALSimGui::GlobalInit() {
manager.GlobalInit();
halProvider.GlobalInit();
ntProvider.GlobalInit();
manager = std::make_unique<glass::WindowManager>(
glass::GetStorageRoot().GetChild("SimWindow"));
manager->GlobalInit();
halProvider = std::make_unique<HALProvider>(
glass::GetStorageRoot().GetChild("HALProvider"));
halProvider->GlobalInit();
ntProvider = std::make_unique<glass::NetworkTablesProvider>(
glass::GetStorageRoot().GetChild("NTProvider"));
ntProvider->GlobalInit();
wpi::gui::AddLateExecute([] { mainMenu.Display(); });
glass::AddStandardNetworkTablesViews(ntProvider);
glass::AddStandardNetworkTablesViews(*ntProvider);
}

View File

@@ -4,6 +4,8 @@
#include "NetworkTablesSimGui.h"
#include <glass/Context.h>
#include <glass/Storage.h>
#include <glass/networktables/NetworkTables.h>
#include <wpigui.h>
@@ -13,21 +15,24 @@
using namespace halsimgui;
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<glass::NetworkTablesView> gNetworkTablesView;
static glass::Window* gNetworkTablesWindow;
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
void NetworkTablesSimGui::Initialize() {
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
gNetworkTablesView =
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get());
wpi::gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
gNetworkTablesWindow = HALSimGui::ntProvider.AddWindow(
"NetworkTables", [] { gNetworkTablesView->Display(); });
if (gNetworkTablesWindow) {
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
}
gNetworkTablesWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables");
gNetworkTablesWindow->SetView(
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
gNetworkTablesWindow->SetDefaultPos(250, 277);
gNetworkTablesWindow->SetDefaultSize(750, 185);
gNetworkTablesWindow->DisableRenamePopup();
wpi::gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
wpi::gui::AddWindowScaler([](float scale) {
// scale default window positions
gNetworkTablesWindow->ScaleDefault(scale);
});
}
void NetworkTablesSimGui::DisplayMenu() {

View File

@@ -197,10 +197,10 @@ static bool PCMsAnyInitialized() {
}
void PCMSimGui::Initialize() {
HALSimGui::halProvider.RegisterModel("CTREPCMs", PCMsAnyInitialized, [] {
HALSimGui::halProvider->RegisterModel("CTREPCMs", PCMsAnyInitialized, [] {
return std::make_unique<PCMsSimModel>();
});
HALSimGui::halProvider.RegisterView(
HALSimGui::halProvider->RegisterView(
"Solenoids", "CTREPCMs",
[](glass::Model* model) {
bool any = false;
@@ -218,14 +218,14 @@ void PCMSimGui::Initialize() {
return glass::MakeFunctionView([=] {
glass::DisplayPCMsSolenoids(
static_cast<PCMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
HALSimGui::halProvider->AreOutputsEnabled());
});
});
SimDeviceGui::GetDeviceTree().Add(
HALSimGui::halProvider.GetModel("CTREPCMs"), [](glass::Model* model) {
HALSimGui::halProvider->GetModel("CTREPCMs"), [](glass::Model* model) {
glass::DisplayCompressorsDevice(
static_cast<PCMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
HALSimGui::halProvider->AreOutputsEnabled());
});
}

View File

@@ -105,7 +105,7 @@ static bool PWMsAnyInitialized() {
}
void PWMSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"PWM Outputs", PWMsAnyInitialized,
[] { return std::make_unique<PWMsSimModel>(); },
[](glass::Window* win, glass::Model* model) {
@@ -113,7 +113,7 @@ void PWMSimGui::Initialize() {
win->SetDefaultPos(910, 20);
return glass::MakeFunctionView([=] {
glass::DisplayPWMs(static_cast<PWMsSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
HALSimGui::halProvider->AreOutputsEnabled());
});
});
}

View File

@@ -124,7 +124,7 @@ static bool PowerDistributionsAnyInitialized() {
}
void PowerDistributionSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"PowerDistributions", PowerDistributionsAnyInitialized,
[] { return std::make_unique<PowerDistributionsSimModel>(); },
[](glass::Window* win, glass::Model* model) {

View File

@@ -104,7 +104,7 @@ static bool RelayAnyInitialized() {
}
void RelaySimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Relays", RelayAnyInitialized,
[] { return std::make_unique<RelaysSimModel>(); },
[](glass::Window* win, glass::Model* model) {
@@ -112,7 +112,7 @@ void RelaySimGui::Initialize() {
win->SetDefaultPos(180, 20);
return glass::MakeFunctionView([=] {
glass::DisplayRelays(static_cast<RelaysSimModel*>(model),
HALSimGui::halProvider.AreOutputsEnabled());
HALSimGui::halProvider->AreOutputsEnabled());
});
});
}

View File

@@ -132,7 +132,7 @@ class RoboRioSimModel : public glass::RoboRioModel {
} // namespace
void RoboRioSimGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"RoboRIO", [] { return true; },
[] { return std::make_unique<RoboRioSimModel>(); },
[](glass::Window* win, glass::Model* model) {

View File

@@ -155,7 +155,7 @@ static void DisplaySimDevice(const char* name, void* data,
}
void SimDeviceGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Other Devices", [] { return true; },
[] { return std::make_unique<glass::DeviceTreeModel>(); },
[](glass::Window* win, glass::Model* model) {
@@ -170,7 +170,7 @@ void SimDeviceGui::Initialize() {
static_cast<glass::DeviceTreeModel*>(model)->Display();
});
});
HALSimGui::halProvider.ShowDefault("Other Devices");
HALSimGui::halProvider->ShowDefault("Other Devices");
auto model = std::make_unique<SimDevicesModel>();
gSimDevicesModel = model.get();
@@ -185,7 +185,7 @@ glass::DataSource* SimDeviceGui::GetValueSource(HAL_SimValueHandle handle) {
}
glass::DeviceTreeModel& SimDeviceGui::GetDeviceTree() {
static auto model = HALSimGui::halProvider.GetModel("Other Devices");
static auto model = HALSimGui::halProvider->GetModel("Other Devices");
assert(model);
return *static_cast<glass::DeviceTreeModel*>(model);
}

View File

@@ -71,7 +71,7 @@ static void DisplayTiming() {
}
void TimingGui::Initialize() {
HALSimGui::halProvider.Register(
HALSimGui::halProvider->Register(
"Timing", [] { return true; },
[] { return std::make_unique<TimingModel>(); },
[](glass::Window* win, glass::Model* model) {
@@ -80,5 +80,5 @@ void TimingGui::Initialize() {
win->SetDefaultPos(5, 150);
return glass::MakeFunctionView(DisplayTiming);
});
HALSimGui::halProvider.ShowDefault("Timing");
HALSimGui::halProvider->ShowDefault("Timing");
}

View File

@@ -3,6 +3,7 @@
// the WPILib BSD license file in the root directory of this project.
#include <glass/Context.h>
#include <glass/Storage.h>
#include <glass/other/Plot.h>
#include <cstdio>
@@ -35,7 +36,7 @@ using namespace halsimgui;
namespace gui = wpi::gui;
static glass::PlotProvider gPlotProvider{"Plot"};
static std::unique_ptr<glass::PlotProvider> gPlotProvider;
extern "C" {
#if defined(WIN32) || defined(_WIN32)
@@ -46,54 +47,59 @@ __declspec(dllexport)
gui::CreateContext();
glass::CreateContext();
glass::SetStorageName("simgui");
HALSimGui::GlobalInit();
DriverStationGui::GlobalInit();
gPlotProvider.GlobalInit();
gPlotProvider = std::make_unique<glass::PlotProvider>(
glass::GetStorageRoot().GetChild("Plot"));
gPlotProvider->GlobalInit();
// These need to initialize first
gui::AddInit(EncoderSimGui::Initialize);
gui::AddInit(SimDeviceGui::Initialize);
EncoderSimGui::Initialize();
SimDeviceGui::Initialize();
gui::AddInit(AccelerometerSimGui::Initialize);
gui::AddInit(AddressableLEDGui::Initialize);
gui::AddInit(AnalogGyroSimGui::Initialize);
gui::AddInit(AnalogInputSimGui::Initialize);
gui::AddInit(AnalogOutputSimGui::Initialize);
gui::AddInit(DIOSimGui::Initialize);
gui::AddInit(NetworkTablesSimGui::Initialize);
gui::AddInit(PCMSimGui::Initialize);
gui::AddInit(PowerDistributionSimGui::Initialize);
gui::AddInit(PWMSimGui::Initialize);
gui::AddInit(RelaySimGui::Initialize);
gui::AddInit(RoboRioSimGui::Initialize);
gui::AddInit(TimingGui::Initialize);
AccelerometerSimGui::Initialize();
AddressableLEDGui::Initialize();
AnalogGyroSimGui::Initialize();
AnalogInputSimGui::Initialize();
AnalogOutputSimGui::Initialize();
DIOSimGui::Initialize();
NetworkTablesSimGui::Initialize();
PCMSimGui::Initialize();
PowerDistributionSimGui::Initialize();
PWMSimGui::Initialize();
RelaySimGui::Initialize();
RoboRioSimGui::Initialize();
TimingGui::Initialize();
HALSimGui::mainMenu.AddMainMenu([] {
if (ImGui::BeginMenu("Hardware")) {
HALSimGui::halProvider.DisplayMenu();
HALSimGui::halProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("NetworkTables")) {
NetworkTablesSimGui::DisplayMenu();
ImGui::Separator();
HALSimGui::ntProvider.DisplayMenu();
HALSimGui::ntProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("DS")) {
DriverStationGui::dsManager.DisplayMenu();
DriverStationGui::dsManager->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Plot")) {
bool paused = gPlotProvider.IsPaused();
bool paused = gPlotProvider->IsPaused();
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
gPlotProvider.SetPaused(paused);
gPlotProvider->SetPaused(paused);
}
ImGui::Separator();
gPlotProvider.DisplayMenu();
gPlotProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Window")) {
HALSimGui::manager.DisplayMenu();
HALSimGui::manager->DisplayMenu();
ImGui::EndMenu();
}
});

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