Compare commits

...

134 Commits

Author SHA1 Message Date
Tyler Veness
7f4265facc [wpimath] Add LinearFilter::FiniteDifference() (#3900)
This allows making more general finite difference filters, like central
finite difference. SysId uses this for acceleration filtering.
2022-01-15 20:18:11 -08:00
Tyler Veness
63d1fb3bed [wpiutil] Modify fmt to not throw on write failure (#3919)
This was causing issues with tools, as the launchers would close stdout/stderr, resulting in write failures.
2022-01-15 20:10:32 -08:00
Tyler Veness
36af6d25a5 [wpimath] Fix input vector in pose estimator docs (NFC) (#3923) 2022-01-15 20:03:39 -08:00
Thad House
8f387f7255 [wpilibj] Switch ControlWord mutex to actual reentrant mutex (#3922)
It seems like the JVM does not handle recursive calls to object monitor based locks correctly. A few bugs in the past have been reported to have caused deadlocks if this occurs. It looks like the version of Java we use is fixed, but there could be other bugs, and it seems like this area of the code isn't tested much. Based on the stacks reported in #3896, it really seems like this is occurring. So we're going to attempt to switch to explicit mutex based classes, which shouldn't have bugs like this, and we will see if that fixes the issue.
2022-01-15 15:24:06 -08:00
David Vo
792e735e08 [wpimath] Move TrajectoryGenerator::SetErrorHandler definition to .cpp (#3920)
Otherwise this function causes linking errors when used on Windows.
2022-01-15 08:58:49 -08:00
Tyler Veness
3b76de83eb [commands] Fix ProfiledPIDCommand use-after-free (#3904)
Fixes #3903.
2022-01-14 23:56:48 -08:00
PJ Reiniger
ad9f738cfa [fieldimages] Fix maven publishing (#3897) 2022-01-14 23:55:10 -08:00
modelmat
49455199e5 [examples] Use left/rightGroup.Get() for simulator inputs to fix inversions (#3908) 2022-01-14 23:54:20 -08:00
modelmat
64426502ea [wpimath] Fix arm -> flywheel typo (NFC) (#3911) 2022-01-14 23:53:45 -08:00
Tyler Veness
8cc112d196 [wpiutil] Fix wpi::array for move-only types (#3917)
Fixes #3916.
2022-01-14 23:53:12 -08:00
Tyler Veness
e78cd49861 [build] Upgrade Java formatter plugins (#3894) 2022-01-11 22:24:16 -08:00
Tyler Veness
cfb4f756d6 [build] Upgrade to shadow 7.1.2 (#3893) 2022-01-11 21:10:15 -08:00
Tyler Veness
ba0908216c [wpimath] Fix crash in KF latency compensator (#3888)
It would crash in C++ if the global measurement was sooner than all the
snapshots.

Align Java with the changes and better document computation approach.
2022-01-09 23:01:04 -08:00
Peter Johnson
a3a0334fad [build] cmake: Move fieldImages to WITH_GUI (#3885)
This will only ever be used by GUI applications, and the jar build
method it uses can misbehave in some cross-compile scenarios.
2022-01-09 20:26:54 -08:00
sciencewhiz
cf7460c3a8 [fieldImages] Add 2022 field (#3883) 2022-01-08 23:24:24 -08:00
Tyler Veness
db0fbb6448 [wpimath] Fix LQR matrix constructor overload for Q, R, and N (#3884)
It was using the continuous B matrix to compute the feedback gain
instead of the discrete B matrix.

Tests were added for the matrix constructor overloads.
2022-01-08 23:23:53 -08:00
sciencewhiz
8ac45f20bb [commands] Update Command documentation (NFC) (#3881)
Add reference to which VendorDep the class is included in.
Add missing OldCommands C++ Documentation (copied from Java).
2022-01-08 11:11:34 -08:00
Tyler Veness
b3707cca0b [wpiutil] Upgrade to fmt 8.1.1 (#3879)
The changes to PneumaticsBase.cpp were to fix errors like the following
from enum classes not being formattable:
```
allwpilib/wpilibc/src/main/native/cpp/PneumaticsBase.cpp:36:9:   required from here
allwpilib/wpiutil/src/main/native/fmtlib/include/fmt/core.h:2672:12: error: use of deleted function ‘fmt::v8::detail::fallback_formatter<T, Char, Enable>::fallback_formatter() [with T = frc::PneumaticsModuleType; Char = char; Enable = void]’
 2672 |   auto f = conditional_t<has_formatter<mapped_type, context>::value,
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 2673 |                          formatter<mapped_type, char_type>,
      |                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 2674 |                          fallback_formatter<T, char_type>>();
      |                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
2022-01-08 11:10:42 -08:00
Tyler Veness
a69ee3ece9 [wpimath] Const-qualify Twist2d scalar multiply (#3882)
Fixes #3880.
2022-01-08 11:09:29 -08:00
Drew Williams
750d9a30c9 [examples] Fix Eigen out of range error when running example (#3877)
Simple typo fix.
2022-01-08 00:15:26 -08:00
Peter Johnson
41c5b2b5ac [rtns] Add cmake build (#3866)
This needs libssh to build, so on Linux systems it's necessary to
install libssh-dev.
2022-01-08 00:14:48 -08:00
Tyler Veness
6cf3f9b28e [build] Upgrade to Gradle 7.3.3 (#3878)
This is the same version robot projects currently use.
2022-01-08 00:14:27 -08:00
Starlight220
269cf03472 [examples] Add communication examples (e.g. arduino) (#2500)
Co-authored-by: Andrew Dassonville <dassonville.andrew@gmail.com>
2022-01-06 18:08:57 -08:00
sciencewhiz
5ccfc4adbd [oldcommands] Deprecate PIDWrappers, since they use deprecated interfaces (#3868) 2022-01-06 18:05:24 -08:00
Peter Johnson
b6f44f98be [hal] Add warning about onboard I2C (#3871)
Adds HAL layer warning for #3842. This is needed in the case when a
vendor uses the HAL directly rather than using the WPILib I2C class.

This should not result in a duplicate warning for WPILib I2C users due
to the duplicate message checking performed in HAL_SendError().

We don't want to remove the WPILib I2C warning because it gives stack
trace information while the HAL layer one can't.
2022-01-06 17:44:27 -08:00
Peter Johnson
0dca57e9ec [templates] romieducational: Invert drivetrain and disable motor safety (#3869) 2022-01-06 11:29:15 -08:00
Tyler Veness
22c4da152e [wpilib] Add GetRate() to ADIS classes (#3864)
The angular rate is treated somewhat like an angle during calibration,
but the datasheet says it's angular rate. The variables were renamed to
make this clearer.
2022-01-04 22:26:23 -08:00
Peter Johnson
05d66f862d [templates] Change the template ordering to put command based first (#3863)
Previously it was a bit buried.
2022-01-04 21:23:57 -08:00
Dustin Spicuzza
b09f5b2cf2 [wpilibc] Add virtual dtor for LinearSystemSim (#3861) 2022-01-03 21:25:02 -08:00
Tyler Veness
a2510aaa0e [wpilib] Make ADIS IMU classes unit-safe (#3860)
The gyro rate getters were removed since that data isn't available.
2022-01-03 20:00:53 -08:00
Tyler Veness
947f589916 [wpilibc] Rename ADIS_16470_IMU.cpp to match class name (#3859) 2022-01-03 17:53:57 -08:00
Peter Johnson
bbd8980a20 [myRobot] Fix cameraserver library order (#3858) 2022-01-03 11:59:29 -08:00
Tyler Veness
831052f118 [wpilib] Add simulation support to ADIS classes (#3857) 2022-01-03 11:44:12 -08:00
Noah Andrews
c137569f91 [wpilib] Throw exception if the REV Pneumatic Hub firmware version is older than 22.0.0 (#3853) 2022-01-03 11:09:30 -08:00
sciencewhiz
dae61226fa Fix Maven Artifacts readme (#3856)
Add wpiutil to wpimath
Add wpimath to wpilibj and wpilibc
2022-01-03 10:18:49 -08:00
sciencewhiz
3ad4594a88 Update Maven artifacts readme for 2022 (#3855) 2022-01-01 13:28:36 -08:00
Matteo Kimura
112acb9a62 [wpilibc] Move ADIS IMU constants to inside class (#3852) 2022-01-01 11:40:28 -08:00
Peter Johnson
ecee224e81 [wpilib] Allow SendableCameraWrappers to take arbitrary URLs (#3850)
Useful for adding cameras that are streamed from a coprocessor

Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
Co-authored-by: Sam Carlberg <sam.carlberg@gmail.com>
2022-01-01 10:10:37 -08:00
Peter Johnson
a3645dea34 LICENSE: Bump year range to include 2022 (#3854) 2022-01-01 00:00:16 -08:00
Jan-Felix Abellera
7c09f44898 [wpilib] Use PSI for compressor config and sensor reading (#3847)
This adds the REV Analog Pressure Sensor PSI to volt (and vice versa) conversion to allow setting the compressor config in PSI and getting the sensor reading in PSI. Also adds input validation for pressure values at the higher level.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2021-12-31 21:04:56 -08:00
Peter Johnson
f401ea9aae [wpigui] Remove wpiutil linkage (#3851)
It was only being used for fs::remove() (added in #3463), which is easily
replaced by std::remove().

The code change does not affect the WPILib tools, as this code is not used when JSON save files are used.
2021-12-31 07:56:31 -08:00
Peter Johnson
bf8517f1e6 [wpimath] TimeInterpolatableBufferTest: Fix lint warnings (#3849) 2021-12-31 00:06:08 -08:00
David Vo
528087e308 [hal] Use enums with fixed underlying type in clang C (#3297)
This will allow static analysis tools that use clang to always determine the correct intended parameter types for HAL functions.
2021-12-30 21:20:05 -08:00
Thad House
1f59ff72f9 [wpilib] Add ADIS IMUs (#3777)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: Matteo Kimura <mateus.sakata@gmail.com>
2021-12-30 19:43:53 -08:00
Matt
315be873c4 [wpimath] Add TimeInterpolatableBuffer (#2695)
These classes are useful for storing previous robot positions to use in conjunction with the upcoming pose estimators.

Co-authored-by: Prateek Machiraju <prateek.machiraju@gmail.com>
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: cttew <cttewari@gmail.com>
2021-12-30 19:08:05 -08:00
Oblarg
b8d019cdb4 [wpilib] Rename NormalizeWheelSpeeds to DesaturateWheelSpeeds (#3791) 2021-12-30 18:30:08 -08:00
Peter Johnson
102f23bbdb [wpilibj] DriverStation: Set thread interrupted state (#3846)
This is a Java best practice when catching InterruptedException.
2021-12-30 13:13:52 -08:00
Kevin-OConnor
b85c24a79c [wpilib] Add warning about onboard I2C (#3842) 2021-12-30 13:13:03 -08:00
Oblarg
eee29daaf9 [newCommands] Trigger: Allow override of debounce type (#3845)
Previously Trigger could only be debounced on rising edges.
This change preserves the default behavior but adds the capability to override it.
2021-12-29 16:10:43 -08:00
Oblarg
aa9dfabde2 [wpimath] Move debouncer to filters (#3838) 2021-12-28 09:49:41 -08:00
Peter Johnson
5999a26fba [wpiutil] Add GetSystemTime() (#3840)
This portably gets the time in microseconds since the Unix epoch.
2021-12-27 23:06:31 -08:00
sciencewhiz
1e82595ffb [examples] Fix arcade inversions (#3841)
Accounts for differences between ArcadeDrive and the methods used
in some other examples.
2021-12-27 23:05:42 -08:00
Peter Johnson
e373fa476b [wpiutil] Add disableMockTime to JNI (#3839)
This exposes the equivalent of SetNowImpl(nullptr) to Java.
2021-12-27 09:51:32 -08:00
sciencewhiz
dceb5364f4 [examples] Ensure right side motors are inverted (#3836)
Fixes #3827
Adds MotorController inversion for right side, removes inversion in
setVoltage methods.

Also fixes various XboxController negations (was inconsistent throughout examples).
2021-12-26 19:25:26 -08:00
Oblarg
baacbc8e24 [wpilib] Tachometer: Add function to return RPS (#3833) 2021-12-26 15:52:18 -08:00
Austin Shalit
84b15f0883 [templates] Add Java Romi Educational template (#3837)
This is a combination of a Romi Gradle project and Educational robot (added in #3309)
2021-12-26 15:46:22 -08:00
Dalton Smith
c0da9d2d35 [examples] Invert Right Motor in Romi Java examples (#3828) 2021-12-26 15:42:53 -08:00
sciencewhiz
0fe0be2733 [build] Change project year to intellisense (#3835)
This means VSCode won't prompt to upgrade (added in beta 4)
2021-12-25 20:50:06 -06:00
Tyler Veness
eafa947338 [wpimath] Make copies of trajectory constraint arguments (#3832)
This avoids stack-use-after-scope bugs in code like the following when
the original argument goes out of scope:
```cpp
frc2::Command* RobotContainer::GetAutonomousCommand() {
  // Create a voltage constraint to ensure we don't accelerate too fast
  frc::DifferentialDriveVoltageConstraint autoVoltageConstraint(
      frc::SimpleMotorFeedforward<units::meters>(
          DriveConstants::ks, DriveConstants::kv, DriveConstants::ka),
      DriveConstants::kDriveKinematics, 10_V);
```
2021-12-25 07:19:43 -06:00
sciencewhiz
9d13ae8d01 [wpilib] Add notes for Servo get that it only returns cmd (NFC) (#3820)
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2021-12-23 22:22:18 -08:00
Tyler Veness
2a64e4bae5 [wpimath] Give drivetrain a more realistic width in TrajectoryJsonTest.java (#3822)
Fixes #3819.
2021-12-22 22:28:23 -08:00
Tyler Veness
c3fd20db59 [wpilib] Fix trajectory sampling in DifferentialDriveSim test (#3821)
Also rename C++ test file to match class name.

Fixes #3818.
2021-12-22 22:27:51 -08:00
WarrenReynolds
6f91f37cd0 [examples] Fix SwerveControllerCommand order of Module States (#3815)
DriveSubsystem::SetModulesStates applies module state to incorrect modules.

Fixes #3814.
2021-12-22 12:26:02 -08:00
Peter Johnson
5158730b81 [wpigui] Upgrade to imgui 1.86, GLFW 3.3.6 (#3817)
The GLFW upgrade fixes gamepads not being mapped at startup.
2021-12-22 12:24:36 -08:00
Thad House
2ad2d2ca96 [wpiutil] MulticastServiceResolver: Fix C array returning functions (#3816) 2021-12-22 09:52:57 -08:00
Starlight220
b5fd29774f [wpilibj] Trigger: implement BooleanSupplier interface (#3811) 2021-12-21 11:33:16 -08:00
sciencewhiz
9f8f330e96 [wpilib] Fix Mecanum and SwerveControllerCommand when desired rotation passed (#3808) 2021-12-19 20:08:28 -08:00
Tyler Veness
1ad3b1b333 [hal] Don't copy byte to where null terminator goes (#3807)
Fixes the following compiler warning:
```
/__w/allwpilib/allwpilib/hal/src/main/native/sim/Notifier.cpp:323:21: error: 'char* strncpy(char*, const char*, size_t)' specified bound 64 equals destination size [-Werror=stringop-truncation]
         std::strncpy(arr[num].name, notifier->name.c_str(),
         ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                      sizeof(arr[num].name));
                      ~~~~~~~~~~~~~~~~~~~~~~
```
2021-12-19 16:46:12 -08:00
Thad House
dfc24425c3 [build] Fix gazebo gradle IDE warnings (#3806)
Also add note on how to generate all files for the IDE.
2021-12-19 14:20:08 -08:00
Thad House
c02577bb51 [glass] Configure delay loading for windows camera server support (#3803)
We don't currently support cameras in glass, but it's something we want to do in the future. However, when we do this, glass will completely stop working on N builds of windows, and it would fail to load at all with no messages. To solve this, we can delayload the media foundation dlls that are missing. The executable will then launch even without the dlls present, and we can attempt to load them at runtime and dynamically disable camera support.

When we get around to implementing it, we can just call HasCameraSupport, and dynamically hide all camera related code behind that flag.
2021-12-19 14:19:24 -08:00
sciencewhiz
c9e6a96a61 [wpilib] Document range of Servo angle (NFC) (#3796) 2021-12-19 13:53:31 -08:00
Thad House
9778626f34 [wpilib, hal] Add support for getting faults and versions from power distribution (#3794) 2021-12-19 13:42:49 -08:00
Thad House
34b2d0dae1 [wpilib, hal] High Level REV PH changes (#3792)
More functionality was implemented at the HAL level, so expose that to the wpilib level.

This also does units changes for all the PH related functionality.
2021-12-19 13:41:35 -08:00
Thad House
59a7528fd6 [cscore] Fix crash when usbcamera is deleted before message pump thread fully starts (#3804)
shared_from_this will assert if the shared pointer is in the middle of being destructed. Because we access shared_from_this in the message pump, this can easily occur. The solution is to grab the weak pointer, manually attempt to lock it, and only continue if that succeeds. The message pump is already synchronized to the usb camera being destructed, so this is a fine behavior.
2021-12-19 13:39:23 -08:00
Thad House
11d9859ef1 [build] Update plugins to remove log4j vulnerabilities (#3805) 2021-12-19 13:38:48 -08:00
Peter Johnson
e44ed752ad [glass] Fix CollapsingHeader in Encoder, PCM, and DeviceTree (#3797)
The new storage approach was attempting to save both the name and the
open status to the same storage key.
2021-12-19 07:35:12 -08:00
Thad House
52b2dd5b89 [build] Bump native utils to remove log4j (#3802) 2021-12-19 07:33:12 -08:00
sciencewhiz
c46636f218 [wpilib] Improve new counter classes documentation (NFC) (#3801) 2021-12-18 21:40:03 -08:00
sciencewhiz
dc531462e1 [build] Update to gradle 7.3.2 (#3800) 2021-12-18 21:34:35 -08:00
Tyler Veness
92ba98621c [wpimath] Add helper variable templates for units type traits (#3790) 2021-12-18 11:32:32 -08:00
sciencewhiz
d41d051f1b [wpilibc] Fix Mecanum & Swerve ControllerCommand lambda capture (#3795)
Fixes #3765
Also fixes SwerveControllerCommand example calling command twice.
2021-12-18 11:30:57 -08:00
sciencewhiz
c5ae0effac OtherVersions.md: Add one missing case of useLocal (#3788) 2021-12-15 20:30:34 -08:00
Tyler Veness
b3974c6ed3 [wpimath] Upgrade to Drake v0.37.0 (#3786) 2021-12-14 06:41:38 -08:00
Peter Johnson
589a00e379 [wpilibc] Start DriverStation thread from RobotBase (#3785)
With the change from GetInstance to static functions, many functions
don't call DriverStation::GetInstance(), so the DS thread wasn't
getting started by default.  Call InDisabled() to make sure this
happens.
2021-12-12 22:23:13 -08:00
Tyler Veness
8d9836ca02 [wpilib] Improve curvature drive documentation (NFC) (#3783) 2021-12-12 17:59:04 -08:00
Peter Johnson
8b5bf8632e [myRobot] Add wpimath and wpiutil JNI (#3784) 2021-12-12 17:57:52 -08:00
sciencewhiz
1846114491 [examples] Update references from characterization to SysId (NFC) (#3782) 2021-12-11 21:25:43 -08:00
Thad House
2c461c794e [build] Update to gradle 7.3 (#3778) 2021-12-10 21:26:28 -08:00
Jan-Felix Abellera
109363daa4 [hal] Add remaining driver functions for REVPH (#3776)
Add the remaining HAL functions needed to fully support the Pneumatic Hub and its latest firmware.

- Clear sticky faults
- Get device voltage
- Get 5v supply voltage (used for analog to PSI calculation)
- Get solenoid voltage
- Get solenoid current
- Get device firmware and hardware version

Some minor refactoring was done for naming of some internal functions for consistency purposes.
2021-12-09 12:29:09 -08:00
Jan-Felix Abellera
41d26bee8d [hal] Refactor REV PDH (#3775)
Refactors retrieving the faults from the device to match the implementation that we have for the Pneumatic Hub. Instead of having a getter function for each fault, there is a single function to get all faults (sticky or normal) for use with the higher level API

Renames functions to be consistent

Removes some functions that don't need to be included in wpilib:
- Identify device - this just flashes the module LED on the device and has no use in wpilib
- Is PDH enabled - the PDH does not change state depending on robot enabled state

PDH frame and signal names were updated in our DBC, and this PR makes use of the newly generated CAN frame helper functions
2021-12-09 12:27:06 -08:00
Tyler Veness
7269a170fb Upgrade maven deps to latest versions and fix new linter errors (#3772)
This also makes the Gradle build work with JDK 17.

The extra JVM args in gradle.properties works around a bug with spotless
and JDK 17: https://github.com/diffplug/spotless/issues/834

PMD.CloseResource was ignored because it's almost always a false
positive, and there are many of them.
2021-12-09 12:20:08 -08:00
Thad House
441f2ed9b0 [build] actions: use fixed image versions instead latest (#3761) 2021-12-09 12:12:59 -08:00
Modelmat
15275433d4 [examples] Fix duplicate port allocations in C++ SwerveBot/SwerveDrivePoseEstimator/RomiReference (#3773)
- Remove duplicate motor port (2) from C++ SwerveBot/SwerveDrivePoseEstimator

Java has the correct motor ports.

- Fix duplicate port allocation in C++ RomiReference by correcting if/else check

Java logic was already correct, and confirms this change.
2021-12-06 21:08:34 -08:00
Jason Daming
1ac02d2f58 [examples] Fix drive Joystick axes in several examples (#3769) 2021-12-06 16:40:10 -08:00
Jason Daming
8ee6257e92 [wpilib] DifferentialDrivetrainSim.KitbotMotor: Add NEO and Falcon 500 (#3762) 2021-12-06 14:46:58 -08:00
Prateek Machiraju
d81ef2bc5c [wpilib] Fix deadlocks in Mechanism2d et al. (#3770)
UpdateEntries() and Flush() are called from methods that lock the mutex,
so locking it again will cause deadlocks. This also updates the Java
code to make MechanismObject2d::update synchronized like in the C++
version.
2021-12-06 14:42:02 -08:00
Tyler Veness
acb64dff97 [wpimath] Make RamseteController::Calculate() more concise (#3763) 2021-12-06 12:57:42 -08:00
Jan-Felix Abellera
3f6cf76a8c [hal] Refactor REV PH CAN frames (#3756) 2021-12-06 10:08:57 -08:00
Peter Johnson
3ef2dab465 [wpilib] DutyCycleEncoder: add setting of duty cycle range (#3759)
As the sensor needs to maintain an actual duty cycle, it can't go all
the way from 0-100, so provide a way to set the min and max and linearly
map between the two.
2021-12-05 14:28:08 -08:00
sciencewhiz
a5a56dd067 Readme: Add Visual Studio 2022 (#3760) 2021-12-05 14:27:37 -08:00
Tyler Veness
04957a6d30 [wpimath] Fix units of RamseteController's b and zeta (#3757)
Fixes #3755.
2021-12-03 18:21:30 -08:00
Peter Johnson
5da54888f8 [glass] Upgrade imgui to 0.85, implot to HEAD, glfw to 3.3.5 (#3754)
This in particular upgrades the plot widget with a few new features
and makes more plot configuration persistent.
2021-12-03 17:23:18 -08:00
Thad House
6c93365b0f [wpiutil] MulticastService cleanup (#3750)
Fix duplicated constructors, and also use simpler utf conversion API on windows.
2021-12-02 21:06:55 -08:00
Thad House
1c4a8bfb66 [cscore] Cleanup Windows USB camera impl (#3751)
- Use wpiutil string conversion rather than codecvt (which is deprecated).
- Force A function types.
2021-12-02 13:48:03 -08:00
Thad House
d51a1d3b3d [rtns] Fix icon (#3749) 2021-11-30 21:58:58 -08:00
Thad House
aced2e7da6 Add roboRIO Team Number Setter tool (#3744) 2021-11-30 11:17:30 -08:00
Thad House
fa1ceca83a [wpilibj] Use DS cache for iterative robot control word cache (#3748)
The root cause of #3747 is CommandScheduler's ds state checks are behind iterative robots checks. This means that the iterative robot state could return enabled, but the DS cache could still be reporting disabled. This results in a race in the Disabled -> Enabled transition, which manifests in commands not running.

Previously, iterative robot base pulled from the DS cache. This meant that the ds cache was always updated before an iterative robot base loop could run. This still had a race, but this could only occur on the Enabled -> Disable transition, which is much less noticeable and would usually just result in a command running for an extra loop.

We can move back to the old behavior by grabbing the new iterative robot base check variables to use the DS cache.
2021-11-29 20:56:58 -08:00
sciencewhiz
0ea05d34e6 [build] Update to gradle 7.2 (#3746) 2021-11-29 20:53:26 -08:00
Thad House
09db4f672b [build] Update to native utils 2022.6.1 (#3745) 2021-11-29 13:04:08 -08:00
Thad House
4ba80a3a8c [wpigui] Don't recursively render frames in size callback (#3743)
WindowSizeCallback can sometimes be called while doing a render. If this occurs, imgui asserts. Avoid this case.
2021-11-28 01:03:40 -08:00
Peter Johnson
ae208d2b17 [wpiutil] StringExtras: Add substr() (#3742)
Unlike std::string and std::string_view, this substr() allows a start
greater than the length of the string, in which case an empty string
is returned.  This matches llvm::StringRef behavior.
2021-11-27 21:31:40 -08:00
Thad House
6f51cb3b98 [wpiutil] MulticastResolver: make event manual reset, change to multiple read (#3736) 2021-11-27 11:16:24 -08:00
Peter Johnson
f6159ee1a2 [glass] Fix Drive widget handling of negative rotation (#3739)
This would crash in debug mode due to an imgui assertion in PathArcTo.
2021-11-27 10:58:45 -08:00
Thad House
7f401ae895 [build] Update NI libraries to 2022.2.3 (#3738) 2021-11-27 09:43:07 -08:00
Peter Johnson
0587b7043a [glass] Use JSON files for storage instead of imgui ini
Storage is now nested.

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

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

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

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

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

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

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

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

View File

@@ -12,7 +12,7 @@ jobs:
name: Linux
container: wpilib/roborio-cross-ubuntu:2022-20.04
flags: ""
- os: macos-latest
- os: macOS-11
name: macOS
container: ""
flags: "-DWITH_JAVA=OFF"
@@ -43,7 +43,7 @@ jobs:
build-vcpkg:
name: "Build - Windows"
runs-on: windows-latest
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Prepare vcpkg

View File

@@ -44,13 +44,13 @@ jobs:
fail-fast: false
matrix:
include:
- os: windows-latest
- os: windows-2019
artifact-name: Win64
architecture: x64
- os: windows-latest
- os: windows-2019
artifact-name: Win32
architecture: x86
- os: macos-latest
- os: macOS-11
artifact-name: macOS
architecture: x64
name: "Build - ${{ matrix.artifact-name }}"

View File

@@ -11,6 +11,7 @@ cppSrcFileInclude {
modifiableFileExclude {
\.patch$
gradlew
}
generatedFileExclude {

View File

@@ -1,6 +1,6 @@
{
"enableCppIntellisense": true,
"currentLanguage": "cpp",
"projectYear": "2021",
"projectYear": "intellisense",
"teamNumber": 0
}

View File

@@ -6,11 +6,17 @@ FATAL: In-source builds are not allowed.
")
endif()
if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
set(CMAKE_SYSTEM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
endif()
project(allwpilib)
cmake_minimum_required(VERSION 3.3.0)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
message(STATUS "Platform version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
INCLUDE(CPack)
@@ -139,6 +145,8 @@ if (USE_VCPKG_EIGEN)
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
endif()
find_package(LIBSSH 0.7.1)
if (WITH_FLAT_INSTALL)
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
set(NTCORE_DEP_REPLACE "include($\{SELF_DIR\}/ntcore-config.cmake)")
@@ -239,7 +247,6 @@ if (WITH_TESTS)
include(GoogleTest)
endif()
add_subdirectory(fieldImages)
add_subdirectory(wpiutil)
add_subdirectory(ntcore)
@@ -248,10 +255,14 @@ if (WITH_WPIMATH)
endif()
if (WITH_GUI)
add_subdirectory(fieldImages)
add_subdirectory(imgui)
add_subdirectory(wpigui)
add_subdirectory(glass)
add_subdirectory(outlineviewer)
if (LIBSSH_FOUND)
add_subdirectory(roborioteamnumbersetter)
endif()
endif()
if (WITH_WPILIB OR WITH_SIMULATION_MODULES)

View File

@@ -1,4 +1,4 @@
Copyright (c) 2009-2021 FIRST and other WPILib contributors
Copyright (c) 2009-2022 FIRST and other WPILib contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -69,15 +69,36 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* wpiutil
* wpigui
* imgui
* ntcore
* wpiutil
* wpimath
* wpiutil
* glass/libglass
* wpiutil
* wpimath
* wpigui
* glass/libglassnt
* wpiutil
* ntcore
* wpimath
* wpigui
* hal
* wpiutil
* halsim
* imgui
* wpiutil
* ntcore
* wpiutil
* ntcore
* wpimath
* wpigui
* libglass
* libglassnt
* cscore
* opencv
@@ -101,6 +122,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cameraserver
* ntcore
* cscore
* wpimath
* wpiutil
* wpilibNewCommands
@@ -109,9 +131,10 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cameraserver
* ntcore
* cscore
* wpimath
* wpiutil
* wpilibNewCommands
* wpilibOldCommands
* wpilibc
* hal
* cameraserver
@@ -119,6 +142,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cscore
* wpiutil
### Third Party Artifacts
This repository provides the builds of the following third party software.
@@ -128,3 +152,4 @@ All artifacts are based at `edu.wpi.first.thirdparty.frcYEAR` in the repository.
* googletest
* imgui
* opencv
* libssh

View File

@@ -11,6 +11,7 @@ Development builds are the per-commit build hosted everytime a commit is pushed
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version.
```groovy
wpi.maven.useLocal = false
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = 'YEAR.+'
wpi.versions.wpimathVersion = 'YEAR.+

View File

@@ -39,7 +39,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
- On macOS, install the JDK 11 .pkg from the link above
- C++ compiler
- On Linux, install GCC 8 or greater
- On Windows, install [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio 2019)
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
- ARM compiler toolchain
- Run `./gradlew installRoboRioToolchain` after cloning this repository
@@ -71,6 +71,8 @@ The gradlew wrapper only exists in the root of the main project, so be sure to r
There are a few tasks other than `build` available. To see them, run the meta-task `tasks`. This will print a list of all available tasks, with a description of each task.
If opening from a fresh clone, generated java dependencies will not exist. Most IDEs will not run the generation tasks, which will cause lots of IDE errors. Manually run `./gradlew compileJava` from a terminal to run all the compile tasks, and then refresh your IDE's configuration (In VS Code open settings.gradle and save).
### Faster builds
`./gradlew build` builds _everything_, which includes debug and release builds for desktop and all installed cross compilers. Many developers don't need or want to build all of this. Therefore, common tasks have shortcuts to only build necessary components for common development and testing tasks.

View File

@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.hubspot.jinjava:jinjava:2.5.8'
classpath 'com.hubspot.jinjava:jinjava:2.6.0'
}
}
@@ -15,13 +15,13 @@ plugins {
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
id 'edu.wpi.first.NativeUtils' apply false
id 'edu.wpi.first.GradleJni' version '1.0.0'
id 'edu.wpi.first.GradleVsCode' version '1.0.0'
id 'edu.wpi.first.GradleVsCode'
id 'idea'
id 'visual-studio'
id 'net.ltgt.errorprone' version '1.1.1' apply false
id 'com.github.johnrengelman.shadow' version '5.2.0' apply false
id 'com.diffplug.spotless' version '5.5.0' apply false
id 'com.github.spotbugs' version '5.0.0-beta.1' apply false
id 'net.ltgt.errorprone' version '2.0.2' apply false
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'com.diffplug.spotless' version '6.1.2' apply false
id 'com.github.spotbugs' version '5.0.4' apply false
}
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
@@ -147,5 +147,5 @@ ext.getCurrentArch = {
}
wrapper {
gradleVersion = '7.1.1'
gradleVersion = '7.3.3'
}

View File

@@ -5,5 +5,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2022.3.1"
implementation "edu.wpi.first:native-utils:2022.7.1"
}

View File

@@ -27,7 +27,7 @@ repositories {
}
dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.google.code.gson:gson:2.8.9'
implementation project(':wpiutil')
implementation project(':ntcore')

View File

@@ -56,9 +56,9 @@ public final class Main {
public JsonObject config;
}
static int team;
static boolean server;
static List<CameraConfig> cameras = new ArrayList<>();
private static int team;
private static boolean server;
private static List<CameraConfig> cameras = new ArrayList<>();
private Main() {}

View File

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

View File

@@ -0,0 +1,116 @@
# - Try to find LibSSH
# Once done this will define
#
# LIBSSH_FOUND - system has LibSSH
# LIBSSH_INCLUDE_DIRS - the LibSSH include directory
# LIBSSH_LIBRARIES - link these to use LibSSH
# LIBSSH_VERSION -
#
# Author Michal Vasko <mvasko@cesnet.cz>
# Copyright (c) 2020 CESNET, z.s.p.o.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
include(FindPackageHandleStandardArgs)
if(LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS)
# in cache already
set(LIBSSH_FOUND TRUE)
else()
find_path(LIBSSH_INCLUDE_DIR
NAMES
libssh/libssh.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
${CMAKE_INCLUDE_PATH}
${CMAKE_INSTALL_PREFIX}/include
)
find_library(LIBSSH_LIBRARY
NAMES
ssh.so
libssh.so
libssh.dylib
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)
if(LIBSSH_INCLUDE_DIR AND LIBSSH_LIBRARY)
# learn libssh version
if(EXISTS ${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h)
set(LIBSSH_HEADER_PATH ${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h)
else()
set(LIBSSH_HEADER_PATH ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h)
endif()
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_MAJOR
REGEX "#define[ ]+LIBSSH_VERSION_MAJOR[ ]+[0-9]+")
if(NOT LIBSSH_VERSION_MAJOR)
message(STATUS "LIBSSH_VERSION_MAJOR not found, assuming libssh is too old and cannot be used!")
set(LIBSSH_INCLUDE_DIR "LIBSSH_INCLUDE_DIR-NOTFOUND")
set(LIBSSH_LIBRARY "LIBSSH_LIBRARY-NOTFOUND")
else()
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MAJOR ${LIBSSH_VERSION_MAJOR})
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_MINOR
REGEX "#define[ ]+LIBSSH_VERSION_MINOR[ ]+[0-9]+")
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MINOR ${LIBSSH_VERSION_MINOR})
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_PATCH
REGEX "#define[ ]+LIBSSH_VERSION_MICRO[ ]+[0-9]+")
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_PATCH ${LIBSSH_VERSION_PATCH})
set(LIBSSH_VERSION ${LIBSSH_VERSION_MAJOR}.${LIBSSH_VERSION_MINOR}.${LIBSSH_VERSION_PATCH})
if(LIBSSH_VERSION VERSION_LESS 0.8.0)
# libssh_threads also needs to be linked for these versions
string(REPLACE "libssh.so" "libssh_threads.so"
LIBSSH_THREADS_LIBRARY
${LIBSSH_LIBRARY}
)
string(REPLACE "libssh.dylib" "libssh_threads.dylib"
LIBSSH_THREADS_LIBRARY
${LIBSSH_THREADS_LIBRARY}
)
string(REPLACE "ssh.so" "ssh_threads.so"
LIBSSH_THREADS_LIBRARY
${LIBSSH_THREADS_LIBRARY}
)
endif()
endif()
endif()
set(LIBSSH_INCLUDE_DIRS ${LIBSSH_INCLUDE_DIR})
set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY} ${LIBSSH_THREADS_LIBRARY})
mark_as_advanced(LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES)
find_package_handle_standard_args(LibSSH FOUND_VAR LIBSSH_FOUND
REQUIRED_VARS LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES
VERSION_VAR LIBSSH_VERSION)
endif()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@
#include <windowsx.h>
#include <cmath>
#include <codecvt>
#include <memory>
#include <string>
#include <vector>
@@ -25,7 +24,9 @@
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <wpi/ConvertUTF.h>
#include <wpi/MemAlloc.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/timestamp.h>
@@ -72,8 +73,9 @@ UsbCameraImpl::UsbCameraImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
std::string_view path)
: SourceImpl{name, logger, notifier, telemetry}, m_path{path} {
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
m_widePath = utf8_conv.from_bytes(m_path.c_str());
wpi::SmallVector<wchar_t, 128> wideStorage;
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
m_deviceId = -1;
StartMessagePump();
}
@@ -227,7 +229,7 @@ void UsbCameraImpl::PostRequestNewFrame() {
bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
bool* connected) {
DEV_BROADCAST_DEVICEINTERFACE* pDi = NULL;
DEV_BROADCAST_DEVICEINTERFACE_A* pDi = NULL;
*connected = false;
@@ -240,9 +242,9 @@ bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
// Compare the device name with the symbolic link.
pDi = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(pHdr);
pDi = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE_A*>(pHdr);
if (_stricmp(m_path.c_str(), pDi->dbcc_name) == 0) {
if (wpi::equals_lower(m_path, pDi->dbcc_name)) {
if (wParam == DBT_DEVICEARRIVAL) {
*connected = true;
return true;
@@ -269,7 +271,7 @@ void UsbCameraImpl::DeviceDisconnect() {
static bool IsPercentageProperty(std::string_view name) {
if (wpi::starts_with(name, "raw_"))
name = name.substr(4);
name = wpi::substr(name, 4);
return name == "Brightness" || name == "Contrast" || name == "Saturation" ||
name == "Hue" || name == "Sharpness" || name == "Gain" ||
name == "Exposure";
@@ -411,11 +413,12 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
// If has device ID, use the device ID from the event
// because of windows bug
auto&& device = devices[m_deviceId];
DEV_BROADCAST_DEVICEINTERFACE* pDi =
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(parameter);
DEV_BROADCAST_DEVICEINTERFACE_A* pDi =
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE_A*>(parameter);
m_path = pDi->dbcc_name;
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
m_widePath = utf8_conv.from_bytes(m_path.c_str());
wpi::SmallVector<wchar_t, 128> wideStorage;
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
} else {
// This device not found
break;
@@ -482,9 +485,16 @@ bool UsbCameraImpl::DeviceConnect() {
const wchar_t* path = m_widePath.c_str();
m_mediaSource = CreateVideoCaptureDevice(path);
if (!m_mediaSource)
if (!m_mediaSource) {
return false;
m_imageCallback = CreateSourceReaderCB(shared_from_this(), m_mode);
}
auto weakThis = weak_from_this();
auto sharedThis = weakThis.lock();
if (sharedThis) {
m_imageCallback = CreateSourceReaderCB(sharedThis, m_mode);
} else {
return false;
}
m_sourceReader =
CreateSourceReader(m_mediaSource.Get(), m_imageCallback.Get());
@@ -747,8 +757,9 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
{
std::scoped_lock lock(m_mutex);
m_path = msg->dataStr;
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
m_widePath = utf8_conv.from_bytes(m_path.c_str());
wpi::SmallVector<wchar_t, 128> wideStorage;
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
}
DeviceDisconnect();
DeviceConnect();
@@ -1048,7 +1059,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
// Ensure we are initialized by grabbing the message pump
// GetMessagePump();
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
wpi::SmallString<128> storage;
WCHAR buf[512];
ComPtr<IMFAttributes> pAttributes;
IMFActivate** ppDevices = nullptr;
UINT32 count = 0;
@@ -1080,14 +1092,19 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
for (UINT32 i = 0; i < count; i++) {
UsbCameraInfo info;
info.dev = i;
WCHAR buf[512];
UINT32 characters = 0;
ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buf,
sizeof(buf) / sizeof(WCHAR), NULL);
info.name = utf8_conv.to_bytes(buf);
sizeof(buf) / sizeof(WCHAR), &characters);
storage.clear();
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
info.name = storage.string();
ppDevices[i]->GetString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
sizeof(buf) / sizeof(WCHAR), NULL);
info.path = utf8_conv.to_bytes(buf);
sizeof(buf) / sizeof(WCHAR), &characters);
storage.clear();
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
info.path = storage.string();
// Try to parse path from symbolic link
ParseVidAndPid(info.path, &info.productId, &info.vendorId);

View File

@@ -89,7 +89,7 @@ static std::shared_ptr<ClassHolder> GetClassHolder() {
WindowsMessagePump::WindowsMessagePump(
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback) {
m_callback = callback;
auto handle = CreateEvent(NULL, true, false, NULL);
auto handle = CreateEventA(NULL, true, false, NULL);
m_mainThread = std::thread([=] { ThreadMain(handle); });
auto waitResult = WaitForSingleObject(handle, 1000);
if (waitResult == WAIT_OBJECT_0) {
@@ -98,7 +98,7 @@ WindowsMessagePump::WindowsMessagePump(
}
WindowsMessagePump::~WindowsMessagePump() {
auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL);
auto res = SendMessageA(hwnd, WM_CLOSE, NULL, NULL);
if (m_mainThread.joinable())
m_mainThread.join();
}
@@ -110,28 +110,28 @@ void WindowsMessagePump::ThreadMain(HANDLE eventHandle) {
MFStartup(MF_VERSION);
auto classHolder = GetClassHolder();
hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0,
HWND_MESSAGE, NULL, NULL, this);
hwnd = CreateWindowExA(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0,
0, HWND_MESSAGE, NULL, NULL, this);
// Register for device notifications
HDEVNOTIFY g_hdevnotify = NULL;
HDEVNOTIFY g_hdevnotify2 = NULL;
DEV_BROADCAST_DEVICEINTERFACE di = {0};
DEV_BROADCAST_DEVICEINTERFACE_A di = {0};
di.dbcc_size = sizeof(di);
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
di.dbcc_classguid = KSCATEGORY_CAPTURE;
g_hdevnotify =
RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
RegisterDeviceNotificationA(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
DEV_BROADCAST_DEVICEINTERFACE di2 = {0};
DEV_BROADCAST_DEVICEINTERFACE_A di2 = {0};
di2.dbcc_size = sizeof(di2);
di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
g_hdevnotify2 =
RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
RegisterDeviceNotificationA(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
SetEvent(eventHandle);

View File

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

View File

@@ -15,10 +15,14 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra
ext {
nativeName = 'fieldImages'
baseId = nativeName
groupId = 'edu.wpi.first.fieldImages'
devMain = "edu.wpi.first.fieldImages.DevMain"
}
apply from: "${rootDir}/shared/resources.gradle"
apply from: "${rootDir}/shared/config.gradle"
apply from: "${rootDir}/shared/java/javacommon.gradle"
def generateTask = createGenerateResourcesTask('main', 'FIELDS', 'fields', project)

View File

@@ -1,14 +1,14 @@
apply plugin: 'maven-publish'
def baseArtifactId = 'fieldImages'
def artifactGroupId = 'edu.wpi.first.fieldImages'
def zipBaseName = '_GROUP_edu_wpi_first_field_images_ID_CLS'
def baseArtifactId = project.nativeName
def artifactGroupId = project.groupId
def cppZipBaseName = "_GROUP_edu_wpi_first_fieldIimages_ID_${baseArtifactId}-cpp_CLS"
def outputsFolder = file("$project.buildDir/outputs")
task cppSourcesZip(type: Zip) {
destinationDirectory = outputsFolder
archiveBaseName = zipBaseName
archiveBaseName = cppZipBaseName
classifier = "sources"
from(licenseFile) {
@@ -25,7 +25,7 @@ task cppSourcesZip(type: Zip) {
task cppHeadersZip(type: Zip) {
destinationDirectory = outputsFolder
archiveBaseName = zipBaseName
archiveBaseName = cppZipBaseName
classifier = "headers"
from(licenseFile) {
@@ -51,7 +51,7 @@ addTaskToCopyAllOutputs(cppSourcesZip)
model {
publishing {
def wpilibCTaskList = createComponentZipTasks($.components, ['fieldImages'], zipBaseName, Zip, project, includeStandardZipFormat)
def wpilibCTaskList = createComponentZipTasks($.components, ['fieldImages'], cppZipBaseName, Zip, project, includeStandardZipFormat)
publications {
cpp(MavenPublication) {
@@ -62,7 +62,7 @@ model {
artifact cppHeadersZip
artifact cppSourcesZip
artifactId = baseArtifactId
artifactId = "${baseArtifactId}-cpp"
groupId artifactGroupId
version wpilibVersioning.version.get()
}

View File

@@ -20,4 +20,6 @@ public class FieldImages {
public static final String k2021GalacticSearchBFieldConfig =
"/edu/wpi/first/fields/2021-galacticsearchb.json";
public static final String k2021SlalomFieldConfig = "/edu/wpi/first/fields/2021-slalompath.json";
public static final String k2022RapidReactFieldConfig =
"/edu/wpi/first/fields/2022-rapidreact.json";
}

View File

@@ -0,0 +1,12 @@
// 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>
namespace fields {
std::string_view GetResource_2022_rapidreact_json();
std::string_view GetResource_2022_field_png();
} // namespace fields

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,10 @@
{
"game": "Rapid React",
"field-image": "2022-field.png",
"field-corners": {
"top-left": [74, 50],
"bottom-right": [1774, 900]
},
"field-size": [54, 27],
"field-unit": "foot"
}

View File

@@ -185,15 +185,18 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra
it.buildable = false
return
}
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib library: 'glassnt', linkage: 'static'
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'opencv_static')
nativeUtils.useRequiredLibrary(it, 'imgui_static')
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
it.linker.args << '/DELAYLOAD:MF.dll' << '/DELAYLOAD:MFReadWrite.dll' << '/DELAYLOAD:MFPlat.dll' << '/delay:nobind'
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
} else {

View File

@@ -0,0 +1,52 @@
// 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 "camerasupport.h"
#ifdef _WIN32
#include "Windows.h"
#include "delayimp.h"
#pragma comment(lib, "delayimp.lib")
static int CheckDelayException(int exception_value) {
if (exception_value ==
VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
exception_value ==
VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND)) {
// This example just executes the handler.
return EXCEPTION_EXECUTE_HANDLER;
}
// Don't attempt to handle other errors
return EXCEPTION_CONTINUE_SEARCH;
}
static bool TryDelayLoadAllImports(LPCSTR szDll) {
__try {
HRESULT hr = __HrLoadAllImportsForDll(szDll);
if (FAILED(hr)) {
return false;
}
} __except (CheckDelayException(GetExceptionCode())) {
return false;
}
return true;
}
namespace glass {
bool HasCameraSupport() {
bool hasCameraSupport = false;
hasCameraSupport = TryDelayLoadAllImports("MF.dll");
if (hasCameraSupport) {
hasCameraSupport = TryDelayLoadAllImports("MFPlat.dll");
}
if (hasCameraSupport) {
hasCameraSupport = TryDelayLoadAllImports("MFReadWrite.dll");
}
return hasCameraSupport;
}
} // namespace glass
#else
namespace glass {
bool HasCameraSupport() {
return true;
}
} // namespace glass
#endif

View File

@@ -0,0 +1,9 @@
// 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
namespace glass {
bool HasCameraSupport();
} // namespace glass

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,30 +10,23 @@
#include <fmt/format.h>
#include <wpigui.h>
#include "glass/Context.h"
#include "glass/Storage.h"
using namespace glass;
WindowManager::WindowManager(std::string_view iniName)
: m_iniSaver{iniName, this} {}
// read/write open state to ini file
void* WindowManager::IniSaver::IniReadOpen(const char* name) {
return m_manager->GetOrAddWindow(name, true);
}
void WindowManager::IniSaver::IniReadLine(void* entry, const char* lineStr) {
static_cast<Window*>(entry)->IniReadLine(lineStr);
}
void WindowManager::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
const char* typeName = GetTypeName();
for (auto&& window : m_manager->m_windows) {
window->IniWriteAll(typeName, out_buf);
}
WindowManager::WindowManager(Storage& storage) : m_storage{storage} {
storage.SetCustomApply([this] {
for (auto&& childIt : m_storage.GetChildren()) {
GetOrAddWindow(childIt.key(), true);
}
});
}
Window* WindowManager::AddWindow(std::string_view id,
wpi::unique_function<void()> display) {
auto win = GetOrAddWindow(id, false);
wpi::unique_function<void()> display,
Window::Visibility defaultVisibility) {
auto win = GetOrAddWindow(id, false, defaultVisibility);
if (!win) {
return nullptr;
}
@@ -46,8 +39,9 @@ Window* WindowManager::AddWindow(std::string_view id,
}
Window* WindowManager::AddWindow(std::string_view id,
std::unique_ptr<View> view) {
auto win = GetOrAddWindow(id, false);
std::unique_ptr<View> view,
Window::Visibility defaultVisibility) {
auto win = GetOrAddWindow(id, false, defaultVisibility);
if (!win) {
return nullptr;
}
@@ -59,7 +53,8 @@ Window* WindowManager::AddWindow(std::string_view id,
return win;
}
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk,
Window::Visibility defaultVisibility) {
// binary search
auto it = std::lower_bound(
m_windows.begin(), m_windows.end(), id,
@@ -72,7 +67,11 @@ Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
return it->get();
}
// insert before (keeps sort)
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
return m_windows
.emplace(it, std::make_unique<Window>(
m_storage.GetChild(id).GetChild("window"), id,
defaultVisibility))
->get();
}
Window* WindowManager::GetWindow(std::string_view id) {
@@ -86,8 +85,12 @@ Window* WindowManager::GetWindow(std::string_view id) {
return it->get();
}
void WindowManager::RemoveWindow(size_t index) {
m_storage.Erase(m_windows[index]->GetId());
m_windows.erase(m_windows.begin() + index);
}
void WindowManager::GlobalInit() {
wpi::gui::AddInit([this] { m_iniSaver.Initialize(); });
wpi::gui::AddWindowScaler([this](float scale) {
// scale default window positions
for (auto&& window : m_windows) {
@@ -104,7 +107,9 @@ void WindowManager::DisplayMenu() {
}
void WindowManager::DisplayWindows() {
PushStorageStack(m_storage);
for (auto&& window : m_windows) {
window->Display();
}
PopStorageStack();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@
#include "glass/Context.h"
#include "glass/DataSource.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/NameSetting.h"
using namespace glass;
@@ -19,16 +19,16 @@ static float DisplayChannel(PowerDistributionModel& pdp, int channel) {
float width = 0;
if (auto currentData = pdp.GetCurrentData(channel)) {
ImGui::PushID(channel);
auto& leftInfo = currentData->GetNameInfo();
NameSetting leftName{currentData->GetName()};
char name[64];
leftInfo.GetLabel(name, sizeof(name), "", channel);
leftName.GetLabel(name, sizeof(name), "", channel);
double val = currentData->GetValue();
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
if (currentData->InputDouble(name, &val, 0, 0, "%.3f")) {
pdp.SetCurrent(channel, val);
}
width = ImGui::GetItemRectSize().x;
leftInfo.PopupEditName(channel);
leftName.PopupEditName(channel);
ImGui::PopID();
}
return width;

View File

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

View File

@@ -51,13 +51,13 @@ bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
PushID(id);
// build label
std::string* name = GetStorage().GetStringRef("name");
std::string& name = GetStorage().GetString("name");
char label[128];
std::snprintf(label, sizeof(label), "%s###name",
name->empty() ? id : name->c_str());
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();

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -160,17 +160,17 @@ bool DeleteButton(ImGuiID id, const ImVec2& pos) {
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;
}

View File

@@ -1,62 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "glass/support/IniSaverBase.h"
#include <imgui_internal.h>
using namespace glass;
namespace {
class ImGuiSaver : public IniSaverBackend {
public:
void Register(IniSaverBase* iniSaver) override;
void Unregister(IniSaverBase* iniSaver) override;
};
} // namespace
void ImGuiSaver::Register(IniSaverBase* iniSaver) {
// hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = iniSaver->GetTypeName();
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
iniHandler.ReadOpenFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name) {
return static_cast<IniSaverBase*>(handler->UserData)->IniReadOpen(name);
};
iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* line) {
static_cast<IniSaverBase*>(handler->UserData)->IniReadLine(entry, line);
};
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
static_cast<IniSaverBase*>(handler->UserData)->IniWriteAll(out_buf);
};
iniHandler.UserData = iniSaver;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
}
void ImGuiSaver::Unregister(IniSaverBase* iniSaver) {
if (auto ctx = ImGui::GetCurrentContext()) {
auto& handlers = ctx->SettingsHandlers;
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
if (it->UserData == iniSaver) {
handlers.erase(it);
return;
}
}
}
}
static ImGuiSaver* GetSaverInstance() {
static ImGuiSaver* inst = new ImGuiSaver;
return inst;
}
IniSaverBase::IniSaverBase(std::string_view typeName, IniSaverBackend* backend)
: m_typeName(typeName), m_backend{backend ? backend : GetSaverInstance()} {}
IniSaverBase::~IniSaverBase() {
m_backend->Unregister(this);
}

View File

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

View File

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

View File

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

View File

@@ -6,42 +6,40 @@
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <imgui.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/StringMap.h>
#include "glass/Context.h"
#include "glass/support/IniSaverInfo.h"
#include "glass/support/IniSaverString.h"
#include "glass/Storage.h"
namespace glass {
class DataSource;
class DataSourceName {
class Context {
public:
DataSourceName() = default;
explicit DataSourceName(DataSource* source) : source{source} {}
Context();
Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
~Context();
bool ReadIni(std::string_view name_, std::string_view value) {
return name->ReadIni(name_, value);
}
void WriteIni(ImGuiTextBuffer* out) { name->WriteIni(out); }
std::unique_ptr<NameInfo> name{new NameInfo};
DataSource* source = nullptr;
};
struct Context {
wpi::SmallString<128> curId;
wpi::SmallVector<size_t, 32> idStack;
wpi::StringMap<std::unique_ptr<Storage>> storage;
std::vector<std::function<void()>> workspaceInit;
std::vector<std::function<void()>> workspaceReset;
std::string storageLoadDir = ".";
std::string storageAutoSaveDir = ".";
std::string storageName = "imgui";
wpi::SmallVector<Storage*, 32> storageStack;
wpi::StringMap<std::unique_ptr<Storage>> storageRoots;
wpi::StringMap<bool> deviceHidden;
IniSaverString<DataSourceName> sources{"Data Sources"};
wpi::StringMap<DataSource*> sources;
Storage& sourceNameStorage;
uint64_t zeroTime = 0;
bool isPlatformSaveDir = false;
};
extern Context* gContext;

View File

@@ -16,8 +16,6 @@
namespace glass {
class NameInfo;
/**
* A data source for numeric/boolean data.
*/
@@ -33,15 +31,9 @@ class DataSource {
const char* GetId() const { return m_id.c_str(); }
void SetName(std::string_view name);
const char* GetName() const;
NameInfo& GetNameInfo() { return *m_name; }
void PushEditNameId(int index);
void PushEditNameId(const char* name);
bool PopupEditName(int index);
bool PopupEditName(const char* name);
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
void SetName(std::string_view name) { m_name = name; }
std::string& GetName() { return m_name; }
const std::string& GetName() const { return m_name; }
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
@@ -53,8 +45,9 @@ class DataSource {
double GetValue() const { return m_value; }
// drag source helpers
void LabelText(const char* label, const char* fmt, ...) const;
void LabelTextV(const char* label, const char* fmt, va_list args) const;
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
void LabelTextV(const char* label, const char* fmt, va_list args) const
IM_FMTLIST(3);
bool Combo(const char* label, int* current_item, const char* const items[],
int items_count, int popup_max_height_in_items = -1) const;
bool SliderFloat(const char* label, float* v, float v_min, float v_max,
@@ -74,7 +67,7 @@ class DataSource {
private:
std::string m_id;
NameInfo* m_name;
std::string& m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,19 +15,21 @@
namespace glass {
class Storage;
/**
* Managed window information.
* A Window owns the View that displays the window's contents.
*/
class Window {
public:
Window() = default;
explicit Window(std::string_view id) : m_id{id}, m_defaultName{id} {}
enum Visibility { kHide = 0, kShow, kDisabled };
Window(Storage& storage, std::string_view id,
Visibility defaultVisibility = kShow);
std::string_view GetId() const { return m_id; }
enum Visibility { kHide = 0, kShow, kDisabled };
bool HasView() { return static_cast<bool>(m_view); }
void SetView(std::unique_ptr<View> view) { m_view = std::move(view); }
@@ -59,6 +61,13 @@ class Window {
*/
void SetVisibility(Visibility visibility);
/**
* Sets default visibility of window.
*
* @param visibility 0=hide, 1=show, 2=disabled (force-hide)
*/
void SetDefaultVisibility(Visibility visibility);
/**
* Sets default position of window.
*
@@ -109,17 +118,16 @@ class Window {
*/
void ScaleDefault(float scale);
void IniReadLine(const char* lineStr);
void IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf);
private:
std::string m_id;
std::string m_name;
std::string& m_name;
std::string m_defaultName;
std::unique_ptr<View> m_view;
ImGuiWindowFlags m_flags = 0;
bool m_visible = true;
bool m_enabled = true;
bool& m_visible;
bool& m_enabled;
bool& m_defaultVisible;
bool& m_defaultEnabled;
bool m_renamePopupEnabled = true;
ImGuiCond m_posCond = 0;
ImGuiCond m_sizeCond = 0;

View File

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

View File

@@ -4,19 +4,16 @@
#pragma once
#include <string_view>
#include "glass/WindowManager.h"
#include "glass/support/IniSaverBase.h"
namespace glass {
class PlotProvider : private WindowManager {
public:
explicit PlotProvider(std::string_view iniName);
explicit PlotProvider(Storage& storage);
~PlotProvider() override;
void GlobalInit() override;
using WindowManager::GlobalInit;
/**
* Pauses or unpauses all plots.
@@ -33,24 +30,6 @@ class PlotProvider : private WindowManager {
void DisplayMenu() override;
private:
void DisplayWindows() override;
class IniSaver : public IniSaverBase {
public:
explicit IniSaver(std::string_view typeName, PlotProvider* provider,
bool forSeries);
void* IniReadOpen(const char* name) override;
void IniReadLine(void* entry, const char* lineStr) override;
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
private:
PlotProvider* m_provider;
bool m_forSeries;
};
IniSaver m_plotSaver;
IniSaver m_seriesSaver;
bool m_paused = false;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1,8 @@
org.gradle.jvmargs=-Xmx1g
# The --add-exports flags work around a bug with spotless and JDK 17
# https://github.com/diffplug/spotless/issues/834
org.gradle.jvmargs=-Xmx1g \
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -28,6 +28,20 @@ public class ControlWord {
m_dsAttached = dsAttached;
}
/**
* Updates from an existing word.
*
* @param word word to update from
*/
public void update(ControlWord word) {
m_enabled = word.m_enabled;
m_autonomous = word.m_autonomous;
m_test = word.m_test;
m_emergencyStop = word.m_emergencyStop;
m_fmsAttached = word.m_fmsAttached;
m_dsAttached = word.m_dsAttached;
}
public boolean getEnabled() {
return m_enabled;
}

View File

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

View File

@@ -196,8 +196,8 @@ public final class HAL extends JNIWrapper {
@SuppressWarnings("MissingJavadocMethod")
public static native boolean waitForDSDataTimeout(double timeout);
public static int kMaxJoystickAxes = 12;
public static int kMaxJoystickPOVs = 12;
public static final int kMaxJoystickAxes = 12;
public static final int kMaxJoystickPOVs = 12;
public static native short getJoystickAxes(byte joystickNum, float[] axesArray);

View File

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

View File

@@ -0,0 +1,123 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.hal;
public class PowerDistributionFaults {
@SuppressWarnings("MemberName")
public final boolean Channel0BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel1BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel2BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel3BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel4BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel5BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel6BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel7BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel8BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel9BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel10BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel11BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel12BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel13BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel14BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel15BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel16BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel17BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel18BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel19BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel20BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel21BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel22BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel23BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Brownout;
@SuppressWarnings("MemberName")
public final boolean CanWarning;
@SuppressWarnings("MemberName")
public final boolean HardwareFault;
/**
* Constructs from a bitfield.
*
* @param faults faults
*/
public PowerDistributionFaults(int faults) {
Channel0BreakerFault = (faults & 0x1) != 0;
Channel1BreakerFault = (faults & 0x2) != 0;
Channel2BreakerFault = (faults & 0x4) != 0;
Channel3BreakerFault = (faults & 0x8) != 0;
Channel4BreakerFault = (faults & 0x10) != 0;
Channel5BreakerFault = (faults & 0x20) != 0;
Channel6BreakerFault = (faults & 0x40) != 0;
Channel7BreakerFault = (faults & 0x80) != 0;
Channel8BreakerFault = (faults & 0x100) != 0;
Channel9BreakerFault = (faults & 0x200) != 0;
Channel10BreakerFault = (faults & 0x400) != 0;
Channel11BreakerFault = (faults & 0x800) != 0;
Channel12BreakerFault = (faults & 0x1000) != 0;
Channel13BreakerFault = (faults & 0x2000) != 0;
Channel14BreakerFault = (faults & 0x4000) != 0;
Channel15BreakerFault = (faults & 0x8000) != 0;
Channel16BreakerFault = (faults & 0x10000) != 0;
Channel17BreakerFault = (faults & 0x20000) != 0;
Channel18BreakerFault = (faults & 0x40000) != 0;
Channel19BreakerFault = (faults & 0x80000) != 0;
Channel20BreakerFault = (faults & 0x100000) != 0;
Channel21BreakerFault = (faults & 0x200000) != 0;
Channel22BreakerFault = (faults & 0x400000) != 0;
Channel23BreakerFault = (faults & 0x800000) != 0;
Brownout = (faults & 0x1000000) != 0;
CanWarning = (faults & 0x2000000) != 0;
HardwareFault = (faults & 0x4000000) != 0;
}
}

View File

@@ -56,4 +56,18 @@ public class PowerDistributionJNI extends JNIWrapper {
public static native boolean getSwitchableChannelNoError(int handle);
public static native void setSwitchableChannelNoError(int handle, boolean enabled);
public static native int getFaultsNative(int handle);
public static PowerDistributionFaults getFaults(int handle) {
return new PowerDistributionFaults(getFaultsNative(handle));
}
public static native int getStickyFaultsNative(int handle);
public static PowerDistributionStickyFaults getStickyFaults(int handle) {
return new PowerDistributionStickyFaults(getStickyFaultsNative(handle));
}
public static native PowerDistributionVersion getVersion(int handle);
}

View File

@@ -0,0 +1,127 @@
// 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.
package edu.wpi.first.hal;
public class PowerDistributionStickyFaults {
@SuppressWarnings("MemberName")
public final boolean Channel0BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel1BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel2BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel3BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel4BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel5BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel6BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel7BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel8BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel9BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel10BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel11BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel12BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel13BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel14BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel15BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel16BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel17BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel18BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel19BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel20BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel21BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel22BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Channel23BreakerFault;
@SuppressWarnings("MemberName")
public final boolean Brownout;
@SuppressWarnings("MemberName")
public final boolean CanWarning;
@SuppressWarnings("MemberName")
public final boolean CanBusOff;
@SuppressWarnings("MemberName")
public final boolean HasReset;
/**
* Constructs from a bitfield.
*
* @param faults faults
*/
public PowerDistributionStickyFaults(int faults) {
Channel0BreakerFault = (faults & 0x1) != 0;
Channel1BreakerFault = (faults & 0x2) != 0;
Channel2BreakerFault = (faults & 0x4) != 0;
Channel3BreakerFault = (faults & 0x8) != 0;
Channel4BreakerFault = (faults & 0x10) != 0;
Channel5BreakerFault = (faults & 0x20) != 0;
Channel6BreakerFault = (faults & 0x40) != 0;
Channel7BreakerFault = (faults & 0x80) != 0;
Channel8BreakerFault = (faults & 0x100) != 0;
Channel9BreakerFault = (faults & 0x200) != 0;
Channel10BreakerFault = (faults & 0x400) != 0;
Channel11BreakerFault = (faults & 0x800) != 0;
Channel12BreakerFault = (faults & 0x1000) != 0;
Channel13BreakerFault = (faults & 0x2000) != 0;
Channel14BreakerFault = (faults & 0x4000) != 0;
Channel15BreakerFault = (faults & 0x8000) != 0;
Channel16BreakerFault = (faults & 0x10000) != 0;
Channel17BreakerFault = (faults & 0x20000) != 0;
Channel18BreakerFault = (faults & 0x40000) != 0;
Channel19BreakerFault = (faults & 0x80000) != 0;
Channel20BreakerFault = (faults & 0x100000) != 0;
Channel21BreakerFault = (faults & 0x200000) != 0;
Channel22BreakerFault = (faults & 0x400000) != 0;
Channel23BreakerFault = (faults & 0x800000) != 0;
Brownout = (faults & 0x1000000) != 0;
CanWarning = (faults & 0x2000000) != 0;
CanBusOff = (faults & 0x4000000) != 0;
HasReset = (faults & 0x8000000) != 0;
}
}

View File

@@ -0,0 +1,50 @@
// 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.
package edu.wpi.first.hal;
public class PowerDistributionVersion {
@SuppressWarnings("MemberName")
public final int firmwareMajor;
@SuppressWarnings("MemberName")
public final int firmwareMinor;
@SuppressWarnings("MemberName")
public final int firmwareFix;
@SuppressWarnings("MemberName")
public final int hardwareMinor;
@SuppressWarnings("MemberName")
public final int hardwareMajor;
@SuppressWarnings("MemberName")
public final int uniqueId;
/**
* Constructs a power distribution version (Called from the HAL).
*
* @param firmwareMajor firmware major
* @param firmwareMinor firmware minor
* @param firmwareFix firmware fix
* @param hardwareMinor hardware minor
* @param hardwareMajor hardware major
* @param uniqueId unique id
*/
public PowerDistributionVersion(
int firmwareMajor,
int firmwareMinor,
int firmwareFix,
int hardwareMinor,
int hardwareMajor,
int uniqueId) {
this.firmwareMajor = firmwareMajor;
this.firmwareMinor = firmwareMinor;
this.firmwareFix = firmwareFix;
this.hardwareMinor = hardwareMinor;
this.hardwareMajor = hardwareMajor;
this.uniqueId = uniqueId;
}
}

View File

@@ -0,0 +1,104 @@
// 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.
package edu.wpi.first.hal;
@SuppressWarnings("AbbreviationAsWordInName")
public class REVPHFaults {
@SuppressWarnings("MemberName")
public final boolean Channel0Fault;
@SuppressWarnings("MemberName")
public final boolean Channel1Fault;
@SuppressWarnings("MemberName")
public final boolean Channel2Fault;
@SuppressWarnings("MemberName")
public final boolean Channel3Fault;
@SuppressWarnings("MemberName")
public final boolean Channel4Fault;
@SuppressWarnings("MemberName")
public final boolean Channel5Fault;
@SuppressWarnings("MemberName")
public final boolean Channel6Fault;
@SuppressWarnings("MemberName")
public final boolean Channel7Fault;
@SuppressWarnings("MemberName")
public final boolean Channel8Fault;
@SuppressWarnings("MemberName")
public final boolean Channel9Fault;
@SuppressWarnings("MemberName")
public final boolean Channel10Fault;
@SuppressWarnings("MemberName")
public final boolean Channel11Fault;
@SuppressWarnings("MemberName")
public final boolean Channel12Fault;
@SuppressWarnings("MemberName")
public final boolean Channel13Fault;
@SuppressWarnings("MemberName")
public final boolean Channel14Fault;
@SuppressWarnings("MemberName")
public final boolean Channel15Fault;
@SuppressWarnings("MemberName")
public final boolean CompressorOverCurrent;
@SuppressWarnings("MemberName")
public final boolean CompressorOpen;
@SuppressWarnings("MemberName")
public final boolean SolenoidOverCurrent;
@SuppressWarnings("MemberName")
public final boolean Brownout;
@SuppressWarnings("MemberName")
public final boolean CanWarning;
@SuppressWarnings("MemberName")
public final boolean HardwareFault;
/**
* Called from HAL to construct.
*
* @param faults the fault bitfields
*/
public REVPHFaults(int faults) {
Channel0Fault = (faults & 0x1) != 0;
Channel1Fault = (faults & 0x2) != 0;
Channel2Fault = (faults & 0x4) != 0;
Channel3Fault = (faults & 0x8) != 0;
Channel4Fault = (faults & 0x10) != 0;
Channel5Fault = (faults & 0x20) != 0;
Channel6Fault = (faults & 0x40) != 0;
Channel7Fault = (faults & 0x80) != 0;
Channel8Fault = (faults & 0x100) != 0;
Channel9Fault = (faults & 0x200) != 0;
Channel10Fault = (faults & 0x400) != 0;
Channel11Fault = (faults & 0x800) != 0;
Channel12Fault = (faults & 0x1000) != 0;
Channel13Fault = (faults & 0x2000) != 0;
Channel14Fault = (faults & 0x4000) != 0;
Channel15Fault = (faults & 0x8000) != 0;
CompressorOverCurrent = (faults & 0x8000) != 0;
CompressorOpen = (faults & 0x10000) != 0;
SolenoidOverCurrent = (faults & 0x20000) != 0;
Brownout = (faults & 0x40000) != 0;
CanWarning = (faults & 0x80000) != 0;
HardwareFault = (faults & 0x100000) != 0;
}
}

View File

@@ -6,6 +6,11 @@ package edu.wpi.first.hal;
@SuppressWarnings("AbbreviationAsWordInName")
public class REVPHJNI extends JNIWrapper {
public static final int COMPRESSOR_CONFIG_TYPE_DISABLED = 0;
public static final int COMPRESSOR_CONFIG_TYPE_DIGITAL = 1;
public static final int COMPRESSOR_CONFIG_TYPE_ANALOG = 2;
public static final int COMPRESSOR_CONFIG_TYPE_HYBRID = 3;
public static native int initialize(int module);
public static native void free(int handle);
@@ -14,13 +19,28 @@ public class REVPHJNI extends JNIWrapper {
public static native boolean getCompressor(int handle);
public static native void setClosedLoopControl(int handle, boolean enabled);
public static native void setCompressorConfig(
int handle,
double minAnalogVoltage,
double maxAnalogVoltage,
boolean forceDisable,
boolean useDigital);
public static native boolean getClosedLoopControl(int handle);
public static native void setClosedLoopControlDisabled(int handle);
public static native void setClosedLoopControlDigital(int handle);
public static native void setClosedLoopControlAnalog(
int handle, double minAnalogVoltage, double maxAnalogVoltage);
public static native void setClosedLoopControlHybrid(
int handle, double minAnalogVoltage, double maxAnalogVoltage);
public static native int getCompressorConfig(int handle);
public static native boolean getPressureSwitch(int handle);
public static native double getAnalogPressure(int handle, int channel);
public static native double getAnalogVoltage(int handle, int channel);
public static native double getCompressorCurrent(int handle);
@@ -29,4 +49,28 @@ public class REVPHJNI extends JNIWrapper {
public static native void setSolenoids(int handle, int mask, int values);
public static native void fireOneShot(int handle, int index, int durMs);
public static native void clearStickyFaults(int handle);
public static native double getInputVoltage(int handle);
public static native double get5VVoltage(int handle);
public static native double getSolenoidCurrent(int handle);
public static native double getSolenoidVoltage(int handle);
public static native int getStickyFaultsNative(int handle);
public static REVPHStickyFaults getStickyFaults(int handle) {
return new REVPHStickyFaults(getStickyFaultsNative(handle));
}
public static native int getFaultsNative(int handle);
public static REVPHFaults getFaults(int handle) {
return new REVPHFaults(getFaultsNative(handle));
}
public static native REVPHVersion getVersion(int handle);
}

View File

@@ -0,0 +1,44 @@
// 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.
package edu.wpi.first.hal;
@SuppressWarnings("AbbreviationAsWordInName")
public class REVPHStickyFaults {
@SuppressWarnings("MemberName")
public final boolean CompressorOverCurrent;
@SuppressWarnings("MemberName")
public final boolean CompressorOpen;
@SuppressWarnings("MemberName")
public final boolean SolenoidOverCurrent;
@SuppressWarnings("MemberName")
public final boolean Brownout;
@SuppressWarnings("MemberName")
public final boolean CanWarning;
@SuppressWarnings("MemberName")
public final boolean CanBusOff;
@SuppressWarnings("MemberName")
public final boolean HasReset;
/**
* Called from HAL.
*
* @param faults sticky fault bit mask
*/
public REVPHStickyFaults(int faults) {
CompressorOverCurrent = (faults & 0x1) != 0;
CompressorOpen = (faults & 0x2) != 0;
SolenoidOverCurrent = (faults & 0x4) != 0;
Brownout = (faults & 0x8) != 0;
CanWarning = (faults & 0x10) != 0;
CanBusOff = (faults & 0x20) != 0;
HasReset = (faults & 0x40) != 0;
}
}

View File

@@ -0,0 +1,51 @@
// 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.
package edu.wpi.first.hal;
@SuppressWarnings("AbbreviationAsWordInName")
public class REVPHVersion {
@SuppressWarnings("MemberName")
public final int firmwareMajor;
@SuppressWarnings("MemberName")
public final int firmwareMinor;
@SuppressWarnings("MemberName")
public final int firmwareFix;
@SuppressWarnings("MemberName")
public final int hardwareMinor;
@SuppressWarnings("MemberName")
public final int hardwareMajor;
@SuppressWarnings("MemberName")
public final int uniqueId;
/**
* Constructs a revph version (Called from the HAL).
*
* @param firmwareMajor firmware major
* @param firmwareMinor firmware minor
* @param firmwareFix firmware fix
* @param hardwareMinor hardware minor
* @param hardwareMajor hardware major
* @param uniqueId unique id
*/
public REVPHVersion(
int firmwareMajor,
int firmwareMinor,
int firmwareFix,
int hardwareMinor,
int hardwareMajor,
int uniqueId) {
this.firmwareMajor = firmwareMajor;
this.firmwareMinor = firmwareMinor;
this.firmwareFix = firmwareFix;
this.hardwareMinor = hardwareMinor;
this.hardwareMajor = hardwareMajor;
this.uniqueId = uniqueId;
}
}

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