mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-29 02:21:44 +00:00
Compare commits
24 Commits
v2022.1.1-
...
v2022.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae208d2b17 | ||
|
|
6f51cb3b98 | ||
|
|
f6159ee1a2 | ||
|
|
7f401ae895 | ||
|
|
0587b7043a | ||
|
|
0bbf51d566 | ||
|
|
92c6eae6b0 | ||
|
|
141354cd79 | ||
|
|
f6e9fc7d71 | ||
|
|
d8418be7d1 | ||
|
|
82066946e5 | ||
|
|
4b1defc8d8 | ||
|
|
da90c1cd2c | ||
|
|
3aa54fa027 | ||
|
|
b156db400d | ||
|
|
9aba2b7583 | ||
|
|
a9931223f0 | ||
|
|
aacf9442e4 | ||
|
|
7db10ecf00 | ||
|
|
a0a5b2aea5 | ||
|
|
eb835598a4 | ||
|
|
f0ab6df5b6 | ||
|
|
075144faa3 | ||
|
|
32468a40cb |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
688
glass/src/lib/native/cpp/Storage.cpp
Normal file
688
glass/src/lib/native/cpp/Storage.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
glass/src/lib/native/cpp/support/ColorSetting.cpp
Normal file
11
glass/src/lib/native/cpp/support/ColorSetting.cpp
Normal 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);
|
||||
}
|
||||
40
glass/src/lib/native/cpp/support/EnumSetting.cpp
Normal file
40
glass/src/lib/native/cpp/support/EnumSetting.cpp
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
123
glass/src/lib/native/cpp/support/NameSetting.cpp
Normal file
123
glass/src/lib/native/cpp/support/NameSetting.cpp
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
293
glass/src/lib/native/include/glass/Storage.h
Normal file
293
glass/src/lib/native/include/glass/Storage.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
53
glass/src/lib/native/include/glass/support/ColorSetting.h
Normal file
53
glass/src/lib/native/include/glass/support/ColorSetting.h
Normal 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
|
||||
31
glass/src/lib/native/include/glass/support/EnumSetting.h
Normal file
31
glass/src/lib/native/include/glass/support/EnumSetting.h
Normal 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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -55,4 +55,6 @@ public class JNIWrapper {
|
||||
loader.loadLibrary();
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
public static void suppressUnused(Object object) {}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user