Squashed 'third_party/allwpilib/' changes from e473a00f97..e4b91005cf
e4b91005cf [examples] Update SwerveModule constructor doc (NFC) (#4042)
a260bfd83b [examples] Remove "this" keyword from SwerveModule (#4043)
18e262a100 [examples] Fix multiple doc typos in SwerveControllerCommand example (NFC) (#4044)
4bd1f526ab [wpilibc] Prevent StopMotor from terminating robot during MotorSafety check (#4038)
27847d7eb2 [sim] Expose GUI control functions via HAL_RegisterExtension (#4034)
b2a8d3f0f3 [wpilibc] Add mechanism to reset MotorSafety list (#4037)
49adac9564 [wpilib] Check for signedness in ArcadeDriveIK() (#4028)
a19d1133b1 [wpiutil] libuv: Fix sign compare warnings in gcc 11.2 (#4031)
dde91717e4 [build] cmake: Add ability to customize target warnings (#4032)
e9050afd67 [sim] Update sim match time to match real robot (#4024)
165d2837cf [wpilib] Preferences: Set Persistent in Init methods (#4025)
ac7549edca [glass] Fix snprintf truncation warning (#4029)
4d96bc72e0 [wpilibj] Fix typos in error messages for non-null assertions (#4014)
3411eee20f [hal] Replace hardcoded sim array sizes with constants (#4015)
74de97eeca [wpilibc] Add mechanism to reset various global structures (#4007)
4e3cc25012 [examples] Fix periodic function rate comment (NFC) (#4013)
90c1db393e [sim] Add exported functions to control the sim GUI (#3995)
2f43274aa4 [wpilibj] MechanismRoot2d: Add flush to setPosition (#4011)
aeca09db09 [glass] Support remapping of Enter key (#3994)
c107f22c67 [sim] Sim GUI: don't force-show Timing and Other Devices (#4001)
68fe51e8da [wpigui] Update PFD to latest, fix kdialog multiselect (#4005)
8d08d67cf1 [wpigui] PFD: Add console warning if file chooser unavailable (#4003)
4f1782f66e [wpilibc] Only call HAL_Report when initializing SmartDashboard (#4006)
3f77725cd3 Remove uses of iostream (#4004)
5635f33a32 [glass] Increase plot depth to 20K points (#3993)
bca4b7111b [glass] Fix PlotSeries::SetSource() (#3991)
6a6366b0d6 [commands] Add until() as alias for withInterrupt() (#3981)
16bf2c70c5 [wpilib] Fix joystick out of range error messages (#3988)
4b3edb742c [wpilib] Fix ADIS16448 IMU default constructor not working in Java (#3989)
fcf23fc9e9 [hal] Fix potential gamedata out of bounds read (#3983)
af5ef510c5 [wpilibc] Fix REV PH pulse duration units (#3982)
05401e2b81 [wpilib] Write REV PH firmware version to roboRIO to display on driver station (#3977)
9fde0110b6 Update to 2022 v4.0 image (#3944)
b03f8ddb2e [examples] fix incorrect variable in Arm Simulation Pref (#3980)
a26df2a022 [examples] Update ArmSimulation example to use Preferences (#3976)
d68d6674e8 [examples] Armbot: rename kCos to kG (#3975)
a8f0f6bb90 [wpilibj] Fix ADIS16448 getRate to return rate instead of angle (#3974)
dd9c92d5bf [build] Remove debug info from examples (#3971)
84df14dd70 [rtns] Fix icons (#3972)
560094ad92 [examples] Correct Mecanum example axes (#3955)
7ea1be9c01 [wpilibc] Fix typo in hardware version for REV PDH (#3969)
700f13bffd [wpilibj] Make methods public for Java REV PDH (#3970)
b6aa7c1aa9 [wpilibj] Make methods public for Java REVPH (#3968)
eb4d183e48 [wpimath] Fix clang-tidy bugprone-integer-division warning (#3966)
77e4e81e1e [wpilib] Add Field widget to BuiltInWidgets in shuffleboard (#3961)
88f5cb6eb0 [build] Publish PDBs with C++ tools (#3960)
efae552f3e [wpimath] Remove DifferentialDriveKinematics include from odometry (#3958)
46b277421a [glass] Update Speed Controller Type name for 2022 WPILib (#3952)
42908126b9 [wpilib] Add DCMotorSim (#3910)
a467392cbd [wpiutil] StackTrace: Add ability to override default implementation (#3951)
78d0bcf49d [templates] Add SimulationInit()/SimulationPeriodic() to robot templates (#3943)
02a0ced9b0 [wpilib] MecanumDrive: update docs for axis to match implementation (NFC) (#3942)
4ccfe1c9f2 [wpilib] Added docs clarification on units for drive class WheelSpeeds (NFC) (#3939)
830c0c5c2f [wpilib] MechanismLigament2d: Add getters for color and line weight (#3947)
5548a37465 [wpilib] PowerDistribution: Add module type getter (#3948)
2f9a600de2 [hal] Fix PCM one shot (#3949)
559db11a20 [myRobot] Skip deploying debug libraries for myRobot deploys (#3950)
76c78e295b [examples] Reorder SwerveModules in SwerveControllerCommand example odometry update (#3934)
debbd5ff4b [wpilib] Improve PowerDistribution docs (NFC) (#3925)
841174f302 [commands] Change command vendordep JSON version number to 1.0.0 (#3938)
8c55844f91 [wpilib] Remove comment about Mecanum right side inverted (NFC) (#3929)
0b990bf0f5 [hal] Fix PCM sticky faults clear function crashing (#3932)
104d7e2abc [hal] Don't throw exceptions in PCM JNI (#3933)
5ba69e1af1 [examples] Updated type in Java SwerveModule (#3928)
f3a0b5c7d7 [wpimath] Fix Java SimpleMotorFeedforward Docs (NFC) (#3926)
7f4265facc [wpimath] Add LinearFilter::FiniteDifference() (#3900)
63d1fb3bed [wpiutil] Modify fmt to not throw on write failure (#3919)
36af6d25a5 [wpimath] Fix input vector in pose estimator docs (NFC) (#3923)
8f387f7255 [wpilibj] Switch ControlWord mutex to actual reentrant mutex (#3922)
792e735e08 [wpimath] Move TrajectoryGenerator::SetErrorHandler definition to .cpp (#3920)
3b76de83eb [commands] Fix ProfiledPIDCommand use-after-free (#3904)
ad9f738cfa [fieldimages] Fix maven publishing (#3897)
49455199e5 [examples] Use left/rightGroup.Get() for simulator inputs to fix inversions (#3908)
64426502ea [wpimath] Fix arm -> flywheel typo (NFC) (#3911)
8cc112d196 [wpiutil] Fix wpi::array for move-only types (#3917)
e78cd49861 [build] Upgrade Java formatter plugins (#3894)
cfb4f756d6 [build] Upgrade to shadow 7.1.2 (#3893)
ba0908216c [wpimath] Fix crash in KF latency compensator (#3888)
a3a0334fad [build] cmake: Move fieldImages to WITH_GUI (#3885)
cf7460c3a8 [fieldImages] Add 2022 field (#3883)
db0fbb6448 [wpimath] Fix LQR matrix constructor overload for Q, R, and N (#3884)
8ac45f20bb [commands] Update Command documentation (NFC) (#3881)
b3707cca0b [wpiutil] Upgrade to fmt 8.1.1 (#3879)
a69ee3ece9 [wpimath] Const-qualify Twist2d scalar multiply (#3882)
750d9a30c9 [examples] Fix Eigen out of range error when running example (#3877)
41c5b2b5ac [rtns] Add cmake build (#3866)
6cf3f9b28e [build] Upgrade to Gradle 7.3.3 (#3878)
269cf03472 [examples] Add communication examples (e.g. arduino) (#2500)
5ccfc4adbd [oldcommands] Deprecate PIDWrappers, since they use deprecated interfaces (#3868)
b6f44f98be [hal] Add warning about onboard I2C (#3871)
0dca57e9ec [templates] romieducational: Invert drivetrain and disable motor safety (#3869)
22c4da152e [wpilib] Add GetRate() to ADIS classes (#3864)
05d66f862d [templates] Change the template ordering to put command based first (#3863)
b09f5b2cf2 [wpilibc] Add virtual dtor for LinearSystemSim (#3861)
a2510aaa0e [wpilib] Make ADIS IMU classes unit-safe (#3860)
947f589916 [wpilibc] Rename ADIS_16470_IMU.cpp to match class name (#3859)
bbd8980a20 [myRobot] Fix cameraserver library order (#3858)
831052f118 [wpilib] Add simulation support to ADIS classes (#3857)
c137569f91 [wpilib] Throw exception if the REV Pneumatic Hub firmware version is older than 22.0.0 (#3853)
dae61226fa Fix Maven Artifacts readme (#3856)
3ad4594a88 Update Maven artifacts readme for 2022 (#3855)
112acb9a62 [wpilibc] Move ADIS IMU constants to inside class (#3852)
ecee224e81 [wpilib] Allow SendableCameraWrappers to take arbitrary URLs (#3850)
a3645dea34 LICENSE: Bump year range to include 2022 (#3854)
7c09f44898 [wpilib] Use PSI for compressor config and sensor reading (#3847)
f401ea9aae [wpigui] Remove wpiutil linkage (#3851)
bf8517f1e6 [wpimath] TimeInterpolatableBufferTest: Fix lint warnings (#3849)
528087e308 [hal] Use enums with fixed underlying type in clang C (#3297)
1f59ff72f9 [wpilib] Add ADIS IMUs (#3777)
315be873c4 [wpimath] Add TimeInterpolatableBuffer (#2695)
b8d019cdb4 [wpilib] Rename NormalizeWheelSpeeds to DesaturateWheelSpeeds (#3791)
102f23bbdb [wpilibj] DriverStation: Set thread interrupted state (#3846)
b85c24a79c [wpilib] Add warning about onboard I2C (#3842)
eee29daaf9 [newCommands] Trigger: Allow override of debounce type (#3845)
aa9dfabde2 [wpimath] Move debouncer to filters (#3838)
5999a26fba [wpiutil] Add GetSystemTime() (#3840)
1e82595ffb [examples] Fix arcade inversions (#3841)
e373fa476b [wpiutil] Add disableMockTime to JNI (#3839)
dceb5364f4 [examples] Ensure right side motors are inverted (#3836)
baacbc8e24 [wpilib] Tachometer: Add function to return RPS (#3833)
84b15f0883 [templates] Add Java Romi Educational template (#3837)
c0da9d2d35 [examples] Invert Right Motor in Romi Java examples (#3828)
0fe0be2733 [build] Change project year to intellisense (#3835)
eafa947338 [wpimath] Make copies of trajectory constraint arguments (#3832)
9d13ae8d01 [wpilib] Add notes for Servo get that it only returns cmd (NFC) (#3820)
2a64e4bae5 [wpimath] Give drivetrain a more realistic width in TrajectoryJsonTest.java (#3822)
c3fd20db59 [wpilib] Fix trajectory sampling in DifferentialDriveSim test (#3821)
6f91f37cd0 [examples] Fix SwerveControllerCommand order of Module States (#3815)
5158730b81 [wpigui] Upgrade to imgui 1.86, GLFW 3.3.6 (#3817)
2ad2d2ca96 [wpiutil] MulticastServiceResolver: Fix C array returning functions (#3816)
b5fd29774f [wpilibj] Trigger: implement BooleanSupplier interface (#3811)
9f8f330e96 [wpilib] Fix Mecanum and SwerveControllerCommand when desired rotation passed (#3808)
1ad3b1b333 [hal] Don't copy byte to where null terminator goes (#3807)
dfc24425c3 [build] Fix gazebo gradle IDE warnings (#3806)
c02577bb51 [glass] Configure delay loading for windows camera server support (#3803)
c9e6a96a61 [wpilib] Document range of Servo angle (NFC) (#3796)
9778626f34 [wpilib, hal] Add support for getting faults and versions from power distribution (#3794)
34b2d0dae1 [wpilib, hal] High Level REV PH changes (#3792)
59a7528fd6 [cscore] Fix crash when usbcamera is deleted before message pump thread fully starts (#3804)
11d9859ef1 [build] Update plugins to remove log4j vulnerabilities (#3805)
e44ed752ad [glass] Fix CollapsingHeader in Encoder, PCM, and DeviceTree (#3797)
52b2dd5b89 [build] Bump native utils to remove log4j (#3802)
c46636f218 [wpilib] Improve new counter classes documentation (NFC) (#3801)
dc531462e1 [build] Update to gradle 7.3.2 (#3800)
92ba98621c [wpimath] Add helper variable templates for units type traits (#3790)
d41d051f1b [wpilibc] Fix Mecanum & Swerve ControllerCommand lambda capture (#3795)
c5ae0effac OtherVersions.md: Add one missing case of useLocal (#3788)
b3974c6ed3 [wpimath] Upgrade to Drake v0.37.0 (#3786)
589a00e379 [wpilibc] Start DriverStation thread from RobotBase (#3785)
8d9836ca02 [wpilib] Improve curvature drive documentation (NFC) (#3783)
8b5bf8632e [myRobot] Add wpimath and wpiutil JNI (#3784)
1846114491 [examples] Update references from characterization to SysId (NFC) (#3782)
2c461c794e [build] Update to gradle 7.3 (#3778)
109363daa4 [hal] Add remaining driver functions for REVPH (#3776)
41d26bee8d [hal] Refactor REV PDH (#3775)
7269a170fb Upgrade maven deps to latest versions and fix new linter errors (#3772)
441f2ed9b0 [build] actions: use fixed image versions instead latest (#3761)
15275433d4 [examples] Fix duplicate port allocations in C++ SwerveBot/SwerveDrivePoseEstimator/RomiReference (#3773)
1ac02d2f58 [examples] Fix drive Joystick axes in several examples (#3769)
8ee6257e92 [wpilib] DifferentialDrivetrainSim.KitbotMotor: Add NEO and Falcon 500 (#3762)
d81ef2bc5c [wpilib] Fix deadlocks in Mechanism2d et al. (#3770)
acb64dff97 [wpimath] Make RamseteController::Calculate() more concise (#3763)
3f6cf76a8c [hal] Refactor REV PH CAN frames (#3756)
3ef2dab465 [wpilib] DutyCycleEncoder: add setting of duty cycle range (#3759)
a5a56dd067 Readme: Add Visual Studio 2022 (#3760)
04957a6d30 [wpimath] Fix units of RamseteController's b and zeta (#3757)
5da54888f8 [glass] Upgrade imgui to 0.85, implot to HEAD, glfw to 3.3.5 (#3754)
6c93365b0f [wpiutil] MulticastService cleanup (#3750)
1c4a8bfb66 [cscore] Cleanup Windows USB camera impl (#3751)
d51a1d3b3d [rtns] Fix icon (#3749)
aced2e7da6 Add roboRIO Team Number Setter tool (#3744)
fa1ceca83a [wpilibj] Use DS cache for iterative robot control word cache (#3748)
0ea05d34e6 [build] Update to gradle 7.2 (#3746)
09db4f672b [build] Update to native utils 2022.6.1 (#3745)
4ba80a3a8c [wpigui] Don't recursively render frames in size callback (#3743)
ae208d2b17 [wpiutil] StringExtras: Add substr() (#3742)
6f51cb3b98 [wpiutil] MulticastResolver: make event manual reset, change to multiple read (#3736)
f6159ee1a2 [glass] Fix Drive widget handling of negative rotation (#3739)
7f401ae895 [build] Update NI libraries to 2022.2.3 (#3738)
0587b7043a [glass] Use JSON files for storage instead of imgui ini
0bbf51d566 [wpigui] Change maximized to bool
92c6eae6b0 [wpigui] PFD: Add explicit to constructors
141354cd79 [wpigui] Add hooks for custom load/save settings
f6e9fc7d71 [wpiutil] Handle multicast service collision on linux (#3734)
d8418be7d1 [glass, outlineviewer] Return 0 from WinMain (#3735)
82066946e5 [wpiutil] Add mDNS resolver and announcer (#3733)
4b1defc8d8 [wpilib] Remove automatic PD type from module type enum (#3732)
da90c1cd2c [wpilib] Add bang-bang controller (#3676)
3aa54fa027 [wpilib] Add new counter implementations (#2447)
b156db400d [hal, wpilib] Incorporate pneumatic control type into wpilibc/j (#3728)
9aba2b7583 [oldCommands] Add wrappers for WPILib objects to work with old PID Controller (#3710)
a9931223f0 [hal] Add REV PH faults (#3729)
aacf9442e4 [wpimath] Fix units typo in LinearSystemId source comment (#3730)
7db10ecf00 [wpilibc] Make SPI destructor virtual since SPI contains virtual functions (#3727)
a0a5b2aea5 [wpimath] Upgrade to EJML 0.41 (#3726)
eb835598a4 [hal] Add HAL functions for compressor config modes on REV PH (#3724)
f0ab6df5b6 [wpimath] Upgrade to Drake v0.36.0 (#3722)
075144faa3 [docs] Parse files without extensions with Doxygen (#3721)
32468a40cb [hal] Remove use of getDmaDescriptor from autospi (#3717)
38611e9dd7 [hal] Fix REVPH Analog Pressure channel selection (#3716)
4d78def31e [wpilib] Add DeadbandElimination forwarding to PWMMotorController (#3714)
3be0c1217a [wpilibcExamples] Make GearsBot use idiomatic C++ (#3711)
42d3a50aa2 [hal] Check error codes during serial port initialization (#3712)
52f1464029 Add project with field images and their json config files (#3668)
68ce62e2e9 [hal] Add autodetect for power modules (#3706)
3dd41c0d37 [wpilib] Don't print PD errors for LiveWindow reads (#3708)
7699a1f827 [hal] Fix sim not working with automatic PD type and default module (#3707)
Change-Id: I6b0b8fa8b2d2a24071240f624db9ec6d127f6648
git-subtree-dir: third_party/allwpilib
git-subtree-split: e4b91005cf69161f1cb3d934f6526232e6b9169e
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/glass/src/lib/native/cpp/Context.cpp b/glass/src/lib/native/cpp/Context.cpp
index 936acb2..1de4af8 100644
--- a/glass/src/lib/native/cpp/Context.cpp
+++ b/glass/src/lib/native/cpp/Context.cpp
@@ -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 @@
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();
+ }
}
- return false;
+
+ for (auto&& root : gContext->storageRoots) {
+ root.getValue()->Apply();
+ }
}
-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 @@
if (!ctx) {
ctx = gContext;
}
- Shutdown(ctx);
if (gContext == ctx) {
SetCurrentContext(nullptr);
}
@@ -217,215 +344,167 @@
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) {
diff --git a/glass/src/lib/native/cpp/DataSource.cpp b/glass/src/lib/native/cpp/DataSource.cpp
index adab6e7..d9bee62 100644
--- a/glass/src/lib/native/cpp/DataSource.cpp
+++ b/glass/src/lib/native/cpp/DataSource.cpp
@@ -12,13 +12,9 @@
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 @@
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::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 @@
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 @@
if (it == gContext->sources.end()) {
return nullptr;
}
- return it->getValue().source;
+ return it->getValue();
}
diff --git a/glass/src/lib/native/cpp/MainMenuBar.cpp b/glass/src/lib/native/cpp/MainMenuBar.cpp
index 2c4d371..879f664 100644
--- a/glass/src/lib/native/cpp/MainMenuBar.cpp
+++ b/glass/src/lib/native/cpp/MainMenuBar.cpp
@@ -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::Display() {
ImGui::BeginMainMenuBar();
+ WorkspaceMenu();
+
if (!m_optionMenus.empty()) {
if (ImGui::BeginMenu("Options")) {
for (auto&& menu : m_optionMenus) {
@@ -55,3 +61,46 @@
#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();
+ }
+}
diff --git a/glass/src/lib/native/cpp/Storage.cpp b/glass/src/lib/native/cpp/Storage.cpp
new file mode 100644
index 0000000..28af20b
--- /dev/null
+++ b/glass/src/lib/native/cpp/Storage.cpp
@@ -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;
+ }
+ }
+}
diff --git a/glass/src/lib/native/cpp/Window.cpp b/glass/src/lib/native/cpp/Window.cpp
index 5c014eb..6296fab 100644
--- a/glass/src/lib/native/cpp/Window.cpp
+++ b/glass/src/lib/native/cpp/Window.cpp
@@ -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 @@
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);
-}
diff --git a/glass/src/lib/native/cpp/WindowManager.cpp b/glass/src/lib/native/cpp/WindowManager.cpp
index 037b9bd..333dad4 100644
--- a/glass/src/lib/native/cpp/WindowManager.cpp
+++ b/glass/src/lib/native/cpp/WindowManager.cpp
@@ -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,
- 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 @@
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 @@
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 @@
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::DisplayWindows() {
+ PushStorageStack(m_storage);
for (auto&& window : m_windows) {
window->Display();
}
+ PopStorageStack();
}
diff --git a/glass/src/lib/native/cpp/hardware/AnalogInput.cpp b/glass/src/lib/native/cpp/hardware/AnalogInput.cpp
index b9699a4..af22511 100644
--- a/glass/src/lib/native/cpp/hardware/AnalogInput.cpp
+++ b/glass/src/lib/native/cpp/hardware/AnalogInput.cpp
@@ -8,6 +8,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
+#include "glass/Storage.h"
using namespace glass;
@@ -18,10 +19,10 @@
}
// 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 @@
}
// context menu to change name
- if (PopupEditName("name", name)) {
- voltageData->SetName(name->c_str());
+ if (PopupEditName("name", &name)) {
+ voltageData->SetName(name);
}
}
diff --git a/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp b/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
index 3a9594b..174e013 100644
--- a/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
+++ b/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
@@ -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 @@
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 @@
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();
diff --git a/glass/src/lib/native/cpp/hardware/DIO.cpp b/glass/src/lib/native/cpp/hardware/DIO.cpp
index 59d71f8..f52974a 100644
--- a/glass/src/lib/native/cpp/hardware/DIO.cpp
+++ b/glass/src/lib/native/cpp/hardware/DIO.cpp
@@ -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 @@
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 @@
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 @@
} 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 @@
}
}
}
- 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());
}
}
}
diff --git a/glass/src/lib/native/cpp/hardware/Encoder.cpp b/glass/src/lib/native/cpp/hardware/Encoder.cpp
index 599a5b8..7032636 100644
--- a/glass/src/lib/native/cpp/hardware/Encoder.cpp
+++ b/glass/src/lib/native/cpp/hardware/Encoder.cpp
@@ -9,6 +9,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
+#include "glass/Storage.h"
using namespace glass;
@@ -66,21 +67,21 @@
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,
- chB);
+ if (!name.empty()) {
+ std::snprintf(label, sizeof(label), "%s [%d,%d]###header", name.c_str(),
+ chA, chB);
} else {
- std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
+ std::snprintf(label, sizeof(label), "Encoder[%d,%d]###header", chA, chB);
}
// header
bool open = CollapsingHeader(label);
// context menu to change name
- if (PopupEditName("name", name)) {
- model->SetName(name->c_str());
+ if (PopupEditName("header", &name)) {
+ model->SetName(name);
}
if (!open) {
diff --git a/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp b/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
index c1ece3a..c3c2406 100644
--- a/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
+++ b/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
@@ -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 @@
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 @@
}
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) {
diff --git a/glass/src/lib/native/cpp/hardware/PCM.cpp b/glass/src/lib/native/cpp/hardware/PCM.cpp
index 23746be..d260bda 100644
--- a/glass/src/lib/native/cpp/hardware/PCM.cpp
+++ b/glass/src/lib/native/cpp/hardware/PCM.cpp
@@ -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,18 +43,19 @@
}
// 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]###header", name.c_str(),
+ index);
} else {
- std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
+ std::snprintf(label, sizeof(label), "PCM[%d]###header", index);
}
// header
bool open = CollapsingHeader(label);
- PopupEditName("name", name);
+ PopupEditName("header", &name);
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
@@ -68,11 +70,11 @@
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();
}
});
diff --git a/glass/src/lib/native/cpp/hardware/PWM.cpp b/glass/src/lib/native/cpp/hardware/PWM.cpp
index 3ff8e52..0200ac6 100644
--- a/glass/src/lib/native/cpp/hardware/PWM.cpp
+++ b/glass/src/lib/native/cpp/hardware/PWM.cpp
@@ -8,6 +8,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
+#include "glass/Storage.h"
using namespace glass;
@@ -18,10 +19,10 @@
}
// 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 @@
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);
}
}
diff --git a/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp b/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp
index 46e550b..f1de461 100644
--- a/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp
+++ b/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp
@@ -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 @@
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;
diff --git a/glass/src/lib/native/cpp/hardware/Relay.cpp b/glass/src/lib/native/cpp/hardware/Relay.cpp
index 59bbc51..e071de7 100644
--- a/glass/src/lib/native/cpp/hardware/Relay.cpp
+++ b/glass/src/lib/native/cpp/hardware/Relay.cpp
@@ -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 @@
}
}
- 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();
diff --git a/glass/src/lib/native/cpp/other/DeviceTree.cpp b/glass/src/lib/native/cpp/other/DeviceTree.cpp
index cd69eb7..cfce8c4 100644
--- a/glass/src/lib/native/cpp/other/DeviceTree.cpp
+++ b/glass/src/lib/native/cpp/other/DeviceTree.cpp
@@ -51,13 +51,13 @@
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());
+ std::snprintf(label, sizeof(label), "%s###header",
+ name.empty() ? id : name.c_str());
bool open = CollapsingHeader(label, flags);
- PopupEditName("name", name);
+ PopupEditName("header", &name);
if (!open) {
PopID();
diff --git a/glass/src/lib/native/cpp/other/Drive.cpp b/glass/src/lib/native/cpp/other/Drive.cpp
index 9dc1675..a73c6de 100644
--- a/glass/src/lib/native/cpp/other/Drive.cpp
+++ b/glass/src/lib/native/cpp/other/Drive.cpp
@@ -90,11 +90,20 @@
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;
diff --git a/glass/src/lib/native/cpp/other/FMS.cpp b/glass/src/lib/native/cpp/other/FMS.cpp
index a19cad4..fbd504e 100644
--- a/glass/src/lib/native/cpp/other/FMS.cpp
+++ b/glass/src/lib/native/cpp/other/FMS.cpp
@@ -14,7 +14,7 @@
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
"Blue 1", "Blue 2", "Blue 3"};
-void glass::DisplayFMS(FMSModel* model, bool* matchTimeEnabled) {
+void glass::DisplayFMS(FMSModel* model) {
if (!model->Exists() || model->IsReadOnly()) {
return DisplayFMSReadOnly(model);
}
@@ -49,10 +49,6 @@
// Match Time
if (auto data = model->GetMatchTimeData()) {
- if (matchTimeEnabled) {
- ImGui::Checkbox("Match Time Enabled", matchTimeEnabled);
- }
-
double val = data->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f",
@@ -60,9 +56,17 @@
model->SetMatchTime(val);
}
data->EmitDrag();
+ bool enabled = false;
+ if (auto enabledData = model->GetEnabledData()) {
+ enabled = enabledData->GetValue();
+ }
ImGui::SameLine();
- if (ImGui::Button("Reset")) {
- model->SetMatchTime(0.0);
+ if (ImGui::Button("Auto") && !enabled) {
+ model->SetMatchTime(15.0);
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Teleop") && !enabled) {
+ model->SetMatchTime(135.0);
}
}
diff --git a/glass/src/lib/native/cpp/other/Field2D.cpp b/glass/src/lib/native/cpp/other/Field2D.cpp
index ec0210e..c3ef860 100644
--- a/glass/src/lib/native/cpp/other/Field2D.cpp
+++ b/glass/src/lib/native/cpp/other/Field2D.cpp
@@ -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 @@
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 ObjectInfo {
public:
- ObjectInfo();
+ explicit ObjectInfo(Storage& storage);
DisplayOptions GetDisplayOptions() const;
void DisplaySettings();
@@ -191,26 +196,26 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
++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 @@
}
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 @@
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 @@
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 @@
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 @@
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>();
}
diff --git a/glass/src/lib/native/cpp/other/Mechanism2D.cpp b/glass/src/lib/native/cpp/other/Mechanism2D.cpp
index 07e83e2..aa801a7 100644
--- a/glass/src/lib/native/cpp/other/Mechanism2D.cpp
+++ b/glass/src/lib/native/cpp/other/Mechanism2D.cpp
@@ -27,6 +27,7 @@
#include <wpigui.h>
#include "glass/Context.h"
+#include "glass/Storage.h"
using namespace glass;
@@ -61,7 +62,7 @@
class BackgroundInfo {
public:
- BackgroundInfo();
+ explicit BackgroundInfo(Storage& storage);
void DisplaySettings();
@@ -72,11 +73,11 @@
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 @@
} // 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::Reset() {
m_texture = gui::Texture{};
- m_pFilename->clear();
+ m_filename.clear();
m_imageWidth = 0;
m_imageHeight = 0;
}
@@ -119,16 +118,16 @@
}
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 @@
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 @@
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 @@
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>();
}
diff --git a/glass/src/lib/native/cpp/other/Plot.cpp b/glass/src/lib/native/cpp/other/Plot.cpp
index 372f8c9..bff55b7 100644
--- a/glass/src/lib/native/cpp/other/Plot.cpp
+++ b/glass/src/lib/native/cpp/other/Plot.cpp
@@ -17,12 +17,15 @@
#include <fmt/format.h>
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
-#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <implot.h>
-#include <wpigui.h>
+#include <implot_internal.h>
#include <wpi/Signal.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
@@ -31,10 +34,15 @@
#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;
+static constexpr int kAxisCount = 3;
+
namespace {
class PlotView;
@@ -45,9 +53,11 @@
};
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 +66,6 @@
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 +76,12 @@
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,22 +89,23 @@
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;
+ static constexpr int kMaxSize = 20000;
static constexpr double kTimeGap = 0.05;
std::atomic<int> m_size = 0;
std::atomic<int> m_offset = 0;
@@ -104,10 +114,7 @@
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 +123,7 @@
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);
@@ -128,31 +136,50 @@
private:
void EmitSettingsLimits(int axis);
+ void DragDropAccept(PlotView& view, size_t i, int yAxis);
- 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;
- struct PlotRange {
- double min = 0;
- double max = 1;
- bool lockMin = false;
- bool lockMax = false;
+
+ std::string& m_name;
+ bool& m_visible;
+ bool& m_showPause;
+ bool& m_lockPrevX;
+ bool& m_legend;
+ bool& m_legendOutside;
+ bool& m_legendHorizontal;
+ int& m_legendLocation;
+ bool& m_crosshairs;
+ bool& m_antialiased;
+ bool& m_mousePosition;
+ bool& m_yAxis2;
+ bool& m_yAxis3;
+ float& m_viewTime;
+ bool& m_autoHeight;
+ int& m_height;
+ struct PlotAxis {
+ PlotAxis(Storage& storage, int num);
+
+ std::string& label;
+ double& min;
+ double& max;
+ bool& lockMin;
+ bool& lockMax;
bool apply = false;
+ bool& autoFit;
+ bool& logScale;
+ bool& invert;
+ bool& opposite;
+ bool& gridLines;
+ bool& tickMarks;
+ bool& tickLabels;
};
- std::string m_axisLabel[3];
- PlotRange m_axisRange[3];
+ std::vector<PlotAxis> 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 +190,32 @@
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", 0)},
+ 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)} {
+ m_yAxis = yAxis;
+}
+
+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 +223,8 @@
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();
}
@@ -198,7 +246,7 @@
m_source = source;
// add initial value
- m_data[m_size++] = ImPlotPoint{wpi::Now() * 1.0e-6, source->GetValue()};
+ AppendValue(source->GetValue(), 0);
m_newValueConn = source->valueChanged.connect_connection(
[this](double value, uint64_t time) { AppendValue(value, time); });
@@ -245,66 +293,14 @@
}
}
-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();
@@ -315,7 +311,8 @@
CheckSource();
char label[128];
- std::snprintf(label, sizeof(label), "%s###name", GetName());
+ std::snprintf(label, sizeof(label), "%s###name%d_%d", GetName(),
+ static_cast<int>(i), static_cast<int>(plotIndex));
int size = m_size;
int offset = m_offset;
@@ -346,10 +343,10 @@
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);
@@ -357,8 +354,12 @@
ImPlot::PopStyleVar();
ImPlot::PopStyleVar();
} else {
- ImPlot::SetPlotYAxis(m_yAxis);
- ImPlot::SetNextMarkerStyle(m_marker - 1);
+ if (ImPlot::GetCurrentPlot()->YAxis(m_yAxis).Enabled) {
+ ImPlot::SetAxis(ImAxis_Y1 + m_yAxis);
+ } else {
+ ImPlot::SetAxis(ImAxis_Y1);
+ }
+ ImPlot::SetNextMarkerStyle(m_marker.GetValue() - 1);
ImPlot::PlotLineG(label, getter, &getterData, size + 1);
}
@@ -413,10 +414,10 @@
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 +429,8 @@
// 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,152 +455,62 @@
// 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::PlotAxis::PlotAxis(Storage& storage, int num)
+ : label{storage.GetString("label")},
+ min{storage.GetDouble("min", 0)},
+ max{storage.GetDouble("max", 1)},
+ lockMin{storage.GetBool("lockMin", false)},
+ lockMax{storage.GetBool("lockMax", false)},
+ autoFit{storage.GetBool("autoFit", false)},
+ logScale{storage.GetBool("logScale", false)},
+ invert{storage.GetBool("invert", false)},
+ opposite{storage.GetBool("opposite", num != 0)},
+ gridLines{storage.GetBool("gridLines", num == 0)},
+ tickMarks{storage.GetBool("tickMarks", true)},
+ tickLabels{storage.GetBool("tickLabels", 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_legendOutside{storage.GetBool("legendOutside", false)},
+ m_legendHorizontal{storage.GetBool("legendHorizontal", false)},
+ m_legendLocation{
+ storage.GetInt("legendLocation", ImPlotLocation_NorthWest)},
+ m_crosshairs{storage.GetBool("crosshairs", false)},
+ m_antialiased{storage.GetBool("antialiased", false)},
+ m_mousePosition{storage.GetBool("mousePosition", 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(kAxisCount);
+ for (int i = 0; i < kAxisCount; ++i) {
+ if (!axesStorage[i]) {
+ axesStorage[i] = std::make_unique<Storage>();
+ }
+ m_axis.emplace_back(*axesStorage[i], i);
+ }
+
+ // loop over series
+ for (auto&& v : m_seriesStorage) {
+ m_series.emplace_back(
+ std::make_unique<PlotSeries>(*v, v->ReadString("id")));
}
}
-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;
- }
- }
- 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());
- }
-}
-
-void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
- if (!ImGui::BeginDragDropTarget()) {
- return;
- }
- // handle dragging onto a specific Y axis
- int yAxis = -1;
- if (inPlot) {
- for (int y = 0; y < 3; ++y) {
- if (ImPlot::IsPlotYAxisHovered(y)) {
- yAxis = y;
- break;
- }
- }
- }
+void Plot::DragDropAccept(PlotView& view, size_t i, int yAxis) {
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload("DataSource")) {
auto source = *static_cast<DataSource**>(payload->Data);
@@ -612,8 +521,9 @@
(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")) {
@@ -627,6 +537,26 @@
}
}
+void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
+ if (inPlot) {
+ if (ImPlot::BeginDragDropTargetPlot() ||
+ ImPlot::BeginDragDropTargetLegend()) {
+ DragDropAccept(view, i, -1);
+ ImPlot::EndDragDropTarget();
+ }
+ for (int y = 0; y < kAxisCount; ++y) {
+ if (ImPlot::GetCurrentPlot()->YAxis(y).Enabled &&
+ ImPlot::BeginDragDropTargetAxis(ImAxis_Y1 + y)) {
+ DragDropAccept(view, i, y);
+ ImPlot::EndDragDropTarget();
+ }
+ }
+ } else if (ImGui::BeginDragDropTarget()) {
+ DragDropAccept(view, i, -1);
+ ImGui::EndDragDropTarget();
+ }
+}
+
void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
if (!m_visible) {
return;
@@ -639,67 +569,115 @@
}
char label[128];
- std::snprintf(label, sizeof(label), "%s##plot", m_name.c_str());
+ std::snprintf(label, sizeof(label), "%s###plot%d", m_name.c_str(),
+ static_cast<int>(i));
+ ImPlotFlags plotFlags = (m_legend ? 0 : ImPlotFlags_NoLegend) |
+ (m_crosshairs ? ImPlotFlags_Crosshairs : 0) |
+ (m_antialiased ? ImPlotFlags_AntiAliased : 0) |
+ (m_mousePosition ? 0 : ImPlotFlags_NoMouseText);
- if (lockX) {
- ImPlot::SetNextPlotLimitsX(view.m_plots[i - 1]->m_xaxisRange.Min,
- view.m_plots[i - 1]->m_xaxisRange.Max,
- ImGuiCond_Always);
- } else {
- // also force-pause plots if overall timing is paused
- double zeroTime = GetZeroTime() * 1.0e-6;
- ImPlot::SetNextPlotLimitsX(
- now - zeroTime - m_viewTime, now - zeroTime,
- (paused || m_paused) ? ImGuiCond_Once : ImGuiCond_Always);
- }
-
- ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_None,
- ImPlotAxisFlags_NoGridLines,
- 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) {
- yFlags[i] |= ImPlotAxisFlags_LockMin;
+ if (ImPlot::BeginPlot(label, ImVec2(-1, m_height), plotFlags)) {
+ // setup legend
+ if (m_legend) {
+ ImPlotLegendFlags legendFlags =
+ (m_legendOutside ? ImPlotLegendFlags_Outside : 0) |
+ (m_legendHorizontal ? ImPlotLegendFlags_Horizontal : 0);
+ ImPlot::SetupLegend(m_legendLocation, legendFlags);
}
- if (m_axisRange[i].lockMax) {
- yFlags[i] |= ImPlotAxisFlags_LockMax;
- }
- }
- 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],
- yFlags[1], yFlags[2],
- m_axisLabel[1].empty() ? nullptr : m_axisLabel[1].c_str(),
- m_axisLabel[2].empty() ? nullptr : m_axisLabel[2].c_str())) {
+ // setup x axis
+ ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoMenus);
+ if (lockX) {
+ ImPlot::SetupAxisLimits(ImAxis_X1, view.m_plots[i - 1]->m_xaxisRange.Min,
+ view.m_plots[i - 1]->m_xaxisRange.Max,
+ ImGuiCond_Always);
+ } else {
+ // also force-pause plots if overall timing is paused
+ double zeroTime = GetZeroTime() * 1.0e-6;
+ ImPlot::SetupAxisLimits(
+ ImAxis_X1, now - zeroTime - m_viewTime, now - zeroTime,
+ (paused || m_paused) ? ImGuiCond_Once : ImGuiCond_Always);
+ }
+
+ // setup y axes
+ for (int i = 0; i < kAxisCount; ++i) {
+ if ((i == 1 && !m_yAxis2) || (i == 2 && !m_yAxis3)) {
+ continue;
+ }
+ ImPlotAxisFlags flags =
+ (m_axis[i].lockMin ? ImPlotAxisFlags_LockMin : 0) |
+ (m_axis[i].lockMax ? ImPlotAxisFlags_LockMax : 0) |
+ (m_axis[i].autoFit ? ImPlotAxisFlags_AutoFit : 0) |
+ (m_axis[i].logScale ? ImPlotAxisFlags_AutoFit : 0) |
+ (m_axis[i].invert ? ImPlotAxisFlags_Invert : 0) |
+ (m_axis[i].opposite ? ImPlotAxisFlags_Opposite : 0) |
+ (m_axis[i].gridLines ? 0 : ImPlotAxisFlags_NoGridLines) |
+ (m_axis[i].tickMarks ? 0 : ImPlotAxisFlags_NoTickMarks) |
+ (m_axis[i].tickLabels ? 0 : ImPlotAxisFlags_NoTickLabels);
+ ImPlot::SetupAxis(
+ ImAxis_Y1 + i,
+ m_axis[i].label.empty() ? nullptr : m_axis[i].label.c_str(), flags);
+ ImPlot::SetupAxisLimits(
+ ImAxis_Y1 + i, m_axis[i].min, m_axis[i].max,
+ m_axis[i].apply ? ImGuiCond_Always : ImGuiCond_Once);
+ m_axis[i].apply = false;
+ }
+
+ ImPlot::SetupFinish();
+
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:
break;
}
- ImGui::PopID();
}
DragDropTarget(view, i, true);
m_xaxisRange = ImPlot::GetPlotLimits().X;
+
+ ImPlotPlot* plot = ImPlot::GetCurrentPlot();
ImPlot::EndPlot();
+
+ // copy plot settings back to storage
+ m_legend = (plot->Flags & ImPlotFlags_NoLegend) == 0;
+ m_crosshairs = (plot->Flags & ImPlotFlags_Crosshairs) != 0;
+ m_antialiased = (plot->Flags & ImPlotFlags_AntiAliased) != 0;
+ m_legendOutside =
+ (plot->Items.Legend.Flags & ImPlotLegendFlags_Outside) != 0;
+ m_legendHorizontal =
+ (plot->Items.Legend.Flags & ImPlotLegendFlags_Horizontal) != 0;
+ m_legendLocation = plot->Items.Legend.Location;
+
+ for (int i = 0; i < kAxisCount; ++i) {
+ if ((i == 1 && !m_yAxis2) || (i == 2 && !m_yAxis3)) {
+ continue;
+ }
+ auto flags = plot->Axes[ImAxis_Y1 + i].Flags;
+ m_axis[i].lockMin = (flags & ImPlotAxisFlags_LockMin) != 0;
+ m_axis[i].lockMax = (flags & ImPlotAxisFlags_LockMax) != 0;
+ m_axis[i].autoFit = (flags & ImPlotAxisFlags_AutoFit) != 0;
+ m_axis[i].logScale = (flags & ImPlotAxisFlags_LogScale) != 0;
+ m_axis[i].invert = (flags & ImPlotAxisFlags_Invert) != 0;
+ m_axis[i].opposite = (flags & ImPlotAxisFlags_Opposite) != 0;
+ m_axis[i].gridLines = (flags & ImPlotAxisFlags_NoGridLines) == 0;
+ m_axis[i].tickMarks = (flags & ImPlotAxisFlags_NoTickMarks) == 0;
+ m_axis[i].tickLabels = (flags & ImPlotAxisFlags_NoTickLabels) == 0;
+ }
}
}
@@ -708,22 +686,22 @@
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 +712,17 @@
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);
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 +755,20 @@
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 +800,7 @@
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 +808,14 @@
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 +832,8 @@
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 +880,21 @@
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 +907,13 @@
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 +929,53 @@
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 +999,9 @@
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");
- }
- }
- }
-}
diff --git a/glass/src/lib/native/cpp/support/ColorSetting.cpp b/glass/src/lib/native/cpp/support/ColorSetting.cpp
new file mode 100644
index 0000000..9f20a0b
--- /dev/null
+++ b/glass/src/lib/native/cpp/support/ColorSetting.cpp
@@ -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);
+}
diff --git a/glass/src/lib/native/cpp/support/EnumSetting.cpp b/glass/src/lib/native/cpp/support/EnumSetting.cpp
new file mode 100644
index 0000000..848b588
--- /dev/null
+++ b/glass/src/lib/native/cpp/support/EnumSetting.cpp
@@ -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;
+}
diff --git a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
index 2af6e5e..56f4e77 100644
--- a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
+++ b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
@@ -160,17 +160,17 @@
bool HeaderDeleteButton(const char* label) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiContext& g = *GImGui;
- ImGuiLastItemDataBackup last_item_backup;
+ ImGuiLastItemData last_item_backup = g.LastItemData;
ImGuiID id = window->GetID(label);
float button_size = g.FontSize;
- float button_x = ImMax(window->DC.LastItemRect.Min.x,
- window->DC.LastItemRect.Max.x -
- g.Style.FramePadding.x * 2.0f - button_size);
- float button_y = window->DC.LastItemRect.Min.y;
+ float button_x = ImMax(
+ g.LastItemData.Rect.Min.x,
+ g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
+ float button_y = g.LastItemData.Rect.Min.y;
bool rv = DeleteButton(
window->GetID(reinterpret_cast<void*>(static_cast<intptr_t>(id) + 1)),
ImVec2(button_x, button_y));
- last_item_backup.Restore();
+ g.LastItemData = last_item_backup;
return rv;
}
diff --git a/glass/src/lib/native/cpp/support/IniSaverBase.cpp b/glass/src/lib/native/cpp/support/IniSaverBase.cpp
deleted file mode 100644
index ae8d811..0000000
--- a/glass/src/lib/native/cpp/support/IniSaverBase.cpp
+++ /dev/null
@@ -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);
-}
diff --git a/glass/src/lib/native/cpp/support/IniSaverInfo.cpp b/glass/src/lib/native/cpp/support/IniSaverInfo.cpp
deleted file mode 100644
index 6525e8e..0000000
--- a/glass/src/lib/native/cpp/support/IniSaverInfo.cpp
+++ /dev/null
@@ -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);
-}
diff --git a/glass/src/lib/native/cpp/support/NameSetting.cpp b/glass/src/lib/native/cpp/support/NameSetting.cpp
new file mode 100644
index 0000000..1dc1d20
--- /dev/null
+++ b/glass/src/lib/native/cpp/support/NameSetting.cpp
@@ -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);
+}
diff --git a/glass/src/lib/native/include/glass/Context.h b/glass/src/lib/native/include/glass/Context.h
index 62b0a33..14ade2c 100644
--- a/glass/src/lib/native/include/glass/Context.h
+++ b/glass/src/lib/native/include/glass/Context.h
@@ -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 @@
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);
diff --git a/glass/src/lib/native/include/glass/ContextInternal.h b/glass/src/lib/native/include/glass/ContextInternal.h
index 39e54f3..7556c70 100644
--- a/glass/src/lib/native/include/glass/ContextInternal.h
+++ b/glass/src/lib/native/include/glass/ContextInternal.h
@@ -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;
diff --git a/glass/src/lib/native/include/glass/DataSource.h b/glass/src/lib/native/include/glass/DataSource.h
index 1d5c37b..5eebb3c 100644
--- a/glass/src/lib/native/include/glass/DataSource.h
+++ b/glass/src/lib/native/include/glass/DataSource.h
@@ -16,8 +16,6 @@
namespace glass {
-class NameInfo;
-
/**
* A data source for numeric/boolean data.
*/
@@ -33,15 +31,9 @@
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 @@
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 @@
private:
std::string m_id;
- NameInfo* m_name;
+ std::string& m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};
diff --git a/glass/src/lib/native/include/glass/MainMenuBar.h b/glass/src/lib/native/include/glass/MainMenuBar.h
index 7a6a2fc..9141536 100644
--- a/glass/src/lib/native/include/glass/MainMenuBar.h
+++ b/glass/src/lib/native/include/glass/MainMenuBar.h
@@ -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.
*/
@@ -22,6 +23,11 @@
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
* appropriate to create a menu with ImGui::BeginMenu()/EndMenu() inside of
@@ -43,6 +49,8 @@
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
diff --git a/glass/src/lib/native/include/glass/Provider.h b/glass/src/lib/native/include/glass/Provider.h
index 53b1e75..f620d52 100644
--- a/glass/src/lib/native/include/glass/Provider.h
+++ b/glass/src/lib/native/include/glass/Provider.h
@@ -19,6 +19,8 @@
namespace glass {
+class Storage;
+
namespace detail {
struct ProviderFunctions {
using Exists = std::function<bool()>;
@@ -49,9 +51,9 @@
/**
* 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 @@
ModelEntry* modelEntry;
ViewExistsFunc exists;
CreateViewFunc createView;
+ bool showDefault = false;
Window* window = nullptr;
};
diff --git a/glass/src/lib/native/include/glass/Provider.inc b/glass/src/lib/native/include/glass/Provider.inc
index 33bb6e0..7370cca 100644
--- a/glass/src/lib/native/include/glass/Provider.inc
+++ b/glass/src/lib/native/include/glass/Provider.inc
@@ -26,7 +26,7 @@
if (it == m_viewEntries.end() || (*it)->name != name) {
return;
}
- this->Show(it->get(), (*it)->window);
+ (*it)->showDefault = true;
}
template <typename Functions>
diff --git a/glass/src/lib/native/include/glass/Storage.h b/glass/src/lib/native/include/glass/Storage.h
new file mode 100644
index 0000000..004b8b4
--- /dev/null
+++ b/glass/src/lib/native/include/glass/Storage.h
@@ -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
diff --git a/glass/src/lib/native/include/glass/Window.h b/glass/src/lib/native/include/glass/Window.h
index 780479a..0a37f9a 100644
--- a/glass/src/lib/native/include/glass/Window.h
+++ b/glass/src/lib/native/include/glass/Window.h
@@ -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); }
@@ -60,6 +62,13 @@
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.
*
* @param x x location of upper left corner
@@ -109,17 +118,16 @@
*/
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;
diff --git a/glass/src/lib/native/include/glass/WindowManager.h b/glass/src/lib/native/include/glass/WindowManager.h
index 4024e15..0ccace2 100644
--- a/glass/src/lib/native/include/glass/WindowManager.h
+++ b/glass/src/lib/native/include/glass/WindowManager.h
@@ -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 @@
/**
* 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 @@
*
* @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 @@
*
* @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 @@
* 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 @@
*/
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
diff --git a/glass/src/lib/native/include/glass/other/FMS.h b/glass/src/lib/native/include/glass/other/FMS.h
index 1e0f8ef..a920f96 100644
--- a/glass/src/lib/native/include/glass/other/FMS.h
+++ b/glass/src/lib/native/include/glass/other/FMS.h
@@ -47,7 +47,7 @@
* @param matchTimeEnabled If not null, a checkbox is displayed for
* "enable match time" linked to this value
*/
-void DisplayFMS(FMSModel* model, bool* matchTimeEnabled = nullptr);
+void DisplayFMS(FMSModel* model);
void DisplayFMSReadOnly(FMSModel* model);
} // namespace glass
diff --git a/glass/src/lib/native/include/glass/other/Plot.h b/glass/src/lib/native/include/glass/other/Plot.h
index f7b196d..3e27b9d 100644
--- a/glass/src/lib/native/include/glass/other/Plot.h
+++ b/glass/src/lib/native/include/glass/other/Plot.h
@@ -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 @@
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;
};
diff --git a/glass/src/lib/native/include/glass/support/ColorSetting.h b/glass/src/lib/native/include/glass/support/ColorSetting.h
new file mode 100644
index 0000000..a6604dd
--- /dev/null
+++ b/glass/src/lib/native/include/glass/support/ColorSetting.h
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/EnumSetting.h b/glass/src/lib/native/include/glass/support/EnumSetting.h
new file mode 100644
index 0000000..c4f0c26
--- /dev/null
+++ b/glass/src/lib/native/include/glass/support/EnumSetting.h
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/IniSaver.h b/glass/src/lib/native/include/glass/support/IniSaver.h
deleted file mode 100644
index a3aa3d0..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaver.h
+++ /dev/null
@@ -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"
diff --git a/glass/src/lib/native/include/glass/support/IniSaver.inc b/glass/src/lib/native/include/glass/support/IniSaver.inc
deleted file mode 100644
index 42efb85..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaver.inc
+++ /dev/null
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/IniSaverBase.h b/glass/src/lib/native/include/glass/support/IniSaverBase.h
deleted file mode 100644
index 85ae1e3..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverBase.h
+++ /dev/null
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/IniSaverInfo.h b/glass/src/lib/native/include/glass/support/IniSaverInfo.h
deleted file mode 100644
index 2014e9d..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverInfo.h
+++ /dev/null
@@ -1,63 +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>
-
-namespace glass {
-
-class NameInfo {
- public:
- NameInfo() { m_name[0] = '\0'; }
-
- bool HasName() const { return m_name[0] != '\0'; }
- void SetName(std::string_view name);
- const char* GetName() const { return m_name; }
- void GetName(char* buf, size_t size, const char* defaultName) const;
- void GetName(char* buf, size_t size, const char* defaultName,
- int index) const;
- void GetName(char* buf, size_t size, const char* defaultName, int index,
- int index2) const;
- void GetLabel(char* buf, size_t size, const char* defaultName) const;
- void GetLabel(char* buf, size_t size, const char* defaultName,
- int index) const;
- 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);
- bool PopupEditName(const char* name);
- 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);
-};
-
-} // namespace glass
diff --git a/glass/src/lib/native/include/glass/support/IniSaverString.h b/glass/src/lib/native/include/glass/support/IniSaverString.h
deleted file mode 100644
index 4134219..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverString.h
+++ /dev/null
@@ -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"
diff --git a/glass/src/lib/native/include/glass/support/IniSaverString.inc b/glass/src/lib/native/include/glass/support/IniSaverString.inc
deleted file mode 100644
index 0d18d29..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverString.inc
+++ /dev/null
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/IniSaverVector.h b/glass/src/lib/native/include/glass/support/IniSaverVector.h
deleted file mode 100644
index e2e57ce..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverVector.h
+++ /dev/null
@@ -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"
diff --git a/glass/src/lib/native/include/glass/support/IniSaverVector.inc b/glass/src/lib/native/include/glass/support/IniSaverVector.inc
deleted file mode 100644
index a86b116..0000000
--- a/glass/src/lib/native/include/glass/support/IniSaverVector.inc
+++ /dev/null
@@ -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
diff --git a/glass/src/lib/native/include/glass/support/NameSetting.h b/glass/src/lib/native/include/glass/support/NameSetting.h
new file mode 100644
index 0000000..e1444e3
--- /dev/null
+++ b/glass/src/lib/native/include/glass/support/NameSetting.h
@@ -0,0 +1,43 @@
+// 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 NameSetting {
+ public:
+ explicit NameSetting(std::string& str) : m_name{str} {}
+
+ 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;
+ void GetName(char* buf, size_t size, const char* defaultName, int index,
+ int index2) const;
+ void GetLabel(char* buf, size_t size, const char* defaultName) const;
+ void GetLabel(char* buf, size_t size, const char* defaultName,
+ int index) const;
+ void GetLabel(char* buf, size_t size, const char* defaultName, int index,
+ int index2) const;
+
+ 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);
+
+ private:
+ std::string& m_name;
+};
+
+} // namespace glass