Squashed 'third_party/allwpilib/' changes from 66b57f032..e473a00f9
e473a00f9 [wpiutil] Base64: Add unsigned span/vector variants (#3702)
52f2d580e [wpiutil] raw_uv_ostream: Add reset() (#3701)
d7b1e3576 [wpiutil] WebSocket: move std::function (#3700)
93799fbe9 [examples] Fix description of TrapezoidProfileSubsystem (#3699)
b84644740 [wpimath] Document pose estimator states, inputs, and outputs (#3698)
2dc35c139 [wpimath] Fix classpaths for JNI class loads (#3697)
2cb171f6f [docs] Set Doxygen extract_all to true and fix Doxygen failures (#3695)
a939cd9c8 [wpimath] Print uncontrollable/unobservable models in LQR and KF (#3694)
d5270d113 [wpimath] Clean up C++ StateSpaceUtil tests (#3692)
b20903960 [wpimath] Remove redundant discretization tests from StateSpaceUtilTest (#3689)
c0cb545b4 [wpilibc] Add deprecated Doxygen attribute to SpeedController (#3691)
35c9f66a7 [wpilib] Rename PneumaticsHub to PneumaticHub (#3686)
796d03d10 [wpiutil] Remove unused LLVM header (#3688)
8723caf78 [wpilibj] Make Java TrapezoidProfile.Constraints an immutable class (#3687)
187f50a34 [wpimath] Catch incorrect parameters to state-space models earlier (#3680)
8d04606c4 Replace instances of frc-characterization with SysId (NFC) (#3681)
b82d4f6e5 [hal, cscore, ntcore] Use WPI common handle type base
87e34967e [wpiutil] Add synchronization primitives
e32499c54 [wpiutil] Add ParallelTcpConnector (#3655)
aa0b49228 [wpilib] Remove redundant "quick turn" docs for curvature drive (NFC) (#3674)
57301a7f9 [hal] REVPH: Start closed-loop compressor control on init (#3673)
d1842ea8f [wpilib] Improve interrupt docs (NFC) (#3679)
558151061 [wpiutil] Add DsClient (#3654)
181723e57 Replace `.to<double>()` and `.template to<double>()` with `.value()` (#3667)
6bc1db44b [commands] Add pointer overload of AddRequirements (artf6003) (#3669)
737b57ed5 [wpimath] Update to drake v0.35.0 (#3665)
4d287d1ae [build] Upgrade WPIJREArtifact to JRE 2022-11.0.12u5 (#3666)
f26eb5ada [hal] Fix another typo (get -> gets) (NFC) (#3663)
94ed275ba [hal] Fix misspelling (numer -> number) (NFC) (#3662)
ac2f44da3 [wpiutil] uv: use move for std::function (#3653)
75fa1fbfb [wpiutil] json::serializer: Optimize construction (#3647)
5e689faea [wpiutil] Import MessagePack implementation (mpack) (#3650)
649a50b40 [wpiutil] Add LEB128 byte-by-byte reader (#3652)
e94397a97 [wpiutil] Move json_serializer.h to public headers (#3646)
4ec58724d [wpiutil] uv::Tcp: Clarify SetNoDelay documentation (#3649)
8cb294aa4 [wpiutil] WebSocket: Make Shutdown() public (#3651)
2b3a9a52b [wpiutil] json: Fix map iterator key() for std::string_view (#3645)
138cbb94b [wpiutil] uv::Async: Add direct call for no-parameter specialization (#3648)
e56d6dea8 [ci] Update testbench pool image to ubuntu-latest (#3643)
43f30e44e [build] Enable comments in doxygen source files (#3644)
9e6db17ef [build] Enable doxygen preprocessor expansion of WPI_DEPRECATED (#3642)
0e631ad2f Add WPILib version to issue template (#3641)
6229d8d2f [build] Docs: set case_sense_names to false (#3392)
4647d09b5 [docs] Fix Doxygen warnings, add CI docs lint job (#3639)
4ad3a5402 [hal] Fix PWM allocation channel (#3637)
05e5feac4 [docs] Fix brownout docs (NFC) (#3638)
67df469c5 [examples] Remove old command-based templates and examples (#3263)
689e9ccfb [hal, wpilib] Add brownout voltage configuration (#3632)
9cd4bc407 [docs] Add useLocal to avoid using installer artifacts (#3634)
61996c2bb [cscore] Fix Java direct callback notifications (#3631)
6d3dd99eb [build] Update to newest native-utils (#3633)
f0b484892 [wpiutil] Fix StringMap iterator equality check (#3629)
8352cbb7a Update development build instructions for 2022 (#3616)
6da08b71d [examples] Fix Intermediate Vision Java Example description (#3628)
5d99059bf [wpiutil] Remove optional.h (#3627)
fa41b106a [glass, wpiutil] Add missing format args (#3626)
4e3fd7d42 [build] Enable Zc:__cplusplus for Windows (#3625)
791d8354d [build] Suppress deprecation/removal warnings for old commands (#3618)
10f19e6fc [hal, wpilib] Add REV PneumaticsHub (#3600)
4c61a1305 [ntcore] Revert to per-element copy for toNative() (#3621)
7b3f62244 [wpiutil] SendableRegistry: Print exception stacktrace (#3620)
d347928e4 [hal] Use better error for when console out is enabled while attempting to use onboard serial port (#3622)
cc31079a1 [hal] Use setcap instead of setuid for setting thread priorities (#3613)
4676648b7 [wpimath] Upgrade to Drake v0.34.0 (#3607)
c7594c911 [build] Allow building wpilibc in cmake without cscore and opencv (#3605)
173cb7359 [wpilib] Add TimesliceRobot (#3502)
af295879f [hal] Set error status for I2C port out of range (#3603)
95dd20a15 [build] Enable spotbugs (#3601)
b65fce86b [wpilib] Remove Timer lock in wpilibj and update docs (#3602)
3b8d3bbcb Remove unused and add missing deprecated.h includes (#3599)
f9e976467 [examples] Rename DriveTrain classes to Drivetrain (#3594)
118a27be2 [wpilib] Add Timer tests (#3595)
59c89428e [wpilib] Deprecate Timer::HasPeriodPassed() (#3598)
202ca5e78 Force C++17 in .clang-format (#3597)
d6f185d8e Rename tests for consistency (#3592)
54ca474db [ci] Enable asan and tsan in CI for tests that pass (#3591)
1ca383b23 Add Debouncer (#3590)
179fde3a7 [build] Update to 2022 native utils and gradle 7 (#3588)
50198ffcf [examples] Add Mechanism2d visualization to Elevator Sim (#3587)
a446c2559 [examples] Synchronize C++ and Java Mechanism2d examples (#3589)
a7fb83103 [ci] clang-tidy: Generate compilation commands DB with Gradle (#3585)
4f5e0c9f8 [examples] Update ArmSimulation example to use Mechanism2d (#3572)
8164b91dc [CI] Print CMake test output on failure (#3583)
4d5fca27e [wpilib] Impove Mechanism2D documentation (NFC) (#3584)
fe59e4b9f Make C++ test names more consistent (#3586)
5c8868549 [wpilibc] Fix C++ MechanisimRoot2D to use same NT entries as Java/Glass (#3582)
9359431ba [wpimath] Clean up Eigen usage
72716f51c [wpimath] Upgrade to Eigen 3.4
382deef75 [wpimath] Explicitly export wpimath symbols
161e21173 [ntcore] Match standard handle layout, only allow 16 instances (#3577)
263a24811 [wpimath] Use jinja for codegen (#3574)
725251d29 [wpilib] Increase tolerances of DifferentialDriveSimTest (#3581)
4dff87301 [wpimath] Make LinearFilter::Factorial() constexpr (#3579)
60ede67ab [hal, wpilib] Switch PCM to be a single object that is allowed to be duplicated (#3475)
906bfc846 [build] Add CMake build support for sanitizers (#3576)
0d4f08ad9 [hal] Simplify string copy of joystick name (#3575)
a52bf87b7 [wpiutil] Add Java function package (#3570)
40c7645d6 [wpiutil] UidVector: Return old object from erase() (#3571)
5b886a23f [wpiutil] jni_util: Add size, operator[] to JArrayRef (#3569)
65797caa7 [sim] Fix halsim_ds_socket stringop overflow warning from GCC 10 (#3568)
66abb3988 [hal] Update runtime enum to allow selecting roborio 2 (#3565)
95a12e0ee [hal] UidSetter: Don't revert euid if its already current value (#3566)
27951442b [wpimath] Use external Eigen headers only (#3564)
c42e053ae [docs] Update to doxygen 1.9.2 (#3562)
e7048c8c8 [docs] Disable doxygen linking for common words that are also classes (#3563)
d8e0b6c97 [wpilibj] Fix java async interrupts (#3559)
5e6c34c61 Update to 2022 roborio image (#3537)
828f073eb [wpiutil] Fix uv::Buffer memory leaks caught by asan (#3555)
2dd5701ac [cscore] Fix mutex use-after-free in cscore test (#3557)
531439198 [ntcore] Fix NetworkTables memory leaks caught by asan (#3556)
3d9a4d585 [wpilibc] Fix AnalogTriggerOutput memory leak reported by asan (#3554)
54eda5928 [wpiutil] Ignore ubsan vptr upcast warning in SendableHelper moves (#3553)
5a4f75c9f [wpilib] Replace Speed controller comments with motor controller (NFC) (#3551)
7810f665f [wpiutil] Fix bug in uleb128 (#3540)
697e2dd33 [wpilib] Fix errant jaguar reference in comments (NFC) (#3550)
936c64ff5 [docs] Enable -linksource for javadocs (#3549)
1ea654954 [build] Upgrade CMake build to googletest 1.11.0 (#3548)
32d9949e4 [wpimath] Move controller tests to wpimath (#3541)
01ba56a8a [hal] Replace strncpy with memcpy (#3539)
e109c4251 [build] Rename makeSim flag to forceGazebo to better describe what it does (#3535)
e4c709164 [docs] Use a doxygen theme and add logo (#3533)
960b6e589 [wpimath] Fix Javadoc warning (#3532)
82eef8d5e [hal] Remove over current fault HAL functions from REV PDH (#3526)
aa3848b2c [wpimath] Move RobotDriveBase::ApplyDeadband() to MathUtil (#3529)
3b5d0d141 [wpimath] Add LinearFilter::BackwardFiniteDifference() (#3528)
c8fc715fe [wpimath] Upgrade drake files to v0.33.0 (#3531)
e5fe3a8e1 [build] Treat javadoc warnings as errors in CI and fix warnings (#3530)
e0c6cd3dc [wpimath] Add an operator for composing two Transform2ds (#3527)
2edd510ab [sim] Add sim wrappers for sensors that use SimDevice (#3517)
2b3e2ebc1 [hal] Fix HAL Notifier thread priority setting (#3522)
ab4cb5932 [gitignore] Update gitignore to ignore bazel / clion files (#3524)
57c8615af [build] Generate spotless patch on failure (#3523)
b90317321 Replace std::cout and std::cerr with fmt::print() (#3519)
10cc8b89c [hal] [wpilib] Add initial support for the REV PDH (#3503)
5d9ae3cdb [hal] Set HAL Notifier thread as RT by default (#3482)
192d251ee [wpilibcIntegrationTests] Properly disable DMA integration tests (#3514)
031962608 [wpilib] Add PS4Controller, remove Hand from GenericHID/XboxController (#3345)
25f6f478a [wpilib] Rename DriverStation::IsOperatorControl() to IsTeleop() (#3505)
e80f09f84 [wpilibj] Add unit tests (#3501)
c159f91f0 [wpilib] Only read DS control word once in IterativeRobotBase (#3504)
eb790a74d Add rio development docs documenting myRobot deploy tasks (#3508)
e47451f5a [wpimath] Replace auto with Eigen types (#3511)
252b8c83b Remove Java formatting from build task in CI (#3507)
09666ff29 Shorten Gazebo CI build (#3506)
baf2e501d Update myRobot to use 2021 java (#3509)
5ac60f0a2 [wpilib] Remove IterativeRobotBase mode init prints (#3500)
fb2ee8ec3 [wpilib] Add TimedRobot functions for running code on mode exit (#3499)
94e0db796 [wpilibc] Add more unit tests (#3494)
b25324695 [wpilibj] Add units to parameter names (NFC) (#3497)
1ac73a247 [hal] Rename PowerDistributionPanel to PowerDistribution (#3466)
2014115bc [examples] frisbeebot: Fix typo and reflow comments (NFC) (#3498)
4a944dc39 [examples] Consistently use 0 for controller port (#3496)
3838cc4ec Use unicode characters in docs equations (#3487)
85748f2e6 [examples] Add C++ TankDrive example (#3493)
d7b8aa56d [wpilibj] Rename DriverStation In[Mode] functions to follow style guide (#3488)
16e096cf8 [build] Fix CMake Windows CI (#3490)
50af74c38 [wpimath] Clean up NumericalIntegration and add Discretization tests (#3489)
bfc209b12 Automate fmt update (#3486)
e7f9331e4 [build] Update to Doxygen 1.9.1 (#3008)
ab8e8aa2a [wpimath] Update drake with upstream (#3484)
1ef826d1d [wpimath] Fix IOException path in WPIMath JNI (#3485)
52bddaa97 [wpimath] Disable iostream support for units and enable fmtlib (#3481)
e4dc3908b [wpiutil] Upgrade to fmtlib 8.0.1 (#3483)
1daadb812 [wpimath] Implement Dormand-Prince integration method (#3476)
9c2723391 [cscore] Add [[nodiscard]] to GrabFrame functions (#3479)
7a8796414 [wpilib] Add Notifier integration tests (#3480)
f8f13c536 [wpilibcExamples] Prefix decimal numbers with 0 (#3478)
1adb69c0f [ntcore] Use "NetworkTables" instead of "Network Tables" in NT specs (#3477)
5f5830b96 Upload wpiformat diff if one exists (#3474)
9fb4f35bb [wpimath] Add tests for DARE overload with Q, R, and N matrices (#3472)
c002e6f92 Run wpiformat (#3473)
c154e5262 [wpilib] Make solenoids exclusive use, PCM act like old sendable compressor (#3464)
6ddef1cca [hal] JNI setDIO: use a boolean and not a short (#3469)
9d68d9582 Remove extra newlines after open curly braces (NFC) (#3471)
a4233e1a1 [wpimath] Add script for updating Drake (#3470)
39373c6d2 Update README.md for new GCC version requirement (#3467)
d29acc90a [wpigui] Add option to reset UI on exit (#3463)
a371235b0 [ntcore] Fix dangling pointer in logger (#3465)
53b4891a5 [wpilibcintegrationtests] Fix deprecated Preferences usage (#3461)
646ded912 [wpimath] Remove incorrect discretization in pose estimators (#3460)
ea0b8f48e Fix some deprecation warnings due to fmtlib upgrade (#3459)
2067d7e30 [wpilibjexamples] Add wpimathjni, wpiutiljni to library path (#3455)
866571ab4 [wpiutil] Upgrade to fmtlib 8.0.0 (#3457)
4e1fa0308 [build] Skip PDB copy on windows build servers (#3458)
b45572167 [build] Change CI back to 18.04 docker images (#3456)
57a160f1b [wpilibc] Fix LiveWindow deprecation warning in RobotBase skeleton template (#3454)
29ae8640d [HLT] Implement duty cycle cross connect tests (#3453)
ee6377e54 [HLT] Add relay and analog cross connects (#3452)
b0f1ae7ea [build] CMake: Build the HAL even if WITH_CSCORE=OFF (#3449)
7aae2b72d Replace std::to_string() with fmt::format() (#3451)
73fcbbd74 [HLT] Add relay digital cross connect tests (#3450)
e7bedde83 [HLT] Add PWM tests that use DMA as the back end (#3447)
7253edb1e [wpilibc] Timer: Fix deprecated warning (#3446)
efa28125c [wpilibc] Add message to RobotBase on how to read stacktrace (#3444)
9832fcfe1 [hal] Fix DIO direction getter (#3445)
49c71f9f2 [wpilibj] Clarify robot quit message (#3364)
791770cf6 [wpimath] Move controller from wpilibj to wpimath (#3439)
9ce9188ff [wpimath] Add ReportWarning to MathShared (#3441)
362066a9b [wpilib] Deprecate getInstance() in favor of static functions (#3440)
26ff9371d Initial commit of cross connect integration test project (#3434)
4a36f86c8 [hal] Add support for DMA to Java (#3158)
85144e47f [commands] Unbreak build (#3438)
b417d961e Split Sendable into NT and non-NT portions (#3432)
ef4ea84cb [commands] Change grouping decorator impl to flatten nested group structures (#3335)
b422665a3 [examples] Invert right side of drive subsystems (#3437)
186dadf14 [hal] Error if attempting to set DIO output on an input port (#3436)
04e64db94 Remove redundant C++ lambda parentheses (NFC) (#3433)
f60994ad2 [wpiutil] Rename Java package to edu.wpi.first.util (#3431)
cfa1ca96f [wpilibc] Make ShuffleboardValue non-copyable (#3430)
4d9ff7643 Fix documentation warnings generated by JavaDoc (NFC) (#3428)
9e1b7e046 [build] Fix clang-tidy and clang-format (#3429)
a77c6ff3a [build] Upgrade clang-format and clang-tidy (NFC) (#3422)
099fde97d [wpilib] Improve PDP comments (NFC) (#3427)
f8fc2463e [wpilibc, wpiutil] Clean up includes (NFC) (#3426)
e246b7884 [wpimath] Clean up member initialization in feedforward classes (#3425)
c1e128bd5 Disable frivolous PMD warnings and enable PMD in ntcore (#3419)
8284075ee Run "Lint and Format" CI job on push as well as pull request (#3412)
f7db09a12 [wpimath] Move C++ filters into filter folder to match Java (#3417)
f9c3d54bd [wpimath] Reset error covariance in pose estimator ResetPosition() (#3418)
0773f4033 [hal] Ensure HAL status variables are initialized to zero (#3421)
d068fb321 [build] Upgrade CI to use 20.04 docker images (#3420)
8d054c940 [wpiutil] Remove STLExtras.h
80f1d7921 [wpiutil] Split function_ref to a separate header
64f541325 Use wpi::span instead of wpi::ArrayRef across all libraries (#3414)
2abbbd9e7 [build] clang-tidy: Remove bugprone-exception-escape (#3415)
a5c471af7 [wpimath] Add LQR template specialization for 2x2 system
edd2f0232 [wpimath] Add DARE solver for Q, R, and N with LQR ctor overloads
b2c3b2dd8 Use std::string_view and fmtlib across all libraries (#3402)
4f1cecb8e [wpiutil] Remove Path.h (#3413)
b336eac34 [build] Publish halsim_ws_core to Maven
2a09f6fa4 [build] Also build sim modules as static libraries
0e702eb79 [hal] Add a unified PCM object (#3331)
dea841103 [wpimath] Add fmtlib formatter overloads for Eigen::Matrix and units (#3409)
82856cf81 [wpiutil] Improve wpi::circular_buffer iterators (#3410)
8aecda03e [wpilib] Fix a documentation typo (#3408)
5c817082a [wpilib] Remove InterruptableSensorBase and replace with interrupt classes (#2410)
15c521a7f [wpimath] Fix drivetrain system identification (#3406)
989de4a1b [build] Force all linker warnings to be fatal for rio builds (#3407)
d9eeb45b0 [wpilibc] Add units to Ultrasonic class API (#3403)
fe570e000 [wpiutil] Replace llvm filesystem with C++17 filesystem (#3401)
01dc0249d [wpimath] Move SlewRateLimiter from wpilib to wpimath (#3399)
93523d572 [wpilibc] Clean up integration tests (#3400)
4f7a4464d [wpiutil] Rewrite StringExtras for std::string_view (#3394)
e09293a15 [wpilibc] Transition C++ classes to units::second_t (#3396)
827b17a52 [build] Create run tasks for Glass and OutlineViewer (#3397)
a61037996 [wpiutil] Avoid MSVC warning on span include (#3393)
4e2c3051b [wpilibc] Use std::string_view instead of Twine (#3380)
50915cb7e [wpilibc] MotorSafety::GetDescription(): Return std::string (#3390)
f4e2d26d5 [wpilibc] Move NullDeleter from frc/Base.h to wpi/NullDeleter.h (#3387)
cb0051ae6 [wpilibc] SimDeviceSim: use fmtlib (#3389)
a238cec12 [wpiutil] Deprecate wpi::math constants in favor of wpi::numbers (#3383)
393bf23c0 [ntcore, cscore, wpiutil] Standardize template impl files on .inc extension (NFC) (#3124)
e7d9ba135 [sim] Disable flaky web server integration tests (#3388)
0a0003c11 [wpilibjExamples] Fix name of Java swerve drive pose estimator example (#3382)
7e1b27554 [wpilibc] Use default copies and moves when possible (#3381)
fb2a56e2d [wpilibc] Remove START_ROBOT_CLASS macro (#3384)
84218bfb4 [wpilibc] Remove frc namespace shim (#3385)
dd7824340 [wpilibc] Remove C++ compiler version static asserts (#3386)
484cf9c0e [wpimath] Suppress the -Wmaybe-uninitialized warning in Eigen (#3378)
a04d1b4f9 [wpilibc] DriverStation: Remove ReportError and ReportWarning
831c10bdf [wpilibc] Errors: Use fmtlib
87603e400 [wpiutil] Import fmtlib (#3375)
442621672 [wpiutil] Add ArrayRef/std::span/wpi::span implicit conversions
bc15b953b [wpiutil] Add std::span implementation
6d20b1204 [wpiutil] StringRef, Twine, raw_ostream: Add std::string_view support (#3373)
2385c2a43 [wpilibc] Remove Utility.h (#3376)
87384ea68 [wpilib] Fix PIDController continuous range error calculations (#3170)
04dae799a [wpimath] Add SimpleMotorFeedforward::Calculate(velocity, nextVelocity) overload (#3183)
0768c3903 [wpilib] DifferentialDrive: Remove right side inversion (#3340)
8dd8d4d2d [wpimath] Fix redundant nested math package introduced by #3316 (#3368)
49b06beed [examples] Add Field2d to RamseteController example (#3371)
4c562a445 [wpimath] Fix typo in comment of update_eigen.py (#3369)
fdbbf1188 [wpimath] Add script for updating Eigen
f1e64b349 [wpimath] Move Eigen unsupported folder into eigeninclude
224f3a05c [sim] Fix build error when building with GCC 11.1 (#3361)
ff56d6861 [wpilibj] Fix SpeedController deprecated warnings (#3360)
1873fbefb [examples] Fix Swerve and Mecanum examples (#3359)
80b479e50 [examples] Fix SwerveBot example to use unique encoder ports (#3358)
1f7c9adee [wpilibjExamples] Fix pose estimator examples (#3356)
9ebc3b058 [outlineviewer] Change default size to 600x400 (#3353)
e21b443a4 [build] Gradle: Make C++ examples runnable (#3348)
da590120c [wpilibj] Add MotorController.setVoltage default (#3347)
561d53885 [build] Update opencv to 4.5.2, imgui/implot to latest (#3344)
44ad67ca8 [wpilibj] Preferences: Add missing Deprecated annotation (#3343)
3fe8fc75a [wpilibc] Revert "Return reference from GetInstance" (#3342)
3cc2da332 Merge branch '2022'
a3cd90dd7 [wpimath] Fix classpath used by generate_numbers.py (#3339)
d6cfdd3ba [wpilib] Preferences: Deprecate Put* in favor of Set* (#3337)
ba08baabb [wpimath] Update Drake DARE solver to v0.29.0 (#3336)
497b712f6 [wpilib] Make IterativeRobotBase::m_period private with getter
f00dfed7a [wpilib] Remove IterativeRobot base class
3c0846168 [hal] Use last error reporting instead of PARAMETER_OUT_OF_RANGE (#3328)
5ef2b4fdc [wpilibj] Fix @deprecated warning for SerialPort constructor (#3329)
23d2326d1 [hal] Report previous allocation location for indexed resource duplicates (#3322)
e338f9f19 [build] Fix wpilibc runCpp task (#3327)
c8ff626fe [wpimath] Move Java classes to edu.wpi.first.math (#3316)
4e424d51f [wpilibj] DifferentialDrivetrainSim: Rename constants to match the style guide (#3312)
6b50323b0 [cscore] Use Lock2DSize if possible for Windows USB cameras (#3326)
65c148536 [wpilibc] Fix "control reaches end of non-void function" warning (#3324)
f99f62bee [wpiutil] uv Handle: Use malloc/free instead of new/delete (#3325)
365f5449c [wpimath] Fix MecanumDriveKinematics (#3266)
ff52f207c [glass, wpilib] Rewrite Mechanism2d (#3281)
ee0eed143 [wpimath] Add DCMotor factory function for Romi motors (#3319)
512738072 [hal] Add HAL_GetLastError to enable better error messages from HAL calls (#3320)
ced654880 [glass, outlineviewer] Update Mac icons to macOS 11 style (#3313)
936d3b9f8 [templates] Add Java template for educational robot (#3309)
6e31230ad [examples] Fix odometry update in SwerveControllerCommand example (#3310)
05ebe9318 Merge branch 'main' into 2022
aaf24e255 [wpilib] Fix initial heading behavior in HolonomicDriveController (#3290)
8d961dfd2 [wpilibc] Remove ErrorBase (#3306)
659b37ef9 [wpiutil] StackTrace: Include offset on Linux (#3305)
0abf6c904 [wpilib] Move motor controllers to motorcontrol package (#3302)
4630191fa [wpiutil] circular_buffer: Use value initialization instead of passing zero (#3303)
b7b178f49 [wpilib] Remove Potentiometer interface
687066af3 [wpilib] Remove GyroBase
6b168ab0c [wpilib] Remove PIDController, PIDOutput, PIDSource
948625de9 [wpimath] Document conversion from filter cutoff frequency to time constant (#3299)
3848eb8b1 [wpilibc] Fix flywhel -> flywheel typo in FlywheelSim (#3298)
3abe0b9d4 [cscore] Move java package to edu.wpi.first.cscore (#3294)
d7fabe81f [wpilib] Remove RobotDrive (#3295)
1dc81669c [wpilib] Remove GearTooth (#3293)
01d0e1260 [wpilib] Revert move of RomiGyro into main wpilibc/j (#3296)
397e569aa [ntcore] Remove "using wpi" from nt namespace
79267f9e6 [ntcore] Remove NetworkTable -> nt::NetworkTable shim
48ebe5736 [ntcore] Remove deprecated Java interfaces and classes
c2064c78b [ntcore] Remove deprecated ITable interfaces
36608a283 [ntcore] Remove deprecated C++ APIs
a1c87e1e1 [glass] LogView: Add "copy to clipboard" button (#3274)
fa7240a50 [wpimath] Fix typo in quintic spline basis matrix
ffb4d38e2 [wpimath] Add derivation for spline basis matrices
f57c188f2 [wpilib] Add AnalogEncoder(int) ctor (#3273)
8471c4fb2 [wpilib] FieldObject2d: Add setTrajectory() method (#3277)
c97acd18e [glass] Field2d enhancements (#3234)
ffb590bfc [wpilib] Fix Compressor sendable properties (#3269)
6137f98eb [hal] Rename SimValueCallback2 to SimValueCallback (#3212)
a6f653969 [hal] Move registerSimPeriodic functions to HAL package (#3211)
10c038d9b [glass] Plot: Fix window creation after removal (#3264)
2d2eaa3ef [wpigui] Ensure window will be initially visible (#3256)
4d28b1f0c [wpimath] Use JNI for trajectory serialization (#3257)
3de800a60 [wpimath] TrajectoryUtil.h: Comment formatting (NFC) (#3262)
eff592377 [glass] Plot: Don't overwrite series ID (#3260)
a79faace1 [wpilibc] Return reference from GetInstance (#3247)
9550777b9 [wpilib] PWMSpeedController: Use PWM by composition (#3248)
c8521a3c3 [glass] Plot: Set reasonable default window size (#3261)
d71eb2cf3 [glass] Plot: Show full source name as tooltip and in popup (#3255)
160fb740f [hal] Use std::lround() instead of adding 0.5 and truncating (#3012)
48e9f3951 [wpilibj] Remove wpilibj package CameraServer (#3213)
8afa596fd [wpilib] Remove deprecated Sendable functions and SendableBase (#3210)
d3e45c297 [wpimath] Make C++ geometry classes immutable (#3249)
2c98939c1 [glass] StringChooser: Don't call SameLine() at end
a18a7409f [glass] NTStringChooser: Clear value of deleted entries
2f19cf452 [glass] NetworkTablesHelper: listen to delete events
da96707dc Merge branch 'main' into 2022
c3a8bdc24 [build] Fix clang-tidy action (#3246)
21624ef27 Add ImGui OutlineViewer (#3220)
1032c9b91 [wpiutil] Unbreak wpi::Format on Windows (#3242)
2e07902d7 [glass] NTField2D: Fix name lookup (#3233)
6e23e1840 [wpilibc] Remove WPILib.h (#3235)
3e22e4506 [wpilib] Make KoP drivetrain simulation weight 60 lbs (#3228)
79d1bd6c8 [glass] NetworkTablesSetting: Allow disable of server option (#3227)
fe341a16f [examples] Use more logical elevator setpoints in GearsBot (#3198)
62abf46b3 [glass] NetworkTablesSettings: Don't block GUI (#3226)
a95a5e0d9 [glass] Move NetworkTablesSettings to libglassnt (#3224)
d6f6ceaba [build] Run Spotless formatter (NFC) (#3221)
0922f8af5 [commands] CommandScheduler.requiring(): Note return can be null (NFC) (#2934)
6812302ff [examples] Make DriveDistanceOffboard example work in sim (#3199)
f3f86b8e7 [wpimath] Add pose estimator overload for vision + std dev measurement (#3200)
1a2680b9e [wpilibj] Change CommandBase.withName() to return CommandBase (#3209)
435bbb6a8 [command] RamseteCommand: Output 0 if interrupted (#3216)
3cf44e0a5 [hal] Add function for changing HAL Notifier thread priority (#3218)
40b367513 [wpimath] Units.java: Add kg-lb conversions (#3203)
9f563d584 [glass] NT: Fix return value in StringToDoubleArray (#3208)
af4adf537 [glass] Auto-size plots to fit window (#3193)
2560146da [sim] GUI: Add option to show prefix in Other Devices (#3186)
eae3a6397 gitignore: Ignore .cache directory (#3196)
959611420 [wpilib] Require non-zero positive value for PIDController.period (#3175)
9522f2e8c [wpimath] Add methods to concatenate trajectories (#3139)
e42a0b6cf [wpimath] Rotation2d comment formatting (NFC) (#3162)
d1c7032de [wpimath] Fix order of setting gyro offset in pose estimators (#3176)
d241bc81a [sim] Add DoubleSolenoidSim and SolenoidSim classes (#3177)
cb7f39afa [wpilibc] Add RobotController::GetBatteryVoltage() to C++ (#3179)
99b5ad9eb [wpilibj] Fix warnings that are not unused variables or deprecation (#3161)
c14b23775 [build] Fixup doxygen generated include dirs to match what users would need (#3154)
d447c7dc3 [sim] Add SimDeviceSim ctor overloads (#3134)
247420c9c [build] Remove jcenter repo (#3157)
04b112e00 [build] Include debug info in plugin published artifacts (#3149)
be0ce9900 [examples] Use PWMSparkMax instead of PWMVictorSPX (#3156)
69e8d0b65 [wpilib] Move RomiGyro into main wpilibc/j (#3143)
94e685e1b [wpimath] Add custom residual support to EKF (#3148)
5899f3dd2 [sim] GUI: Make keyboard settings loading more robust (#3167)
f82aa1d56 [wpilib] Fix HolonomicDriveController atReference() behavior (#3163)
fe5c2cf4b [wpimath] Remove ControllerUtil.java (#3169)
43d40c6e9 [wpiutil] Suppress unchecked cast in CombinedRuntimeLoader (#3155)
3d44d8f79 [wpimath] Fix argument order in UKF docs (NFC) (#3147)
ba6fe8ff2 [cscore] Add USB camera change event (#3123)
533725888 [build] Tweak OpenCV cmake search paths to work better on Linux (#3144)
29bf9d6ef [cscore] Add polled support to listener
483beb636 [ntcore] Move CallbackManager to wpiutil
fdaec7759 [examples] Instantiate m_ramseteController in example (#3142)
8494a5761 Rename default branch to main (#3140)
45590eea2 [wpigui] Hardcode window scale to 1 on macOS (#3135)
834a64920 [build] Publish libglass and libglassnt to Maven (#3127)
2c2ccb361 [wpimath] Fix Rotation2d equality operator (#3128)
fb5c8c39a [wpigui] clang-tidy: readability-braces-around-statements
f7d39193a [wpigui] Fix copyright in pfd and wpigui_metal.mm
aec796b21 [ntcore] Fix conditional jump on uninitialized value (#3125)
fb13bb239 [sim] GUI: Add right click popup for keyboard joystick settings (#3119)
c517ec677 [build] Update thirdparty-imgui to 1.79-2 (#3118)
e8cbf2a71 [wpimath] Fix typo in SwerveDrivePoseEstimator doc (NFC) (#3112)
e9c86df46 [wpimath] Add tests for swerve module optimization (#3100)
6ba8c289c [examples] Remove negative of ArcadeDrive(fwd, ..) in the C++ Getting Started Example (#3102)
3f1672e89 [hal] Add SimDevice createInt() and createLong() (#3110)
15be5cbf1 [examples] Fix segfault in GearsBot C++ example (#3111)
4cf0e5e6d Add quick links to API documentation in README (#3082)
6b1898f12 Fix RT priority docs (NFC) (#3098)
b3426e9c0 [wpimath] Fix missing whitespace in pose estimator doc (#3097)
38c1a1f3e [examples] Fix feildRelative -> fieldRelative typo in XControllerCommand examples (#3104)
4488e25f1 [glass] Shorten SmartDashboard window names (#3096)
cfdb3058e [wpilibj] Update SimDeviceSimTest (#3095)
64adff5fe [examples] Fix typo in ArcadeDrive constructor parameter name (#3092)
6efc58e3d [build] Fix issues with build on windows, deprecations, and native utils (#3090)
f393989a5 [wpimath, wpiutil] Add wpi::array for compile time size checking (#3087)
d6ed20c1e [build] Set macOS deployment target to 10.14 (#3088)
7c524014c [hal] Add [[nodiscard]] to HAL_WaitForNotifierAlarm() (#3085)
406d055f0 [wpilib] Fixup wouldHitLowerLimit in elevator and arm simulation classes. (#3076)
04a90b5dd [examples] Don't continually set setpoint in PotentiometerPID Examples (#3084)
8c5bfa013 [sim] GUI: Add max value setting for keyboard joysticks (#3083)
bc80c5535 [hal] Add SimValue reset() function (#3064)
9c3b51ca0 [wpilib] Document simulation APIs (#3079)
26584ff14 [wpimath] Add model description to LinearSystemId Javadocs (#3080)
42c3d5286 [examples] Sync Java and C++ trajectories in sim example (#3081)
64e72f710 [wpilibc] Add missing function RoboRioSim::ResetData (#3073)
e95503798 [wpimath] Add optimize() to SwerveModuleState (#3065)
fb99910c2 [hal] Add SimInt and SimLong wrappers for int/long SimValue (#3066)
e620bd4d3 [doc] Add machine-readable websocket specification (#3059)
a44e761d9 [glass] Add support for plot Y axis labels
ea1974d57 [wpigui] Update imgui and implot to latest
85a0bd43c [wpimath] Add RKF45 integration (#3047)
278e0f126 [glass] Use .controllable to set widgets' read-only state (#3035)
d8652cfd4 [wpimath] Make Java DCMotor API consistent with C++ and fix motor calcs (#3046)
377b7065a [build] Add toggleOffOn to Java spotless (#3053)
1e9c79c58 [sim] Use plant output to retrieve simulated position (#3043)
78147aa34 [sim] GUI: Fix Keyboard Joystick (#3052)
cd4a2265b [ntcore] Fix NetworkTableEntry::GetRaw() (#3051)
767ac1de1 [build] Use deploy key for doc publish (#3048)
d762215d1 [build] Add publish documentation script (#3040)
1fd09593c [examples] Add missing TestInit method to GettingStarted Example (#3039)
e45a0f6ce [examples] Add RomiGyro to the Romi Reference example (#3037)
94f852572 Update imaging link and fix typo (#3038)
d73cf64e5 [examples] Update RomiReference to match motor directions (#3036)
f945462ba Bump copyright year to 2021 (#3033)
b05946175 [wpimath] Catch Drake JNI exceptions and rethrow them (#3032)
62f0f8190 [wpimath] Deduplicate angle modulus functions (#2998)
bf8c0da4b [glass] Add "About" popup with version number (#3031)
dfdd6b389 [build] Increase Gradle heap size in Gazebo build (#3028)
f5e0fc3e9 Finish clang-tidy cleanups (#3003)
d741101fe [sim] Revert accidental commit of WSProvider_PDP.h (#3027)
e1620799c [examples] Add C++ RomiReference example (#2969)
749c7adb1 [command] Fix use-after-free in CommandScheduler (#3024)
921a73391 [sim] Add WS providers for AddressableLED, PCM, and Solenoid (#3026)
26d0004fe [build] Split Actions into different yml files (#3025)
948af6d5b [wpilib] PWMSpeedController.get(): Apply Inversion (#3016)
670a187a3 [wpilibc] SuppliedValueWidget.h: Forward declare ShuffleboardContainer (#3021)
be9f72502 [ntcore] NetworkTableValue: Use std::forward instead of std::move (#3022)
daf3f4cb1 [cscore] cscore_raw_cv.h: Fix error in PutFrame() (#3019)
5acda4cc7 [wpimath] ElevatorFeedforward.h: Add time.h include
8452af606 [wpimath] units/radiation.h: Add mass.h include
630d44952 [hal] ErrorsInternal.h: Add stdint.h include
7372cf7d9 [cscore] Windows NetworkUtil.cpp: Add missing include
b7e46c558 Include .h from .inc/.inl files (NFC) (#3017)
bf8f8710e [examples] Update Romi template and example (#2996)
6ffe5b775 [glass] Ensure NetworkTableTree parent context menu has an id (#3015)
be0805b85 [build] Update to WPILibVersioningPlugin 4.1.0 (#3014)
65b2359b2 [build] Add spotless for other files (#3007)
8651aa73e [examples] Enable NT Flush in Field2d examples (#3013)
78b542737 [build] Add Gazebo build to Actions CI (#3004)
fccf86532 [sim] DriverStationGui: Fix two bugs (#3010)
185741760 [sim] WSProvider_Joystick: Fix off-by-1 in incoming buttons (#3011)
ee7114a58 [glass] Add drive class widgets (#2975)
00fa91d0d [glass] Use ImGui style for gyro widget colors (#3009)
b7a25bfc3 ThirdPartyNotices: Add portable file dialogs license (#3005)
a2e46b9a1 [glass] modernize-use-nullptr (NFC) (#3006)
a751fa22d [build] Apply spotless for java formatting (#1768)
e563a0b7d [wpimath] Make LinearSystemLoop move-constructible and move-assignable (#2967)
49085ca94 [glass] Add context menus to remove and add NetworkTables values (#2979)
560a850a2 [glass] Add NetworkTables Log window (#2997)
66782e231 [sim] Create Left/Right drivetrain current accessors (#3001)
b60eb1544 clang-tidy: bugprone-virtual-near-miss
cbe59fa3b clang-tidy: google-explicit-constructor
c97c6dc06 clang-tidy: google-readability-casting (NFC)
32fa97d68 clang-tidy: modernize-use-nullptr (NFC)
aee460326 clang-tidy: modernize-pass-by-value
29c7da5f1 clang-tidy: modernize-make-unique
6131f4e32 clang-tidy: modernize-concat-nested-namespaces (NFC)
67e03e625 clang-tidy: modernize-use-equals-default
b124f9101 clang-tidy: modernize-use-default-member-init
d11a3a638 clang-tidy: modernize-use-override (NFC)
4cc0706b0 clang-tidy: modernize-use-using (NFC)
885f5a978 [wpilibc] Speed up ScopedTracerTest (#2999)
60b596457 [wpilibj] Fix typos (NFC) (#3000)
6e1919414 [build] Bring naming checkstyle rules up to date with Google Style guide (#1781)
8c8ec5e63 [wpilibj] Suppress unchecked cast warnings (#2995)
b8413ddd5 [wpiutil] Add noexcept to timestamp static functions (#2994)
5d976b6e1 [glass] Load NetworkTableView settings on first draw (#2993)
2b4317452 Replace NOLINT(runtime/explicit) comments with NOLINT (NFC) (#2992)
1c3011ba4 [glass] Fix handling of "/" NetworkTables key (#2991)
574a42f3b [hal] Fix UnsafeManipulateDIO status check (#2987)
9005cd59e [wpilib] Clamp input voltage in sim classes (#2955)
dd494d4ab [glass] NetworkTablesModel::Update(): Avoid use-after-move (#2988)
7cca469a1 [wpimath] NormalizeAngle: Make inline, remove unnamed namespace (#2986)
2aed432b4 Add braces to C++ single-line loops and conditionals (NFC) (#2973)
0291a3ff5 [wpiutil] StringRef: Add noexcept to several constructors (#2984)
5d7315280 [wpimath] Update UnitsTest.cpp copyright (#2985)
254931b9a [wpimath] Remove LinearSystem from LinearSystemLoop (#2968)
aa89744c9 Update OtherVersions.md to include wpimath info (#2983)
1cda3f5ad [glass] Fix styleguide (#2976)
8f1f64ffb Remove year from file copyright message (NFC) (#2972)
2bc0a7795 [examples] Fix wpiformat warning about utility include (#2971)
4204da6ad [glass] Add application icon
7ac39b10f [wpigui] Add icon support
6b567e006 [wpimath] Add support for varying vision standard deviations in pose estimators (#2956)
df299d6ed [wpimath] Add UnscentedKalmanFilter::Correct() overload (#2966)
4e34f0523 [examples] Use ADXRS450_GyroSim class in simulation example (#2964)
9962f6fd7 [wpilib] Give Field2d a default Sendable name (#2953)
f9d492f4b [sim] GUI: Show "Other Devices" window by default (#2961)
a8bb2ef1c [sim] Fix ADXRS450_GyroSim and DutyCycleEncoderSim (#2963)
240c629cd [sim] Try to guess "Map Gamepad" setting (#2960)
952567dd3 [wpilibc] Add missing move constructors and assignment operators (#2959)
10b396b4c [sim] Various WebSockets fixes and enhancements (#2952)
699bbe21a [examples] Fix comments in Gearsbot to match implementation (NFC) (#2957)
27b67deca [glass] Add more widgets (#2947)
581b7ec55 [wpilib] Add option to flush NetworkTables every iterative loop
acfbb1a44 [ntcore] DispatcherBase::Flush: Use wpi::Now()
d85a6d8fe [ntcore] Reduce limit on flush and update rate to 5 ms
20fbb5c63 [sim] Fix stringop truncation warning from GCC 10 (#2945)
1051a06a7 [glass] Show NT timestamps in seconds (#2944)
98dfc2620 [glass] Fix plots (#2943)
1ba0a2ced [sim] GUI: Add keyboard virtual joystick support (#2940)
4afb13f98 [examples] Replace M_PI with wpi::math::pi (#2938)
b27d33675 [examples] Enhance Romi templates (#2931)
00b9ae77f [sim] Change default WS port number to 3300 (#2932)
65219f309 [examples] Update Field2d position in periodic() (#2928)
f78d1d434 [sim] Process WS Encoder reset internally (#2927)
941edca59 [hal] Add Java SimDeviceDataJNI.getSimDeviceName (#2924)
a699435ed [wpilibj] Fix FlywheelSim argument order in constructor (#2922)
66d641718 [examples] Add tasks to run Java examples (#2920)
558e37c41 [examples] Add simple differential drive simulation example (#2918)
4f40d991e [glass] Switch name of Glass back to glass (#2919)
549af9900 [build] Update native-utils to 2021.0.6 (#2914)
b33693009 [glass] Change basename of glass to Glass (#2915)
c9a0edfb8 [glass] Package macOS application bundle
2c5668af4 [wpigui] Add platform-specific preferences save
751dea32a [wpilibc] Try to work around ABI break introduced in #2901 (#2917)
cd8f4bfb1 [build] Package up msvc runtime into maven artifact (#2913)
a6cfcc686 [wpilibc] Move SendableChooser Doxygen comments to header (NFC) (#2911)
b8c4f603d [wpimath] Upgrade to Eigen 3.3.9 (#2910)
0075e4b39 [wpilibj] Fix NPE in Field2d (#2909)
125af556c [simulation] Fix halsim_gui ntcore and wpiutil deps (#2908)
963ad5c25 [wpilib] Add noise to Differential Drive simulator (#2903)
387f56cb7 [examples] Add Romi reference Java example and templates (#2905)
b3deda38c [examples] Zero motors on disabledInit() in sim physics examples (#2906)
2a5ca7745 [glass] Add glass: an application for display of robot data
727940d84 [wpilib] Move Field2d to SmartDashboard
8cd42478e [wpilib] SendableBuilder: Make GetTable() visible
c11d34b26 [command] Use addCommands in command group templates (#2900)
339d7445b [sim] Add HAL hooks for simulationPeriodic (#2881)
d16f05f2c [wpilib] Fix SmartDashboard update order (#2896)
5427b32a4 [wpiutil] unique_function: Restrict implicit conversion (#2899)
f73701239 [ntcore] Add missing SetDefault initializer_list functions (#2898)
f5a6fc070 [sim] Add initialized flag for all solenoids on a PCM (#2897)
bdf5ba91a [wpilibj] Fix typo in ElevatorSim (#2895)
bc8f33877 [wpilib] Add pose estimators (#2867)
3413bfc06 [wpilib] PIDController: Recompute the error in AtSetpoint() (#2822)
2056f0ce0 [wpilib] Fix bugs in Hatchbot examples (#2893)
5eb8cfd69 [wpilibc] Fix MatchDataSender (#2892)
e6a425448 [build] Delete test folders after tests execute (#2891)
d478ad00d [imgui] Allow usage of imgui_stdlib (#2889)
53eda861d [build] Add unit-testing infrastructure to examples (#2863)
cc1d86ba6 [sim] Add title to simulator GUI window (#2888)
f0528f00e [build] CMake: Use project-specific binary and source dirs (#2886)
5cd2ad124 [wpilibc] Add Color::operator!= (#2887)
6c00e7a90 [build] CI CMake: build with GUI enabled (#2884)
53170bbb5 Update roboRIO toolchain installation instructions (#2883)
467258e05 [sim] GUI: Add option to not zero disconnected joysticks (#2876)
129be23c9 Clarify JDK installation instructions in readme (#2882)
8e9290e86 [build] Add separate CMake setting for wpimath (#2885)
7cf5bebf8 [wpilibj] Cache NT writes from DriverStation (#2780)
f7f9087fb [command] Fix timing issue in RamseteCommand (#2871)
256e7904f [wpilibj] SimDeviceSim: Fix sim value changed callback (#2880)
c8ea1b6c3 [wpilib] Add function to adjust LQR controller gain for pure time delay (#2878)
2816b06c0 [sim] HAL_GetControlWord: Fully zero output (#2873)
4c695ea08 Add toolchain installation instructions to README (#2875)
a14d51806 [wpimath] DCMotor: fix doc typo (NFC) (#2868)
017097791 [build] CMake: build sim extensions as shared libs (#2866)
f61726b5a [build] Fix cmake-config files (#2865)
fc27fdac5 [wpilibc] Cache NT values from driver station (#2768)
47c59859e [sim] Make SimDevice callbacks synchronous (#2861)
6e76ab9c0 [build] Turn on WITH_GUI for Windows cmake CI
5f78b7670 [build] Set GLFW_INSTALL to OFF
5e0808c84 [wpigui] Fix Windows cmake build
508f05a47 [imgui] Fix typo in Windows CMake target sources
Change-Id: I1737b45965f31803a96676bedc7dc40e337aa321
git-subtree-dir: third_party/allwpilib
git-subtree-split: e473a00f9785f9949e5ced30901baeaf426d2fc9
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/glass/src/libnt/native/cpp/NTCommandScheduler.cpp b/glass/src/libnt/native/cpp/NTCommandScheduler.cpp
new file mode 100644
index 0000000..ccc6412
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTCommandScheduler.cpp
@@ -0,0 +1,58 @@
+// 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/networktables/NTCommandScheduler.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTCommandSchedulerModel::NTCommandSchedulerModel(std::string_view path)
+ : NTCommandSchedulerModel(nt::GetDefaultInstance(), path) {}
+
+NTCommandSchedulerModel::NTCommandSchedulerModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_commands(m_nt.GetEntry(fmt::format("{}/Names", path))),
+ m_ids(m_nt.GetEntry(fmt::format("{}/Ids", path))),
+ m_cancel(m_nt.GetEntry(fmt::format("{}/Cancel", path))),
+ m_nameValue(wpi::rsplit(path, '/').second) {
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_commands);
+ m_nt.AddListener(m_ids);
+ m_nt.AddListener(m_cancel);
+}
+
+void NTCommandSchedulerModel::CancelCommand(size_t index) {
+ if (index < m_idsValue.size()) {
+ nt::SetEntryValue(
+ m_cancel, nt::NetworkTableValue::MakeDoubleArray({m_idsValue[index]}));
+ }
+}
+
+void NTCommandSchedulerModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ } else if (event.entry == m_commands) {
+ if (event.value && event.value->IsStringArray()) {
+ auto arr = event.value->GetStringArray();
+ m_commandsValue.assign(arr.begin(), arr.end());
+ }
+ } else if (event.entry == m_ids) {
+ if (event.value && event.value->IsDoubleArray()) {
+ auto arr = event.value->GetDoubleArray();
+ m_idsValue.assign(arr.begin(), arr.end());
+ }
+ }
+ }
+}
+
+bool NTCommandSchedulerModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_commands) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTCommandSelector.cpp b/glass/src/libnt/native/cpp/NTCommandSelector.cpp
new file mode 100644
index 0000000..efcbac2
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTCommandSelector.cpp
@@ -0,0 +1,47 @@
+// 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/networktables/NTCommandSelector.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTCommandSelectorModel::NTCommandSelectorModel(std::string_view path)
+ : NTCommandSelectorModel(nt::GetDefaultInstance(), path) {}
+
+NTCommandSelectorModel::NTCommandSelectorModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_running(m_nt.GetEntry(fmt::format("{}/running", path))),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_runningData(fmt::format("NTCmd:{}", path)),
+ m_nameValue(wpi::rsplit(path, '/').second) {
+ m_runningData.SetDigital(true);
+ m_nt.AddListener(m_running);
+ m_nt.AddListener(m_name);
+}
+
+void NTCommandSelectorModel::SetRunning(bool run) {
+ nt::SetEntryValue(m_running, nt::NetworkTableValue::MakeBoolean(run));
+}
+
+void NTCommandSelectorModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_running) {
+ if (event.value && event.value->IsBoolean()) {
+ m_runningData.SetValue(event.value->GetBoolean());
+ }
+ } else if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ }
+ }
+}
+
+bool NTCommandSelectorModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_running) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp b/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp
new file mode 100644
index 0000000..b44ea07
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTDifferentialDrive.cpp
@@ -0,0 +1,66 @@
+// 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/networktables/NTDifferentialDrive.h"
+
+#include <fmt/format.h>
+#include <imgui.h>
+#include <wpi/MathExtras.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTDifferentialDriveModel::NTDifferentialDriveModel(std::string_view path)
+ : NTDifferentialDriveModel(nt::GetDefaultInstance(), path) {}
+
+NTDifferentialDriveModel::NTDifferentialDriveModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
+ m_lPercent(m_nt.GetEntry(fmt::format("{}/Left Motor Speed", path))),
+ m_rPercent(m_nt.GetEntry(fmt::format("{}/Right Motor Speed", path))),
+ m_nameValue(wpi::rsplit(path, '/').second),
+ m_lPercentData(fmt::format("NTDiffDriveL:{}", path)),
+ m_rPercentData(fmt::format("NTDiffDriveR:{}", path)) {
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_controllable);
+ m_nt.AddListener(m_lPercent);
+ m_nt.AddListener(m_rPercent);
+
+ m_wheels.emplace_back("L % Output", &m_lPercentData, [this](auto value) {
+ nt::SetEntryValue(m_lPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+
+ m_wheels.emplace_back("R % Output", &m_rPercentData, [this](auto value) {
+ nt::SetEntryValue(m_rPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+}
+
+void NTDifferentialDriveModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_name && event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ } else if (event.entry == m_lPercent && event.value &&
+ event.value->IsDouble()) {
+ m_lPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_rPercent && event.value &&
+ event.value->IsDouble()) {
+ m_rPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_controllable && event.value &&
+ event.value->IsBoolean()) {
+ m_controllableValue = event.value->GetBoolean();
+ }
+ }
+
+ double l = m_lPercentData.GetValue();
+ double r = m_rPercentData.GetValue();
+
+ m_speedVector = ImVec2(0.0, -(l + r) / 2.0);
+ m_rotation = (l - r) / 2.0;
+}
+
+bool NTDifferentialDriveModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_lPercent) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTDigitalInput.cpp b/glass/src/libnt/native/cpp/NTDigitalInput.cpp
new file mode 100644
index 0000000..5de6c29
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTDigitalInput.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "glass/networktables/NTDigitalInput.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTDigitalInputModel::NTDigitalInputModel(std::string_view path)
+ : NTDigitalInputModel{nt::GetDefaultInstance(), path} {}
+
+NTDigitalInputModel::NTDigitalInputModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
+ m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
+ m_valueData{fmt::format("NT_DIn:{}", path)},
+ m_nameValue{wpi::rsplit(path, '/').second} {
+ m_nt.AddListener(m_value);
+ m_nt.AddListener(m_name);
+
+ m_valueData.SetDigital(true);
+}
+
+void NTDigitalInputModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_value) {
+ if (event.value && event.value->IsBoolean()) {
+ m_valueData.SetValue(event.value->GetBoolean());
+ }
+ } else if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ }
+ }
+}
+
+bool NTDigitalInputModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTDigitalOutput.cpp b/glass/src/libnt/native/cpp/NTDigitalOutput.cpp
new file mode 100644
index 0000000..a09d424
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTDigitalOutput.cpp
@@ -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.
+
+#include "glass/networktables/NTDigitalOutput.h"
+
+#include <fmt/format.h>
+
+using namespace glass;
+
+NTDigitalOutputModel::NTDigitalOutputModel(std::string_view path)
+ : NTDigitalOutputModel{nt::GetDefaultInstance(), path} {}
+
+NTDigitalOutputModel::NTDigitalOutputModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
+ m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
+ m_controllable{m_nt.GetEntry(fmt::format("{}/.controllable", path))},
+ m_valueData{fmt::format("NT_DOut:{}", path)} {
+ m_nt.AddListener(m_value);
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_controllable);
+
+ m_valueData.SetDigital(true);
+}
+
+void NTDigitalOutputModel::SetValue(bool val) {
+ nt::SetEntryValue(m_value, nt::Value::MakeBoolean(val));
+}
+
+void NTDigitalOutputModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_value) {
+ if (event.value && event.value->IsBoolean()) {
+ m_valueData.SetValue(event.value->GetBoolean());
+ }
+ } else if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ } else if (event.entry == m_controllable) {
+ if (event.value && event.value->IsBoolean()) {
+ m_controllableValue = event.value->GetBoolean();
+ }
+ }
+ }
+}
+
+bool NTDigitalOutputModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTFMS.cpp b/glass/src/libnt/native/cpp/NTFMS.cpp
new file mode 100644
index 0000000..84c1ce7
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTFMS.cpp
@@ -0,0 +1,91 @@
+// 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/networktables/NTFMS.h"
+
+#include <stdint.h>
+
+#include <fmt/format.h>
+#include <wpi/SmallVector.h>
+#include <wpi/timestamp.h>
+
+using namespace glass;
+
+NTFMSModel::NTFMSModel(std::string_view path)
+ : NTFMSModel{nt::GetDefaultInstance(), path} {}
+
+NTFMSModel::NTFMSModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_gameSpecificMessage{
+ m_nt.GetEntry(fmt::format("{}/GameSpecificMessage", path))},
+ m_alliance{m_nt.GetEntry(fmt::format("{}/IsRedAlliance", path))},
+ m_station{m_nt.GetEntry(fmt::format("{}/StationNumber", path))},
+ m_controlWord{m_nt.GetEntry(fmt::format("{}/FMSControlData", path))},
+ m_fmsAttached{fmt::format("NT_FMS:FMSAttached:{}", path)},
+ m_dsAttached{fmt::format("NT_FMS:DSAttached:{}", path)},
+ m_allianceStationId{fmt::format("NT_FMS:AllianceStationID:{}", path)},
+ m_estop{fmt::format("NT_FMS:EStop:{}", path)},
+ m_enabled{fmt::format("NT_FMS:RobotEnabled:{}", path)},
+ m_test{fmt::format("NT_FMS:TestMode:{}", path)},
+ m_autonomous{fmt::format("NT_FMS:AutonomousMode:{}", path)} {
+ m_nt.AddListener(m_alliance);
+ m_nt.AddListener(m_station);
+ m_nt.AddListener(m_controlWord);
+
+ m_fmsAttached.SetDigital(true);
+ m_dsAttached.SetDigital(true);
+ m_estop.SetDigital(true);
+ m_enabled.SetDigital(true);
+ m_test.SetDigital(true);
+ m_autonomous.SetDigital(true);
+}
+
+std::string_view NTFMSModel::GetGameSpecificMessage(
+ wpi::SmallVectorImpl<char>& buf) {
+ buf.clear();
+ auto value = nt::GetEntryValue(m_gameSpecificMessage);
+ if (value && value->IsString()) {
+ auto str = value->GetString();
+ buf.append(str.begin(), str.end());
+ }
+ return std::string_view{buf.data(), buf.size()};
+}
+
+void NTFMSModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_alliance) {
+ if (event.value && event.value->IsBoolean()) {
+ int allianceStationId = m_allianceStationId.GetValue();
+ allianceStationId %= 3;
+ // true if red
+ allianceStationId += 3 * (event.value->GetBoolean() ? 0 : 1);
+ m_allianceStationId.SetValue(allianceStationId);
+ }
+ } else if (event.entry == m_station) {
+ if (event.value && event.value->IsDouble()) {
+ int allianceStationId = m_allianceStationId.GetValue();
+ bool isRed = (allianceStationId < 3);
+ // the NT value is 1-indexed
+ m_allianceStationId.SetValue(event.value->GetDouble() - 1 +
+ 3 * (isRed ? 0 : 1));
+ }
+ } else if (event.entry == m_controlWord) {
+ if (event.value && event.value->IsDouble()) {
+ uint32_t controlWord = event.value->GetDouble();
+ // See HAL_ControlWord definition
+ auto time = wpi::Now();
+ m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, time);
+ m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, time);
+ m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, time);
+ m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, time);
+ m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, time);
+ m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, time);
+ }
+ }
+ }
+}
+
+bool NTFMSModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_controlWord) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTField2D.cpp b/glass/src/libnt/native/cpp/NTField2D.cpp
new file mode 100644
index 0000000..47fa9a7
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTField2D.cpp
@@ -0,0 +1,255 @@
+// 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/networktables/NTField2D.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <fmt/format.h>
+#include <ntcore_cpp.h>
+#include <wpi/Endian.h>
+#include <wpi/MathExtras.h>
+#include <wpi/SmallVector.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+class NTField2DModel::ObjectModel : public FieldObjectModel {
+ public:
+ ObjectModel(std::string_view name, NT_Entry entry)
+ : m_name{name}, m_entry{entry} {}
+
+ const char* GetName() const override { return m_name.c_str(); }
+ NT_Entry GetEntry() const { return m_entry; }
+
+ void NTUpdate(const nt::Value& value);
+
+ void Update() override {
+ if (auto value = nt::GetEntryValue(m_entry)) {
+ NTUpdate(*value);
+ }
+ }
+ bool Exists() override { return nt::GetEntryType(m_entry) != NT_UNASSIGNED; }
+ bool IsReadOnly() override { return false; }
+
+ wpi::span<const frc::Pose2d> GetPoses() override { return m_poses; }
+ void SetPoses(wpi::span<const frc::Pose2d> poses) override;
+ void SetPose(size_t i, frc::Pose2d pose) override;
+ void SetPosition(size_t i, frc::Translation2d pos) override;
+ void SetRotation(size_t i, frc::Rotation2d rot) override;
+
+ private:
+ void UpdateNT();
+
+ std::string m_name;
+ NT_Entry m_entry;
+
+ std::vector<frc::Pose2d> m_poses;
+};
+
+void NTField2DModel::ObjectModel::NTUpdate(const nt::Value& value) {
+ if (value.IsDoubleArray()) {
+ auto arr = value.GetDoubleArray();
+ auto size = arr.size();
+ if ((size % 3) != 0) {
+ return;
+ }
+ m_poses.resize(size / 3);
+ for (size_t i = 0; i < size / 3; ++i) {
+ m_poses[i] = frc::Pose2d{
+ units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
+ frc::Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
+ }
+ } else if (value.IsRaw()) {
+ // treat it simply as an array of doubles
+ std::string_view data = value.GetRaw();
+
+ // must be triples of doubles
+ auto size = data.size();
+ if ((size % (3 * 8)) != 0) {
+ return;
+ }
+ m_poses.resize(size / (3 * 8));
+ const char* p = data.data();
+ for (size_t i = 0; i < size / (3 * 8); ++i) {
+ double x = wpi::BitsToDouble(
+ wpi::support::endian::readNext<uint64_t, wpi::support::big,
+ wpi::support::unaligned>(p));
+ double y = wpi::BitsToDouble(
+ wpi::support::endian::readNext<uint64_t, wpi::support::big,
+ wpi::support::unaligned>(p));
+ double rot = wpi::BitsToDouble(
+ wpi::support::endian::readNext<uint64_t, wpi::support::big,
+ wpi::support::unaligned>(p));
+ m_poses[i] = frc::Pose2d{units::meter_t{x}, units::meter_t{y},
+ frc::Rotation2d{units::degree_t{rot}}};
+ }
+ }
+}
+
+void NTField2DModel::ObjectModel::UpdateNT() {
+ if (m_poses.size() < (255 / 3)) {
+ wpi::SmallVector<double, 9> arr;
+ for (auto&& pose : m_poses) {
+ auto& translation = pose.Translation();
+ arr.push_back(translation.X().value());
+ arr.push_back(translation.Y().value());
+ arr.push_back(pose.Rotation().Degrees().value());
+ }
+ nt::SetEntryTypeValue(m_entry, nt::Value::MakeDoubleArray(arr));
+ } else {
+ // send as raw array of doubles if too big for NT array
+ std::vector<char> arr;
+ arr.resize(m_poses.size() * 3 * 8);
+ char* p = arr.data();
+ for (auto&& pose : m_poses) {
+ auto& translation = pose.Translation();
+ wpi::support::endian::write64be(
+ p, wpi::DoubleToBits(translation.X().value()));
+ p += 8;
+ wpi::support::endian::write64be(
+ p, wpi::DoubleToBits(translation.Y().value()));
+ p += 8;
+ wpi::support::endian::write64be(
+ p, wpi::DoubleToBits(pose.Rotation().Degrees().value()));
+ p += 8;
+ }
+ nt::SetEntryTypeValue(m_entry,
+ nt::Value::MakeRaw({arr.data(), arr.size()}));
+ }
+}
+
+void NTField2DModel::ObjectModel::SetPoses(wpi::span<const frc::Pose2d> poses) {
+ m_poses.assign(poses.begin(), poses.end());
+ UpdateNT();
+}
+
+void NTField2DModel::ObjectModel::SetPose(size_t i, frc::Pose2d pose) {
+ if (i < m_poses.size()) {
+ m_poses[i] = pose;
+ UpdateNT();
+ }
+}
+
+void NTField2DModel::ObjectModel::SetPosition(size_t i,
+ frc::Translation2d pos) {
+ if (i < m_poses.size()) {
+ m_poses[i] = frc::Pose2d{pos, m_poses[i].Rotation()};
+ UpdateNT();
+ }
+}
+
+void NTField2DModel::ObjectModel::SetRotation(size_t i, frc::Rotation2d rot) {
+ if (i < m_poses.size()) {
+ m_poses[i] = frc::Pose2d{m_poses[i].Translation(), rot};
+ UpdateNT();
+ }
+}
+
+NTField2DModel::NTField2DModel(std::string_view path)
+ : NTField2DModel{nt::GetDefaultInstance(), path} {}
+
+NTField2DModel::NTField2DModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_path{fmt::format("{}/", path)},
+ m_name{m_nt.GetEntry(fmt::format("{}/.name", path))} {
+ m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
+ NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
+}
+
+NTField2DModel::~NTField2DModel() = default;
+
+void NTField2DModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ // .name
+ if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ continue;
+ }
+
+ // common case: update of existing entry; search by entry
+ if (event.flags & NT_NOTIFY_UPDATE) {
+ auto it = std::find_if(
+ m_objects.begin(), m_objects.end(),
+ [&](const auto& e) { return e->GetEntry() == event.entry; });
+ if (it != m_objects.end()) {
+ (*it)->NTUpdate(*event.value);
+ continue;
+ }
+ }
+
+ // handle create/delete
+ std::string_view name = event.name;
+ if (wpi::starts_with(name, m_path)) {
+ name.remove_prefix(m_path.size());
+ if (name.empty() || name[0] == '.') {
+ continue;
+ }
+ auto [it, match] = Find(event.name);
+ if (event.flags & NT_NOTIFY_DELETE) {
+ if (match) {
+ m_objects.erase(it);
+ }
+ continue;
+ } else if (event.flags & NT_NOTIFY_NEW) {
+ if (!match) {
+ it = m_objects.emplace(
+ it, std::make_unique<ObjectModel>(event.name, event.entry));
+ }
+ } else if (!match) {
+ continue;
+ }
+ if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
+ (*it)->NTUpdate(*event.value);
+ }
+ }
+ }
+}
+
+bool NTField2DModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
+}
+
+bool NTField2DModel::IsReadOnly() {
+ return false;
+}
+
+FieldObjectModel* NTField2DModel::AddFieldObject(std::string_view name) {
+ auto fullName = fmt::format("{}{}", m_path, name);
+ auto [it, match] = Find(fullName);
+ if (!match) {
+ it = m_objects.emplace(
+ it, std::make_unique<ObjectModel>(fullName, m_nt.GetEntry(fullName)));
+ }
+ return it->get();
+}
+
+void NTField2DModel::RemoveFieldObject(std::string_view name) {
+ auto [it, match] = Find(fmt::format("{}{}", m_path, name));
+ if (match) {
+ nt::DeleteEntry((*it)->GetEntry());
+ m_objects.erase(it);
+ }
+}
+
+void NTField2DModel::ForEachFieldObject(
+ wpi::function_ref<void(FieldObjectModel& model, std::string_view name)>
+ func) {
+ for (auto&& obj : m_objects) {
+ if (obj->Exists()) {
+ func(*obj, wpi::drop_front(obj->GetName(), m_path.size()));
+ }
+ }
+}
+
+std::pair<NTField2DModel::Objects::iterator, bool> NTField2DModel::Find(
+ std::string_view fullName) {
+ auto it = std::lower_bound(
+ m_objects.begin(), m_objects.end(), fullName,
+ [](const auto& e, std::string_view name) { return e->GetName() < name; });
+ return {it, it != m_objects.end() && (*it)->GetName() == fullName};
+}
diff --git a/glass/src/libnt/native/cpp/NTGyro.cpp b/glass/src/libnt/native/cpp/NTGyro.cpp
new file mode 100644
index 0000000..7651d2c
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTGyro.cpp
@@ -0,0 +1,41 @@
+// 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/networktables/NTGyro.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTGyroModel::NTGyroModel(std::string_view path)
+ : NTGyroModel(nt::GetDefaultInstance(), path) {}
+
+NTGyroModel::NTGyroModel(NT_Inst instance, std::string_view path)
+ : m_nt(instance),
+ m_angle(m_nt.GetEntry(fmt::format("{}/Value", path))),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_angleData(fmt::format("NT_Gyro:{}", path)),
+ m_nameValue(wpi::rsplit(path, '/').second) {
+ m_nt.AddListener(m_angle);
+ m_nt.AddListener(m_name);
+}
+
+void NTGyroModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_angle) {
+ if (event.value && event.value->IsDouble()) {
+ m_angleData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ }
+ }
+}
+
+bool NTGyroModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_angle) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTMecanumDrive.cpp b/glass/src/libnt/native/cpp/NTMecanumDrive.cpp
new file mode 100644
index 0000000..28c0a67
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTMecanumDrive.cpp
@@ -0,0 +1,92 @@
+// 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/networktables/NTMecanumDrive.h"
+
+#include <fmt/format.h>
+#include <imgui.h>
+#include <wpi/MathExtras.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTMecanumDriveModel::NTMecanumDriveModel(std::string_view path)
+ : NTMecanumDriveModel(nt::GetDefaultInstance(), path) {}
+
+NTMecanumDriveModel::NTMecanumDriveModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
+ m_flPercent(
+ m_nt.GetEntry(fmt::format("{}/Front Left Motor Speed", path))),
+ m_frPercent(
+ m_nt.GetEntry(fmt::format("{}/Front Right Motor Speed", path))),
+ m_rlPercent(m_nt.GetEntry(fmt::format("{}/Rear Left Motor Speed", path))),
+ m_rrPercent(
+ m_nt.GetEntry(fmt::format("{}/Rear Right Motor Speed", path))),
+ m_nameValue(wpi::rsplit(path, '/').second),
+ m_flPercentData(fmt::format("NTMcnmDriveFL:{}", path)),
+ m_frPercentData(fmt::format("NTMcnmDriveFR:{}", path)),
+ m_rlPercentData(fmt::format("NTMcnmDriveRL:{}", path)),
+ m_rrPercentData(fmt::format("NTMcnmDriveRR:{}", path)) {
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_controllable);
+ m_nt.AddListener(m_flPercent);
+ m_nt.AddListener(m_frPercent);
+ m_nt.AddListener(m_rlPercent);
+ m_nt.AddListener(m_rrPercent);
+
+ m_wheels.emplace_back("FL % Output", &m_flPercentData, [this](auto value) {
+ nt::SetEntryValue(m_flPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+
+ m_wheels.emplace_back("FR % Output", &m_frPercentData, [this](auto value) {
+ nt::SetEntryValue(m_frPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+
+ m_wheels.emplace_back("RL % Output", &m_rlPercentData, [this](auto value) {
+ nt::SetEntryValue(m_rlPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+
+ m_wheels.emplace_back("RR % Output", &m_rrPercentData, [this](auto value) {
+ nt::SetEntryValue(m_rrPercent, nt::NetworkTableValue::MakeDouble(value));
+ });
+}
+
+void NTMecanumDriveModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_name && event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ } else if (event.entry == m_flPercent && event.value &&
+ event.value->IsDouble()) {
+ m_flPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_frPercent && event.value &&
+ event.value->IsDouble()) {
+ m_frPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_rlPercent && event.value &&
+ event.value->IsDouble()) {
+ m_rlPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_rrPercent && event.value &&
+ event.value->IsDouble()) {
+ m_rrPercentData.SetValue(event.value->GetDouble());
+ } else if (event.entry == m_controllable && event.value &&
+ event.value->IsBoolean()) {
+ m_controllableValue = event.value->GetBoolean();
+ }
+ }
+
+ double fl = m_flPercentData.GetValue();
+ double fr = m_frPercentData.GetValue();
+ double rl = m_rlPercentData.GetValue();
+ double rr = m_rrPercentData.GetValue();
+
+ m_speedVector =
+ ImVec2((fl - fr - rl + rr) / 4.0f, -(fl + fr + rl + rr) / 4.0f);
+ m_rotation = -(-fl + fr - rl + rr) / 4;
+}
+
+bool NTMecanumDriveModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_flPercent) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTMechanism2D.cpp b/glass/src/libnt/native/cpp/NTMechanism2D.cpp
new file mode 100644
index 0000000..9c73af2
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTMechanism2D.cpp
@@ -0,0 +1,302 @@
+// 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/networktables/NTMechanism2D.h"
+
+#include <algorithm>
+#include <string_view>
+#include <vector>
+
+#include <fmt/format.h>
+#include <imgui.h>
+#include <ntcore_cpp.h>
+#include <wpi/StringExtras.h>
+
+#include "glass/other/Mechanism2D.h"
+
+using namespace glass;
+
+// Convert "#RRGGBB" hex color to ImU32 color
+static void ConvertColor(std::string_view in, ImU32* out) {
+ if (in.size() != 7 || in[0] != '#') {
+ return;
+ }
+ if (auto v = wpi::parse_integer<ImU32>(wpi::drop_front(in), 16)) {
+ ImU32 val = v.value();
+ *out = IM_COL32((val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff, 255);
+ }
+}
+
+namespace {
+
+class NTMechanismObjectModel;
+
+class NTMechanismGroupImpl final {
+ public:
+ NTMechanismGroupImpl(NT_Inst inst, std::string_view path,
+ std::string_view name)
+ : m_inst{inst}, m_path{path}, m_name{name} {}
+
+ const char* GetName() const { return m_name.c_str(); }
+ void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
+ void NTUpdate(const nt::EntryNotification& event, std::string_view name);
+
+ protected:
+ NT_Inst m_inst;
+ std::string m_path;
+ std::string m_name;
+ std::vector<std::unique_ptr<NTMechanismObjectModel>> m_objects;
+};
+
+class NTMechanismObjectModel final : public MechanismObjectModel {
+ public:
+ NTMechanismObjectModel(NT_Inst inst, std::string_view path,
+ std::string_view name)
+ : m_group{inst, path, name},
+ m_type{nt::GetEntry(inst, fmt::format("{}/.type", path))},
+ m_color{nt::GetEntry(inst, fmt::format("{}/color", path))},
+ m_weight{nt::GetEntry(inst, fmt::format("{}/weight", path))},
+ m_angle{nt::GetEntry(inst, fmt::format("{}/angle", path))},
+ m_length{nt::GetEntry(inst, fmt::format("{}/length", path))} {}
+
+ const char* GetName() const final { return m_group.GetName(); }
+ void ForEachObject(
+ wpi::function_ref<void(MechanismObjectModel& model)> func) final {
+ m_group.ForEachObject(func);
+ }
+
+ const char* GetType() const final { return m_typeValue.c_str(); }
+ ImU32 GetColor() const final { return m_colorValue; }
+ double GetWeight() const final { return m_weightValue; }
+ frc::Rotation2d GetAngle() const final { return m_angleValue; }
+ units::meter_t GetLength() const final { return m_lengthValue; }
+
+ bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
+
+ private:
+ NTMechanismGroupImpl m_group;
+
+ NT_Entry m_type;
+ NT_Entry m_color;
+ NT_Entry m_weight;
+ NT_Entry m_angle;
+ NT_Entry m_length;
+
+ std::string m_typeValue;
+ ImU32 m_colorValue = IM_COL32_WHITE;
+ double m_weightValue = 1.0;
+ frc::Rotation2d m_angleValue;
+ units::meter_t m_lengthValue = 0.0_m;
+};
+
+} // namespace
+
+void NTMechanismGroupImpl::ForEachObject(
+ wpi::function_ref<void(MechanismObjectModel& model)> func) {
+ for (auto&& obj : m_objects) {
+ func(*obj);
+ }
+}
+
+void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
+ std::string_view name) {
+ if (name.empty()) {
+ return;
+ }
+ std::string_view childName;
+ std::tie(name, childName) = wpi::split(name, '/');
+ if (childName.empty()) {
+ return;
+ }
+
+ auto it = std::lower_bound(
+ m_objects.begin(), m_objects.end(), name,
+ [](const auto& e, std::string_view name) { return e->GetName() < name; });
+ bool match = it != m_objects.end() && (*it)->GetName() == name;
+
+ if (event.flags & NT_NOTIFY_NEW) {
+ if (!match) {
+ it = m_objects.emplace(
+ it, std::make_unique<NTMechanismObjectModel>(
+ m_inst, fmt::format("{}/{}", m_path, name), name));
+ match = true;
+ }
+ }
+ if (match) {
+ if ((*it)->NTUpdate(event, childName)) {
+ m_objects.erase(it);
+ }
+ }
+}
+
+bool NTMechanismObjectModel::NTUpdate(const nt::EntryNotification& event,
+ std::string_view childName) {
+ if (event.entry == m_type) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0) {
+ return true;
+ }
+ if (event.value && event.value->IsString()) {
+ m_typeValue = event.value->GetString();
+ }
+ } else if (event.entry == m_color) {
+ if (event.value && event.value->IsString()) {
+ ConvertColor(event.value->GetString(), &m_colorValue);
+ }
+ } else if (event.entry == m_weight) {
+ if (event.value && event.value->IsDouble()) {
+ m_weightValue = event.value->GetDouble();
+ }
+ } else if (event.entry == m_angle) {
+ if (event.value && event.value->IsDouble()) {
+ m_angleValue = units::degree_t{event.value->GetDouble()};
+ }
+ } else if (event.entry == m_length) {
+ if (event.value && event.value->IsDouble()) {
+ m_lengthValue = units::meter_t{event.value->GetDouble()};
+ }
+ } else {
+ m_group.NTUpdate(event, childName);
+ }
+ return false;
+}
+
+class NTMechanism2DModel::RootModel final : public MechanismRootModel {
+ public:
+ RootModel(NT_Inst inst, std::string_view path, std::string_view name)
+ : m_group{inst, path, name},
+ m_x{nt::GetEntry(inst, fmt::format("{}/x", path))},
+ m_y{nt::GetEntry(inst, fmt::format("{}/y", path))} {}
+
+ const char* GetName() const final { return m_group.GetName(); }
+ void ForEachObject(
+ wpi::function_ref<void(MechanismObjectModel& model)> func) final {
+ m_group.ForEachObject(func);
+ }
+
+ bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
+
+ frc::Translation2d GetPosition() const override { return m_pos; };
+
+ private:
+ NTMechanismGroupImpl m_group;
+ NT_Entry m_x;
+ NT_Entry m_y;
+ frc::Translation2d m_pos;
+};
+
+bool NTMechanism2DModel::RootModel::NTUpdate(const nt::EntryNotification& event,
+ std::string_view childName) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0 &&
+ (event.entry == m_x || event.entry == m_y)) {
+ return true;
+ } else if (event.entry == m_x) {
+ if (event.value && event.value->IsDouble()) {
+ m_pos = frc::Translation2d{units::meter_t{event.value->GetDouble()},
+ m_pos.Y()};
+ }
+ } else if (event.entry == m_y) {
+ if (event.value && event.value->IsDouble()) {
+ m_pos = frc::Translation2d{m_pos.X(),
+ units::meter_t{event.value->GetDouble()}};
+ }
+ } else {
+ m_group.NTUpdate(event, childName);
+ }
+ return false;
+}
+
+NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
+ : NTMechanism2DModel{nt::GetDefaultInstance(), path} {}
+
+NTMechanism2DModel::NTMechanism2DModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_path{fmt::format("{}/", path)},
+ m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
+ m_dimensions{m_nt.GetEntry(fmt::format("{}/dims", path))},
+ m_bgColor{m_nt.GetEntry(fmt::format("{}/backgroundColor", path))},
+ m_dimensionsValue{1_m, 1_m} {
+ m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
+ NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
+}
+
+NTMechanism2DModel::~NTMechanism2DModel() = default;
+
+void NTMechanism2DModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ // .name
+ if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ continue;
+ }
+
+ // dims
+ if (event.entry == m_dimensions) {
+ if (event.value && event.value->IsDoubleArray()) {
+ auto arr = event.value->GetDoubleArray();
+ if (arr.size() == 2) {
+ m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
+ units::meter_t{arr[1]}};
+ }
+ }
+ }
+
+ // backgroundColor
+ if (event.entry == m_bgColor) {
+ if (event.value && event.value->IsString()) {
+ ConvertColor(event.value->GetString(), &m_bgColorValue);
+ }
+ }
+
+ std::string_view name = event.name;
+ if (wpi::starts_with(name, m_path)) {
+ name.remove_prefix(m_path.size());
+ if (name.empty() || name[0] == '.') {
+ continue;
+ }
+ std::string_view childName;
+ std::tie(name, childName) = wpi::split(name, '/');
+ if (childName.empty()) {
+ continue;
+ }
+
+ auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
+ [](const auto& e, std::string_view name) {
+ return e->GetName() < name;
+ });
+ bool match = it != m_roots.end() && (*it)->GetName() == name;
+
+ if (event.flags & NT_NOTIFY_NEW) {
+ if (!match) {
+ it = m_roots.emplace(
+ it,
+ std::make_unique<RootModel>(
+ m_nt.GetInstance(), fmt::format("{}{}", m_path, name), name));
+ match = true;
+ }
+ }
+ if (match) {
+ if ((*it)->NTUpdate(event, childName)) {
+ m_roots.erase(it);
+ }
+ }
+ }
+ }
+}
+
+bool NTMechanism2DModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
+}
+
+bool NTMechanism2DModel::IsReadOnly() {
+ return false;
+}
+
+void NTMechanism2DModel::ForEachRoot(
+ wpi::function_ref<void(MechanismRootModel& model)> func) {
+ for (auto&& obj : m_roots) {
+ func(*obj);
+ }
+}
diff --git a/glass/src/libnt/native/cpp/NTPIDController.cpp b/glass/src/libnt/native/cpp/NTPIDController.cpp
new file mode 100644
index 0000000..7936057
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTPIDController.cpp
@@ -0,0 +1,85 @@
+// 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/networktables/NTPIDController.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTPIDControllerModel::NTPIDControllerModel(std::string_view path)
+ : NTPIDControllerModel(nt::GetDefaultInstance(), path) {}
+
+NTPIDControllerModel::NTPIDControllerModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
+ m_p(m_nt.GetEntry(fmt::format("{}/p", path))),
+ m_i(m_nt.GetEntry(fmt::format("{}/i", path))),
+ m_d(m_nt.GetEntry(fmt::format("{}/d", path))),
+ m_setpoint(m_nt.GetEntry(fmt::format("{}/setpoint", path))),
+ m_pData(fmt::format("NTPIDCtrlP:{}", path)),
+ m_iData(fmt::format("NTPIDCtrlI:{}", path)),
+ m_dData(fmt::format("NTPIDCtrlD:{}", path)),
+ m_setpointData(fmt::format("NTPIDCtrlStpt:{}", path)),
+ m_nameValue(wpi::rsplit(path, '/').second) {
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_controllable);
+ m_nt.AddListener(m_p);
+ m_nt.AddListener(m_i);
+ m_nt.AddListener(m_d);
+ m_nt.AddListener(m_setpoint);
+}
+
+void NTPIDControllerModel::SetP(double value) {
+ nt::SetEntryValue(m_p, nt::NetworkTableValue::MakeDouble(value));
+}
+
+void NTPIDControllerModel::SetI(double value) {
+ nt::SetEntryValue(m_i, nt::NetworkTableValue::MakeDouble(value));
+}
+
+void NTPIDControllerModel::SetD(double value) {
+ nt::SetEntryValue(m_d, nt::NetworkTableValue::MakeDouble(value));
+}
+
+void NTPIDControllerModel::SetSetpoint(double value) {
+ nt::SetEntryValue(m_setpoint, nt::NetworkTableValue::MakeDouble(value));
+}
+
+void NTPIDControllerModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ } else if (event.entry == m_p) {
+ if (event.value && event.value->IsDouble()) {
+ m_pData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_i) {
+ if (event.value && event.value->IsDouble()) {
+ m_iData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_d) {
+ if (event.value && event.value->IsDouble()) {
+ m_dData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_setpoint) {
+ if (event.value && event.value->IsDouble()) {
+ m_setpointData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_controllable) {
+ if (event.value && event.value->IsBoolean()) {
+ m_controllableValue = event.value->GetBoolean();
+ }
+ }
+ }
+}
+
+bool NTPIDControllerModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_setpoint) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTSpeedController.cpp b/glass/src/libnt/native/cpp/NTSpeedController.cpp
new file mode 100644
index 0000000..3dc351a
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTSpeedController.cpp
@@ -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 "glass/networktables/NTSpeedController.h"
+
+#include <fmt/format.h>
+#include <wpi/StringExtras.h>
+
+using namespace glass;
+
+NTSpeedControllerModel::NTSpeedControllerModel(std::string_view path)
+ : NTSpeedControllerModel(nt::GetDefaultInstance(), path) {}
+
+NTSpeedControllerModel::NTSpeedControllerModel(NT_Inst instance,
+ std::string_view path)
+ : m_nt(instance),
+ m_value(m_nt.GetEntry(fmt::format("{}/Value", path))),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
+ m_valueData(fmt::format("NT_SpdCtrl:{}", path)),
+ m_nameValue(wpi::rsplit(path, '/').second) {
+ m_nt.AddListener(m_value);
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_controllable);
+}
+
+void NTSpeedControllerModel::SetPercent(double value) {
+ nt::SetEntryValue(m_value, nt::NetworkTableValue::MakeDouble(value));
+}
+
+void NTSpeedControllerModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_value) {
+ if (event.value && event.value->IsDouble()) {
+ m_valueData.SetValue(event.value->GetDouble());
+ }
+ } else if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ } else if (event.entry == m_controllable) {
+ if (event.value && event.value->IsBoolean()) {
+ m_controllableValue = event.value->GetBoolean();
+ }
+ }
+ }
+}
+
+bool NTSpeedControllerModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTStringChooser.cpp b/glass/src/libnt/native/cpp/NTStringChooser.cpp
new file mode 100644
index 0000000..e6a97fa
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTStringChooser.cpp
@@ -0,0 +1,75 @@
+// 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/networktables/NTStringChooser.h"
+
+#include <fmt/format.h>
+
+using namespace glass;
+
+NTStringChooserModel::NTStringChooserModel(std::string_view path)
+ : NTStringChooserModel{nt::GetDefaultInstance(), path} {}
+
+NTStringChooserModel::NTStringChooserModel(NT_Inst inst, std::string_view path)
+ : m_nt{inst},
+ m_default{m_nt.GetEntry(fmt::format("{}/default", path))},
+ m_selected{m_nt.GetEntry(fmt::format("{}/selected", path))},
+ m_active{m_nt.GetEntry(fmt::format("{}/active", path))},
+ m_options{m_nt.GetEntry(fmt::format("{}/options", path))} {
+ m_nt.AddListener(m_default);
+ m_nt.AddListener(m_selected);
+ m_nt.AddListener(m_active);
+ m_nt.AddListener(m_options);
+}
+
+void NTStringChooserModel::SetDefault(std::string_view val) {
+ nt::SetEntryValue(m_default, nt::Value::MakeString(val));
+}
+
+void NTStringChooserModel::SetSelected(std::string_view val) {
+ nt::SetEntryValue(m_selected, nt::Value::MakeString(val));
+}
+
+void NTStringChooserModel::SetActive(std::string_view val) {
+ nt::SetEntryValue(m_active, nt::Value::MakeString(val));
+}
+
+void NTStringChooserModel::SetOptions(wpi::span<const std::string> val) {
+ nt::SetEntryValue(m_options, nt::Value::MakeStringArray(val));
+}
+
+void NTStringChooserModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_default) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0) {
+ m_defaultValue.clear();
+ } else if (event.value && event.value->IsString()) {
+ m_defaultValue = event.value->GetString();
+ }
+ } else if (event.entry == m_selected) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0) {
+ m_selectedValue.clear();
+ } else if (event.value && event.value->IsString()) {
+ m_selectedValue = event.value->GetString();
+ }
+ } else if (event.entry == m_active) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0) {
+ m_activeValue.clear();
+ } else if (event.value && event.value->IsString()) {
+ m_activeValue = event.value->GetString();
+ }
+ } else if (event.entry == m_options) {
+ if ((event.flags & NT_NOTIFY_DELETE) != 0) {
+ m_optionsValue.clear();
+ } else if (event.value && event.value->IsStringArray()) {
+ auto arr = event.value->GetStringArray();
+ m_optionsValue.assign(arr.begin(), arr.end());
+ }
+ }
+ }
+}
+
+bool NTStringChooserModel::Exists() {
+ return m_nt.IsConnected() && nt::GetEntryType(m_options) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NTSubsystem.cpp b/glass/src/libnt/native/cpp/NTSubsystem.cpp
new file mode 100644
index 0000000..b2bdf8c
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NTSubsystem.cpp
@@ -0,0 +1,45 @@
+// 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/networktables/NTSubsystem.h"
+
+#include <fmt/format.h>
+
+using namespace glass;
+
+NTSubsystemModel::NTSubsystemModel(std::string_view path)
+ : NTSubsystemModel(nt::GetDefaultInstance(), path) {}
+
+NTSubsystemModel::NTSubsystemModel(NT_Inst instance, std::string_view path)
+ : m_nt(instance),
+ m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
+ m_defaultCommand(m_nt.GetEntry(fmt::format("{}/.default", path))),
+ m_currentCommand(m_nt.GetEntry(fmt::format("{}/.command", path))) {
+ m_nt.AddListener(m_name);
+ m_nt.AddListener(m_defaultCommand);
+ m_nt.AddListener(m_currentCommand);
+}
+
+void NTSubsystemModel::Update() {
+ for (auto&& event : m_nt.PollListener()) {
+ if (event.entry == m_name) {
+ if (event.value && event.value->IsString()) {
+ m_nameValue = event.value->GetString();
+ }
+ } else if (event.entry == m_defaultCommand) {
+ if (event.value && event.value->IsString()) {
+ m_defaultCommandValue = event.value->GetString();
+ }
+ } else if (event.entry == m_currentCommand) {
+ if (event.value && event.value->IsString()) {
+ m_currentCommandValue = event.value->GetString();
+ }
+ }
+ }
+}
+
+bool NTSubsystemModel::Exists() {
+ return m_nt.IsConnected() &&
+ nt::GetEntryType(m_defaultCommand) != NT_UNASSIGNED;
+}
diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp
new file mode 100644
index 0000000..596ef0a
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NetworkTables.cpp
@@ -0,0 +1,780 @@
+// 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/networktables/NetworkTables.h"
+
+#include <networktables/NetworkTableValue.h>
+
+#include <cinttypes>
+#include <cstdio>
+#include <cstring>
+#include <initializer_list>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include <fmt/format.h>
+#include <imgui.h>
+#include <ntcore_cpp.h>
+#include <wpi/SmallString.h>
+#include <wpi/SpanExtras.h>
+#include <wpi/StringExtras.h>
+#include <wpi/raw_ostream.h>
+#include <wpi/span.h>
+
+#include "glass/Context.h"
+#include "glass/DataSource.h"
+
+using namespace glass;
+
+static std::string BooleanArrayToString(wpi::span<const int> in) {
+ std::string rv;
+ wpi::raw_string_ostream os{rv};
+ os << '[';
+ bool first = true;
+ for (auto v : in) {
+ if (!first) {
+ os << ',';
+ }
+ first = false;
+ if (v) {
+ os << "true";
+ } else {
+ os << "false";
+ }
+ }
+ os << ']';
+ return rv;
+}
+
+static std::string DoubleArrayToString(wpi::span<const double> in) {
+ return fmt::format("[{:.6f}]", fmt::join(in, ","));
+}
+
+static std::string StringArrayToString(wpi::span<const std::string> in) {
+ std::string rv;
+ wpi::raw_string_ostream os{rv};
+ os << '[';
+ bool first = true;
+ for (auto&& v : in) {
+ if (!first) {
+ os << ',';
+ }
+ first = false;
+ os << '"';
+ os.write_escaped(v);
+ os << '"';
+ }
+ os << ']';
+ return rv;
+}
+
+NetworkTablesModel::NetworkTablesModel()
+ : NetworkTablesModel{nt::GetDefaultInstance()} {}
+
+NetworkTablesModel::NetworkTablesModel(NT_Inst inst)
+ : m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {
+ nt::AddPolledEntryListener(m_poller, "",
+ NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
+ NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
+ NT_NOTIFY_FLAGS | NT_NOTIFY_IMMEDIATE);
+}
+
+NetworkTablesModel::~NetworkTablesModel() {
+ nt::DestroyEntryListenerPoller(m_poller);
+}
+
+NetworkTablesModel::Entry::Entry(nt::EntryNotification&& event)
+ : entry{event.entry},
+ name{std::move(event.name)},
+ value{std::move(event.value)},
+ flags{nt::GetEntryFlags(event.entry)} {
+ UpdateValue();
+}
+
+void NetworkTablesModel::Entry::UpdateValue() {
+ switch (value->type()) {
+ case NT_BOOLEAN:
+ if (!source) {
+ source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
+ }
+ source->SetValue(value->GetBoolean() ? 1 : 0);
+ source->SetDigital(true);
+ break;
+ case NT_DOUBLE:
+ if (!source) {
+ source = std::make_unique<DataSource>(fmt::format("NT:{}", name));
+ }
+ source->SetValue(value->GetDouble());
+ source->SetDigital(false);
+ break;
+ case NT_BOOLEAN_ARRAY:
+ valueStr = BooleanArrayToString(value->GetBooleanArray());
+ break;
+ case NT_DOUBLE_ARRAY:
+ valueStr = DoubleArrayToString(value->GetDoubleArray());
+ break;
+ case NT_STRING_ARRAY:
+ valueStr = StringArrayToString(value->GetStringArray());
+ break;
+ default:
+ break;
+ }
+}
+
+void NetworkTablesModel::Update() {
+ bool timedOut = false;
+ bool updateTree = false;
+ for (auto&& event : nt::PollEntryListener(m_poller, 0, &timedOut)) {
+ auto& entry = m_entries[event.entry];
+ if (event.flags & NT_NOTIFY_NEW) {
+ if (!entry) {
+ entry = std::make_unique<Entry>(std::move(event));
+ m_sortedEntries.emplace_back(entry.get());
+ updateTree = true;
+ continue;
+ }
+ }
+ if (!entry) {
+ continue;
+ }
+ if (event.flags & NT_NOTIFY_DELETE) {
+ auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
+ entry.get());
+ // will be removed completely below
+ if (it != m_sortedEntries.end()) {
+ *it = nullptr;
+ }
+ m_entries.erase(event.entry);
+ updateTree = true;
+ continue;
+ }
+ if (event.flags & NT_NOTIFY_UPDATE) {
+ entry->value = std::move(event.value);
+ entry->UpdateValue();
+ }
+ if (event.flags & NT_NOTIFY_FLAGS) {
+ entry->flags = nt::GetEntryFlags(event.entry);
+ }
+ }
+
+ // shortcut common case (updates)
+ if (!updateTree) {
+ return;
+ }
+
+ // remove deleted entries
+ m_sortedEntries.erase(
+ std::remove(m_sortedEntries.begin(), m_sortedEntries.end(), nullptr),
+ m_sortedEntries.end());
+
+ // sort by name
+ std::sort(m_sortedEntries.begin(), m_sortedEntries.end(),
+ [](const auto& a, const auto& b) { return a->name < b->name; });
+
+ // rebuild tree
+ m_root.clear();
+ wpi::SmallVector<std::string_view, 16> parts;
+ for (auto& entry : m_sortedEntries) {
+ parts.clear();
+ wpi::split(entry->name, parts, '/', -1, false);
+
+ // ignore a raw "/" key
+ if (parts.empty()) {
+ continue;
+ }
+
+ // get to leaf
+ auto nodes = &m_root;
+ for (auto part : wpi::drop_back(wpi::span{parts.begin(), parts.end()})) {
+ auto it =
+ std::find_if(nodes->begin(), nodes->end(),
+ [&](const auto& node) { return node.name == part; });
+ if (it == nodes->end()) {
+ nodes->emplace_back(part);
+ // path is from the beginning of the string to the end of the current
+ // part; this works because part is a reference to the internals of
+ // entry->name
+ nodes->back().path.assign(
+ entry->name.data(), part.data() + part.size() - entry->name.data());
+ it = nodes->end() - 1;
+ }
+ nodes = &it->children;
+ }
+
+ auto it = std::find_if(nodes->begin(), nodes->end(), [&](const auto& node) {
+ return node.name == parts.back();
+ });
+ if (it == nodes->end()) {
+ nodes->emplace_back(parts.back());
+ // no need to set path, as it's identical to entry->name
+ it = nodes->end() - 1;
+ }
+ it->entry = entry;
+ }
+}
+
+bool NetworkTablesModel::Exists() {
+ return nt::IsConnected(m_inst);
+}
+
+static std::shared_ptr<nt::Value> StringToBooleanArray(std::string_view in) {
+ in = wpi::trim(in);
+ if (in.empty()) {
+ return nt::NetworkTableValue::MakeBooleanArray(
+ std::initializer_list<bool>{});
+ }
+ if (in.front() == '[') {
+ in.remove_prefix(1);
+ }
+ if (in.back() == ']') {
+ in.remove_suffix(1);
+ }
+ in = wpi::trim(in);
+
+ wpi::SmallVector<std::string_view, 16> inSplit;
+ wpi::SmallVector<int, 16> out;
+
+ wpi::split(in, inSplit, ',', -1, false);
+ for (auto val : inSplit) {
+ val = wpi::trim(val);
+ if (wpi::equals_lower(val, "true")) {
+ out.emplace_back(1);
+ } else if (wpi::equals_lower(val, "false")) {
+ out.emplace_back(0);
+ } else {
+ fmt::print(stderr,
+ "GUI: NetworkTables: Could not understand value '{}'\n", val);
+ return nullptr;
+ }
+ }
+
+ return nt::NetworkTableValue::MakeBooleanArray(out);
+}
+
+static std::shared_ptr<nt::Value> StringToDoubleArray(std::string_view in) {
+ in = wpi::trim(in);
+ if (in.empty()) {
+ return nt::NetworkTableValue::MakeDoubleArray(
+ std::initializer_list<double>{});
+ }
+ if (in.front() == '[') {
+ in.remove_prefix(1);
+ }
+ if (in.back() == ']') {
+ in.remove_suffix(1);
+ }
+ in = wpi::trim(in);
+
+ wpi::SmallVector<std::string_view, 16> inSplit;
+ wpi::SmallVector<double, 16> out;
+
+ wpi::split(in, inSplit, ',', -1, false);
+ for (auto val : inSplit) {
+ if (auto num = wpi::parse_float<double>(wpi::trim(val))) {
+ out.emplace_back(num.value());
+ } else {
+ fmt::print(stderr,
+ "GUI: NetworkTables: Could not understand value '{}'\n", val);
+ return nullptr;
+ }
+ }
+
+ return nt::NetworkTableValue::MakeDoubleArray(out);
+}
+
+static int fromxdigit(char ch) {
+ if (ch >= 'a' && ch <= 'f') {
+ return (ch - 'a' + 10);
+ } else if (ch >= 'A' && ch <= 'F') {
+ return (ch - 'A' + 10);
+ } else {
+ return ch - '0';
+ }
+}
+
+static std::string_view UnescapeString(std::string_view source,
+ wpi::SmallVectorImpl<char>& buf) {
+ assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
+ buf.clear();
+ buf.reserve(source.size() - 2);
+ for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
+ if (*s != '\\') {
+ buf.push_back(*s);
+ continue;
+ }
+ switch (*++s) {
+ case 't':
+ buf.push_back('\t');
+ break;
+ case 'n':
+ buf.push_back('\n');
+ break;
+ case 'x': {
+ if (!isxdigit(*(s + 1))) {
+ buf.push_back('x'); // treat it like a unknown escape
+ break;
+ }
+ int ch = fromxdigit(*++s);
+ if (std::isxdigit(*(s + 1))) {
+ ch <<= 4;
+ ch |= fromxdigit(*++s);
+ }
+ buf.push_back(static_cast<char>(ch));
+ break;
+ }
+ default:
+ buf.push_back(*s);
+ break;
+ }
+ }
+ return {buf.data(), buf.size()};
+}
+
+static std::shared_ptr<nt::Value> StringToStringArray(std::string_view in) {
+ in = wpi::trim(in);
+ if (in.empty()) {
+ return nt::NetworkTableValue::MakeStringArray(
+ std::initializer_list<std::string>{});
+ }
+ if (in.front() == '[') {
+ in.remove_prefix(1);
+ }
+ if (in.back() == ']') {
+ in.remove_suffix(1);
+ }
+ in = wpi::trim(in);
+
+ wpi::SmallVector<std::string_view, 16> inSplit;
+ std::vector<std::string> out;
+ wpi::SmallString<32> buf;
+
+ wpi::split(in, inSplit, ',', -1, false);
+ for (auto val : inSplit) {
+ val = wpi::trim(val);
+ if (val.empty()) {
+ continue;
+ }
+ if (val.front() != '"' || val.back() != '"') {
+ fmt::print(stderr,
+ "GUI: NetworkTables: Could not understand value '{}'\n", val);
+ return nullptr;
+ }
+ out.emplace_back(UnescapeString(val, buf));
+ }
+
+ return nt::NetworkTableValue::MakeStringArray(std::move(out));
+}
+
+static void EmitEntryValueReadonly(NetworkTablesModel::Entry& entry) {
+ auto& val = entry.value;
+ if (!val) {
+ return;
+ }
+
+ switch (val->type()) {
+ case NT_BOOLEAN:
+ ImGui::LabelText("boolean", "%s", val->GetBoolean() ? "true" : "false");
+ break;
+ case NT_DOUBLE:
+ ImGui::LabelText("double", "%.6f", val->GetDouble());
+ break;
+ case NT_STRING: {
+ // GetString() comes from a std::string, so it's null terminated
+ ImGui::LabelText("string", "%s", val->GetString().data());
+ break;
+ }
+ case NT_BOOLEAN_ARRAY:
+ ImGui::LabelText("boolean[]", "%s", entry.valueStr.c_str());
+ break;
+ case NT_DOUBLE_ARRAY:
+ ImGui::LabelText("double[]", "%s", entry.valueStr.c_str());
+ break;
+ case NT_STRING_ARRAY:
+ ImGui::LabelText("string[]", "%s", entry.valueStr.c_str());
+ break;
+ case NT_RAW:
+ ImGui::LabelText("raw", "[...]");
+ break;
+ case NT_RPC:
+ ImGui::LabelText("rpc", "[...]");
+ break;
+ default:
+ ImGui::LabelText("other", "?");
+ break;
+ }
+}
+
+static constexpr size_t kTextBufferSize = 4096;
+
+static char* GetTextBuffer(std::string_view in) {
+ static char textBuffer[kTextBufferSize];
+ size_t len = (std::min)(in.size(), kTextBufferSize - 1);
+ std::memcpy(textBuffer, in.data(), len);
+ textBuffer[len] = '\0';
+ return textBuffer;
+}
+
+static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry) {
+ auto& val = entry.value;
+ if (!val) {
+ return;
+ }
+
+ ImGui::PushID(entry.name.c_str());
+ switch (val->type()) {
+ case NT_BOOLEAN: {
+ static const char* boolOptions[] = {"false", "true"};
+ int v = val->GetBoolean() ? 1 : 0;
+ if (ImGui::Combo("boolean", &v, boolOptions, 2)) {
+ nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeBoolean(v));
+ }
+ break;
+ }
+ case NT_DOUBLE: {
+ double v = val->GetDouble();
+ if (ImGui::InputDouble("double", &v, 0, 0, "%.6f",
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeDouble(v));
+ }
+ break;
+ }
+ case NT_STRING: {
+ char* v = GetTextBuffer(val->GetString());
+ if (ImGui::InputText("string", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ nt::SetEntryValue(entry.entry, nt::NetworkTableValue::MakeString(v));
+ }
+ break;
+ }
+ case NT_BOOLEAN_ARRAY: {
+ char* v = GetTextBuffer(entry.valueStr);
+ if (ImGui::InputText("boolean[]", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ if (auto outv = StringToBooleanArray(v)) {
+ nt::SetEntryValue(entry.entry, std::move(outv));
+ }
+ }
+ break;
+ }
+ case NT_DOUBLE_ARRAY: {
+ char* v = GetTextBuffer(entry.valueStr);
+ if (ImGui::InputText("double[]", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ if (auto outv = StringToDoubleArray(v)) {
+ nt::SetEntryValue(entry.entry, std::move(outv));
+ }
+ }
+ break;
+ }
+ case NT_STRING_ARRAY: {
+ char* v = GetTextBuffer(entry.valueStr);
+ if (ImGui::InputText("string[]", v, kTextBufferSize,
+ ImGuiInputTextFlags_EnterReturnsTrue)) {
+ if (auto outv = StringToStringArray(v)) {
+ nt::SetEntryValue(entry.entry, std::move(outv));
+ }
+ }
+ break;
+ }
+ case NT_RAW:
+ ImGui::LabelText("raw", "[...]");
+ break;
+ case NT_RPC:
+ ImGui::LabelText("rpc", "[...]");
+ break;
+ default:
+ ImGui::LabelText("other", "?");
+ break;
+ }
+ ImGui::PopID();
+}
+
+static void EmitParentContextMenu(const std::string& path,
+ NetworkTablesFlags flags) {
+ // Workaround https://github.com/ocornut/imgui/issues/331
+ bool openWarningPopup = false;
+ static char nameBuffer[kTextBufferSize];
+ if (ImGui::BeginPopupContextItem(path.c_str())) {
+ ImGui::Text("%s", path.c_str());
+ ImGui::Separator();
+
+ if (ImGui::BeginMenu("Add new...")) {
+ if (ImGui::IsWindowAppearing()) {
+ nameBuffer[0] = '\0';
+ }
+
+ ImGui::InputTextWithHint("New item name", "example", nameBuffer,
+ kTextBufferSize);
+ std::string fullNewPath;
+ if (path == "/") {
+ fullNewPath = path + nameBuffer;
+ } else {
+ fullNewPath = fmt::format("{}/{}", path, nameBuffer);
+ }
+
+ ImGui::Text("Adding: %s", fullNewPath.c_str());
+ ImGui::Separator();
+ auto entry = nt::GetEntry(nt::GetDefaultInstance(), fullNewPath);
+ bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
+ nameBuffer[0] != '\0') &&
+ nt::GetEntryType(entry) == NT_Type::NT_UNASSIGNED;
+ if (ImGui::MenuItem("string", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeString(""))) {
+ openWarningPopup = true;
+ }
+ }
+ if (ImGui::MenuItem("double", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeDouble(0.0))) {
+ openWarningPopup = true;
+ }
+ }
+ if (ImGui::MenuItem("boolean", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeBoolean(false))) {
+ openWarningPopup = true;
+ }
+ }
+ if (ImGui::MenuItem("string[]", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeStringArray({""}))) {
+ openWarningPopup = true;
+ }
+ }
+ if (ImGui::MenuItem("double[]", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeDoubleArray({0.0}))) {
+ openWarningPopup = true;
+ }
+ }
+ if (ImGui::MenuItem("boolean[]", nullptr, false, enabled)) {
+ if (!nt::SetEntryValue(entry, nt::Value::MakeBooleanArray({false}))) {
+ openWarningPopup = true;
+ }
+ }
+
+ ImGui::EndMenu();
+ }
+
+ ImGui::Separator();
+ if (ImGui::MenuItem("Remove All")) {
+ for (auto&& entry : nt::GetEntries(nt::GetDefaultInstance(), path, 0)) {
+ nt::DeleteEntry(entry);
+ }
+ }
+ ImGui::EndPopup();
+ }
+
+ // Workaround https://github.com/ocornut/imgui/issues/331
+ if (openWarningPopup) {
+ ImGui::OpenPopup("Value exists");
+ }
+ if (ImGui::BeginPopupModal("Value exists", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::Text("The provided name %s already exists in the tree!", nameBuffer);
+ ImGui::Separator();
+
+ if (ImGui::Button("OK", ImVec2(120, 0))) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SetItemDefaultFocus();
+ ImGui::EndPopup();
+ }
+}
+
+static void EmitEntry(NetworkTablesModel::Entry& entry, const char* name,
+ NetworkTablesFlags flags) {
+ if (entry.source) {
+ ImGui::Selectable(name);
+ entry.source->EmitDrag();
+ } else {
+ ImGui::Text("%s", name);
+ }
+ if (ImGui::BeginPopupContextItem(entry.name.c_str())) {
+ ImGui::Text("%s", entry.name.c_str());
+ ImGui::Separator();
+ if (ImGui::MenuItem("Remove")) {
+ nt::DeleteEntry(entry.entry);
+ }
+ ImGui::EndPopup();
+ }
+ ImGui::NextColumn();
+
+ if (flags & NetworkTablesFlags_ReadOnly) {
+ EmitEntryValueReadonly(entry);
+ } else {
+ EmitEntryValueEditable(entry);
+ }
+ ImGui::NextColumn();
+
+ if (flags & NetworkTablesFlags_ShowFlags) {
+ if ((entry.flags & NT_PERSISTENT) != 0) {
+ ImGui::Text("Persistent");
+ } else if (entry.flags != 0) {
+ ImGui::Text("%02x", entry.flags);
+ }
+ ImGui::NextColumn();
+ }
+
+ if (flags & NetworkTablesFlags_ShowTimestamp) {
+ if (entry.value) {
+ ImGui::Text("%f", (entry.value->last_change() * 1.0e-6) -
+ (GetZeroTime() * 1.0e-6));
+ } else {
+ ImGui::TextUnformatted("");
+ }
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+}
+
+static void EmitTree(const std::vector<NetworkTablesModel::TreeNode>& tree,
+ NetworkTablesFlags flags) {
+ for (auto&& node : tree) {
+ if (node.entry) {
+ EmitEntry(*node.entry, node.name.c_str(), flags);
+ }
+
+ if (!node.children.empty()) {
+ bool open =
+ TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
+ EmitParentContextMenu(node.path, flags);
+ ImGui::NextColumn();
+ ImGui::NextColumn();
+ if (flags & NetworkTablesFlags_ShowFlags) {
+ ImGui::NextColumn();
+ }
+ if (flags & NetworkTablesFlags_ShowTimestamp) {
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+ if (open) {
+ EmitTree(node.children, flags);
+ TreePop();
+ }
+ }
+ }
+}
+
+void glass::DisplayNetworkTables(NetworkTablesModel* model,
+ NetworkTablesFlags flags) {
+ auto inst = model->GetInstance();
+
+ if (flags & NetworkTablesFlags_ShowConnections) {
+ if (CollapsingHeader("Connections")) {
+ ImGui::Columns(4, "connections");
+ ImGui::Text("Id");
+ ImGui::NextColumn();
+ ImGui::Text("Address");
+ ImGui::NextColumn();
+ ImGui::Text("Updated");
+ ImGui::NextColumn();
+ ImGui::Text("Proto");
+ ImGui::NextColumn();
+ ImGui::Separator();
+ for (auto&& i : nt::GetConnections(inst)) {
+ ImGui::Text("%s", i.remote_id.c_str());
+ ImGui::NextColumn();
+ ImGui::Text("%s", i.remote_ip.c_str());
+ ImGui::NextColumn();
+ ImGui::Text("%llu",
+ static_cast<unsigned long long>( // NOLINT(runtime/int)
+ i.last_update));
+ ImGui::NextColumn();
+ ImGui::Text("%d.%d", i.protocol_version >> 8,
+ i.protocol_version & 0xff);
+ ImGui::NextColumn();
+ }
+ ImGui::Columns();
+ }
+
+ if (!CollapsingHeader("Values", ImGuiTreeNodeFlags_DefaultOpen)) {
+ return;
+ }
+ }
+
+ const bool showFlags = (flags & NetworkTablesFlags_ShowFlags);
+ const bool showTimestamp = (flags & NetworkTablesFlags_ShowTimestamp);
+
+ static bool first = true;
+ ImGui::Columns(2 + (showFlags ? 1 : 0) + (showTimestamp ? 1 : 0), "values");
+ if (first) {
+ ImGui::SetColumnWidth(-1, 0.5f * ImGui::GetWindowWidth());
+ }
+ ImGui::Text("Name");
+ EmitParentContextMenu("/", flags);
+ ImGui::NextColumn();
+ ImGui::Text("Value");
+ ImGui::NextColumn();
+ if (showFlags) {
+ if (first) {
+ ImGui::SetColumnWidth(-1, 12 * ImGui::GetFontSize());
+ }
+ ImGui::Text("Flags");
+ ImGui::NextColumn();
+ }
+ if (showTimestamp) {
+ ImGui::Text("Changed");
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+ first = false;
+
+ if (flags & NetworkTablesFlags_TreeView) {
+ EmitTree(model->GetTreeRoot(), flags);
+ } else {
+ for (auto entry : model->GetEntries()) {
+ EmitEntry(*entry, entry->name.c_str(), flags);
+ }
+ }
+ ImGui::Columns();
+}
+
+void NetworkTablesFlagsSettings::Update() {
+ if (!m_pTreeView) {
+ auto& storage = GetStorage();
+ m_pTreeView = storage.GetBoolRef(
+ "tree", m_defaultFlags & NetworkTablesFlags_TreeView);
+ m_pShowConnections = storage.GetBoolRef(
+ "connections", m_defaultFlags & NetworkTablesFlags_ShowConnections);
+ m_pShowFlags = storage.GetBoolRef(
+ "flags", m_defaultFlags & NetworkTablesFlags_ShowFlags);
+ m_pShowTimestamp = storage.GetBoolRef(
+ "timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp);
+ m_pCreateNoncanonicalKeys = storage.GetBoolRef(
+ "createNonCanonical",
+ m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys);
+ }
+
+ m_flags &=
+ ~(NetworkTablesFlags_TreeView | NetworkTablesFlags_ShowConnections |
+ NetworkTablesFlags_ShowFlags | NetworkTablesFlags_ShowTimestamp |
+ NetworkTablesFlags_CreateNoncanonicalKeys);
+ m_flags |=
+ (*m_pTreeView ? NetworkTablesFlags_TreeView : 0) |
+ (*m_pShowConnections ? NetworkTablesFlags_ShowConnections : 0) |
+ (*m_pShowFlags ? NetworkTablesFlags_ShowFlags : 0) |
+ (*m_pShowTimestamp ? NetworkTablesFlags_ShowTimestamp : 0) |
+ (*m_pCreateNoncanonicalKeys ? NetworkTablesFlags_CreateNoncanonicalKeys
+ : 0);
+}
+
+void NetworkTablesFlagsSettings::DisplayMenu() {
+ if (!m_pTreeView) {
+ return;
+ }
+ ImGui::MenuItem("Tree View", "", m_pTreeView);
+ ImGui::MenuItem("Show Connections", "", m_pShowConnections);
+ ImGui::MenuItem("Show Flags", "", m_pShowFlags);
+ ImGui::MenuItem("Show Timestamp", "", m_pShowTimestamp);
+ ImGui::Separator();
+ ImGui::MenuItem("Allow creation of non-canonical keys", "",
+ m_pCreateNoncanonicalKeys);
+}
+
+void NetworkTablesView::Display() {
+ m_flags.Update();
+ if (ImGui::BeginPopupContextItem()) {
+ m_flags.DisplayMenu();
+ ImGui::EndPopup();
+ }
+ DisplayNetworkTables(m_model, m_flags.GetFlags());
+}
diff --git a/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp b/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
new file mode 100644
index 0000000..5cb5bbc
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NetworkTablesHelper.cpp
@@ -0,0 +1,19 @@
+// 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/networktables/NetworkTablesHelper.h"
+
+using namespace glass;
+
+NetworkTablesHelper::NetworkTablesHelper(NT_Inst inst)
+ : m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {}
+
+NetworkTablesHelper::~NetworkTablesHelper() {
+ nt::DestroyEntryListenerPoller(m_poller);
+}
+
+bool NetworkTablesHelper::IsConnected() const {
+ return nt::GetNetworkMode(m_inst) == NT_NET_MODE_SERVER ||
+ nt::IsConnected(m_inst);
+}
diff --git a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
new file mode 100644
index 0000000..8d991cb
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp
@@ -0,0 +1,205 @@
+// 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/networktables/NetworkTablesProvider.h"
+
+#include <algorithm>
+
+#include <fmt/format.h>
+#include <ntcore_cpp.h>
+#include <wpi/SmallString.h>
+#include <wpi/StringExtras.h>
+#include <wpigui.h>
+
+using namespace glass;
+
+NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName)
+ : NetworkTablesProvider{iniName, nt::GetDefaultInstance()} {}
+
+NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName,
+ NT_Inst inst)
+ : Provider{fmt::format("{}Window", iniName)},
+ m_nt{inst},
+ m_typeCache{iniName} {
+ m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
+ NT_NOTIFY_IMMEDIATE);
+}
+
+void NetworkTablesProvider::GlobalInit() {
+ Provider::GlobalInit();
+ wpi::gui::AddInit([this] { m_typeCache.Initialize(); });
+}
+
+void NetworkTablesProvider::DisplayMenu() {
+ wpi::SmallVector<std::string_view, 6> path;
+ wpi::SmallString<64> name;
+ for (auto&& entry : m_viewEntries) {
+ path.clear();
+ wpi::split(entry->name, path, '/', -1, false);
+
+ bool fullDepth = true;
+ int depth = 0;
+ for (; depth < (static_cast<int>(path.size()) - 1); ++depth) {
+ name = path[depth];
+ if (!ImGui::BeginMenu(name.c_str())) {
+ fullDepth = false;
+ break;
+ }
+ }
+
+ if (fullDepth) {
+ bool visible = entry->window && entry->window->IsVisible();
+ bool wasVisible = visible;
+ // FIXME: enabled?
+ // data is the last item, so is guaranteed to be null-terminated
+ ImGui::MenuItem(path.back().data(), nullptr, &visible, true);
+ if (!wasVisible && visible) {
+ Show(entry.get(), entry->window);
+ } else if (wasVisible && !visible && entry->window) {
+ entry->window->SetVisible(false);
+ }
+ }
+
+ for (; depth > 0; --depth) {
+ ImGui::EndMenu();
+ }
+ }
+}
+
+void NetworkTablesProvider::Update() {
+ Provider::Update();
+
+ // add/remove entries from NT changes
+ for (auto&& event : m_nt.PollListener()) {
+ // look for .type fields
+ std::string_view eventName{event.name};
+ if (!wpi::ends_with(eventName, "/.type") || !event.value ||
+ !event.value->IsString()) {
+ continue;
+ }
+ auto tableName = wpi::drop_back(eventName, 6);
+
+ // only handle ones where we have a builder
+ auto builderIt = m_typeMap.find(event.value->GetString());
+ if (builderIt == m_typeMap.end()) {
+ continue;
+ }
+
+ if (event.flags & NT_NOTIFY_DELETE) {
+ auto it = std::find_if(
+ m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
+ return static_cast<Entry*>(elem->modelEntry)->typeEntry ==
+ event.entry;
+ });
+ if (it != m_viewEntries.end()) {
+ m_viewEntries.erase(it);
+ }
+ } else if (event.flags & NT_NOTIFY_NEW) {
+ GetOrCreateView(builderIt->second, event.entry, tableName);
+ // cache the type
+ m_typeCache[tableName].SetName(event.value->GetString());
+ }
+ }
+
+ // check for visible windows that need displays (typically this is due to
+ // file loading)
+ for (auto&& window : m_windows) {
+ if (!window->IsVisible() || window->HasView()) {
+ continue;
+ }
+ auto id = window->GetId();
+ auto typeIt = m_typeCache.find(id);
+ if (typeIt == m_typeCache.end()) {
+ continue;
+ }
+
+ // only handle ones where we have a builder
+ auto builderIt = m_typeMap.find(typeIt->second.GetName());
+ if (builderIt == m_typeMap.end()) {
+ continue;
+ }
+
+ auto entry = GetOrCreateView(
+ builderIt->second,
+ nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id);
+ if (entry) {
+ Show(entry, window.get());
+ }
+ }
+}
+
+void NetworkTablesProvider::Register(std::string_view typeName,
+ CreateModelFunc createModel,
+ CreateViewFunc createView) {
+ m_typeMap[typeName] = Builder{std::move(createModel), std::move(createView)};
+}
+
+void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
+ // if there's already a window, just show it
+ if (entry->window) {
+ entry->window->SetVisible(true);
+ return;
+ }
+
+ // get or create model
+ if (!entry->modelEntry->model) {
+ entry->modelEntry->model =
+ entry->modelEntry->createModel(m_nt.GetInstance(), entry->name.c_str());
+ }
+ if (!entry->modelEntry->model) {
+ return;
+ }
+
+ // the window might exist and we're just not associated to it yet
+ if (!window) {
+ window = GetOrAddWindow(entry->name, true);
+ }
+ if (!window) {
+ return;
+ }
+ if (wpi::starts_with(entry->name, "/SmartDashboard/")) {
+ window->SetDefaultName(
+ fmt::format("{} (SmartDashboard)", wpi::drop_front(entry->name, 16)));
+ }
+ entry->window = window;
+
+ // create view
+ auto view = entry->createView(window, entry->modelEntry->model.get(),
+ entry->name.c_str());
+ if (!view) {
+ return;
+ }
+ window->SetView(std::move(view));
+
+ entry->window->SetVisible(true);
+}
+
+NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
+ const Builder& builder, NT_Entry typeEntry, std::string_view name) {
+ // get view entry if it already exists
+ auto viewIt = FindViewEntry(name);
+ if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
+ // make sure typeEntry is set in model
+ static_cast<Entry*>((*viewIt)->modelEntry)->typeEntry = typeEntry;
+ return viewIt->get();
+ }
+
+ // get or create model entry
+ auto modelIt = FindModelEntry(name);
+ if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
+ static_cast<Entry*>(modelIt->get())->typeEntry = typeEntry;
+ } else {
+ modelIt = m_modelEntries.emplace(
+ modelIt, std::make_unique<Entry>(typeEntry, name, builder));
+ }
+
+ // create new view entry
+ viewIt = m_viewEntries.emplace(
+ viewIt,
+ std::make_unique<ViewEntry>(
+ name, modelIt->get(), [](Model*, const char*) { return true; },
+ builder.createView));
+
+ return viewIt->get();
+}
diff --git a/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
new file mode 100644
index 0000000..28f4de4
--- /dev/null
+++ b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
@@ -0,0 +1,133 @@
+// 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/networktables/NetworkTablesSettings.h"
+
+#include <optional>
+#include <string_view>
+#include <utility>
+
+#include <imgui.h>
+#include <imgui_stdlib.h>
+#include <ntcore_cpp.h>
+#include <wpi/SmallVector.h>
+#include <wpi/StringExtras.h>
+
+#include "glass/Context.h"
+
+using namespace glass;
+
+void NetworkTablesSettings::Thread::Main() {
+ while (m_active) {
+ // wait to be woken up
+ std::unique_lock lock(m_mutex);
+ m_cond.wait(lock, [&] { return !m_active || m_restart; });
+ if (!m_active) {
+ break;
+ }
+
+ // clear restart flag
+ m_restart = false;
+
+ int mode;
+ bool dsClient;
+
+ do {
+ mode = m_mode;
+ dsClient = m_dsClient;
+
+ // release lock while stopping to avoid blocking GUI
+ lock.unlock();
+
+ // if just changing servers in client mode, no need to stop and restart
+ unsigned int curMode = nt::GetNetworkMode(m_inst);
+ if (mode != 1 || (curMode & NT_NET_MODE_SERVER) != 0) {
+ nt::StopClient(m_inst);
+ nt::StopServer(m_inst);
+ nt::StopLocal(m_inst);
+ }
+
+ if (m_mode != 1 || !dsClient) {
+ nt::StopDSClient(m_inst);
+ }
+
+ lock.lock();
+ } while (mode != m_mode || dsClient != m_dsClient);
+
+ if (m_mode == 1) {
+ std::string_view serverTeam{m_serverTeam};
+ std::optional<unsigned int> team;
+ if (!wpi::contains(serverTeam, '.') &&
+ (team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
+ nt::StartClientTeam(m_inst, team.value(), NT_DEFAULT_PORT);
+ } else {
+ wpi::SmallVector<std::string_view, 4> serverNames;
+ wpi::SmallVector<std::pair<std::string_view, unsigned int>, 4> servers;
+ wpi::split(serverTeam, serverNames, ',', -1, false);
+ for (auto&& serverName : serverNames) {
+ servers.emplace_back(serverName, NT_DEFAULT_PORT);
+ }
+ nt::StartClient(m_inst, servers);
+ }
+
+ if (m_dsClient) {
+ nt::StartDSClient(m_inst, NT_DEFAULT_PORT);
+ }
+ } else if (m_mode == 2) {
+ nt::StartServer(m_inst, m_iniName.c_str(), m_listenAddress.c_str(),
+ NT_DEFAULT_PORT);
+ }
+ }
+}
+
+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);
+
+ m_thread.Start(inst);
+}
+
+void NetworkTablesSettings::Update() {
+ if (!m_restart) {
+ return;
+ }
+ m_restart = false;
+
+ // 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_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) {
+ case 1:
+ ImGui::InputText("Team/IP", m_pServerTeam);
+ ImGui::Checkbox("Get Address from DS", m_pDsClient);
+ break;
+ case 2:
+ ImGui::InputText("Listen Address", m_pListenAddress);
+ ImGui::InputText("ini Filename", m_pIniName);
+ break;
+ default:
+ break;
+ }
+ if (ImGui::Button("Apply")) {
+ m_restart = true;
+ return true;
+ }
+ return false;
+}
diff --git a/glass/src/libnt/native/cpp/StandardNetworkTables.cpp b/glass/src/libnt/native/cpp/StandardNetworkTables.cpp
new file mode 100644
index 0000000..0a5f234
--- /dev/null
+++ b/glass/src/libnt/native/cpp/StandardNetworkTables.cpp
@@ -0,0 +1,176 @@
+// 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/networktables/NTCommandScheduler.h"
+#include "glass/networktables/NTCommandSelector.h"
+#include "glass/networktables/NTDifferentialDrive.h"
+#include "glass/networktables/NTDigitalInput.h"
+#include "glass/networktables/NTDigitalOutput.h"
+#include "glass/networktables/NTFMS.h"
+#include "glass/networktables/NTField2D.h"
+#include "glass/networktables/NTGyro.h"
+#include "glass/networktables/NTMecanumDrive.h"
+#include "glass/networktables/NTMechanism2D.h"
+#include "glass/networktables/NTPIDController.h"
+#include "glass/networktables/NTSpeedController.h"
+#include "glass/networktables/NTStringChooser.h"
+#include "glass/networktables/NTSubsystem.h"
+#include "glass/networktables/NetworkTablesProvider.h"
+
+using namespace glass;
+
+void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
+ provider.Register(
+ NTCommandSchedulerModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTCommandSchedulerModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetDefaultSize(400, 200);
+ return MakeFunctionView([=] {
+ DisplayCommandScheduler(static_cast<NTCommandSchedulerModel*>(model));
+ });
+ });
+ provider.Register(
+ NTCommandSelectorModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTCommandSelectorModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplayCommandSelector(static_cast<NTCommandSelectorModel*>(model));
+ });
+ });
+ provider.Register(
+ NTDifferentialDriveModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTDifferentialDriveModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetDefaultSize(300, 350);
+ return MakeFunctionView([=] {
+ DisplayDrive(static_cast<NTDifferentialDriveModel*>(model));
+ });
+ });
+ provider.Register(
+ NTFMSModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTFMSModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView(
+ [=] { DisplayFMS(static_cast<FMSModel*>(model)); });
+ });
+ provider.Register(
+ NTDigitalInputModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTDigitalInputModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplayDIO(static_cast<NTDigitalInputModel*>(model), 0, true);
+ });
+ });
+ provider.Register(
+ NTDigitalOutputModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTDigitalOutputModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplayDIO(static_cast<NTDigitalOutputModel*>(model), 0, true);
+ });
+ });
+ provider.Register(
+ NTField2DModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTField2DModel>(inst, path);
+ },
+ [=](Window* win, Model* model, const char* path) {
+ win->SetDefaultPos(200, 200);
+ win->SetDefaultSize(400, 200);
+ win->SetPadding(0, 0);
+ return std::make_unique<Field2DView>(
+ static_cast<NTField2DModel*>(model));
+ });
+ provider.Register(
+ NTGyroModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTGyroModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char* path) {
+ win->SetDefaultSize(320, 380);
+ return MakeFunctionView(
+ [=] { DisplayGyro(static_cast<NTGyroModel*>(model)); });
+ });
+ provider.Register(
+ NTMecanumDriveModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTMecanumDriveModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetDefaultSize(300, 350);
+ return MakeFunctionView(
+ [=] { DisplayDrive(static_cast<NTMecanumDriveModel*>(model)); });
+ });
+ provider.Register(
+ NTMechanism2DModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTMechanism2DModel>(inst, path);
+ },
+ [=](Window* win, Model* model, const char* path) {
+ win->SetDefaultPos(400, 400);
+ win->SetDefaultSize(200, 200);
+ win->SetPadding(0, 0);
+ return std::make_unique<Mechanism2DView>(
+ static_cast<NTMechanism2DModel*>(model));
+ });
+ provider.Register(
+ NTPIDControllerModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTPIDControllerModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char* path) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplayPIDController(static_cast<NTPIDControllerModel*>(model));
+ });
+ });
+ provider.Register(
+ NTSpeedControllerModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTSpeedControllerModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char* path) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplaySpeedController(static_cast<NTSpeedControllerModel*>(model));
+ });
+ });
+ provider.Register(
+ NTStringChooserModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTStringChooserModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView([=] {
+ DisplayStringChooser(static_cast<NTStringChooserModel*>(model));
+ });
+ });
+ provider.Register(
+ NTSubsystemModel::kType,
+ [](NT_Inst inst, const char* path) {
+ return std::make_unique<NTSubsystemModel>(inst, path);
+ },
+ [](Window* win, Model* model, const char*) {
+ win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+ return MakeFunctionView(
+ [=] { DisplaySubsystem(static_cast<NTSubsystemModel*>(model)); });
+ });
+}