Compare commits
204 Commits
v2022.1.1-
...
v2022.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f4265facc | ||
|
|
63d1fb3bed | ||
|
|
36af6d25a5 | ||
|
|
8f387f7255 | ||
|
|
792e735e08 | ||
|
|
3b76de83eb | ||
|
|
ad9f738cfa | ||
|
|
49455199e5 | ||
|
|
64426502ea | ||
|
|
8cc112d196 | ||
|
|
e78cd49861 | ||
|
|
cfb4f756d6 | ||
|
|
ba0908216c | ||
|
|
a3a0334fad | ||
|
|
cf7460c3a8 | ||
|
|
db0fbb6448 | ||
|
|
8ac45f20bb | ||
|
|
b3707cca0b | ||
|
|
a69ee3ece9 | ||
|
|
750d9a30c9 | ||
|
|
41c5b2b5ac | ||
|
|
6cf3f9b28e | ||
|
|
269cf03472 | ||
|
|
5ccfc4adbd | ||
|
|
b6f44f98be | ||
|
|
0dca57e9ec | ||
|
|
22c4da152e | ||
|
|
05d66f862d | ||
|
|
b09f5b2cf2 | ||
|
|
a2510aaa0e | ||
|
|
947f589916 | ||
|
|
bbd8980a20 | ||
|
|
831052f118 | ||
|
|
c137569f91 | ||
|
|
dae61226fa | ||
|
|
3ad4594a88 | ||
|
|
112acb9a62 | ||
|
|
ecee224e81 | ||
|
|
a3645dea34 | ||
|
|
7c09f44898 | ||
|
|
f401ea9aae | ||
|
|
bf8517f1e6 | ||
|
|
528087e308 | ||
|
|
1f59ff72f9 | ||
|
|
315be873c4 | ||
|
|
b8d019cdb4 | ||
|
|
102f23bbdb | ||
|
|
b85c24a79c | ||
|
|
eee29daaf9 | ||
|
|
aa9dfabde2 | ||
|
|
5999a26fba | ||
|
|
1e82595ffb | ||
|
|
e373fa476b | ||
|
|
dceb5364f4 | ||
|
|
baacbc8e24 | ||
|
|
84b15f0883 | ||
|
|
c0da9d2d35 | ||
|
|
0fe0be2733 | ||
|
|
eafa947338 | ||
|
|
9d13ae8d01 | ||
|
|
2a64e4bae5 | ||
|
|
c3fd20db59 | ||
|
|
6f91f37cd0 | ||
|
|
5158730b81 | ||
|
|
2ad2d2ca96 | ||
|
|
b5fd29774f | ||
|
|
9f8f330e96 | ||
|
|
1ad3b1b333 | ||
|
|
dfc24425c3 | ||
|
|
c02577bb51 | ||
|
|
c9e6a96a61 | ||
|
|
9778626f34 | ||
|
|
34b2d0dae1 | ||
|
|
59a7528fd6 | ||
|
|
11d9859ef1 | ||
|
|
e44ed752ad | ||
|
|
52b2dd5b89 | ||
|
|
c46636f218 | ||
|
|
dc531462e1 | ||
|
|
92ba98621c | ||
|
|
d41d051f1b | ||
|
|
c5ae0effac | ||
|
|
b3974c6ed3 | ||
|
|
589a00e379 | ||
|
|
8d9836ca02 | ||
|
|
8b5bf8632e | ||
|
|
1846114491 | ||
|
|
2c461c794e | ||
|
|
109363daa4 | ||
|
|
41d26bee8d | ||
|
|
7269a170fb | ||
|
|
441f2ed9b0 | ||
|
|
15275433d4 | ||
|
|
1ac02d2f58 | ||
|
|
8ee6257e92 | ||
|
|
d81ef2bc5c | ||
|
|
acb64dff97 | ||
|
|
3f6cf76a8c | ||
|
|
3ef2dab465 | ||
|
|
a5a56dd067 | ||
|
|
04957a6d30 | ||
|
|
5da54888f8 | ||
|
|
6c93365b0f | ||
|
|
1c4a8bfb66 | ||
|
|
d51a1d3b3d | ||
|
|
aced2e7da6 | ||
|
|
fa1ceca83a | ||
|
|
0ea05d34e6 | ||
|
|
09db4f672b | ||
|
|
4ba80a3a8c | ||
|
|
ae208d2b17 | ||
|
|
6f51cb3b98 | ||
|
|
f6159ee1a2 | ||
|
|
7f401ae895 | ||
|
|
0587b7043a | ||
|
|
0bbf51d566 | ||
|
|
92c6eae6b0 | ||
|
|
141354cd79 | ||
|
|
f6e9fc7d71 | ||
|
|
d8418be7d1 | ||
|
|
82066946e5 | ||
|
|
4b1defc8d8 | ||
|
|
da90c1cd2c | ||
|
|
3aa54fa027 | ||
|
|
b156db400d | ||
|
|
9aba2b7583 | ||
|
|
a9931223f0 | ||
|
|
aacf9442e4 | ||
|
|
7db10ecf00 | ||
|
|
a0a5b2aea5 | ||
|
|
eb835598a4 | ||
|
|
f0ab6df5b6 | ||
|
|
075144faa3 | ||
|
|
32468a40cb | ||
|
|
38611e9dd7 | ||
|
|
4d78def31e | ||
|
|
3be0c1217a | ||
|
|
42d3a50aa2 | ||
|
|
52f1464029 | ||
|
|
68ce62e2e9 | ||
|
|
3dd41c0d37 | ||
|
|
7699a1f827 | ||
|
|
e473a00f97 | ||
|
|
52f2d580eb | ||
|
|
d7b1e3576f | ||
|
|
93799fbe9d | ||
|
|
b84644740d | ||
|
|
2dc35c1399 | ||
|
|
2cb171f6f5 | ||
|
|
a939cd9c89 | ||
|
|
d5270d113b | ||
|
|
b20903960b | ||
|
|
c0cb545b41 | ||
|
|
35c9f66a75 | ||
|
|
796d03d105 | ||
|
|
8723caf78d | ||
|
|
187f50a344 | ||
|
|
8d04606c4d | ||
|
|
b82d4f6e58 | ||
|
|
87e34967ef | ||
|
|
e32499c546 | ||
|
|
aa0b49228d | ||
|
|
57301a7f9c | ||
|
|
d1842ea8fb | ||
|
|
558151061e | ||
|
|
181723e573 | ||
|
|
6bc1db44bc | ||
|
|
737b57ed5f | ||
|
|
4d287d1ae2 | ||
|
|
f26eb5ada4 | ||
|
|
94ed275ba6 | ||
|
|
ac2f44da33 | ||
|
|
75fa1fbfbf | ||
|
|
5e689faea8 | ||
|
|
649a50b401 | ||
|
|
e94397a97d | ||
|
|
4ec58724d7 | ||
|
|
8cb294aa4a | ||
|
|
2b3a9a52b3 | ||
|
|
138cbb94b2 | ||
|
|
e56d6dea81 | ||
|
|
43f30e44e4 | ||
|
|
9e6db17ef4 | ||
|
|
0e631ad2f2 | ||
|
|
6229d8d2ff | ||
|
|
4647d09b50 | ||
|
|
4ad3a54026 | ||
|
|
05e5feac41 | ||
|
|
67df469c58 | ||
|
|
689e9ccfb5 | ||
|
|
9cd4bc407a | ||
|
|
61996c2bbf | ||
|
|
6d3dd99eb2 | ||
|
|
f0b484892c | ||
|
|
8352cbb7a3 | ||
|
|
6da08b71dc | ||
|
|
5d99059bf9 | ||
|
|
fa41b106ab | ||
|
|
4e3fd7d420 | ||
|
|
791d8354da | ||
|
|
10f19e6fc3 | ||
|
|
4c61a13057 | ||
|
|
7b3f62244f | ||
|
|
d347928e4d |
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -22,7 +22,8 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- WPILib Version: [e.g. 2021.3.1]
|
||||
- OS: [e.g. Windows 11]
|
||||
- Java version [e.g. 1.10.2]
|
||||
- C++ version [e.g. 17]
|
||||
|
||||
|
||||
4
.github/workflows/cmake.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
flags: ""
|
||||
- os: macos-latest
|
||||
- os: macOS-11
|
||||
name: macOS
|
||||
container: ""
|
||||
flags: "-DWITH_JAVA=OFF"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
build-vcpkg:
|
||||
name: "Build - Windows"
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Prepare vcpkg
|
||||
|
||||
6
.github/workflows/gradle.yml
vendored
@@ -44,13 +44,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
- os: windows-2019
|
||||
artifact-name: Win64
|
||||
architecture: x64
|
||||
- os: windows-latest
|
||||
- os: windows-2019
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
- os: macos-latest
|
||||
- os: macOS-11
|
||||
artifact-name: macOS
|
||||
architecture: x64
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
|
||||
14
.github/workflows/lint-format.yml
vendored
@@ -84,3 +84,17 @@ jobs:
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > javaformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 13
|
||||
- name: Install libclang-9
|
||||
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
|
||||
@@ -11,11 +11,13 @@ cppSrcFileInclude {
|
||||
|
||||
modifiableFileExclude {
|
||||
\.patch$
|
||||
gradlew
|
||||
}
|
||||
|
||||
generatedFileExclude {
|
||||
FRCNetComm\.java$
|
||||
simulation/gz_msgs/src/include/simulation/gz_msgs/msgs\.h$
|
||||
fieldImages/src/main/native/resources/
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2021",
|
||||
"projectYear": "intellisense",
|
||||
"teamNumber": 0
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@ FATAL: In-source builds are not allowed.
|
||||
")
|
||||
endif()
|
||||
|
||||
if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
|
||||
set(CMAKE_SYSTEM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
|
||||
set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION 10.0.18362.0 CACHE STRING INTERNAL FORCE)
|
||||
endif()
|
||||
|
||||
project(allwpilib)
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
message(STATUS "Platform version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
|
||||
|
||||
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
INCLUDE(CPack)
|
||||
@@ -119,13 +125,6 @@ FATAL: Cannot build wpilib without wpimath.
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_OLD_COMMANDS AND WITH_EXAMPLES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build examples with old commands disabled.
|
||||
Enable old commands by setting WITH_OLD_COMMANDS=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
@@ -146,6 +145,8 @@ if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
find_package(LIBSSH 0.7.1)
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
|
||||
set(NTCORE_DEP_REPLACE "include($\{SELF_DIR\}/ntcore-config.cmake)")
|
||||
@@ -254,10 +255,14 @@ if (WITH_WPIMATH)
|
||||
endif()
|
||||
|
||||
if (WITH_GUI)
|
||||
add_subdirectory(fieldImages)
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(wpigui)
|
||||
add_subdirectory(glass)
|
||||
add_subdirectory(outlineviewer)
|
||||
if (LIBSSH_FOUND)
|
||||
add_subdirectory(roborioteamnumbersetter)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WITH_WPILIB OR WITH_SIMULATION_MODULES)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2021 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2022 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -69,15 +69,36 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
|
||||
* wpiutil
|
||||
|
||||
* wpigui
|
||||
* imgui
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
|
||||
* wpimath
|
||||
* wpiutil
|
||||
|
||||
* glass/libglass
|
||||
* wpiutil
|
||||
* wpimath
|
||||
* wpigui
|
||||
|
||||
* glass/libglassnt
|
||||
* wpiutil
|
||||
* ntcore
|
||||
* wpimath
|
||||
* wpigui
|
||||
|
||||
* hal
|
||||
* wpiutil
|
||||
|
||||
* halsim
|
||||
* imgui
|
||||
* wpiutil
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
* ntcore
|
||||
* wpimath
|
||||
* wpigui
|
||||
* libglass
|
||||
* libglassnt
|
||||
|
||||
* cscore
|
||||
* opencv
|
||||
@@ -101,6 +122,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpimath
|
||||
* wpiutil
|
||||
|
||||
* wpilibNewCommands
|
||||
@@ -109,9 +131,10 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpimath
|
||||
* wpiutil
|
||||
|
||||
* wpilibNewCommands
|
||||
* wpilibOldCommands
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
@@ -119,6 +142,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
|
||||
### Third Party Artifacts
|
||||
|
||||
This repository provides the builds of the following third party software.
|
||||
@@ -128,3 +152,4 @@ All artifacts are based at `edu.wpi.first.thirdparty.frcYEAR` in the repository.
|
||||
* googletest
|
||||
* imgui
|
||||
* opencv
|
||||
* libssh
|
||||
|
||||
@@ -11,9 +11,10 @@ Development builds are the per-commit build hosted everytime a commit is pushed
|
||||
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version.
|
||||
|
||||
```groovy
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.+'
|
||||
wpi.wpimathVersion = 'YEAR.+
|
||||
wpi.versions.wpilibVersion = 'YEAR.+'
|
||||
wpi.versions.wpimathVersion = 'YEAR.+
|
||||
```
|
||||
|
||||
The top of your ``build.gradle`` file should now look similar to the code below. Ignore any differences in versions.
|
||||
@@ -22,12 +23,13 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.wpilibVersion = '2021.+'
|
||||
wpi.wpimathVersion = '2021.+'
|
||||
wpi.versions.wpilibVersion = '2022.+'
|
||||
wpi.versions.wpimathVersion = '2022.+'
|
||||
```
|
||||
|
||||
C++
|
||||
@@ -35,12 +37,13 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.wpilibVersion = '2021.+'
|
||||
wpi.wpimathVersion = '2021.+'
|
||||
wpi.versions.wpilibVersion = '2022.+'
|
||||
wpi.versions.wpimathVersion = '2022.+'
|
||||
```
|
||||
|
||||
## Local Build
|
||||
@@ -51,12 +54,13 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useFrcMavenLocalDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.424242.+'
|
||||
wpi.wpimathVersion = 'YEAR.424242.+'
|
||||
wpi.versions.wpilibVersion = 'YEAR.424242.+'
|
||||
wpi.versions.wpimathVersion = 'YEAR.424242.+'
|
||||
```
|
||||
|
||||
C++
|
||||
@@ -64,12 +68,13 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
id "edu.wpi.first.GradleRIO" version "2022.1.1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useFrcMavenLocalDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.424242.+'
|
||||
wpi.wpimathVersion = 'YEAR.424242.+'
|
||||
wpi.versions.wpilibVersion = 'YEAR.424242.+'
|
||||
wpi.versions.wpimathVersion = 'YEAR.424242.+'
|
||||
```
|
||||
|
||||
# roboRIO Development
|
||||
|
||||
@@ -39,7 +39,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- On macOS, install the JDK 11 .pkg from the link above
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 8 or greater
|
||||
- On Windows, install [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio 2019)
|
||||
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
@@ -71,6 +71,8 @@ The gradlew wrapper only exists in the root of the main project, so be sure to r
|
||||
|
||||
There are a few tasks other than `build` available. To see them, run the meta-task `tasks`. This will print a list of all available tasks, with a description of each task.
|
||||
|
||||
If opening from a fresh clone, generated java dependencies will not exist. Most IDEs will not run the generation tasks, which will cause lots of IDE errors. Manually run `./gradlew compileJava` from a terminal to run all the compile tasks, and then refresh your IDE's configuration (In VS Code open settings.gradle and save).
|
||||
|
||||
### Faster builds
|
||||
|
||||
`./gradlew build` builds _everything_, which includes debug and release builds for desktop and all installed cross compilers. Many developers don't need or want to build all of this. Therefore, common tasks have shortcuts to only build necessary components for common development and testing tasks.
|
||||
|
||||
@@ -32,6 +32,8 @@ sigslot wpiutil/src/main/native/include/wpi/Signal.h
|
||||
wpiutil/src/test/native/cpp/sigslot/
|
||||
tcpsockets wpiutil/src/main/native/cpp/TCP{Stream,Connector,Acceptor}.cpp
|
||||
wpiutil/src/main/native/include/wpi/TCP*.h
|
||||
MPack wpiutil/src/main/native/include/mpack.h
|
||||
wpiutil/src/main/native/cpp/mpack.cpp
|
||||
Bootstrap wpiutil/src/main/native/resources/bootstrap-*
|
||||
CoreUI wpiutil/src/main/native/resources/coreui-*
|
||||
Feather Icons wpiutil/src/main/native/resources/feather-*
|
||||
@@ -235,6 +237,32 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
==============================================================================
|
||||
MPacks License
|
||||
==============================================================================
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Bootstrap License
|
||||
==============================================================================
|
||||
|
||||
@@ -12,7 +12,7 @@ stages:
|
||||
- job: IntegrationTests
|
||||
displayName: Integration Tests
|
||||
pool:
|
||||
vmImage: "Ubuntu 16.04"
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2022-18.04
|
||||
|
||||
14
build.gradle
@@ -5,7 +5,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.hubspot.jinjava:jinjava:2.5.8'
|
||||
classpath 'com.hubspot.jinjava:jinjava:2.6.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ plugins {
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
|
||||
id 'edu.wpi.first.NativeUtils' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '1.0.0'
|
||||
id 'edu.wpi.first.GradleVsCode' version '1.0.0'
|
||||
id 'edu.wpi.first.GradleVsCode'
|
||||
id 'idea'
|
||||
id 'visual-studio'
|
||||
id 'net.ltgt.errorprone' version '1.1.1' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0' apply false
|
||||
id 'com.diffplug.spotless' version '5.5.0' apply false
|
||||
id 'com.github.spotbugs' version '5.0.0-beta.1' apply false
|
||||
id 'net.ltgt.errorprone' version '2.0.2' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
|
||||
id 'com.diffplug.spotless' version '6.1.2' apply false
|
||||
id 'com.github.spotbugs' version '5.0.4' apply false
|
||||
}
|
||||
|
||||
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
|
||||
@@ -147,5 +147,5 @@ ext.getCurrentArch = {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '7.1.1'
|
||||
gradleVersion = '7.3.3'
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2022.3.0"
|
||||
implementation "edu.wpi.first:native-utils:2022.7.1"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class WPIJREArtifact extends MavenArtifact {
|
||||
this.configName = configName;
|
||||
Project project = target.getProject();
|
||||
getConfiguration().set(project.getConfigurations().create(configName));
|
||||
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.9u12-1"));
|
||||
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.12u5-1"));
|
||||
|
||||
setOnlyIf(new PredicateWrapper({ DeployContext ctx ->
|
||||
return jreMissing(ctx) || project.hasProperty("force-redeploy-jre");
|
||||
|
||||
@@ -27,7 +27,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
|
||||
implementation project(':wpiutil')
|
||||
implementation project(':ntcore')
|
||||
|
||||
@@ -56,9 +56,9 @@ public final class Main {
|
||||
public JsonObject config;
|
||||
}
|
||||
|
||||
static int team;
|
||||
static boolean server;
|
||||
static List<CameraConfig> cameras = new ArrayList<>();
|
||||
private static int team;
|
||||
private static boolean server;
|
||||
private static List<CameraConfig> cameras = new ArrayList<>();
|
||||
|
||||
private Main() {}
|
||||
|
||||
|
||||
@@ -450,9 +450,9 @@ Instance::Instance() {
|
||||
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
|
||||
return;
|
||||
} else if (wpi::starts_with(relativeKey, "Property/")) {
|
||||
propName = relativeKey.substr(9);
|
||||
propName = wpi::substr(relativeKey, 9);
|
||||
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
|
||||
propName = relativeKey.substr(12);
|
||||
propName = wpi::substr(relativeKey, 12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
@@ -229,6 +229,7 @@ class CameraServer {
|
||||
* Adds a MJPEG server.
|
||||
*
|
||||
* @param name Server name
|
||||
* @param port Port number
|
||||
*/
|
||||
static cs::MjpegServer AddServer(std::string_view name, int port);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class VisionRunnerBase {
|
||||
*
|
||||
* <p>This method is exposed to allow teams to add additional functionality or
|
||||
* have their own ways to run the pipeline. Most teams, however, should just
|
||||
* use {@link #runForever} in its own thread using a std::thread.</p>
|
||||
* use RunForever() in its own thread using a std::thread.</p>
|
||||
*/
|
||||
void RunOnce();
|
||||
|
||||
|
||||
116
cmake/modules/FindLIBSSH.cmake
Normal file
@@ -0,0 +1,116 @@
|
||||
# - Try to find LibSSH
|
||||
# Once done this will define
|
||||
#
|
||||
# LIBSSH_FOUND - system has LibSSH
|
||||
# LIBSSH_INCLUDE_DIRS - the LibSSH include directory
|
||||
# LIBSSH_LIBRARIES - link these to use LibSSH
|
||||
# LIBSSH_VERSION -
|
||||
#
|
||||
# Author Michal Vasko <mvasko@cesnet.cz>
|
||||
# Copyright (c) 2020 CESNET, z.s.p.o.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
if(LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(LIBSSH_FOUND TRUE)
|
||||
else()
|
||||
find_path(LIBSSH_INCLUDE_DIR
|
||||
NAMES
|
||||
libssh/libssh.h
|
||||
PATHS
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
/sw/include
|
||||
${CMAKE_INCLUDE_PATH}
|
||||
${CMAKE_INSTALL_PREFIX}/include
|
||||
)
|
||||
|
||||
find_library(LIBSSH_LIBRARY
|
||||
NAMES
|
||||
ssh.so
|
||||
libssh.so
|
||||
libssh.dylib
|
||||
PATHS
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
/sw/lib
|
||||
${CMAKE_LIBRARY_PATH}
|
||||
${CMAKE_INSTALL_PREFIX}/lib
|
||||
)
|
||||
|
||||
if(LIBSSH_INCLUDE_DIR AND LIBSSH_LIBRARY)
|
||||
# learn libssh version
|
||||
if(EXISTS ${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h)
|
||||
set(LIBSSH_HEADER_PATH ${LIBSSH_INCLUDE_DIR}/libssh/libssh_version.h)
|
||||
else()
|
||||
set(LIBSSH_HEADER_PATH ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h)
|
||||
endif()
|
||||
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_MAJOR
|
||||
REGEX "#define[ ]+LIBSSH_VERSION_MAJOR[ ]+[0-9]+")
|
||||
if(NOT LIBSSH_VERSION_MAJOR)
|
||||
message(STATUS "LIBSSH_VERSION_MAJOR not found, assuming libssh is too old and cannot be used!")
|
||||
set(LIBSSH_INCLUDE_DIR "LIBSSH_INCLUDE_DIR-NOTFOUND")
|
||||
set(LIBSSH_LIBRARY "LIBSSH_LIBRARY-NOTFOUND")
|
||||
else()
|
||||
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MAJOR ${LIBSSH_VERSION_MAJOR})
|
||||
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_MINOR
|
||||
REGEX "#define[ ]+LIBSSH_VERSION_MINOR[ ]+[0-9]+")
|
||||
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MINOR ${LIBSSH_VERSION_MINOR})
|
||||
file(STRINGS ${LIBSSH_HEADER_PATH} LIBSSH_VERSION_PATCH
|
||||
REGEX "#define[ ]+LIBSSH_VERSION_MICRO[ ]+[0-9]+")
|
||||
string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_PATCH ${LIBSSH_VERSION_PATCH})
|
||||
|
||||
set(LIBSSH_VERSION ${LIBSSH_VERSION_MAJOR}.${LIBSSH_VERSION_MINOR}.${LIBSSH_VERSION_PATCH})
|
||||
|
||||
if(LIBSSH_VERSION VERSION_LESS 0.8.0)
|
||||
# libssh_threads also needs to be linked for these versions
|
||||
string(REPLACE "libssh.so" "libssh_threads.so"
|
||||
LIBSSH_THREADS_LIBRARY
|
||||
${LIBSSH_LIBRARY}
|
||||
)
|
||||
string(REPLACE "libssh.dylib" "libssh_threads.dylib"
|
||||
LIBSSH_THREADS_LIBRARY
|
||||
${LIBSSH_THREADS_LIBRARY}
|
||||
)
|
||||
string(REPLACE "ssh.so" "ssh_threads.so"
|
||||
LIBSSH_THREADS_LIBRARY
|
||||
${LIBSSH_THREADS_LIBRARY}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIBSSH_INCLUDE_DIRS ${LIBSSH_INCLUDE_DIR})
|
||||
set(LIBSSH_LIBRARIES ${LIBSSH_LIBRARY} ${LIBSSH_THREADS_LIBRARY})
|
||||
mark_as_advanced(LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES)
|
||||
|
||||
find_package_handle_standard_args(LibSSH FOUND_VAR LIBSSH_FOUND
|
||||
REQUIRED_VARS LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES
|
||||
VERSION_VAR LIBSSH_VERSION)
|
||||
endif()
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef CSCORE_HANDLE_H_
|
||||
#define CSCORE_HANDLE_H_
|
||||
|
||||
#include <wpi/Synchronization.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -18,7 +20,7 @@ class Handle {
|
||||
public:
|
||||
enum Type {
|
||||
kUndefined = 0,
|
||||
kProperty = 0x40,
|
||||
kProperty = wpi::kHandleTypeCSBase,
|
||||
kSource,
|
||||
kSink,
|
||||
kListener,
|
||||
|
||||
@@ -208,7 +208,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
if (wpi::trim(key) == "boundary") {
|
||||
value = wpi::trim(wpi::trim(value), '"'); // value may be quoted
|
||||
if (wpi::starts_with(value, "--")) {
|
||||
value = value.substr(2);
|
||||
value = wpi::substr(value, 2);
|
||||
}
|
||||
boundary.append(value.begin(), value.end());
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "JpegUtil.h"
|
||||
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
|
||||
namespace cs {
|
||||
@@ -64,7 +65,7 @@ bool GetJpegSize(std::string_view data, int* width, int* height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = data.substr(2); // Get to the first block
|
||||
data = wpi::substr(data, 2); // Get to the first block
|
||||
for (;;) {
|
||||
if (data.size() < 4) {
|
||||
return false; // EOF
|
||||
@@ -89,7 +90,7 @@ bool GetJpegSize(std::string_view data, int* width, int* height) {
|
||||
return true;
|
||||
}
|
||||
// Go to the next block
|
||||
data = data.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
data = wpi::substr(data, bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
|
||||
*locSOF = *size;
|
||||
|
||||
// Search until SOS for DHT tag
|
||||
sdata = sdata.substr(2); // Get to the first block
|
||||
sdata = wpi::substr(sdata, 2); // Get to the first block
|
||||
for (;;) {
|
||||
if (sdata.size() < 4) {
|
||||
return false; // EOF
|
||||
@@ -121,7 +122,7 @@ bool JpegNeedsDHT(const char* data, size_t* size, size_t* locSOF) {
|
||||
*locSOF = sdata.data() - data; // SOF
|
||||
}
|
||||
// Go to the next block
|
||||
sdata = sdata.substr(bytes[2] * 256 + bytes[3] + 2);
|
||||
sdata = wpi::substr(sdata, bytes[2] * 256 + bytes[3] + 2);
|
||||
}
|
||||
|
||||
// Only add DHT if we also found SOF (insertion point)
|
||||
|
||||
@@ -797,14 +797,14 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
// compatibility, others are for Axis camera compatibility.
|
||||
if ((pos = req.find("POST /stream")) != std::string_view::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('?', pos + 12)).substr(1);
|
||||
parameters = wpi::substr(wpi::substr(req, req.find('?', pos + 12)), 1);
|
||||
} else if ((pos = req.find("GET /?action=stream")) !=
|
||||
std::string_view::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('&', pos + 19)).substr(1);
|
||||
parameters = wpi::substr(wpi::substr(req, req.find('&', pos + 19)), 1);
|
||||
} else if ((pos = req.find("GET /stream.mjpg")) != std::string_view::npos) {
|
||||
kind = kStream;
|
||||
parameters = req.substr(req.find('?', pos + 16)).substr(1);
|
||||
parameters = wpi::substr(wpi::substr(req, req.find('?', pos + 16)), 1);
|
||||
} else if (req.find("GET /settings") != std::string_view::npos &&
|
||||
req.find(".json") != std::string_view::npos) {
|
||||
kind = kGetSettings;
|
||||
@@ -820,7 +820,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if ((pos = req.find("GET /?action=command")) !=
|
||||
std::string_view::npos) {
|
||||
kind = kCommand;
|
||||
parameters = req.substr(req.find('&', pos + 20)).substr(1);
|
||||
parameters = wpi::substr(wpi::substr(req, req.find('&', pos + 20)), 1);
|
||||
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
@@ -833,7 +833,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
pos = parameters.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
|
||||
"-=&1234567890%./");
|
||||
parameters = parameters.substr(0, pos);
|
||||
parameters = wpi::substr(parameters, 0, pos);
|
||||
SDEBUG("command parameters: \"{}\"", parameters);
|
||||
|
||||
// Read the rest of the HTTP request.
|
||||
|
||||
@@ -35,8 +35,11 @@ struct ListenerData : public wpi::CallbackListenerData<
|
||||
class NotifierThread
|
||||
: public wpi::CallbackThread<NotifierThread, RawEvent, ListenerData> {
|
||||
public:
|
||||
bool Matches(const ListenerData& /*listener*/, const RawEvent& /*data*/) {
|
||||
return true;
|
||||
NotifierThread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: CallbackThread(std::move(on_start), std::move(on_exit)) {}
|
||||
|
||||
bool Matches(const ListenerData& listener, const RawEvent& data) {
|
||||
return (data.kind & listener.eventMask) != 0;
|
||||
}
|
||||
|
||||
void SetListener(RawEvent* data, unsigned int listener_uid) {
|
||||
|
||||
@@ -708,9 +708,13 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
// Listener Functions
|
||||
//
|
||||
|
||||
void SetListenerOnStart(std::function<void()> onStart) {}
|
||||
void SetListenerOnStart(std::function<void()> onStart) {
|
||||
Instance::GetInstance().notifier.SetOnStart(onStart);
|
||||
}
|
||||
|
||||
void SetListenerOnExit(std::function<void()> onExit) {}
|
||||
void SetListenerOnExit(std::function<void()> onExit) {
|
||||
Instance::GetInstance().notifier.SetOnExit(onExit);
|
||||
}
|
||||
|
||||
static void StartBackground(int eventMask, bool immediateNotify) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
|
||||
@@ -570,7 +570,6 @@ class AxisCamera : public HttpCamera {
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(std::string_view name, std::string_view host);
|
||||
|
||||
@@ -579,7 +578,6 @@ class AxisCamera : public HttpCamera {
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(std::string_view name, const char* host);
|
||||
|
||||
@@ -588,7 +586,6 @@ class AxisCamera : public HttpCamera {
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(std::string_view name, const std::string& host);
|
||||
|
||||
@@ -597,7 +594,6 @@ class AxisCamera : public HttpCamera {
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
AxisCamera(std::string_view name, wpi::span<const std::string> hosts);
|
||||
|
||||
@@ -606,7 +602,6 @@ class AxisCamera : public HttpCamera {
|
||||
*
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
template <typename T>
|
||||
AxisCamera(std::string_view name, std::initializer_list<T> hosts);
|
||||
@@ -623,6 +618,8 @@ class ImageSource : public VideoSource {
|
||||
/**
|
||||
* Signal sinks that an error has occurred. This should be called instead
|
||||
* of NotifyFrame when an error occurs.
|
||||
*
|
||||
* @param msg Notification message.
|
||||
*/
|
||||
void NotifyError(std::string_view msg);
|
||||
|
||||
@@ -686,7 +683,6 @@ class ImageSource : public VideoSource {
|
||||
* Create a string property.
|
||||
*
|
||||
* @param name Property name
|
||||
* @param defaultValue Default value
|
||||
* @param value Current value
|
||||
* @return Property
|
||||
*/
|
||||
|
||||
@@ -107,7 +107,7 @@ static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
|
||||
|
||||
static bool IsPercentageProperty(std::string_view name) {
|
||||
if (wpi::starts_with(name, "raw_")) {
|
||||
name = name.substr(4);
|
||||
name = wpi::substr(name, 4);
|
||||
}
|
||||
return name == "brightness" || name == "contrast" || name == "saturation" ||
|
||||
name == "hue" || name == "sharpness" || name == "gain" ||
|
||||
@@ -181,13 +181,13 @@ static bool GetVendorProduct(int dev, int* vendor, int* product) {
|
||||
}
|
||||
std::string_view readStr{readBuf};
|
||||
if (auto v = wpi::parse_integer<int>(
|
||||
readStr.substr(readStr.find('v')).substr(1, 4), 16)) {
|
||||
wpi::substr(wpi::substr(readStr, readStr.find('v')), 1, 4), 16)) {
|
||||
*vendor = v.value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (auto v = wpi::parse_integer<int>(
|
||||
readStr.substr(readStr.find('p')).substr(1, 4), 16)) {
|
||||
wpi::substr(wpi::substr(readStr, readStr.find('p')), 1, 4), 16)) {
|
||||
*product = v.value();
|
||||
} else {
|
||||
return false;
|
||||
@@ -236,8 +236,8 @@ static bool GetDescriptionIoctl(const char* cpath, std::string* desc) {
|
||||
std::optional<int> vendor;
|
||||
std::optional<int> product;
|
||||
if (wpi::starts_with(card, "UVC Camera (") &&
|
||||
(vendor = wpi::parse_integer<int>(card.substr(12, 4), 16)) &&
|
||||
(product = wpi::parse_integer<int>(card.substr(17, 4), 16))) {
|
||||
(vendor = wpi::parse_integer<int>(wpi::substr(card, 12, 4), 16)) &&
|
||||
(product = wpi::parse_integer<int>(wpi::substr(card, 17, 4), 16))) {
|
||||
std::string card2 = GetUsbNameFromId(vendor.value(), product.value());
|
||||
if (!card2.empty()) {
|
||||
*desc = std::move(card2);
|
||||
@@ -283,7 +283,7 @@ static int GetDeviceNum(const char* cpath) {
|
||||
if (!wpi::starts_with(fn, "video")) {
|
||||
return -1;
|
||||
}
|
||||
if (auto dev = wpi::parse_integer<int>(fn.substr(5), 10)) {
|
||||
if (auto dev = wpi::parse_integer<int>(wpi::substr(fn, 5), 10)) {
|
||||
return dev.value();
|
||||
}
|
||||
return -1;
|
||||
@@ -1635,7 +1635,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
}
|
||||
|
||||
unsigned int dev = 0;
|
||||
if (auto v = wpi::parse_integer<unsigned int>(fname.substr(5), 10)) {
|
||||
if (auto v =
|
||||
wpi::parse_integer<unsigned int>(wpi::substr(fname, 5), 10)) {
|
||||
dev = v.value();
|
||||
} else {
|
||||
continue;
|
||||
@@ -1686,7 +1687,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
std::string fname = fs::path{target}.filename();
|
||||
std::optional<unsigned int> dev;
|
||||
if (wpi::starts_with(fname, "video") &&
|
||||
(dev = wpi::parse_integer<unsigned int>(fname.substr(5), 10)) &&
|
||||
(dev = wpi::parse_integer<unsigned int>(wpi::substr(fname, 5),
|
||||
10)) &&
|
||||
dev.value() < retval.size()) {
|
||||
retval[dev.value()].otherPaths.emplace_back(path.str());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
#include "UsbUtil.h"
|
||||
|
||||
@@ -93,7 +94,7 @@ static int GetStringCtrlIoctl(int fd, int id, int maximum, std::string* value) {
|
||||
|
||||
static int SetStringCtrlIoctl(int fd, int id, int maximum,
|
||||
std::string_view value) {
|
||||
wpi::SmallString<64> str{value.substr(0, maximum)};
|
||||
wpi::SmallString<64> str{wpi::substr(value, 0, maximum)};
|
||||
|
||||
struct v4l2_ext_control ctrl;
|
||||
struct v4l2_ext_controls ctrls;
|
||||
|
||||
@@ -49,7 +49,7 @@ static std::string GetUsbNameFromFile(int vendor, int product) {
|
||||
// look for vendor at start of line
|
||||
if (wpi::starts_with(line, vendorStr)) {
|
||||
foundVendor = true;
|
||||
buf += wpi::trim(line.substr(5));
|
||||
buf += wpi::trim(wpi::substr(line, 5));
|
||||
buf += ' ';
|
||||
continue;
|
||||
}
|
||||
@@ -62,8 +62,8 @@ static std::string GetUsbNameFromFile(int vendor, int product) {
|
||||
}
|
||||
|
||||
// look for product
|
||||
if (wpi::starts_with(line.substr(1), productStr)) {
|
||||
buf += wpi::trim(line.substr(6));
|
||||
if (wpi::starts_with(wpi::substr(line, 1), productStr)) {
|
||||
buf += wpi::trim(wpi::substr(line, 6));
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <codecvt>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -25,7 +24,9 @@
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <wpi/ConvertUTF.h>
|
||||
#include <wpi/MemAlloc.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
@@ -72,8 +73,9 @@ UsbCameraImpl::UsbCameraImpl(std::string_view name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
std::string_view path)
|
||||
: SourceImpl{name, logger, notifier, telemetry}, m_path{path} {
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
||||
m_widePath = utf8_conv.from_bytes(m_path.c_str());
|
||||
wpi::SmallVector<wchar_t, 128> wideStorage;
|
||||
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
||||
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
||||
m_deviceId = -1;
|
||||
StartMessagePump();
|
||||
}
|
||||
@@ -227,7 +229,7 @@ void UsbCameraImpl::PostRequestNewFrame() {
|
||||
|
||||
bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
||||
bool* connected) {
|
||||
DEV_BROADCAST_DEVICEINTERFACE* pDi = NULL;
|
||||
DEV_BROADCAST_DEVICEINTERFACE_A* pDi = NULL;
|
||||
|
||||
*connected = false;
|
||||
|
||||
@@ -240,9 +242,9 @@ bool UsbCameraImpl::CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
||||
|
||||
// Compare the device name with the symbolic link.
|
||||
|
||||
pDi = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(pHdr);
|
||||
pDi = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE_A*>(pHdr);
|
||||
|
||||
if (_stricmp(m_path.c_str(), pDi->dbcc_name) == 0) {
|
||||
if (wpi::equals_lower(m_path, pDi->dbcc_name)) {
|
||||
if (wParam == DBT_DEVICEARRIVAL) {
|
||||
*connected = true;
|
||||
return true;
|
||||
@@ -269,7 +271,7 @@ void UsbCameraImpl::DeviceDisconnect() {
|
||||
|
||||
static bool IsPercentageProperty(std::string_view name) {
|
||||
if (wpi::starts_with(name, "raw_"))
|
||||
name = name.substr(4);
|
||||
name = wpi::substr(name, 4);
|
||||
return name == "Brightness" || name == "Contrast" || name == "Saturation" ||
|
||||
name == "Hue" || name == "Sharpness" || name == "Gain" ||
|
||||
name == "Exposure";
|
||||
@@ -411,11 +413,12 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
// If has device ID, use the device ID from the event
|
||||
// because of windows bug
|
||||
auto&& device = devices[m_deviceId];
|
||||
DEV_BROADCAST_DEVICEINTERFACE* pDi =
|
||||
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(parameter);
|
||||
DEV_BROADCAST_DEVICEINTERFACE_A* pDi =
|
||||
reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE_A*>(parameter);
|
||||
m_path = pDi->dbcc_name;
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
||||
m_widePath = utf8_conv.from_bytes(m_path.c_str());
|
||||
wpi::SmallVector<wchar_t, 128> wideStorage;
|
||||
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
||||
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
||||
} else {
|
||||
// This device not found
|
||||
break;
|
||||
@@ -482,9 +485,16 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
const wchar_t* path = m_widePath.c_str();
|
||||
m_mediaSource = CreateVideoCaptureDevice(path);
|
||||
|
||||
if (!m_mediaSource)
|
||||
if (!m_mediaSource) {
|
||||
return false;
|
||||
m_imageCallback = CreateSourceReaderCB(shared_from_this(), m_mode);
|
||||
}
|
||||
auto weakThis = weak_from_this();
|
||||
auto sharedThis = weakThis.lock();
|
||||
if (sharedThis) {
|
||||
m_imageCallback = CreateSourceReaderCB(sharedThis, m_mode);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sourceReader =
|
||||
CreateSourceReader(m_mediaSource.Get(), m_imageCallback.Get());
|
||||
@@ -747,8 +757,9 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_path = msg->dataStr;
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
||||
m_widePath = utf8_conv.from_bytes(m_path.c_str());
|
||||
wpi::SmallVector<wchar_t, 128> wideStorage;
|
||||
wpi::sys::windows::UTF8ToUTF16(m_path, wideStorage);
|
||||
m_widePath = std::wstring{wideStorage.data(), wideStorage.size()};
|
||||
}
|
||||
DeviceDisconnect();
|
||||
DeviceConnect();
|
||||
@@ -1048,7 +1059,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
// Ensure we are initialized by grabbing the message pump
|
||||
// GetMessagePump();
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
||||
wpi::SmallString<128> storage;
|
||||
WCHAR buf[512];
|
||||
ComPtr<IMFAttributes> pAttributes;
|
||||
IMFActivate** ppDevices = nullptr;
|
||||
UINT32 count = 0;
|
||||
@@ -1080,14 +1092,19 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
for (UINT32 i = 0; i < count; i++) {
|
||||
UsbCameraInfo info;
|
||||
info.dev = i;
|
||||
WCHAR buf[512];
|
||||
|
||||
UINT32 characters = 0;
|
||||
ppDevices[i]->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buf,
|
||||
sizeof(buf) / sizeof(WCHAR), NULL);
|
||||
info.name = utf8_conv.to_bytes(buf);
|
||||
sizeof(buf) / sizeof(WCHAR), &characters);
|
||||
storage.clear();
|
||||
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
||||
info.name = storage.string();
|
||||
ppDevices[i]->GetString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
|
||||
sizeof(buf) / sizeof(WCHAR), NULL);
|
||||
info.path = utf8_conv.to_bytes(buf);
|
||||
sizeof(buf) / sizeof(WCHAR), &characters);
|
||||
storage.clear();
|
||||
wpi::sys::windows::UTF16ToUTF8(buf, characters, storage);
|
||||
info.path = storage.string();
|
||||
|
||||
// Try to parse path from symbolic link
|
||||
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
||||
|
||||
@@ -89,7 +89,7 @@ static std::shared_ptr<ClassHolder> GetClassHolder() {
|
||||
WindowsMessagePump::WindowsMessagePump(
|
||||
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback) {
|
||||
m_callback = callback;
|
||||
auto handle = CreateEvent(NULL, true, false, NULL);
|
||||
auto handle = CreateEventA(NULL, true, false, NULL);
|
||||
m_mainThread = std::thread([=] { ThreadMain(handle); });
|
||||
auto waitResult = WaitForSingleObject(handle, 1000);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
@@ -98,7 +98,7 @@ WindowsMessagePump::WindowsMessagePump(
|
||||
}
|
||||
|
||||
WindowsMessagePump::~WindowsMessagePump() {
|
||||
auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL);
|
||||
auto res = SendMessageA(hwnd, WM_CLOSE, NULL, NULL);
|
||||
if (m_mainThread.joinable())
|
||||
m_mainThread.join();
|
||||
}
|
||||
@@ -110,28 +110,28 @@ void WindowsMessagePump::ThreadMain(HANDLE eventHandle) {
|
||||
MFStartup(MF_VERSION);
|
||||
|
||||
auto classHolder = GetClassHolder();
|
||||
hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, NULL, NULL, this);
|
||||
hwnd = CreateWindowExA(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0,
|
||||
0, HWND_MESSAGE, NULL, NULL, this);
|
||||
|
||||
// Register for device notifications
|
||||
HDEVNOTIFY g_hdevnotify = NULL;
|
||||
HDEVNOTIFY g_hdevnotify2 = NULL;
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di = {0};
|
||||
DEV_BROADCAST_DEVICEINTERFACE_A di = {0};
|
||||
di.dbcc_size = sizeof(di);
|
||||
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di.dbcc_classguid = KSCATEGORY_CAPTURE;
|
||||
|
||||
g_hdevnotify =
|
||||
RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
RegisterDeviceNotificationA(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
DEV_BROADCAST_DEVICEINTERFACE di2 = {0};
|
||||
DEV_BROADCAST_DEVICEINTERFACE_A di2 = {0};
|
||||
di2.dbcc_size = sizeof(di2);
|
||||
di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
|
||||
|
||||
g_hdevnotify2 =
|
||||
RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
RegisterDeviceNotificationA(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
SetEvent(eventHandle);
|
||||
|
||||
|
||||
@@ -56,29 +56,109 @@ doxygen {
|
||||
}
|
||||
}
|
||||
|
||||
exclude 'Eigen/**'
|
||||
exclude 'unsupported/**'
|
||||
exclude 'units/**'
|
||||
exclude 'uv.h'
|
||||
exclude 'uv/**'
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
exclude 'wpi/ghc/filesystem.hpp'
|
||||
exclude 'wpi/span.h'
|
||||
|
||||
extension_mapping 'inc=C++'
|
||||
// Drake
|
||||
exclude 'drake/common/**'
|
||||
|
||||
// Eigen
|
||||
exclude 'Eigen/**'
|
||||
exclude 'unsupported/**'
|
||||
|
||||
// LLVM
|
||||
exclude 'wpi/AlignOf.h'
|
||||
exclude 'wpi/Chrono.h'
|
||||
exclude 'wpi/Compiler.h'
|
||||
exclude 'wpi/ConvertUTF.h'
|
||||
exclude 'wpi/DenseMap.h'
|
||||
exclude 'wpi/DenseMapInfo.h'
|
||||
exclude 'wpi/Endian.h'
|
||||
exclude 'wpi/EpochTracker.h'
|
||||
exclude 'wpi/Errc.h'
|
||||
exclude 'wpi/Errno.h'
|
||||
exclude 'wpi/ErrorHandling.h'
|
||||
exclude 'wpi/fs.h'
|
||||
exclude 'wpi/FunctionExtras.h'
|
||||
exclude 'wpi/function_ref.h'
|
||||
exclude 'wpi/Hashing.h'
|
||||
exclude 'wpi/iterator.h'
|
||||
exclude 'wpi/iterator_range.h'
|
||||
exclude 'wpi/ManagedStatic.h'
|
||||
exclude 'wpi/MapVector.h'
|
||||
exclude 'wpi/MathExtras.h'
|
||||
exclude 'wpi/MemAlloc.h'
|
||||
exclude 'wpi/PointerIntPair.h'
|
||||
exclude 'wpi/PointerLikeTypeTraits.h'
|
||||
exclude 'wpi/PointerUnion.h'
|
||||
exclude 'wpi/raw_os_ostream.h'
|
||||
exclude 'wpi/raw_ostream.h'
|
||||
exclude 'wpi/SmallPtrSet.h'
|
||||
exclude 'wpi/SmallSet.h'
|
||||
exclude 'wpi/SmallString.h'
|
||||
exclude 'wpi/SmallVector.h'
|
||||
exclude 'wpi/StringExtras.h'
|
||||
exclude 'wpi/StringMap.h'
|
||||
exclude 'wpi/SwapByteOrder.h'
|
||||
exclude 'wpi/type_traits.h'
|
||||
exclude 'wpi/VersionTuple.h'
|
||||
exclude 'wpi/WindowsError.h'
|
||||
|
||||
// fmtlib
|
||||
exclude 'fmt/**'
|
||||
|
||||
// libuv
|
||||
exclude 'uv.h'
|
||||
exclude 'uv/**'
|
||||
exclude 'wpi/uv/**'
|
||||
|
||||
// json
|
||||
exclude 'wpi/json.h'
|
||||
|
||||
// mpack
|
||||
exclude 'wpi/mpack.h'
|
||||
|
||||
// units
|
||||
exclude 'units/**'
|
||||
}
|
||||
|
||||
case_sense_names false
|
||||
extension_mapping 'inc=C++', 'no_extension=C++'
|
||||
extract_all true
|
||||
extract_static true
|
||||
file_patterns '*'
|
||||
full_path_names true
|
||||
generate_html true
|
||||
generate_latex false
|
||||
generate_treeview true
|
||||
html_extra_stylesheet 'theme.css'
|
||||
html_timestamp true
|
||||
javadoc_autobrief true
|
||||
project_name 'WPILibC++'
|
||||
project_logo '../wpiutil/src/main/native/resources/wpilib-128.png'
|
||||
project_number wpilibVersioning.version.get()
|
||||
javadoc_autobrief true
|
||||
recursive true
|
||||
quiet true
|
||||
warnings false
|
||||
warn_if_undocumented false
|
||||
generate_latex false
|
||||
use_mathjax true
|
||||
html_timestamp true
|
||||
generate_treeview true
|
||||
extract_static true
|
||||
full_path_names true
|
||||
recursive true
|
||||
strip_code_comments false
|
||||
strip_from_inc_path cppIncludeRoots as String[]
|
||||
strip_from_path cppIncludeRoots as String[]
|
||||
use_mathjax true
|
||||
warnings false
|
||||
warn_if_incomplete_doc true
|
||||
warn_if_undocumented false
|
||||
warn_no_paramdoc true
|
||||
|
||||
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix SpeedController docs
|
||||
enable_preprocessing true
|
||||
macro_expansion true
|
||||
expand_only_predef true
|
||||
predefined "WPI_DEPRECATED(x)=[[deprecated(x)]]"
|
||||
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
warn_as_error 'FAIL_ON_WARNINGS'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("zipCppDocs", Zip) {
|
||||
@@ -133,7 +213,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
title = "WPILib API ${wpilibVersioning.version.get()}"
|
||||
ext.entryPoint = "$destinationDir/index.html"
|
||||
|
||||
if (JavaVersion.current().isJava8Compatible() && project.hasProperty('buildServer')) {
|
||||
if (JavaVersion.current().isJava8Compatible() && project.hasProperty('docWarningsAsErrors')) {
|
||||
// Treat javadoc warnings as errors.
|
||||
//
|
||||
// The second argument '-quiet' is a hack. The one paramater
|
||||
|
||||
37
fieldImages/CMakeLists.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
project(fieldImages)
|
||||
|
||||
include(CompileWarnings)
|
||||
include(GenResources)
|
||||
|
||||
if (WITH_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
include(UseJava)
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json src/main/native/resources/*.png src/main/native/resources/*.jpg)
|
||||
add_jar(field_images_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/fields" ${JAVA_RESOURCES} OUTPUT_NAME fieldImages)
|
||||
|
||||
get_property(FIELD_IMAGES_JAR_FILE TARGET field_images_jar PROPERTY JAR_FILE)
|
||||
install(FILES ${FIELD_IMAGES_JAR_FILE} DESTINATION "${java_lib_dest}")
|
||||
|
||||
set_property(TARGET field_images_jar PROPERTY FOLDER "java")
|
||||
|
||||
endif()
|
||||
|
||||
|
||||
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/fields generated/main/cpp FIELDS fields field_images_resources_src)
|
||||
|
||||
|
||||
add_library(fieldImages ${field_images_resources_src})
|
||||
set_target_properties(fieldImages PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET fieldImages PROPERTY FOLDER "libraries")
|
||||
target_compile_features(fieldImages PUBLIC cxx_std_17)
|
||||
if (MSVC)
|
||||
target_compile_options(fieldImages PUBLIC /bigobj)
|
||||
endif()
|
||||
wpilib_target_warnings(fieldImages)
|
||||
|
||||
target_include_directories(fieldImages PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/fields>)
|
||||
70
fieldImages/build.gradle
Normal file
@@ -0,0 +1,70 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'c'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'google-test-test-suite'
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
apply plugin: 'windows-resources'
|
||||
}
|
||||
|
||||
ext {
|
||||
nativeName = 'fieldImages'
|
||||
baseId = nativeName
|
||||
groupId = 'edu.wpi.first.fieldImages'
|
||||
devMain = "edu.wpi.first.fieldImages.DevMain"
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/resources.gradle"
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
apply from: "${rootDir}/shared/java/javacommon.gradle"
|
||||
|
||||
def generateTask = createGenerateResourcesTask('main', 'FIELDS', 'fields', project)
|
||||
|
||||
project(':').libraryBuild.dependsOn build
|
||||
tasks.withType(CppCompile) {
|
||||
dependsOn generateTask
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDirs 'src/main/native/resources'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
"${nativeName}"(NativeLibrarySpec) {
|
||||
baseName = 'fieldImages'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs "$buildDir/generated/main/cpp"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/main/native/include'
|
||||
}
|
||||
}
|
||||
if (OperatingSystem.current().isWindows()) {
|
||||
rc {
|
||||
source {
|
||||
srcDirs 'src/main/native/win'
|
||||
include '*.rc'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
71
fieldImages/publish.gradle
Normal file
@@ -0,0 +1,71 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
def baseArtifactId = project.nativeName
|
||||
def artifactGroupId = project.groupId
|
||||
def cppZipBaseName = "_GROUP_edu_wpi_first_fieldIimages_ID_${baseArtifactId}-cpp_CLS"
|
||||
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
task cppSourcesZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = cppZipBaseName
|
||||
classifier = "sources"
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from('src/main/native/cpp') {
|
||||
into '/'
|
||||
}
|
||||
from("$buildDir/generated/cpp") {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
|
||||
task cppHeadersZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = cppZipBaseName
|
||||
classifier = "headers"
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
ext.includeDirs = [
|
||||
project.file('src/main/native/include')
|
||||
]
|
||||
|
||||
ext.includeDirs.each {
|
||||
from(it) {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn cppHeadersZip
|
||||
build.dependsOn cppSourcesZip
|
||||
|
||||
addTaskToCopyAllOutputs(cppHeadersZip)
|
||||
addTaskToCopyAllOutputs(cppSourcesZip)
|
||||
|
||||
model {
|
||||
publishing {
|
||||
def wpilibCTaskList = createComponentZipTasks($.components, ['fieldImages'], cppZipBaseName, Zip, project, includeStandardZipFormat)
|
||||
|
||||
publications {
|
||||
cpp(MavenPublication) {
|
||||
wpilibCTaskList.each {
|
||||
artifact it
|
||||
}
|
||||
|
||||
artifact cppHeadersZip
|
||||
artifact cppSourcesZip
|
||||
|
||||
artifactId = "${baseArtifactId}-cpp"
|
||||
groupId artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
fieldImages/src/main/java/edu/wpi/fields/FieldImages.java
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.fields;
|
||||
|
||||
public class FieldImages {
|
||||
public static final String k2018PowerUpFieldConfig = "/edu/wpi/first/fields/2018-powerup.json";
|
||||
public static final String k2019DeepSpaceFieldConfig =
|
||||
"/edu/wpi/first/fields/2019-deepspace.json";
|
||||
public static final String k2020InfiniteRechargeFieldConfig =
|
||||
"/edu/wpi/first/fields/2020-infiniterecharge.json";
|
||||
public static final String k2021InfiniteRechargeFieldConfig =
|
||||
"/edu/wpi/first/fields/2021-infiniterecharge.json";
|
||||
public static final String k2021BarrelFieldConfig =
|
||||
"/edu/wpi/first/fields/2021-barrelracingpath.json";
|
||||
public static final String k2021BounceFieldConfig = "/edu/wpi/first/fields/2021-bouncepath.json";
|
||||
public static final String k2021GalacticSearchAFieldConfig =
|
||||
"/edu/wpi/first/fields/2021-galacticsearcha.json";
|
||||
public static final String k2021GalacticSearchBFieldConfig =
|
||||
"/edu/wpi/first/fields/2021-galacticsearchb.json";
|
||||
public static final String k2021SlalomFieldConfig = "/edu/wpi/first/fields/2021-slalompath.json";
|
||||
public static final String k2022RapidReactFieldConfig =
|
||||
"/edu/wpi/first/fields/2022-rapidreact.json";
|
||||
}
|
||||
@@ -4,14 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <frc/commands/Command.h>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* Opens the claw
|
||||
*/
|
||||
class OpenClaw : public frc::Command {
|
||||
public:
|
||||
OpenClaw();
|
||||
void Initialize() override;
|
||||
bool IsFinished() override;
|
||||
};
|
||||
namespace fields {
|
||||
std::string_view GetResource_2018_powerup_json();
|
||||
std::string_view GetResource_2018_field_jpg();
|
||||
} // namespace fields
|
||||
12
fieldImages/src/main/native/include/fields/2019-deepspace.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2019_deepspace_json();
|
||||
std::string_view GetResource_2019_field_jpg();
|
||||
} // namespace fields
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2020_infiniterecharge_json();
|
||||
std::string_view GetResource_2020_field_png();
|
||||
} // namespace fields
|
||||
12
fieldImages/src/main/native/include/fields/2021-barrel.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_barrelracingpath_json();
|
||||
std::string_view GetResource_2021_barrel_png();
|
||||
} // namespace fields
|
||||
12
fieldImages/src/main/native/include/fields/2021-bounce.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_bouncepath_json();
|
||||
std::string_view GetResource_2021_bounce_png();
|
||||
} // namespace fields
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_galacticsearcha_json();
|
||||
std::string_view GetResource_2021_galacticsearcha_png();
|
||||
} // namespace fields
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_galacticsearchb_json();
|
||||
std::string_view GetResource_2021_galacticsearchb_png();
|
||||
} // namespace fields
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_field_png();
|
||||
std::string_view GetResource_2021_infiniterecharge_json();
|
||||
} // namespace fields
|
||||
12
fieldImages/src/main/native/include/fields/2021-slalom.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2021_slalompath_json();
|
||||
std::string_view GetResource_2021_slalom_png();
|
||||
} // namespace fields
|
||||
12
fieldImages/src/main/native/include/fields/2022-rapidreact.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2022_rapidreact_json();
|
||||
std::string_view GetResource_2022_field_png();
|
||||
} // namespace fields
|
||||
|
After Width: | Height: | Size: 191 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "FIRST Power Up",
|
||||
"field-image": "2018-field.jpg",
|
||||
"field-corners": {
|
||||
"top-left": [125, 20],
|
||||
"bottom-right": [827, 370]
|
||||
},
|
||||
"field-size": [54, 27],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game" : "Destination: Deep Space",
|
||||
"field-image" : "2019-field.jpg",
|
||||
"field-corners": {
|
||||
"top-left" : [217, 40],
|
||||
"bottom-right" : [1372, 615]
|
||||
},
|
||||
"field-size" : [54, 27],
|
||||
"field-unit" : "foot"
|
||||
}
|
||||
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 747 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game" : "Infinite Recharge",
|
||||
"field-image" : "2020-field.png",
|
||||
"field-corners": {
|
||||
"top-left" : [96, 25],
|
||||
"bottom-right" : [1040, 514]
|
||||
},
|
||||
"field-size" : [52.4375, 26.9375],
|
||||
"field-unit" : "foot"
|
||||
}
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Barrel Racing Path",
|
||||
"field-image": "2021-barrel.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Bounce Path",
|
||||
"field-image": "2021-bounce.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Galactic Search A",
|
||||
"field-image": "2021-galacticsearcha.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Galactic Search B",
|
||||
"field-image": "2021-galacticsearchb.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Infinite Recharge 2021",
|
||||
"field-image": "2021-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [127, 34],
|
||||
"bottom-right": [1323, 649]
|
||||
},
|
||||
"field-size": [52.4375, 26.9375],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Slalom Path",
|
||||
"field-image": "2021-slalom.png",
|
||||
"field-corners": {
|
||||
"top-left": [20, 20],
|
||||
"bottom-right": [780, 400]
|
||||
},
|
||||
"field-size": [30, 15],
|
||||
"field-unit": "feet"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 MiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"game": "Rapid React",
|
||||
"field-image": "2022-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [74, 50],
|
||||
"bottom-right": [1774, 900]
|
||||
},
|
||||
"field-size": [54, 27],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
@@ -185,15 +185,18 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib library: 'glassnt', linkage: 'static'
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'opencv_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
it.linker.args << '/DELAYLOAD:MF.dll' << '/DELAYLOAD:MFReadWrite.dll' << '/DELAYLOAD:MFPlat.dll' << '/delay:nobind'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
|
||||
52
glass/src/app/native/cpp/camerasupport.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "camerasupport.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Windows.h"
|
||||
#include "delayimp.h"
|
||||
#pragma comment(lib, "delayimp.lib")
|
||||
static int CheckDelayException(int exception_value) {
|
||||
if (exception_value ==
|
||||
VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
|
||||
exception_value ==
|
||||
VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND)) {
|
||||
// This example just executes the handler.
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
// Don't attempt to handle other errors
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
static bool TryDelayLoadAllImports(LPCSTR szDll) {
|
||||
__try {
|
||||
HRESULT hr = __HrLoadAllImportsForDll(szDll);
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
} __except (CheckDelayException(GetExceptionCode())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
namespace glass {
|
||||
bool HasCameraSupport() {
|
||||
bool hasCameraSupport = false;
|
||||
hasCameraSupport = TryDelayLoadAllImports("MF.dll");
|
||||
if (hasCameraSupport) {
|
||||
hasCameraSupport = TryDelayLoadAllImports("MFPlat.dll");
|
||||
}
|
||||
if (hasCameraSupport) {
|
||||
hasCameraSupport = TryDelayLoadAllImports("MFReadWrite.dll");
|
||||
}
|
||||
return hasCameraSupport;
|
||||
}
|
||||
} // namespace glass
|
||||
#else
|
||||
namespace glass {
|
||||
bool HasCameraSupport() {
|
||||
return true;
|
||||
}
|
||||
} // namespace glass
|
||||
#endif
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
class OI {
|
||||
public:
|
||||
OI();
|
||||
};
|
||||
namespace glass {
|
||||
bool HasCameraSupport();
|
||||
} // namespace glass
|
||||
@@ -11,7 +11,9 @@
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/MainMenuBar.h"
|
||||
#include "glass/Model.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/View.h"
|
||||
#include "glass/networktables/NetworkTables.h"
|
||||
#include "glass/networktables/NetworkTablesProvider.h"
|
||||
@@ -39,9 +41,12 @@ static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
|
||||
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
|
||||
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static glass::LogData gNetworkTablesLog;
|
||||
static glass::Window* gNetworkTablesWindow;
|
||||
static glass::Window* gNetworkTablesSettingsWindow;
|
||||
static glass::Window* gNetworkTablesLogWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
|
||||
|
||||
static glass::MainMenuBar gMainMenu;
|
||||
static bool gAbout = false;
|
||||
|
||||
static void NtInitialize() {
|
||||
// update window title when connection status changes
|
||||
@@ -84,48 +89,65 @@ static void NtInitialize() {
|
||||
}
|
||||
});
|
||||
|
||||
gNetworkTablesLogWindow = gNtProvider->AddWindow(
|
||||
"NetworkTables Log",
|
||||
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Log"),
|
||||
"NetworkTables Log", glass::Window::kHide);
|
||||
gNetworkTablesLogWindow->SetView(
|
||||
std::make_unique<glass::LogView>(&gNetworkTablesLog));
|
||||
if (gNetworkTablesLogWindow) {
|
||||
gNetworkTablesLogWindow->SetDefaultPos(250, 615);
|
||||
gNetworkTablesLogWindow->SetDefaultSize(600, 130);
|
||||
gNetworkTablesLogWindow->SetVisible(false);
|
||||
gNetworkTablesLogWindow->DisableRenamePopup();
|
||||
}
|
||||
gNetworkTablesLogWindow->SetDefaultPos(250, 615);
|
||||
gNetworkTablesLogWindow->SetDefaultSize(600, 130);
|
||||
gNetworkTablesLogWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesLogWindow->Display(); });
|
||||
|
||||
// NetworkTables table window
|
||||
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
|
||||
gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
|
||||
|
||||
gNetworkTablesWindow = gNtProvider->AddWindow(
|
||||
"NetworkTables",
|
||||
gNetworkTablesWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables");
|
||||
gNetworkTablesWindow->SetView(
|
||||
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
|
||||
if (gNetworkTablesWindow) {
|
||||
gNetworkTablesWindow->SetDefaultPos(250, 277);
|
||||
gNetworkTablesWindow->SetDefaultSize(750, 185);
|
||||
gNetworkTablesWindow->DisableRenamePopup();
|
||||
}
|
||||
gNetworkTablesWindow->SetDefaultPos(250, 277);
|
||||
gNetworkTablesWindow->SetDefaultSize(750, 185);
|
||||
gNetworkTablesWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
|
||||
|
||||
// NetworkTables settings window
|
||||
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>();
|
||||
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
|
||||
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
|
||||
|
||||
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
|
||||
"NetworkTables Settings", [] { gNetworkTablesSettings->Display(); });
|
||||
if (gNetworkTablesSettingsWindow) {
|
||||
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
|
||||
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
gNetworkTablesSettingsWindow->DisableRenamePopup();
|
||||
}
|
||||
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Settings"),
|
||||
"NetworkTables Settings");
|
||||
gNetworkTablesSettingsWindow->SetView(
|
||||
glass::MakeFunctionView([] { gNetworkTablesSettings->Display(); }));
|
||||
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
|
||||
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
gNetworkTablesSettingsWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesSettingsWindow->Display(); });
|
||||
|
||||
gui::AddWindowScaler([](float scale) {
|
||||
// scale default window positions
|
||||
gNetworkTablesLogWindow->ScaleDefault(scale);
|
||||
gNetworkTablesWindow->ScaleDefault(scale);
|
||||
gNetworkTablesSettingsWindow->ScaleDefault(scale);
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
gui::CreateContext();
|
||||
glass::CreateContext();
|
||||
|
||||
@@ -137,20 +159,24 @@ int main() {
|
||||
gui::AddIcon(glass::GetResource_glass_256_png());
|
||||
gui::AddIcon(glass::GetResource_glass_512_png());
|
||||
|
||||
gPlotProvider = std::make_unique<glass::PlotProvider>("Plot");
|
||||
gNtProvider = std::make_unique<glass::NetworkTablesProvider>("NTProvider");
|
||||
gPlotProvider = std::make_unique<glass::PlotProvider>(
|
||||
glass::GetStorageRoot().GetChild("Plots"));
|
||||
gNtProvider = std::make_unique<glass::NetworkTablesProvider>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables"));
|
||||
|
||||
gui::ConfigurePlatformSaveFile("glass.ini");
|
||||
glass::SetStorageName("glass");
|
||||
glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
|
||||
: saveDir);
|
||||
gPlotProvider->GlobalInit();
|
||||
gui::AddInit([] { glass::ResetTime(); });
|
||||
gNtProvider->GlobalInit();
|
||||
gui::AddInit(NtInitialize);
|
||||
NtInitialize();
|
||||
|
||||
glass::AddStandardNetworkTablesViews(*gNtProvider);
|
||||
|
||||
gui::AddLateExecute([] {
|
||||
ImGui::BeginMainMenuBar();
|
||||
gui::EmitViewMenu();
|
||||
gui::AddLateExecute([] { gMainMenu.Display(); });
|
||||
|
||||
gMainMenu.AddMainMenu([] {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::MenuItem("Reset Time")) {
|
||||
glass::ResetTime();
|
||||
@@ -181,23 +207,25 @@ int main() {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
bool about = false;
|
||||
if (ImGui::BeginMenu("Info")) {
|
||||
if (ImGui::MenuItem("About")) {
|
||||
about = true;
|
||||
gAbout = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
});
|
||||
|
||||
if (about) {
|
||||
gui::AddLateExecute([] {
|
||||
if (gAbout) {
|
||||
ImGui::OpenPopup("About");
|
||||
about = false;
|
||||
gAbout = false;
|
||||
}
|
||||
if (ImGui::BeginPopupModal("About")) {
|
||||
ImGui::Text("Glass: A different kind of dashboard");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("v%s", GetWPILibVersion());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -208,11 +236,15 @@ int main() {
|
||||
gui::Initialize("Glass - DISCONNECTED", 1024, 768);
|
||||
gui::Main();
|
||||
|
||||
gNetworkTablesSettingsWindow.reset();
|
||||
gNetworkTablesLogWindow.reset();
|
||||
gNetworkTablesWindow.reset();
|
||||
gNetworkTablesModel.reset();
|
||||
gNetworkTablesSettings.reset();
|
||||
gNtProvider.reset();
|
||||
gPlotProvider.reset();
|
||||
|
||||
glass::DestroyContext();
|
||||
gui::DestroyContext();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpigui.h>
|
||||
#include <wpigui_internal.h>
|
||||
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
@@ -21,172 +29,292 @@ using namespace glass;
|
||||
|
||||
Context* glass::gContext;
|
||||
|
||||
static bool ConvertInt(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt;
|
||||
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
|
||||
value->intVal = val.value();
|
||||
return true;
|
||||
static void WorkspaceResetImpl() {
|
||||
// call reset functions
|
||||
for (auto&& reset : gContext->workspaceReset) {
|
||||
if (reset) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
// clear storage
|
||||
for (auto&& root : gContext->storageRoots) {
|
||||
root.second->Clear();
|
||||
}
|
||||
|
||||
// ImGui reset
|
||||
ImGui::ClearIniSettings();
|
||||
}
|
||||
|
||||
static bool ConvertInt64(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt64;
|
||||
if (auto val = wpi::parse_integer<int64_t>(value->stringVal, 10)) {
|
||||
value->int64Val = val.value();
|
||||
return true;
|
||||
static void WorkspaceInit() {
|
||||
for (auto&& init : gContext->workspaceInit) {
|
||||
if (init) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto&& root : gContext->storageRoots) {
|
||||
root.getValue()->Apply();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ConvertBool(Storage::Value* value) {
|
||||
value->type = Storage::Value::kBool;
|
||||
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
|
||||
value->intVal = (val.value() != 0);
|
||||
return true;
|
||||
static bool JsonToWindow(const wpi::json& jfile, const char* filename) {
|
||||
if (!jfile.is_object()) {
|
||||
ImGui::LogText("%s top level is not object", filename);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
// loop over JSON and generate ini format
|
||||
std::string iniStr;
|
||||
wpi::raw_string_ostream ini{iniStr};
|
||||
|
||||
for (auto&& jsection : jfile.items()) {
|
||||
if (!jsection.value().is_object()) {
|
||||
ImGui::LogText("%s section %s is not object", filename,
|
||||
jsection.key().c_str());
|
||||
return false;
|
||||
}
|
||||
for (auto&& jsubsection : jsection.value().items()) {
|
||||
if (!jsubsection.value().is_object()) {
|
||||
ImGui::LogText("%s section %s subsection %s is not object", filename,
|
||||
jsection.key().c_str(), jsubsection.key().c_str());
|
||||
return false;
|
||||
}
|
||||
ini << '[' << jsection.key() << "][" << jsubsection.key() << "]\n";
|
||||
for (auto&& jkv : jsubsection.value().items()) {
|
||||
try {
|
||||
auto& value = jkv.value().get_ref<const std::string&>();
|
||||
ini << jkv.key() << '=' << value << "\n";
|
||||
} catch (wpi::json::exception&) {
|
||||
ImGui::LogText("%s section %s subsection %s value %s is not string",
|
||||
filename, jsection.key().c_str(),
|
||||
jsubsection.key().c_str(), jkv.key().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ini << '\n';
|
||||
}
|
||||
}
|
||||
ini.flush();
|
||||
|
||||
ImGui::LoadIniSettingsFromMemory(iniStr.data(), iniStr.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertFloat(Storage::Value* value) {
|
||||
value->type = Storage::Value::kFloat;
|
||||
if (auto val = wpi::parse_float<float>(value->stringVal)) {
|
||||
value->floatVal = val.value();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ConvertDouble(Storage::Value* value) {
|
||||
value->type = Storage::Value::kDouble;
|
||||
if (auto val = wpi::parse_float<double>(value->stringVal)) {
|
||||
value->doubleVal = val.value();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
auto ctx = static_cast<Context*>(handler->UserData);
|
||||
auto& storage = ctx->storage[name];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return storage.get();
|
||||
}
|
||||
|
||||
static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
|
||||
void* entry, const char* line) {
|
||||
auto storage = static_cast<Storage*>(entry);
|
||||
auto [key, val] = wpi::split(line, '=');
|
||||
auto& keys = storage->GetKeys();
|
||||
auto& values = storage->GetValues();
|
||||
auto it = std::find(keys.begin(), keys.end(), key);
|
||||
if (it == keys.end()) {
|
||||
keys.emplace_back(key);
|
||||
values.emplace_back(std::make_unique<Storage::Value>(val));
|
||||
static bool LoadWindowStorageImpl(const std::string& filename) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
} else {
|
||||
auto& value = *values[it - keys.begin()];
|
||||
value.stringVal = val;
|
||||
switch (value.type) {
|
||||
case Storage::Value::kInt:
|
||||
ConvertInt(&value);
|
||||
break;
|
||||
case Storage::Value::kInt64:
|
||||
ConvertInt64(&value);
|
||||
break;
|
||||
case Storage::Value::kBool:
|
||||
ConvertBool(&value);
|
||||
break;
|
||||
case Storage::Value::kFloat:
|
||||
ConvertFloat(&value);
|
||||
break;
|
||||
case Storage::Value::kDouble:
|
||||
ConvertDouble(&value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
try {
|
||||
return JsonToWindow(wpi::json::parse(is), filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
auto ctx = static_cast<Context*>(handler->UserData);
|
||||
|
||||
// sort for output
|
||||
std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
|
||||
for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
|
||||
sorted.emplace_back(it);
|
||||
static bool LoadStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
std::string_view rootName) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
} else {
|
||||
auto& storage = ctx->storageRoots[rootName];
|
||||
bool createdStorage = false;
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
createdStorage = true;
|
||||
}
|
||||
try {
|
||||
storage->FromJson(wpi::json::parse(is), filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
if (createdStorage) {
|
||||
ctx->storageRoots.erase(rootName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
|
||||
return a->getKey() < b->getKey();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto&& entryIt : sorted) {
|
||||
auto& entry = *entryIt;
|
||||
out_buf->append("[GlassStorage][");
|
||||
out_buf->append(entry.first().data(),
|
||||
entry.first().data() + entry.first().size());
|
||||
out_buf->append("]\n");
|
||||
auto& keys = entry.second->GetKeys();
|
||||
auto& values = entry.second->GetValues();
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
|
||||
out_buf->append("=");
|
||||
auto& value = *values[i];
|
||||
switch (value.type) {
|
||||
case Storage::Value::kInt:
|
||||
out_buf->appendf("%d\n", value.intVal);
|
||||
break;
|
||||
case Storage::Value::kInt64:
|
||||
out_buf->appendf("%" PRId64 "\n", value.int64Val);
|
||||
break;
|
||||
case Storage::Value::kBool:
|
||||
out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
|
||||
break;
|
||||
case Storage::Value::kFloat:
|
||||
out_buf->appendf("%f\n", value.floatVal);
|
||||
break;
|
||||
case Storage::Value::kDouble:
|
||||
out_buf->appendf("%f\n", value.doubleVal);
|
||||
break;
|
||||
case Storage::Value::kNone:
|
||||
case Storage::Value::kString:
|
||||
out_buf->append(value.stringVal.data(),
|
||||
value.stringVal.data() + value.stringVal.size());
|
||||
out_buf->append("\n");
|
||||
break;
|
||||
static bool LoadStorageImpl(Context* ctx, std::string_view dir,
|
||||
std::string_view name) {
|
||||
WorkspaceResetImpl();
|
||||
|
||||
bool rv = true;
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
std::string filename;
|
||||
auto rootName = root.getKey();
|
||||
if (rootName.empty()) {
|
||||
filename = (fs::path{dir} / fmt::format("{}.json", name)).string();
|
||||
} else {
|
||||
filename =
|
||||
(fs::path{dir} / fmt::format("{}-{}.json", name, rootName)).string();
|
||||
}
|
||||
if (!LoadStorageRootImpl(ctx, filename, rootName)) {
|
||||
rv = false;
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceInit();
|
||||
return rv;
|
||||
}
|
||||
|
||||
static wpi::json WindowToJson() {
|
||||
size_t iniLen;
|
||||
const char* iniData = ImGui::SaveIniSettingsToMemory(&iniLen);
|
||||
std::string_view ini{iniData, iniLen};
|
||||
|
||||
// parse the ini data and build JSON
|
||||
// JSON format:
|
||||
// {
|
||||
// "Section": {
|
||||
// "Subsection": {
|
||||
// "Key": "Value" // all values are saved as strings
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
wpi::json out = wpi::json::object();
|
||||
wpi::json* curSection = nullptr;
|
||||
while (!ini.empty()) {
|
||||
std::string_view line;
|
||||
std::tie(line, ini) = wpi::split(ini, '\n');
|
||||
line = wpi::trim(line);
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (line[0] == '[') {
|
||||
// new section
|
||||
auto [section, subsection] = wpi::split(line, ']');
|
||||
section = wpi::drop_front(section); // drop '['; ']' was dropped by split
|
||||
subsection = wpi::drop_back(wpi::drop_front(subsection)); // drop []
|
||||
auto& jsection = out[section];
|
||||
if (jsection.is_null()) {
|
||||
jsection = wpi::json::object();
|
||||
}
|
||||
curSection = &jsection[subsection];
|
||||
if (curSection->is_null()) {
|
||||
*curSection = wpi::json::object();
|
||||
}
|
||||
} else {
|
||||
// value
|
||||
if (!curSection) {
|
||||
continue; // shouldn't happen, but just in case
|
||||
}
|
||||
auto [name, value] = wpi::split(line, '=');
|
||||
(*curSection)[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool SaveWindowStorageImpl(const std::string& filename) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_ostream os{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
WindowToJson().dump(os, 2);
|
||||
os << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SaveStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
const Storage& storage) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_ostream os{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
storage.ToJson().dump(os, 2);
|
||||
os << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SaveStorageImpl(Context* ctx, std::string_view dir,
|
||||
std::string_view name, bool exiting) {
|
||||
fs::path dirPath{dir};
|
||||
|
||||
std::error_code ec;
|
||||
fs::create_directories(dirPath, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle erasing save files on exit if requested
|
||||
if (exiting && wpi::gui::gContext->resetOnExit) {
|
||||
fs::remove(dirPath / fmt::format("{}-window.json", name), ec);
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
auto rootName = root.getKey();
|
||||
if (rootName.empty()) {
|
||||
fs::remove(dirPath / fmt::format("{}.json", name), ec);
|
||||
} else {
|
||||
fs::remove(dirPath / fmt::format("{}-{}.json", name, rootName), ec);
|
||||
}
|
||||
}
|
||||
out_buf->append("\n");
|
||||
}
|
||||
|
||||
bool rv = SaveWindowStorageImpl(
|
||||
(dirPath / fmt::format("{}-window.json", name)).string());
|
||||
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
auto rootName = root.getKey();
|
||||
std::string filename;
|
||||
if (rootName.empty()) {
|
||||
filename = (dirPath / fmt::format("{}.json", name)).string();
|
||||
} else {
|
||||
filename = (dirPath / fmt::format("{}-{}.json", name, rootName)).string();
|
||||
}
|
||||
if (!SaveStorageRootImpl(ctx, filename, *root.getValue())) {
|
||||
rv = false;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void Initialize(Context* ctx) {
|
||||
wpi::gui::AddInit([=] {
|
||||
ImGuiSettingsHandler ini_handler;
|
||||
ini_handler.TypeName = "GlassStorage";
|
||||
ini_handler.TypeHash = ImHashStr("GlassStorage");
|
||||
ini_handler.ReadOpenFn = GlassStorageReadOpen;
|
||||
ini_handler.ReadLineFn = GlassStorageReadLine;
|
||||
ini_handler.WriteAllFn = GlassStorageWriteAll;
|
||||
ini_handler.UserData = ctx;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
|
||||
Context::Context()
|
||||
: sourceNameStorage{storageRoots.insert({"", std::make_unique<Storage>()})
|
||||
.first->getValue()
|
||||
->GetChild("sourceNames")} {
|
||||
storageStack.emplace_back(storageRoots[""].get());
|
||||
|
||||
ctx->sources.Initialize();
|
||||
});
|
||||
// override ImGui ini saving
|
||||
wpi::gui::ConfigureCustomSaveSettings(
|
||||
[this] { LoadStorageImpl(this, storageLoadDir, storageName); },
|
||||
[this] {
|
||||
LoadWindowStorageImpl((fs::path{storageLoadDir} /
|
||||
fmt::format("{}-window.json", storageName))
|
||||
.string());
|
||||
},
|
||||
[this](bool exiting) {
|
||||
SaveStorageImpl(this, storageAutoSaveDir, storageName, exiting);
|
||||
});
|
||||
}
|
||||
|
||||
static void Shutdown(Context* ctx) {}
|
||||
Context::~Context() {
|
||||
wpi::gui::ConfigureCustomSaveSettings(nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
Context* glass::CreateContext() {
|
||||
Context* ctx = new Context;
|
||||
if (!gContext) {
|
||||
SetCurrentContext(ctx);
|
||||
}
|
||||
Initialize(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -194,7 +322,6 @@ void glass::DestroyContext(Context* ctx) {
|
||||
if (!ctx) {
|
||||
ctx = gContext;
|
||||
}
|
||||
Shutdown(ctx);
|
||||
if (gContext == ctx) {
|
||||
SetCurrentContext(nullptr);
|
||||
}
|
||||
@@ -217,215 +344,167 @@ uint64_t glass::GetZeroTime() {
|
||||
return gContext->zeroTime;
|
||||
}
|
||||
|
||||
Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>());
|
||||
return *m_values.back();
|
||||
} else {
|
||||
return *m_values[it - m_keys.begin()];
|
||||
void glass::WorkspaceReset() {
|
||||
WorkspaceResetImpl();
|
||||
WorkspaceInit();
|
||||
}
|
||||
|
||||
void glass::AddWorkspaceInit(std::function<void()> init) {
|
||||
if (init) {
|
||||
gContext->workspaceInit.emplace_back(std::move(init));
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFUN(CapsName, LowerName, CType) \
|
||||
CType Storage::Get##CapsName(std::string_view key, CType defaultVal) const { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) \
|
||||
return defaultVal; \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
} \
|
||||
return value.LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
void Storage::Set##CapsName(std::string_view key, CType val) { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) { \
|
||||
m_keys.emplace_back(key); \
|
||||
m_values.emplace_back(std::make_unique<Value>()); \
|
||||
m_values.back()->type = Value::k##CapsName; \
|
||||
m_values.back()->LowerName##Val = val; \
|
||||
} else { \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
value.type = Value::k##CapsName; \
|
||||
value.LowerName##Val = val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
CType* Storage::Get##CapsName##Ref(std::string_view key, CType defaultVal) { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) { \
|
||||
m_keys.emplace_back(key); \
|
||||
m_values.emplace_back(std::make_unique<Value>()); \
|
||||
m_values.back()->type = Value::k##CapsName; \
|
||||
m_values.back()->LowerName##Val = defaultVal; \
|
||||
return &m_values.back()->LowerName##Val; \
|
||||
} else { \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
} \
|
||||
return &value.LowerName##Val; \
|
||||
} \
|
||||
}
|
||||
|
||||
DEFUN(Int, int, int)
|
||||
DEFUN(Int64, int64, int64_t)
|
||||
DEFUN(Bool, bool, bool)
|
||||
DEFUN(Float, float, float)
|
||||
DEFUN(Double, double, double)
|
||||
|
||||
std::string Storage::GetString(std::string_view key,
|
||||
std::string_view defaultVal) const {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
return std::string{defaultVal};
|
||||
}
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
return value.stringVal;
|
||||
}
|
||||
|
||||
void Storage::SetString(std::string_view key, std::string_view val) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>(val));
|
||||
m_values.back()->type = Value::kString;
|
||||
} else {
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
value.stringVal = val;
|
||||
void glass::AddWorkspaceReset(std::function<void()> reset) {
|
||||
if (reset) {
|
||||
gContext->workspaceReset.emplace_back(std::move(reset));
|
||||
}
|
||||
}
|
||||
|
||||
std::string* Storage::GetStringRef(std::string_view key,
|
||||
std::string_view defaultVal) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>(defaultVal));
|
||||
m_values.back()->type = Value::kString;
|
||||
return &m_values.back()->stringVal;
|
||||
void glass::SetStorageName(std::string_view name) {
|
||||
gContext->storageName = name;
|
||||
}
|
||||
|
||||
void glass::SetStorageDir(std::string_view dir) {
|
||||
if (dir.empty()) {
|
||||
gContext->storageLoadDir = ".";
|
||||
gContext->storageAutoSaveDir = ".";
|
||||
} else {
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
return &value.stringVal;
|
||||
gContext->storageLoadDir = dir;
|
||||
gContext->storageAutoSaveDir = dir;
|
||||
gContext->isPlatformSaveDir = (dir == wpi::gui::GetPlatformSaveFileDir());
|
||||
}
|
||||
}
|
||||
|
||||
std::string glass::GetStorageDir() {
|
||||
return gContext->storageAutoSaveDir;
|
||||
}
|
||||
|
||||
bool glass::LoadStorage(std::string_view dir) {
|
||||
SaveStorage();
|
||||
SetStorageDir(dir);
|
||||
LoadWindowStorageImpl((fs::path{gContext->storageLoadDir} /
|
||||
fmt::format("{}-window.json", gContext->storageName))
|
||||
.string());
|
||||
return LoadStorageImpl(gContext, dir, gContext->storageName);
|
||||
}
|
||||
|
||||
bool glass::SaveStorage() {
|
||||
return SaveStorageImpl(gContext, gContext->storageAutoSaveDir,
|
||||
gContext->storageName, false);
|
||||
}
|
||||
|
||||
bool glass::SaveStorage(std::string_view dir) {
|
||||
return SaveStorageImpl(gContext, dir, gContext->storageName, false);
|
||||
}
|
||||
|
||||
Storage& glass::GetCurStorageRoot() {
|
||||
return *gContext->storageStack.front();
|
||||
}
|
||||
|
||||
Storage& glass::GetStorageRoot(std::string_view rootName) {
|
||||
auto& storage = gContext->storageRoots[rootName];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
}
|
||||
|
||||
void glass::ResetStorageStack(std::string_view rootName) {
|
||||
if (gContext->storageStack.size() != 1) {
|
||||
ImGui::LogText("resetting non-empty storage stack");
|
||||
}
|
||||
gContext->storageStack.clear();
|
||||
gContext->storageStack.emplace_back(&GetStorageRoot(rootName));
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage() {
|
||||
auto& storage = gContext->storage[gContext->curId];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
return *gContext->storageStack.back();
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage(std::string_view id) {
|
||||
auto& storage = gContext->storage[id];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
void glass::PushStorageStack(std::string_view label_id) {
|
||||
gContext->storageStack.emplace_back(
|
||||
&gContext->storageStack.back()->GetChild(label_id));
|
||||
}
|
||||
|
||||
static void PushIDStack(std::string_view label_id) {
|
||||
gContext->idStack.emplace_back(gContext->curId.size());
|
||||
|
||||
auto [label, id] = wpi::split(label_id, "###");
|
||||
// if no ###id, use label as id
|
||||
if (id.empty()) {
|
||||
id = label;
|
||||
}
|
||||
if (!gContext->curId.empty()) {
|
||||
gContext->curId += "###";
|
||||
}
|
||||
gContext->curId += id;
|
||||
void glass::PushStorageStack(Storage& storage) {
|
||||
gContext->storageStack.emplace_back(&storage);
|
||||
}
|
||||
|
||||
static void PopIDStack() {
|
||||
gContext->curId.resize(gContext->idStack.back());
|
||||
gContext->idStack.pop_back();
|
||||
void glass::PopStorageStack() {
|
||||
if (gContext->storageStack.size() <= 1) {
|
||||
ImGui::LogText("attempted to pop empty storage stack, mismatch push/pop?");
|
||||
return; // ignore
|
||||
}
|
||||
gContext->storageStack.pop_back();
|
||||
}
|
||||
|
||||
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
|
||||
PushIDStack(name);
|
||||
PushStorageStack(name);
|
||||
return ImGui::Begin(name, p_open, flags);
|
||||
}
|
||||
|
||||
void glass::End() {
|
||||
ImGui::End();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
|
||||
ImGuiWindowFlags flags) {
|
||||
PushIDStack(str_id);
|
||||
PushStorageStack(str_id);
|
||||
return ImGui::BeginChild(str_id, size, border, flags);
|
||||
}
|
||||
|
||||
void glass::EndChild() {
|
||||
ImGui::EndChild();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
wpi::SmallString<64> openKey;
|
||||
auto [name, id] = wpi::split(label, "###");
|
||||
// if no ###id, use name as id
|
||||
if (id.empty()) {
|
||||
id = name;
|
||||
}
|
||||
openKey = id;
|
||||
openKey += "###open";
|
||||
|
||||
bool* open = GetStorage().GetBoolRef(openKey.str());
|
||||
*open = ImGui::CollapsingHeader(
|
||||
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
return *open;
|
||||
bool& open = GetStorage().GetChild(label).GetBool(
|
||||
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
|
||||
ImGui::SetNextItemOpen(open);
|
||||
open = ImGui::CollapsingHeader(label, flags);
|
||||
return open;
|
||||
}
|
||||
|
||||
bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
PushIDStack(label);
|
||||
bool* open = GetStorage().GetBoolRef("open");
|
||||
*open = ImGui::TreeNodeEx(
|
||||
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
if (!*open) {
|
||||
PopIDStack();
|
||||
PushStorageStack(label);
|
||||
bool& open = GetStorage().GetBool(
|
||||
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
|
||||
ImGui::SetNextItemOpen(open);
|
||||
open = ImGui::TreeNodeEx(label, flags);
|
||||
if (!open) {
|
||||
PopStorageStack();
|
||||
}
|
||||
return *open;
|
||||
return open;
|
||||
}
|
||||
|
||||
void glass::TreePop() {
|
||||
ImGui::TreePop();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id) {
|
||||
PushIDStack(str_id);
|
||||
PushStorageStack(str_id);
|
||||
ImGui::PushID(str_id);
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
|
||||
PushIDStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
|
||||
PushStorageStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
|
||||
ImGui::PushID(str_id_begin, str_id_end);
|
||||
}
|
||||
|
||||
void glass::PushID(int int_id) {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%d", int_id);
|
||||
PushIDStack(buf);
|
||||
PushStorageStack(buf);
|
||||
ImGui::PushID(int_id);
|
||||
}
|
||||
|
||||
void glass::PopID() {
|
||||
ImGui::PopID();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::PopupEditName(const char* label, std::string* name) {
|
||||
|
||||
@@ -12,13 +12,9 @@ using namespace glass;
|
||||
|
||||
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
|
||||
|
||||
DataSource::DataSource(std::string_view id) : m_id{id} {
|
||||
auto it = gContext->sources.try_emplace(m_id, this);
|
||||
auto& srcName = it.first->getValue();
|
||||
m_name = srcName.name.get();
|
||||
if (!srcName.source) {
|
||||
srcName.source = this;
|
||||
}
|
||||
DataSource::DataSource(std::string_view id)
|
||||
: m_id{id}, m_name{gContext->sourceNameStorage.GetString(m_id)} {
|
||||
gContext->sources.try_emplace(m_id, this);
|
||||
sourceCreated(m_id.c_str(), this);
|
||||
}
|
||||
|
||||
@@ -32,43 +28,7 @@ DataSource::~DataSource() {
|
||||
if (!gContext) {
|
||||
return;
|
||||
}
|
||||
auto it = gContext->sources.find(m_id);
|
||||
if (it == gContext->sources.end()) {
|
||||
return;
|
||||
}
|
||||
auto& srcName = it->getValue();
|
||||
if (srcName.source == this) {
|
||||
srcName.source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DataSource::SetName(std::string_view name) {
|
||||
m_name->SetName(name);
|
||||
}
|
||||
|
||||
const char* DataSource::GetName() const {
|
||||
return m_name->GetName();
|
||||
}
|
||||
|
||||
void DataSource::PushEditNameId(int index) {
|
||||
m_name->PushEditNameId(index);
|
||||
}
|
||||
|
||||
void DataSource::PushEditNameId(const char* name) {
|
||||
m_name->PushEditNameId(name);
|
||||
}
|
||||
|
||||
bool DataSource::PopupEditName(int index) {
|
||||
return m_name->PopupEditName(index);
|
||||
}
|
||||
|
||||
bool DataSource::PopupEditName(const char* name) {
|
||||
return m_name->PopupEditName(name);
|
||||
}
|
||||
|
||||
bool DataSource::InputTextName(const char* label_id,
|
||||
ImGuiInputTextFlags flags) {
|
||||
return m_name->InputTextName(label_id, flags);
|
||||
gContext->sources.erase(m_id);
|
||||
}
|
||||
|
||||
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
|
||||
@@ -82,7 +42,7 @@ void DataSource::LabelText(const char* label, const char* fmt, ...) const {
|
||||
void DataSource::LabelTextV(const char* label, const char* fmt,
|
||||
va_list args) const {
|
||||
ImGui::PushID(label);
|
||||
ImGui::LabelTextV("##input", fmt, args);
|
||||
ImGui::LabelTextV("##input", fmt, args); // NOLINT
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
ImGui::PopID();
|
||||
@@ -141,7 +101,7 @@ void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
|
||||
if (ImGui::BeginDragDropSource(flags)) {
|
||||
auto self = this;
|
||||
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self)); // NOLINT
|
||||
const char* name = GetName();
|
||||
const char* name = GetName().c_str();
|
||||
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
@@ -152,5 +112,5 @@ DataSource* DataSource::Find(std::string_view id) {
|
||||
if (it == gContext->sources.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->getValue().source;
|
||||
return it->getValue();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
|
||||
@@ -25,6 +29,8 @@ void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
|
||||
void MainMenuBar::Display() {
|
||||
ImGui::BeginMainMenuBar();
|
||||
|
||||
WorkspaceMenu();
|
||||
|
||||
if (!m_optionMenus.empty()) {
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
for (auto&& menu : m_optionMenus) {
|
||||
@@ -55,3 +61,46 @@ void MainMenuBar::Display() {
|
||||
#endif
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
void MainMenuBar::WorkspaceMenu() {
|
||||
if (ImGui::BeginMenu("Workspace")) {
|
||||
if (ImGui::MenuItem("Open...")) {
|
||||
m_openFolder =
|
||||
std::make_unique<pfd::select_folder>("Choose folder to open");
|
||||
}
|
||||
if (ImGui::MenuItem("Save As...")) {
|
||||
m_saveFolder = std::make_unique<pfd::select_folder>("Choose save folder");
|
||||
}
|
||||
if (ImGui::MenuItem("Save As Global", nullptr, false,
|
||||
!gContext->isPlatformSaveDir)) {
|
||||
SetStorageDir(wpi::gui::GetPlatformSaveFileDir());
|
||||
SaveStorage();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Reset")) {
|
||||
WorkspaceReset();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit")) {
|
||||
wpi::gui::Exit();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (m_openFolder && m_openFolder->ready(0)) {
|
||||
auto result = m_openFolder->result();
|
||||
if (!result.empty()) {
|
||||
LoadStorage(result);
|
||||
}
|
||||
m_openFolder.reset();
|
||||
}
|
||||
|
||||
if (m_saveFolder && m_saveFolder->ready(0)) {
|
||||
auto result = m_saveFolder->result();
|
||||
if (!result.empty()) {
|
||||
SetStorageDir(result);
|
||||
SaveStorage(result);
|
||||
}
|
||||
m_saveFolder.reset();
|
||||
}
|
||||
}
|
||||
|
||||
688
glass/src/lib/native/cpp/Storage.cpp
Normal file
@@ -0,0 +1,688 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/Storage.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
template <typename To>
|
||||
bool ConvertFromString(To* out, std::string_view str) {
|
||||
if constexpr (std::is_same_v<To, bool>) {
|
||||
if (str == "true") {
|
||||
*out = true;
|
||||
} else if (str == "false") {
|
||||
*out = false;
|
||||
} else if (auto val = wpi::parse_integer<int>(str, 10)) {
|
||||
*out = val.value() != 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (std::is_floating_point_v<To>) {
|
||||
if (auto val = wpi::parse_float<To>(str)) {
|
||||
*out = val.value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (auto val = wpi::parse_integer<To>(str, 10)) {
|
||||
*out = val.value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CONVERT(CapsName, LowerName, CType) \
|
||||
static bool Convert##CapsName(Storage::Value* value) { \
|
||||
switch (value->type) { \
|
||||
case Storage::Value::kBool: \
|
||||
value->LowerName##Val = value->boolVal; \
|
||||
value->LowerName##Default = value->boolDefault; \
|
||||
break; \
|
||||
case Storage::Value::kDouble: \
|
||||
value->LowerName##Val = value->doubleVal; \
|
||||
value->LowerName##Default = value->doubleDefault; \
|
||||
break; \
|
||||
case Storage::Value::kFloat: \
|
||||
value->LowerName##Val = value->floatVal; \
|
||||
value->LowerName##Default = value->floatDefault; \
|
||||
break; \
|
||||
case Storage::Value::kInt: \
|
||||
value->LowerName##Val = value->intVal; \
|
||||
value->LowerName##Default = value->intDefault; \
|
||||
break; \
|
||||
case Storage::Value::kInt64: \
|
||||
value->LowerName##Val = value->int64Val; \
|
||||
value->LowerName##Default = value->int64Default; \
|
||||
break; \
|
||||
case Storage::Value::kString: \
|
||||
if (!ConvertFromString(&value->LowerName##Val, value->stringVal)) { \
|
||||
return false; \
|
||||
} \
|
||||
if (!ConvertFromString(&value->LowerName##Default, \
|
||||
value->stringDefault)) { \
|
||||
return false; \
|
||||
} \
|
||||
break; \
|
||||
default: \
|
||||
return false; \
|
||||
} \
|
||||
value->type = Storage::Value::k##CapsName; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
CONVERT(Int, int, int)
|
||||
CONVERT(Int64, int64, int64_t)
|
||||
CONVERT(Float, float, float)
|
||||
CONVERT(Double, double, double)
|
||||
CONVERT(Bool, bool, bool)
|
||||
|
||||
static inline bool ConvertString(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrays can only come from JSON, so we only have to worry about conversions
|
||||
// between the various number types, not bool or string
|
||||
|
||||
template <typename From, typename To>
|
||||
static void ConvertArray(std::vector<To>** outPtr, std::vector<From>** inPtr) {
|
||||
if (*inPtr) {
|
||||
std::vector<To>* tmp;
|
||||
tmp = new std::vector<To>{(*inPtr)->begin(), (*inPtr)->end()};
|
||||
delete *inPtr;
|
||||
*outPtr = tmp;
|
||||
} else {
|
||||
*outPtr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#define CONVERT_ARRAY(CapsName, LowerName) \
|
||||
static bool Convert##CapsName##Array(Storage::Value* value) { \
|
||||
switch (value->type) { \
|
||||
case Storage::Value::kDoubleArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->doubleArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->doubleArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kFloatArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->floatArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->floatArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kIntArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->intArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->intArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kInt64Array: \
|
||||
ConvertArray(&value->LowerName##Array, &value->int64Array); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->int64ArrayDefault); \
|
||||
break; \
|
||||
default: \
|
||||
return false; \
|
||||
} \
|
||||
value->type = Storage::Value::k##CapsName##Array; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
CONVERT_ARRAY(Int, int)
|
||||
CONVERT_ARRAY(Int64, int64)
|
||||
CONVERT_ARRAY(Float, float)
|
||||
CONVERT_ARRAY(Double, double)
|
||||
|
||||
static inline bool ConvertBoolArray(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool ConvertStringArray(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Storage::Value::Reset(Type newType) {
|
||||
switch (type) {
|
||||
case kChild:
|
||||
delete child;
|
||||
break;
|
||||
case kIntArray:
|
||||
delete intArray;
|
||||
delete intArrayDefault;
|
||||
break;
|
||||
case kInt64Array:
|
||||
delete int64Array;
|
||||
delete int64ArrayDefault;
|
||||
break;
|
||||
case kBoolArray:
|
||||
delete boolArray;
|
||||
delete boolArrayDefault;
|
||||
break;
|
||||
case kFloatArray:
|
||||
delete floatArray;
|
||||
delete floatArrayDefault;
|
||||
break;
|
||||
case kDoubleArray:
|
||||
delete doubleArray;
|
||||
delete doubleArrayDefault;
|
||||
break;
|
||||
case kStringArray:
|
||||
delete stringArray;
|
||||
delete stringArrayDefault;
|
||||
break;
|
||||
case kChildArray:
|
||||
delete childArray;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
type = newType;
|
||||
}
|
||||
|
||||
Storage::Value* Storage::FindValue(std::string_view key) {
|
||||
auto it = m_values.find(key);
|
||||
if (it == m_values.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
auto& val = m_values[key];
|
||||
if (!val) {
|
||||
val = std::make_unique<Value>();
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
#define DEFUN(CapsName, LowerName, CType, CParamType, ArrCType) \
|
||||
CType Storage::Read##CapsName(std::string_view key, CParamType defaultVal) \
|
||||
const { \
|
||||
auto it = m_values.find(key); \
|
||||
if (it == m_values.end()) { \
|
||||
return CType{defaultVal}; \
|
||||
} \
|
||||
Value& value = *it->second; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) { \
|
||||
value.Reset(Value::k##CapsName); \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
value.LowerName##Default = defaultVal; \
|
||||
value.hasDefault = true; \
|
||||
} \
|
||||
} \
|
||||
return value.LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
void Storage::Set##CapsName(std::string_view key, CParamType val) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
|
||||
} else { \
|
||||
valuePtr->Reset(Value::k##CapsName); \
|
||||
} \
|
||||
valuePtr->LowerName##Val = val; \
|
||||
valuePtr->LowerName##Default = {}; \
|
||||
} \
|
||||
\
|
||||
CType& Storage::Get##CapsName(std::string_view key, CParamType defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
|
||||
setValue = true; \
|
||||
} else if (valuePtr->type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(valuePtr.get())) { \
|
||||
valuePtr->Reset(Value::k##CapsName); \
|
||||
setValue = true; \
|
||||
} \
|
||||
} \
|
||||
if (setValue) { \
|
||||
valuePtr->LowerName##Val = defaultVal; \
|
||||
} \
|
||||
if (!valuePtr->hasDefault) { \
|
||||
valuePtr->LowerName##Default = defaultVal; \
|
||||
valuePtr->hasDefault = true; \
|
||||
} \
|
||||
return valuePtr->LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
|
||||
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName##Array); \
|
||||
setValue = true; \
|
||||
} else if (valuePtr->type != Value::k##CapsName##Array) { \
|
||||
if (!Convert##CapsName##Array(valuePtr.get())) { \
|
||||
valuePtr->Reset(Value::k##CapsName##Array); \
|
||||
setValue = true; \
|
||||
} \
|
||||
} \
|
||||
if (setValue) { \
|
||||
valuePtr->LowerName##Array = \
|
||||
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
|
||||
} \
|
||||
if (!valuePtr->hasDefault) { \
|
||||
if (defaultVal.empty()) { \
|
||||
valuePtr->LowerName##ArrayDefault = nullptr; \
|
||||
} else { \
|
||||
valuePtr->LowerName##ArrayDefault = \
|
||||
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
|
||||
} \
|
||||
valuePtr->hasDefault = true; \
|
||||
} \
|
||||
assert(valuePtr->LowerName##Array); \
|
||||
return *valuePtr->LowerName##Array; \
|
||||
}
|
||||
|
||||
DEFUN(Int, int, int, int, int)
|
||||
DEFUN(Int64, int64, int64_t, int64_t, int64_t)
|
||||
DEFUN(Bool, bool, bool, bool, int)
|
||||
DEFUN(Float, float, float, float, float)
|
||||
DEFUN(Double, double, double, double, double)
|
||||
DEFUN(String, string, std::string, std::string_view, std::string)
|
||||
|
||||
Storage& Storage::GetChild(std::string_view label_id) {
|
||||
auto [label, id] = wpi::split(label_id, "###");
|
||||
if (id.empty()) {
|
||||
id = label;
|
||||
}
|
||||
auto& childPtr = m_values[id];
|
||||
if (!childPtr) {
|
||||
childPtr = std::make_unique<Value>();
|
||||
}
|
||||
if (childPtr->type != Value::kChild) {
|
||||
childPtr->type = Value::kChild;
|
||||
childPtr->child = new Storage;
|
||||
}
|
||||
return *childPtr->child;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Storage>>& Storage::GetChildArray(
|
||||
std::string_view key) {
|
||||
auto& valuePtr = m_values[key];
|
||||
if (!valuePtr) {
|
||||
valuePtr = std::make_unique<Value>(Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
} else if (valuePtr->type != Value::kChildArray) {
|
||||
valuePtr->Reset(Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
}
|
||||
|
||||
return *valuePtr->childArray;
|
||||
}
|
||||
|
||||
std::unique_ptr<Storage::Value> Storage::Erase(std::string_view key) {
|
||||
auto it = m_values.find(key);
|
||||
if (it != m_values.end()) {
|
||||
auto rv = std::move(it->getValue());
|
||||
m_values.erase(it);
|
||||
return rv;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Storage::EraseChildren() {
|
||||
for (auto&& kv : m_values) {
|
||||
if (kv.getValue()->type == Value::kChild) {
|
||||
m_values.remove(&kv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool JsonArrayToStorage(Storage::Value* valuePtr, const wpi::json& jarr,
|
||||
const char* filename) {
|
||||
auto& arr = jarr.get_ref<const wpi::json::array_t&>();
|
||||
if (arr.empty()) {
|
||||
ImGui::LogText("empty array in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// guess array type from first element
|
||||
switch (arr[0].type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
if (valuePtr->type != Storage::Value::kBoolArray) {
|
||||
valuePtr->Reset(Storage::Value::kBoolArray);
|
||||
valuePtr->boolArray = new std::vector<int>();
|
||||
valuePtr->boolArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
if (valuePtr->type != Storage::Value::kDoubleArray) {
|
||||
valuePtr->Reset(Storage::Value::kDoubleArray);
|
||||
valuePtr->doubleArray = new std::vector<double>();
|
||||
valuePtr->doubleArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
if (valuePtr->type != Storage::Value::kInt64Array) {
|
||||
valuePtr->Reset(Storage::Value::kInt64Array);
|
||||
valuePtr->int64Array = new std::vector<int64_t>();
|
||||
valuePtr->int64ArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
if (valuePtr->type != Storage::Value::kStringArray) {
|
||||
valuePtr->Reset(Storage::Value::kStringArray);
|
||||
valuePtr->stringArray = new std::vector<std::string>();
|
||||
valuePtr->stringArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type != Storage::Value::kChildArray) {
|
||||
valuePtr->Reset(Storage::Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
ImGui::LogText("nested array in %s, ignoring", filename);
|
||||
return false;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// loop over array to store elements
|
||||
for (auto jvalue : arr) {
|
||||
switch (jvalue.type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
if (valuePtr->type == Storage::Value::kBoolArray) {
|
||||
valuePtr->boolArray->push_back(jvalue.get<bool>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<double>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
if (valuePtr->type == Storage::Value::kInt64Array) {
|
||||
valuePtr->int64Array->push_back(jvalue.get<int64_t>());
|
||||
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<int64_t>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
if (valuePtr->type == Storage::Value::kInt64Array) {
|
||||
valuePtr->int64Array->push_back(jvalue.get<uint64_t>());
|
||||
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<uint64_t>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
if (valuePtr->type == Storage::Value::kStringArray) {
|
||||
valuePtr->stringArray->emplace_back(
|
||||
jvalue.get_ref<const std::string&>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type == Storage::Value::kChildArray) {
|
||||
valuePtr->childArray->emplace_back(std::make_unique<Storage>());
|
||||
valuePtr->childArray->back()->FromJson(jvalue, filename);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
ImGui::LogText("nested array in %s, ignoring", filename);
|
||||
return false;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
ImGui::LogText("array with variant types in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Storage::FromJson(const wpi::json& json, const char* filename) {
|
||||
if (m_fromJson) {
|
||||
return m_fromJson(json, filename);
|
||||
}
|
||||
|
||||
if (!json.is_object()) {
|
||||
ImGui::LogText("non-object in %s", filename);
|
||||
return false;
|
||||
}
|
||||
for (auto&& jkv : json.items()) {
|
||||
auto& valuePtr = m_values[jkv.key()];
|
||||
bool created = false;
|
||||
if (!valuePtr) {
|
||||
valuePtr = std::make_unique<Value>();
|
||||
created = true;
|
||||
}
|
||||
auto& jvalue = jkv.value();
|
||||
switch (jvalue.type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
valuePtr->Reset(Value::kBool);
|
||||
valuePtr->boolVal = jvalue.get<bool>();
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
valuePtr->Reset(Value::kDouble);
|
||||
valuePtr->doubleVal = jvalue.get<double>();
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
valuePtr->Reset(Value::kInt64);
|
||||
valuePtr->int64Val = jvalue.get<int64_t>();
|
||||
break;
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
valuePtr->Reset(Value::kInt64);
|
||||
valuePtr->int64Val = jvalue.get<uint64_t>();
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
valuePtr->Reset(Value::kString);
|
||||
valuePtr->stringVal = jvalue.get_ref<const std::string&>();
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type != Value::kChild) {
|
||||
valuePtr->Reset(Value::kChild);
|
||||
valuePtr->child = new Storage;
|
||||
}
|
||||
valuePtr->child->FromJson(jvalue, filename); // recurse
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
if (!JsonArrayToStorage(valuePtr.get(), jvalue, filename)) {
|
||||
if (created) {
|
||||
m_values.erase(jkv.key());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
if (created) {
|
||||
m_values.erase(jkv.key());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static wpi::json StorageToJsonArray(const std::vector<T>& arr) {
|
||||
wpi::json jarr = wpi::json::array();
|
||||
for (auto&& v : arr) {
|
||||
jarr.emplace_back(v);
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
template <>
|
||||
wpi::json StorageToJsonArray<std::unique_ptr<Storage>>(
|
||||
const std::vector<std::unique_ptr<Storage>>& arr) {
|
||||
wpi::json jarr = wpi::json::array();
|
||||
for (auto&& v : arr) {
|
||||
jarr.emplace_back(v->ToJson());
|
||||
}
|
||||
// remove any trailing empty items
|
||||
while (!jarr.empty() && jarr.back().empty()) {
|
||||
jarr.get_ref<wpi::json::array_t&>().pop_back();
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
wpi::json Storage::ToJson() const {
|
||||
if (m_toJson) {
|
||||
return m_toJson();
|
||||
}
|
||||
|
||||
wpi::json j = wpi::json::object();
|
||||
for (auto&& kv : m_values) {
|
||||
wpi::json jelem;
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
#define CASE(CapsName, LowerName) \
|
||||
case Value::k##CapsName: \
|
||||
if (value.hasDefault && \
|
||||
value.LowerName##Val == value.LowerName##Default) { \
|
||||
continue; \
|
||||
} \
|
||||
jelem = value.LowerName##Val; \
|
||||
break; \
|
||||
case Value::k##CapsName##Array: \
|
||||
if (value.hasDefault && \
|
||||
((!value.LowerName##ArrayDefault && \
|
||||
value.LowerName##Array->empty()) || \
|
||||
(value.LowerName##ArrayDefault && \
|
||||
*value.LowerName##Array == *value.LowerName##ArrayDefault))) { \
|
||||
continue; \
|
||||
} \
|
||||
jelem = StorageToJsonArray(*value.LowerName##Array); \
|
||||
break;
|
||||
|
||||
CASE(Int, int)
|
||||
CASE(Int64, int64)
|
||||
CASE(Bool, bool)
|
||||
CASE(Float, float)
|
||||
CASE(Double, double)
|
||||
CASE(String, string)
|
||||
|
||||
case Value::kChild:
|
||||
jelem = value.child->ToJson(); // recurse
|
||||
if (jelem.empty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
jelem = StorageToJsonArray(*value.childArray);
|
||||
if (jelem.empty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
j.emplace(kv.getKey(), std::move(jelem));
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void Storage::Clear() {
|
||||
if (m_clear) {
|
||||
return m_clear();
|
||||
}
|
||||
|
||||
ClearValues();
|
||||
}
|
||||
|
||||
void Storage::ClearValues() {
|
||||
for (auto&& kv : m_values) {
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
case Value::kInt:
|
||||
value.intVal = value.intDefault;
|
||||
break;
|
||||
case Value::kInt64:
|
||||
value.int64Val = value.int64Default;
|
||||
break;
|
||||
case Value::kBool:
|
||||
value.boolVal = value.boolDefault;
|
||||
break;
|
||||
case Value::kFloat:
|
||||
value.floatVal = value.floatDefault;
|
||||
break;
|
||||
case Value::kDouble:
|
||||
value.doubleVal = value.doubleDefault;
|
||||
break;
|
||||
case Value::kString:
|
||||
value.stringVal = value.stringDefault;
|
||||
break;
|
||||
case Value::kIntArray:
|
||||
*value.intArray = *value.intArrayDefault;
|
||||
break;
|
||||
case Value::kInt64Array:
|
||||
*value.int64Array = *value.int64ArrayDefault;
|
||||
break;
|
||||
case Value::kBoolArray:
|
||||
*value.boolArray = *value.boolArrayDefault;
|
||||
break;
|
||||
case Value::kFloatArray:
|
||||
*value.floatArray = *value.floatArrayDefault;
|
||||
break;
|
||||
case Value::kDoubleArray:
|
||||
*value.doubleArray = *value.doubleArrayDefault;
|
||||
break;
|
||||
case Value::kStringArray:
|
||||
*value.stringArray = *value.stringArrayDefault;
|
||||
break;
|
||||
case Value::kChild:
|
||||
value.child->Clear();
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
for (auto&& child : *value.childArray) {
|
||||
child->Clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::Apply() {
|
||||
if (m_apply) {
|
||||
return m_apply();
|
||||
}
|
||||
|
||||
ApplyChildren();
|
||||
}
|
||||
|
||||
void Storage::ApplyChildren() {
|
||||
for (auto&& kv : m_values) {
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
case Value::kChild:
|
||||
value.child->Apply();
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
for (auto&& child : *value.childArray) {
|
||||
child->Apply();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,28 @@
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
Window::Window(Storage& storage, std::string_view id,
|
||||
Visibility defaultVisibility)
|
||||
: m_id{id},
|
||||
m_name{storage.GetString("name")},
|
||||
m_defaultName{id},
|
||||
m_visible{storage.GetBool("visible", defaultVisibility != kHide)},
|
||||
m_enabled{storage.GetBool("enabled", defaultVisibility != kDisabled)},
|
||||
m_defaultVisible{storage.GetValue("visible").boolDefault},
|
||||
m_defaultEnabled{storage.GetValue("enabled").boolDefault} {}
|
||||
|
||||
void Window::SetVisibility(Visibility visibility) {
|
||||
switch (visibility) {
|
||||
case kHide:
|
||||
m_visible = false;
|
||||
m_enabled = true;
|
||||
break;
|
||||
case kShow:
|
||||
m_visible = true;
|
||||
m_enabled = true;
|
||||
break;
|
||||
case kDisabled:
|
||||
m_enabled = false;
|
||||
break;
|
||||
}
|
||||
m_visible = visibility != kHide;
|
||||
m_enabled = visibility != kDisabled;
|
||||
}
|
||||
|
||||
void Window::SetDefaultVisibility(Visibility visibility) {
|
||||
m_defaultVisible = visibility != kHide;
|
||||
m_defaultEnabled = visibility != kDisabled;
|
||||
}
|
||||
|
||||
void Window::Display() {
|
||||
@@ -85,27 +90,3 @@ void Window::ScaleDefault(float scale) {
|
||||
m_size.y *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::IniReadLine(const char* line) {
|
||||
auto [name, value] = wpi::split(line, '=');
|
||||
name = wpi::trim(name);
|
||||
value = wpi::trim(value);
|
||||
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
} else if (name == "visible") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_visible = num.value();
|
||||
}
|
||||
} else if (name == "enabled") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_enabled = num.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf) {
|
||||
out_buf->appendf("[%s][%s]\nname=%s\nvisible=%d\nenabled=%d\n\n", typeName,
|
||||
m_id.c_str(), m_name.c_str(), m_visible ? 1 : 0,
|
||||
m_enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -10,30 +10,23 @@
|
||||
#include <fmt/format.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
WindowManager::WindowManager(std::string_view iniName)
|
||||
: m_iniSaver{iniName, this} {}
|
||||
|
||||
// read/write open state to ini file
|
||||
void* WindowManager::IniSaver::IniReadOpen(const char* name) {
|
||||
return m_manager->GetOrAddWindow(name, true);
|
||||
}
|
||||
|
||||
void WindowManager::IniSaver::IniReadLine(void* entry, const char* lineStr) {
|
||||
static_cast<Window*>(entry)->IniReadLine(lineStr);
|
||||
}
|
||||
|
||||
void WindowManager::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
|
||||
const char* typeName = GetTypeName();
|
||||
for (auto&& window : m_manager->m_windows) {
|
||||
window->IniWriteAll(typeName, out_buf);
|
||||
}
|
||||
WindowManager::WindowManager(Storage& storage) : m_storage{storage} {
|
||||
storage.SetCustomApply([this] {
|
||||
for (auto&& childIt : m_storage.GetChildren()) {
|
||||
GetOrAddWindow(childIt.key(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(std::string_view id,
|
||||
wpi::unique_function<void()> display) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
wpi::unique_function<void()> display,
|
||||
Window::Visibility defaultVisibility) {
|
||||
auto win = GetOrAddWindow(id, false, defaultVisibility);
|
||||
if (!win) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -46,8 +39,9 @@ Window* WindowManager::AddWindow(std::string_view id,
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(std::string_view id,
|
||||
std::unique_ptr<View> view) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
std::unique_ptr<View> view,
|
||||
Window::Visibility defaultVisibility) {
|
||||
auto win = GetOrAddWindow(id, false, defaultVisibility);
|
||||
if (!win) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -59,7 +53,8 @@ Window* WindowManager::AddWindow(std::string_view id,
|
||||
return win;
|
||||
}
|
||||
|
||||
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
|
||||
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk,
|
||||
Window::Visibility defaultVisibility) {
|
||||
// binary search
|
||||
auto it = std::lower_bound(
|
||||
m_windows.begin(), m_windows.end(), id,
|
||||
@@ -72,7 +67,11 @@ Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
|
||||
return it->get();
|
||||
}
|
||||
// insert before (keeps sort)
|
||||
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
|
||||
return m_windows
|
||||
.emplace(it, std::make_unique<Window>(
|
||||
m_storage.GetChild(id).GetChild("window"), id,
|
||||
defaultVisibility))
|
||||
->get();
|
||||
}
|
||||
|
||||
Window* WindowManager::GetWindow(std::string_view id) {
|
||||
@@ -86,8 +85,12 @@ Window* WindowManager::GetWindow(std::string_view id) {
|
||||
return it->get();
|
||||
}
|
||||
|
||||
void WindowManager::RemoveWindow(size_t index) {
|
||||
m_storage.Erase(m_windows[index]->GetId());
|
||||
m_windows.erase(m_windows.begin() + index);
|
||||
}
|
||||
|
||||
void WindowManager::GlobalInit() {
|
||||
wpi::gui::AddInit([this] { m_iniSaver.Initialize(); });
|
||||
wpi::gui::AddWindowScaler([this](float scale) {
|
||||
// scale default window positions
|
||||
for (auto&& window : m_windows) {
|
||||
@@ -104,7 +107,9 @@ void WindowManager::DisplayMenu() {
|
||||
}
|
||||
|
||||
void WindowManager::DisplayWindows() {
|
||||
PushStorageStack(m_storage);
|
||||
for (auto&& window : m_windows) {
|
||||
window->Display();
|
||||
}
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -18,10 +19,10 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
|
||||
}
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "In[%d]###name", index);
|
||||
}
|
||||
@@ -42,8 +43,8 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
|
||||
}
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) {
|
||||
voltageData->SetName(name->c_str());
|
||||
if (PopupEditName("name", &name)) {
|
||||
voltageData->SetName(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -26,10 +27,10 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
|
||||
PushID(i);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), i);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "Out[%d]###name", i);
|
||||
}
|
||||
@@ -37,9 +38,9 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
|
||||
double value = analogOutData->GetValue();
|
||||
DeviceDouble(label, true, &value, analogOutData);
|
||||
|
||||
if (PopupEditName("name", name)) {
|
||||
if (PopupEditName("name", &name)) {
|
||||
if (analogOutData) {
|
||||
analogOutData->SetName(name->c_str());
|
||||
analogOutData->SetName(name);
|
||||
}
|
||||
}
|
||||
PopID();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/Encoder.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -28,17 +28,18 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
|
||||
|
||||
bool exists = model->Exists();
|
||||
auto& info = dioData->GetNameInfo();
|
||||
NameSetting dioName{dioData->GetName()};
|
||||
char label[128];
|
||||
if (exists && dpwmData) {
|
||||
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
|
||||
NameSetting{dpwmData->GetName()}.GetLabel(label, sizeof(label), "PWM",
|
||||
index);
|
||||
if (auto simDevice = dpwm->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
dpwmData->LabelText(label, "%0.3f", dpwmData->GetValue());
|
||||
}
|
||||
} else if (exists && encoder) {
|
||||
info.GetLabel(label, sizeof(label), " In", index);
|
||||
dioName.GetLabel(label, sizeof(label), " In", index);
|
||||
if (auto simDevice = encoder->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
@@ -48,7 +49,8 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
} else if (exists && dutyCycleData) {
|
||||
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
|
||||
NameSetting{dutyCycleData->GetName()}.GetLabel(label, sizeof(label), "Dty",
|
||||
index);
|
||||
if (auto simDevice = dutyCycle->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
@@ -60,10 +62,10 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
} else {
|
||||
const char* name = model->GetName();
|
||||
if (name[0] != '\0') {
|
||||
info.GetLabel(label, sizeof(label), name);
|
||||
dioName.GetLabel(label, sizeof(label), name);
|
||||
} else {
|
||||
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
|
||||
index);
|
||||
dioName.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
|
||||
index);
|
||||
}
|
||||
if (auto simDevice = model->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
@@ -87,12 +89,12 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.PopupEditName(index)) {
|
||||
if (dioName.PopupEditName(index)) {
|
||||
if (dpwmData) {
|
||||
dpwmData->SetName(info.GetName());
|
||||
dpwmData->SetName(dioName.GetName());
|
||||
}
|
||||
if (dutyCycleData) {
|
||||
dutyCycleData->SetName(info.GetName());
|
||||
dutyCycleData->SetName(dioName.GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -66,21 +67,21 @@ void glass::DisplayEncoder(EncoderModel* model) {
|
||||
int chB = model->GetChannelB();
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
|
||||
chB);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d,%d]###header", name.c_str(),
|
||||
chA, chB);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
|
||||
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###header", chA, chB);
|
||||
}
|
||||
|
||||
// header
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) {
|
||||
model->SetName(name->c_str());
|
||||
if (PopupEditName("header", &name)) {
|
||||
model->SetName(name);
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -25,27 +26,27 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
|
||||
bool running = model->IsRunning();
|
||||
auto& storage = GetStorage();
|
||||
|
||||
int* numColumns = storage.GetIntRef("columns", 10);
|
||||
bool* serpentine = storage.GetBoolRef("serpentine", false);
|
||||
int* order = storage.GetIntRef("order", LEDConfig::RowMajor);
|
||||
int* start = storage.GetIntRef("start", LEDConfig::UpperLeft);
|
||||
int& numColumns = storage.GetInt("columns", 10);
|
||||
bool& serpentine = storage.GetBool("serpentine", false);
|
||||
int& order = storage.GetInt("order", LEDConfig::RowMajor);
|
||||
int& start = storage.GetInt("start", LEDConfig::UpperLeft);
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::LabelText("Length", "%d", length);
|
||||
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
|
||||
ImGui::InputInt("Columns", numColumns);
|
||||
ImGui::InputInt("Columns", &numColumns);
|
||||
{
|
||||
static const char* options[] = {"Row Major", "Column Major"};
|
||||
ImGui::Combo("Order", order, options, 2);
|
||||
ImGui::Combo("Order", &order, options, 2);
|
||||
}
|
||||
{
|
||||
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
|
||||
"Lower Right"};
|
||||
ImGui::Combo("Start", start, options, 4);
|
||||
ImGui::Combo("Start", &start, options, 4);
|
||||
}
|
||||
ImGui::Checkbox("Serpentine", serpentine);
|
||||
if (*numColumns < 1) {
|
||||
*numColumns = 1;
|
||||
ImGui::Checkbox("Serpentine", &serpentine);
|
||||
if (numColumns < 1) {
|
||||
numColumns = 1;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
@@ -74,12 +75,12 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
|
||||
}
|
||||
|
||||
LEDConfig config;
|
||||
config.serpentine = *serpentine;
|
||||
config.order = static_cast<LEDConfig::Order>(*order);
|
||||
config.start = static_cast<LEDConfig::Start>(*start);
|
||||
config.serpentine = serpentine;
|
||||
config.order = static_cast<LEDConfig::Order>(order);
|
||||
config.start = static_cast<LEDConfig::Start>(start);
|
||||
|
||||
DrawLEDs(iData->values.data(), length, *numColumns, iData->colors.data(), 0,
|
||||
0, config);
|
||||
DrawLEDs(iData->values.data(), length, numColumns, iData->colors.data(), 0, 0,
|
||||
config);
|
||||
}
|
||||
|
||||
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -42,18 +43,19 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
}
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###header", name.c_str(),
|
||||
index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
|
||||
std::snprintf(label, sizeof(label), "PCM[%d]###header", index);
|
||||
}
|
||||
|
||||
// header
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
PopupEditName("name", name);
|
||||
PopupEditName("header", &name);
|
||||
|
||||
ImGui::SetItemAllowOverlap();
|
||||
ImGui::SameLine();
|
||||
@@ -68,11 +70,11 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
|
||||
if (auto data = solenoid.GetOutputData()) {
|
||||
PushID(j);
|
||||
char solenoidName[64];
|
||||
auto& info = data->GetNameInfo();
|
||||
info.GetLabel(solenoidName, sizeof(solenoidName), "Solenoid", j);
|
||||
data->LabelText(solenoidName, "%s", channels[j] == 1 ? "On" : "Off");
|
||||
info.PopupEditName(j);
|
||||
char label[64];
|
||||
NameSetting name{data->GetName()};
|
||||
name.GetLabel(label, sizeof(label), "Solenoid", j);
|
||||
data->LabelText(label, "%s", channels[j] == 1 ? "On" : "Off");
|
||||
name.PopupEditName(j);
|
||||
PopID();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -18,10 +19,10 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "PWM[%d]###name", index);
|
||||
}
|
||||
@@ -35,8 +36,8 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
|
||||
float val = outputsEnabled ? data->GetValue() : 0;
|
||||
data->LabelText(label, "%0.3f", val);
|
||||
}
|
||||
if (PopupEditName("name", name)) {
|
||||
data->SetName(name->c_str());
|
||||
if (PopupEditName("name", &name)) {
|
||||
data->SetName(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -19,16 +19,16 @@ static float DisplayChannel(PowerDistributionModel& pdp, int channel) {
|
||||
float width = 0;
|
||||
if (auto currentData = pdp.GetCurrentData(channel)) {
|
||||
ImGui::PushID(channel);
|
||||
auto& leftInfo = currentData->GetNameInfo();
|
||||
NameSetting leftName{currentData->GetName()};
|
||||
char name[64];
|
||||
leftInfo.GetLabel(name, sizeof(name), "", channel);
|
||||
leftName.GetLabel(name, sizeof(name), "", channel);
|
||||
double val = currentData->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (currentData->InputDouble(name, &val, 0, 0, "%.3f")) {
|
||||
pdp.SetCurrent(channel, val);
|
||||
}
|
||||
width = ImGui::GetItemRectSize().x;
|
||||
leftInfo.PopupEditName(channel);
|
||||
leftName.PopupEditName(channel);
|
||||
ImGui::PopID();
|
||||
}
|
||||
return width;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -31,20 +32,20 @@ void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
ImGui::PushID("name");
|
||||
if (!name->empty()) {
|
||||
ImGui::Text("%s [%d]", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
ImGui::Text("%s [%d]", name.c_str(), index);
|
||||
} else {
|
||||
ImGui::Text("Relay[%d]", index);
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (PopupEditName("name", name)) {
|
||||
if (PopupEditName("name", &name)) {
|
||||
if (forwardData) {
|
||||
forwardData->SetName(name->c_str());
|
||||
forwardData->SetName(name);
|
||||
}
|
||||
if (reverseData) {
|
||||
reverseData->SetName(name->c_str());
|
||||
reverseData->SetName(name);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -51,13 +51,13 @@ bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
|
||||
PushID(id);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###name",
|
||||
name->empty() ? id : name->c_str());
|
||||
std::snprintf(label, sizeof(label), "%s###header",
|
||||
name.empty() ? id : name.c_str());
|
||||
|
||||
bool open = CollapsingHeader(label, flags);
|
||||
PopupEditName("name", name);
|
||||
PopupEditName("header", &name);
|
||||
|
||||
if (!open) {
|
||||
PopID();
|
||||
|
||||
@@ -90,11 +90,20 @@ void glass::DisplayDrive(DriveModel* m) {
|
||||
double a1 = 0.0;
|
||||
double a2 = wpi::numbers::pi / 2 * rotation;
|
||||
|
||||
draw->PathArcTo(center, radius, a1, a2, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
|
||||
a2 + wpi::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
// PathArcTo requires a_min <= a_max, and rotation can be negative
|
||||
if (a1 > a2) {
|
||||
draw->PathArcTo(center, radius, a2, a1, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a2 + wpi::numbers::pi,
|
||||
a1 + wpi::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
} else {
|
||||
draw->PathArcTo(center, radius, a1, a2, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
|
||||
a2 + wpi::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
}
|
||||
|
||||
double adder = rotation < 0 ? wpi::numbers::pi : 0;
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ColorSetting.h"
|
||||
#include "glass/support/EnumSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -114,12 +117,14 @@ struct DisplayOptions {
|
||||
|
||||
static constexpr Style kDefaultStyle = kBoxImage;
|
||||
static constexpr float kDefaultWeight = 4.0f;
|
||||
static constexpr float kDefaultColorFloat[] = {255, 0, 0, 255};
|
||||
static constexpr ImU32 kDefaultColor = IM_COL32(255, 0, 0, 255);
|
||||
static constexpr auto kDefaultWidth = 0.6858_m;
|
||||
static constexpr auto kDefaultLength = 0.8204_m;
|
||||
static constexpr bool kDefaultArrows = true;
|
||||
static constexpr int kDefaultArrowSize = 50;
|
||||
static constexpr float kDefaultArrowWeight = 4.0f;
|
||||
static constexpr float kDefaultArrowColorFloat[] = {0, 255, 0, 255};
|
||||
static constexpr ImU32 kDefaultArrowColor = IM_COL32(0, 255, 0, 255);
|
||||
static constexpr bool kDefaultSelectable = true;
|
||||
|
||||
@@ -180,7 +185,7 @@ class PoseFrameData {
|
||||
|
||||
class ObjectInfo {
|
||||
public:
|
||||
ObjectInfo();
|
||||
explicit ObjectInfo(Storage& storage);
|
||||
|
||||
DisplayOptions GetDisplayOptions() const;
|
||||
void DisplaySettings();
|
||||
@@ -191,26 +196,26 @@ class ObjectInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
// in meters
|
||||
float* m_pWidth;
|
||||
float* m_pLength;
|
||||
float& m_width;
|
||||
float& m_length;
|
||||
|
||||
int* m_pStyle; // DisplayOptions::Style
|
||||
float* m_pWeight;
|
||||
int* m_pColor;
|
||||
EnumSetting m_style; // DisplayOptions::Style
|
||||
float& m_weight;
|
||||
ColorSetting m_color;
|
||||
|
||||
bool* m_pArrows;
|
||||
int* m_pArrowSize;
|
||||
float* m_pArrowWeight;
|
||||
int* m_pArrowColor;
|
||||
bool& m_arrows;
|
||||
int& m_arrowSize;
|
||||
float& m_arrowWeight;
|
||||
ColorSetting m_arrowColor;
|
||||
|
||||
bool* m_pSelectable;
|
||||
bool& m_selectable;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
};
|
||||
|
||||
@@ -219,7 +224,7 @@ class FieldInfo {
|
||||
static constexpr auto kDefaultWidth = 15.98_m;
|
||||
static constexpr auto kDefaultHeight = 8.21_m;
|
||||
|
||||
FieldInfo();
|
||||
explicit FieldInfo(Storage& storage);
|
||||
|
||||
void DisplaySettings();
|
||||
|
||||
@@ -231,25 +236,25 @@ class FieldInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
void LoadJson(std::string_view jsonfile);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
|
||||
// in meters
|
||||
float* m_pWidth;
|
||||
float* m_pHeight;
|
||||
float& m_width;
|
||||
float& m_height;
|
||||
|
||||
// in image pixels
|
||||
int m_imageWidth;
|
||||
int m_imageHeight;
|
||||
int* m_pTop;
|
||||
int* m_pLeft;
|
||||
int* m_pBottom;
|
||||
int* m_pRight;
|
||||
int& m_top;
|
||||
int& m_left;
|
||||
int& m_bottom;
|
||||
int& m_right;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -261,17 +266,17 @@ static DisplayUnits gDisplayUnits = kDisplayMeters;
|
||||
static double ConvertDisplayLength(units::meter_t v) {
|
||||
switch (gDisplayUnits) {
|
||||
case kDisplayFeet:
|
||||
return v.convert<units::feet>().to<double>();
|
||||
return v.convert<units::feet>().value();
|
||||
case kDisplayInches:
|
||||
return v.convert<units::inches>().to<double>();
|
||||
return v.convert<units::inches>().value();
|
||||
case kDisplayMeters:
|
||||
default:
|
||||
return v.to<double>();
|
||||
return v.value();
|
||||
}
|
||||
}
|
||||
|
||||
static double ConvertDisplayAngle(units::degree_t v) {
|
||||
return v.to<double>();
|
||||
return v.value();
|
||||
}
|
||||
|
||||
static bool InputLength(const char* label, units::meter_t* v, double step = 0.0,
|
||||
@@ -334,16 +339,14 @@ static bool InputPose(frc::Pose2d* pose) {
|
||||
return changed;
|
||||
}
|
||||
|
||||
FieldInfo::FieldInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
m_pTop = storage.GetIntRef("top", 0);
|
||||
m_pLeft = storage.GetIntRef("left", 0);
|
||||
m_pBottom = storage.GetIntRef("bottom", -1);
|
||||
m_pRight = storage.GetIntRef("right", -1);
|
||||
m_pWidth = storage.GetFloatRef("width", kDefaultWidth.to<float>());
|
||||
m_pHeight = storage.GetFloatRef("height", kDefaultHeight.to<float>());
|
||||
}
|
||||
FieldInfo::FieldInfo(Storage& storage)
|
||||
: m_filename{storage.GetString("image")},
|
||||
m_width{storage.GetFloat("width", kDefaultWidth.to<float>())},
|
||||
m_height{storage.GetFloat("height", kDefaultHeight.to<float>())},
|
||||
m_top{storage.GetInt("top", 0)},
|
||||
m_left{storage.GetInt("left", 0)},
|
||||
m_bottom{storage.GetInt("bottom", -1)},
|
||||
m_right{storage.GetInt("right", -1)} {}
|
||||
|
||||
void FieldInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
@@ -357,23 +360,23 @@ void FieldInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Reset image")) {
|
||||
Reset();
|
||||
}
|
||||
InputFloatLength("Field Width", m_pWidth);
|
||||
InputFloatLength("Field Height", m_pHeight);
|
||||
// ImGui::InputInt("Field Top", m_pTop);
|
||||
// ImGui::InputInt("Field Left", m_pLeft);
|
||||
// ImGui::InputInt("Field Right", m_pRight);
|
||||
// ImGui::InputInt("Field Bottom", m_pBottom);
|
||||
InputFloatLength("Field Width", &m_width);
|
||||
InputFloatLength("Field Height", &m_height);
|
||||
// ImGui::InputInt("Field Top", &m_top);
|
||||
// ImGui::InputInt("Field Left", &m_left);
|
||||
// ImGui::InputInt("Field Right", &m_right);
|
||||
// ImGui::InputInt("Field Bottom", &m_bottom);
|
||||
}
|
||||
|
||||
void FieldInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
m_imageWidth = 0;
|
||||
m_imageHeight = 0;
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
m_top = 0;
|
||||
m_left = 0;
|
||||
m_bottom = -1;
|
||||
m_right = -1;
|
||||
}
|
||||
|
||||
void FieldInfo::LoadImage() {
|
||||
@@ -384,17 +387,17 @@ void FieldInfo::LoadImage() {
|
||||
LoadJson(result[0]);
|
||||
} else {
|
||||
LoadImageImpl(result[0].c_str());
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
m_top = 0;
|
||||
m_left = 0;
|
||||
m_bottom = -1;
|
||||
m_right = -1;
|
||||
}
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,18 +481,18 @@ void FieldInfo::LoadJson(std::string_view jsonfile) {
|
||||
}
|
||||
|
||||
// save to field info
|
||||
*m_pFilename = pathname;
|
||||
*m_pTop = top;
|
||||
*m_pLeft = left;
|
||||
*m_pBottom = bottom;
|
||||
*m_pRight = right;
|
||||
*m_pWidth = width;
|
||||
*m_pHeight = height;
|
||||
m_filename = pathname;
|
||||
m_top = top;
|
||||
m_left = left;
|
||||
m_bottom = bottom;
|
||||
m_right = right;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
bool FieldInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading field image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::puts("GUI: could not read field image");
|
||||
return false;
|
||||
@@ -497,7 +500,7 @@ bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
m_texture = std::move(texture);
|
||||
m_imageWidth = m_texture.GetWidth();
|
||||
m_imageHeight = m_texture.GetHeight();
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -512,19 +515,19 @@ FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
|
||||
ffd.imageMax = max;
|
||||
|
||||
// size down the box by the image corners (if any)
|
||||
if (*m_pBottom > 0 && *m_pRight > 0) {
|
||||
min.x += *m_pLeft * (max.x - min.x) / m_imageWidth;
|
||||
min.y += *m_pTop * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - *m_pRight) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - *m_pBottom) * (max.y - min.y) / m_imageHeight;
|
||||
if (m_bottom > 0 && m_right > 0) {
|
||||
min.x += m_left * (max.x - min.x) / m_imageWidth;
|
||||
min.y += m_top * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
|
||||
ffd.min = min;
|
||||
ffd.max = max;
|
||||
ffd.scale = (max.x - min.x) / *m_pWidth;
|
||||
ffd.scale = (max.x - min.x) / m_width;
|
||||
return ffd;
|
||||
}
|
||||
|
||||
@@ -537,48 +540,47 @@ void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
|
||||
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
m_pWidth =
|
||||
storage.GetFloatRef("width", DisplayOptions::kDefaultWidth.to<float>());
|
||||
m_pLength =
|
||||
storage.GetFloatRef("length", DisplayOptions::kDefaultLength.to<float>());
|
||||
m_pStyle = storage.GetIntRef("style", DisplayOptions::kDefaultStyle);
|
||||
m_pWeight = storage.GetFloatRef("weight", DisplayOptions::kDefaultWeight);
|
||||
m_pColor = storage.GetIntRef("color", DisplayOptions::kDefaultColor);
|
||||
m_pArrows = storage.GetBoolRef("arrows", DisplayOptions::kDefaultArrows);
|
||||
m_pArrowSize =
|
||||
storage.GetIntRef("arrowSize", DisplayOptions::kDefaultArrowSize);
|
||||
m_pArrowWeight =
|
||||
storage.GetFloatRef("arrowWeight", DisplayOptions::kDefaultArrowWeight);
|
||||
m_pArrowColor =
|
||||
storage.GetIntRef("arrowColor", DisplayOptions::kDefaultArrowColor);
|
||||
m_pSelectable =
|
||||
storage.GetBoolRef("selectable", DisplayOptions::kDefaultSelectable);
|
||||
}
|
||||
ObjectInfo::ObjectInfo(Storage& storage)
|
||||
: m_width{storage.GetFloat("width",
|
||||
DisplayOptions::kDefaultWidth.to<float>())},
|
||||
m_length{storage.GetFloat("length",
|
||||
DisplayOptions::kDefaultLength.to<float>())},
|
||||
m_style{storage.GetString("style"),
|
||||
DisplayOptions::kDefaultStyle,
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track"}},
|
||||
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
|
||||
m_color{
|
||||
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
|
||||
m_arrows{storage.GetBool("arrows", DisplayOptions::kDefaultArrows)},
|
||||
m_arrowSize{
|
||||
storage.GetInt("arrowSize", DisplayOptions::kDefaultArrowSize)},
|
||||
m_arrowWeight{
|
||||
storage.GetFloat("arrowWeight", DisplayOptions::kDefaultArrowWeight)},
|
||||
m_arrowColor{storage.GetFloatArray(
|
||||
"arrowColor", DisplayOptions::kDefaultArrowColorFloat)},
|
||||
m_selectable{
|
||||
storage.GetBool("selectable", DisplayOptions::kDefaultSelectable)},
|
||||
m_filename{storage.GetString("image")} {}
|
||||
|
||||
DisplayOptions ObjectInfo::GetDisplayOptions() const {
|
||||
DisplayOptions rv{m_texture};
|
||||
rv.style = static_cast<DisplayOptions::Style>(*m_pStyle);
|
||||
rv.weight = *m_pWeight;
|
||||
rv.color = *m_pColor;
|
||||
rv.width = units::meter_t{*m_pWidth};
|
||||
rv.length = units::meter_t{*m_pLength};
|
||||
rv.arrows = *m_pArrows;
|
||||
rv.arrowSize = *m_pArrowSize;
|
||||
rv.arrowWeight = *m_pArrowWeight;
|
||||
rv.arrowColor = *m_pArrowColor;
|
||||
rv.selectable = *m_pSelectable;
|
||||
rv.style = static_cast<DisplayOptions::Style>(m_style.GetValue());
|
||||
rv.weight = m_weight;
|
||||
rv.color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
|
||||
rv.width = units::meter_t{m_width};
|
||||
rv.length = units::meter_t{m_length};
|
||||
rv.arrows = m_arrows;
|
||||
rv.arrowSize = m_arrowSize;
|
||||
rv.arrowWeight = m_arrowWeight;
|
||||
rv.arrowColor = ImGui::ColorConvertFloat4ToU32(m_arrowColor.GetColor());
|
||||
rv.selectable = m_selectable;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void ObjectInfo::DisplaySettings() {
|
||||
static const char* styleChoices[] = {"Box/Image", "Line", "Line (Closed)",
|
||||
"Track"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Style", m_pStyle, styleChoices, IM_ARRAYSIZE(styleChoices));
|
||||
switch (*m_pStyle) {
|
||||
m_style.Combo("Style");
|
||||
switch (m_style.GetValue()) {
|
||||
case DisplayOptions::kBoxImage:
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
m_fileOpener = std::make_unique<pfd::open_file>(
|
||||
@@ -591,35 +593,27 @@ void ObjectInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Reset image")) {
|
||||
Reset();
|
||||
}
|
||||
InputFloatLength("Width", m_pWidth);
|
||||
InputFloatLength("Length", m_pLength);
|
||||
InputFloatLength("Width", &m_width);
|
||||
InputFloatLength("Length", &m_length);
|
||||
break;
|
||||
case DisplayOptions::kTrack:
|
||||
InputFloatLength("Width", m_pWidth);
|
||||
InputFloatLength("Width", &m_width);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::InputFloat("Line Weight", m_pWeight);
|
||||
ImColor col(*m_pColor);
|
||||
if (ImGui::ColorEdit3("Line Color", &col.Value.x,
|
||||
ImGuiColorEditFlags_NoInputs)) {
|
||||
*m_pColor = col;
|
||||
}
|
||||
ImGui::Checkbox("Arrows", m_pArrows);
|
||||
if (*m_pArrows) {
|
||||
ImGui::SliderInt("Arrow Size", m_pArrowSize, 0, 100, "%d%%",
|
||||
ImGui::InputFloat("Line Weight", &m_weight);
|
||||
m_color.ColorEdit3("Line Color", ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::Checkbox("Arrows", &m_arrows);
|
||||
if (m_arrows) {
|
||||
ImGui::SliderInt("Arrow Size", &m_arrowSize, 0, 100, "%d%%",
|
||||
ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::InputFloat("Arrow Weight", m_pArrowWeight);
|
||||
ImColor col(*m_pArrowColor);
|
||||
if (ImGui::ColorEdit3("Arrow Color", &col.Value.x,
|
||||
ImGuiColorEditFlags_NoInputs)) {
|
||||
*m_pArrowColor = col;
|
||||
}
|
||||
ImGui::InputFloat("Arrow Weight", &m_arrowWeight);
|
||||
m_arrowColor.ColorEdit3("Arrow Color", ImGuiColorEditFlags_NoInputs);
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Selectable", m_pSelectable);
|
||||
ImGui::Checkbox("Selectable", &m_selectable);
|
||||
}
|
||||
|
||||
void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
@@ -629,10 +623,12 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
}
|
||||
|
||||
if (points.size() == 1) {
|
||||
drawList->AddCircleFilled(points.front(), *m_pWeight, *m_pWeight);
|
||||
drawList->AddCircleFilled(points.front(), m_weight, m_weight);
|
||||
return;
|
||||
}
|
||||
|
||||
ImU32 color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
|
||||
|
||||
// PolyLine doesn't handle acute angles well; workaround from
|
||||
// https://github.com/ocornut/imgui/issues/3366
|
||||
size_t i = 0;
|
||||
@@ -651,18 +647,18 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
++nlin;
|
||||
}
|
||||
|
||||
drawList->AddPolyline(&points[i], nlin, *m_pColor, false, *m_pWeight);
|
||||
drawList->AddPolyline(&points[i], nlin, color, false, m_weight);
|
||||
i += nlin - 1;
|
||||
}
|
||||
|
||||
if (points.size() > 2 && *m_pStyle == DisplayOptions::kLineClosed) {
|
||||
drawList->AddLine(points.back(), points.front(), *m_pColor, *m_pWeight);
|
||||
if (points.size() > 2 && m_style.GetValue() == DisplayOptions::kLineClosed) {
|
||||
drawList->AddLine(points.back(), points.front(), color, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
}
|
||||
|
||||
void ObjectInfo::LoadImage() {
|
||||
@@ -673,22 +669,22 @@ void ObjectInfo::LoadImage() {
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectInfo::LoadImageImpl(const char* fn) {
|
||||
bool ObjectInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading object image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::fputs("GUI: could not read object image\n", stderr);
|
||||
return false;
|
||||
}
|
||||
m_texture = std::move(texture);
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -857,15 +853,16 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
storage.SetData(std::make_shared<FieldInfo>(storage));
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
static const char* unitNames[] = {"meters", "feet", "inches"};
|
||||
int* pDisplayUnits = GetStorage().GetIntRef("units", kDisplayMeters);
|
||||
EnumSetting displayUnits{GetStorage().GetString("units"),
|
||||
kDisplayMeters,
|
||||
{"meters", "feet", "inches"}};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Units", pDisplayUnits, unitNames, IM_ARRAYSIZE(unitNames));
|
||||
gDisplayUnits = static_cast<DisplayUnits>(*pDisplayUnits);
|
||||
displayUnits.Combo("Units");
|
||||
gDisplayUnits = static_cast<DisplayUnits>(displayUnits.GetValue());
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::CollapsingHeader("Field")) {
|
||||
@@ -881,7 +878,7 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
|
||||
PushID(name);
|
||||
auto& objRef = field->m_objects[name];
|
||||
if (!objRef) {
|
||||
objRef = std::make_unique<ObjectInfo>();
|
||||
objRef = std::make_unique<ObjectInfo>(GetStorage());
|
||||
}
|
||||
auto obj = objRef.get();
|
||||
|
||||
@@ -1025,7 +1022,7 @@ void FieldDisplay::DisplayObject(FieldObjectModel& model,
|
||||
PushID(name);
|
||||
auto& objRef = m_field->m_objects[name];
|
||||
if (!objRef) {
|
||||
objRef = std::make_unique<ObjectInfo>();
|
||||
objRef = std::make_unique<ObjectInfo>(GetStorage());
|
||||
}
|
||||
auto obj = objRef.get();
|
||||
obj->LoadImage();
|
||||
@@ -1205,7 +1202,7 @@ void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
storage.SetData(std::make_shared<FieldInfo>(storage));
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -61,7 +62,7 @@ struct FrameData {
|
||||
|
||||
class BackgroundInfo {
|
||||
public:
|
||||
BackgroundInfo();
|
||||
explicit BackgroundInfo(Storage& storage);
|
||||
|
||||
void DisplaySettings();
|
||||
|
||||
@@ -72,11 +73,11 @@ class BackgroundInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
|
||||
// in image pixels
|
||||
@@ -86,10 +87,8 @@ class BackgroundInfo {
|
||||
|
||||
} // namespace
|
||||
|
||||
BackgroundInfo::BackgroundInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
}
|
||||
BackgroundInfo::BackgroundInfo(Storage& storage)
|
||||
: m_filename{storage.GetString("image")} {}
|
||||
|
||||
void BackgroundInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
@@ -106,7 +105,7 @@ void BackgroundInfo::DisplaySettings() {
|
||||
|
||||
void BackgroundInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
m_imageWidth = 0;
|
||||
m_imageHeight = 0;
|
||||
}
|
||||
@@ -119,16 +118,16 @@ void BackgroundInfo::LoadImage() {
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundInfo::LoadImageImpl(const char* fn) {
|
||||
bool BackgroundInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading background image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::puts("GUI: could not read background image");
|
||||
return false;
|
||||
@@ -136,7 +135,7 @@ bool BackgroundInfo::LoadImageImpl(const char* fn) {
|
||||
m_texture = std::move(texture);
|
||||
m_imageWidth = m_texture.GetWidth();
|
||||
m_imageHeight = m_texture.GetHeight();
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,7 +174,7 @@ void glass::DisplayMechanism2DSettings(Mechanism2DModel* model) {
|
||||
auto& storage = GetStorage();
|
||||
auto bg = storage.GetData<BackgroundInfo>();
|
||||
if (!bg) {
|
||||
storage.SetData(std::make_shared<BackgroundInfo>());
|
||||
storage.SetData(std::make_shared<BackgroundInfo>(storage));
|
||||
bg = storage.GetData<BackgroundInfo>();
|
||||
}
|
||||
bg->DisplaySettings();
|
||||
@@ -208,7 +207,7 @@ void glass::DisplayMechanism2D(Mechanism2DModel* model,
|
||||
auto& storage = GetStorage();
|
||||
auto bg = storage.GetData<BackgroundInfo>();
|
||||
if (!bg) {
|
||||
storage.SetData(std::make_shared<BackgroundInfo>());
|
||||
storage.SetData(std::make_shared<BackgroundInfo>(storage));
|
||||
bg = storage.GetData<BackgroundInfo>();
|
||||
}
|
||||
|
||||
|
||||
11
glass/src/lib/native/cpp/support/ColorSetting.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/ColorSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
ColorSetting::ColorSetting(std::vector<float>& color) : m_color{color} {
|
||||
m_color.resize(4);
|
||||
}
|
||||
40
glass/src/lib/native/cpp/support/EnumSetting.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/EnumSetting.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
EnumSetting::EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices)
|
||||
: m_str{str}, m_choices{choices}, m_value{defaultValue} {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : choices) {
|
||||
if (str == choice) {
|
||||
m_value = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void EnumSetting::SetValue(int value) {
|
||||
m_value = value;
|
||||
m_str = m_choices[m_value];
|
||||
}
|
||||
|
||||
bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
int popup_max_height_in_items) {
|
||||
if (ImGui::Combo(
|
||||
label, &m_value, m_choices.data(),
|
||||
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
|
||||
popup_max_height_in_items)) {
|
||||
m_str = m_choices[m_value]; // update stored string
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -160,17 +160,17 @@ bool DeleteButton(ImGuiID id, const ImVec2& pos) {
|
||||
bool HeaderDeleteButton(const char* label) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiLastItemDataBackup last_item_backup;
|
||||
ImGuiLastItemData last_item_backup = g.LastItemData;
|
||||
ImGuiID id = window->GetID(label);
|
||||
float button_size = g.FontSize;
|
||||
float button_x = ImMax(window->DC.LastItemRect.Min.x,
|
||||
window->DC.LastItemRect.Max.x -
|
||||
g.Style.FramePadding.x * 2.0f - button_size);
|
||||
float button_y = window->DC.LastItemRect.Min.y;
|
||||
float button_x = ImMax(
|
||||
g.LastItemData.Rect.Min.x,
|
||||
g.LastItemData.Rect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
|
||||
float button_y = g.LastItemData.Rect.Min.y;
|
||||
bool rv = DeleteButton(
|
||||
window->GetID(reinterpret_cast<void*>(static_cast<intptr_t>(id) + 1)),
|
||||
ImVec2(button_x, button_y));
|
||||
last_item_backup.Restore();
|
||||
g.LastItemData = last_item_backup;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/IniSaverBase.h"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace {
|
||||
class ImGuiSaver : public IniSaverBackend {
|
||||
public:
|
||||
void Register(IniSaverBase* iniSaver) override;
|
||||
void Unregister(IniSaverBase* iniSaver) override;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void ImGuiSaver::Register(IniSaverBase* iniSaver) {
|
||||
// hook ini handler to save settings
|
||||
ImGuiSettingsHandler iniHandler;
|
||||
iniHandler.TypeName = iniSaver->GetTypeName();
|
||||
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
||||
iniHandler.ReadOpenFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
return static_cast<IniSaverBase*>(handler->UserData)->IniReadOpen(name);
|
||||
};
|
||||
iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
void* entry, const char* line) {
|
||||
static_cast<IniSaverBase*>(handler->UserData)->IniReadLine(entry, line);
|
||||
};
|
||||
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
static_cast<IniSaverBase*>(handler->UserData)->IniWriteAll(out_buf);
|
||||
};
|
||||
iniHandler.UserData = iniSaver;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
||||
}
|
||||
|
||||
void ImGuiSaver::Unregister(IniSaverBase* iniSaver) {
|
||||
if (auto ctx = ImGui::GetCurrentContext()) {
|
||||
auto& handlers = ctx->SettingsHandlers;
|
||||
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
|
||||
if (it->UserData == iniSaver) {
|
||||
handlers.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ImGuiSaver* GetSaverInstance() {
|
||||
static ImGuiSaver* inst = new ImGuiSaver;
|
||||
return inst;
|
||||
}
|
||||
|
||||
IniSaverBase::IniSaverBase(std::string_view typeName, IniSaverBackend* backend)
|
||||
: m_typeName(typeName), m_backend{backend ? backend : GetSaverInstance()} {}
|
||||
|
||||
IniSaverBase::~IniSaverBase() {
|
||||
m_backend->Unregister(this);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NameInfo::SetName(std::string_view name) {
|
||||
size_t len = (std::min)(name.size(), sizeof(m_name) - 1);
|
||||
std::memcpy(m_name, name.data(), len);
|
||||
m_name[len] = '\0';
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s", m_name);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s", defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d]", m_name, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]", defaultName, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d,%d]", m_name, index, index2);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s###Name%s", m_name, defaultName);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d]###Name%d", m_name, index, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name, index, index2,
|
||||
index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
|
||||
index);
|
||||
}
|
||||
}
|
||||
|
||||
bool NameInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name != "name") {
|
||||
return false;
|
||||
}
|
||||
size_t len = (std::min)(value.size(), sizeof(m_name) - 1);
|
||||
std::memcpy(m_name, value.data(), len);
|
||||
m_name[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void NameInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf("name=%s\n", m_name);
|
||||
}
|
||||
|
||||
void NameInfo::PushEditNameId(int index) {
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
void NameInfo::PushEditNameId(const char* name) {
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
bool NameInfo::PopupEditName(int index) {
|
||||
bool rv = false;
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameInfo::PopupEditName(const char* name) {
|
||||
bool rv = false;
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameInfo::InputTextName(const char* label_id, ImGuiInputTextFlags flags) {
|
||||
return ImGui::InputText(label_id, m_name, sizeof(m_name), flags);
|
||||
}
|
||||
|
||||
bool OpenInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name != "open") {
|
||||
return false;
|
||||
}
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_open = num.value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf("open=%d\n", m_open ? 1 : 0);
|
||||
}
|
||||
|
||||
bool NameOpenInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (NameInfo::ReadIni(name, value)) {
|
||||
return true;
|
||||
}
|
||||
if (OpenInfo::ReadIni(name, value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NameOpenInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
NameInfo::WriteIni(out);
|
||||
OpenInfo::WriteIni(out);
|
||||
}
|
||||
123
glass/src/lib/native/cpp/support/NameSetting.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size,
|
||||
const char* defaultName) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s", m_name.c_str());
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s", defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d]", m_name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]", defaultName, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d,%d]", m_name.c_str(), index, index2);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size,
|
||||
const char* defaultName) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s###Name%s", m_name.c_str(), defaultName);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d]###Name%d", m_name.c_str(), index, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name.c_str(), index,
|
||||
index2, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
|
||||
index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::PushEditNameId(int index) {
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
void NameSetting::PushEditNameId(const char* name) {
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
bool NameSetting::PopupEditName(int index) {
|
||||
bool rv = false;
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameSetting::PopupEditName(const char* name) {
|
||||
bool rv = false;
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameSetting::InputTextName(const char* label_id,
|
||||
ImGuiInputTextFlags flags) {
|
||||
return ImGui::InputText(label_id, &m_name, flags);
|
||||
}
|
||||
@@ -4,17 +4,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
struct Context;
|
||||
class Context;
|
||||
class Storage;
|
||||
|
||||
Context* CreateContext();
|
||||
void DestroyContext(Context* ctx = nullptr);
|
||||
@@ -32,86 +31,135 @@ void ResetTime();
|
||||
uint64_t GetZeroTime();
|
||||
|
||||
/**
|
||||
* Storage provides both persistent and non-persistent key/value storage for
|
||||
* widgets.
|
||||
*
|
||||
* Keys are always strings. The storage also provides non-persistent arbitrary
|
||||
* data storage (via std::shared_ptr<void>).
|
||||
*
|
||||
* Storage is automatically indexed internally by the ID stack. Note it is
|
||||
* necessary to use the glass wrappers for PushID et al to preserve naming in
|
||||
* the save file (unnamed values are still stored, but this is non-ideal for
|
||||
* users trying to hand-edit the save file).
|
||||
* Resets the workspace (all storage except window storage).
|
||||
* Operates effectively like calling LoadStorage() on a path with no existing
|
||||
* storage files. Note this will result in auto-saving of the reset state to
|
||||
* storage.
|
||||
*/
|
||||
class Storage {
|
||||
public:
|
||||
struct Value {
|
||||
Value() = default;
|
||||
explicit Value(std::string_view str) : stringVal{str} {}
|
||||
void WorkspaceReset();
|
||||
|
||||
enum Type { kNone, kInt, kInt64, kBool, kFloat, kDouble, kString };
|
||||
Type type = kNone;
|
||||
union {
|
||||
int intVal;
|
||||
int64_t int64Val;
|
||||
bool boolVal;
|
||||
float floatVal;
|
||||
double doubleVal;
|
||||
};
|
||||
std::string stringVal;
|
||||
};
|
||||
/**
|
||||
* Adds function to be called during workspace (storage) initialization/load.
|
||||
* This should set up any initial default state, restore stored
|
||||
* settings/windows, etc. This will be called after the storage is initialized.
|
||||
* This must be called prior to WorkspaceInit() for proper automatic startup
|
||||
* loading.
|
||||
*
|
||||
* @param init initialization function
|
||||
*/
|
||||
void AddWorkspaceInit(std::function<void()> init);
|
||||
|
||||
int GetInt(std::string_view key, int defaultVal = 0) const;
|
||||
int64_t GetInt64(std::string_view key, int64_t defaultVal = 0) const;
|
||||
bool GetBool(std::string_view key, bool defaultVal = false) const;
|
||||
float GetFloat(std::string_view key, float defaultVal = 0.0f) const;
|
||||
double GetDouble(std::string_view key, double defaultVal = 0.0) const;
|
||||
std::string GetString(std::string_view key,
|
||||
std::string_view defaultVal = {}) const;
|
||||
/**
|
||||
* Adds function to be called during workspace (storage) reset. This should
|
||||
* bring back the state to startup state (e.g. remove any storage references,
|
||||
* destroy windows, etc). This will be called prior to the storage being
|
||||
* destroyed.
|
||||
*
|
||||
* @param reset reset function
|
||||
*/
|
||||
void AddWorkspaceReset(std::function<void()> reset);
|
||||
|
||||
void SetInt(std::string_view key, int val);
|
||||
void SetInt64(std::string_view key, int64_t val);
|
||||
void SetBool(std::string_view key, bool val);
|
||||
void SetFloat(std::string_view key, float val);
|
||||
void SetDouble(std::string_view key, double val);
|
||||
void SetString(std::string_view key, std::string_view val);
|
||||
/**
|
||||
* Sets storage load and auto-save name.
|
||||
* Call this prior to calling wpi::gui::Initialize() for automatic startup
|
||||
* loading.
|
||||
*
|
||||
* @param name base name, suffix will be generated
|
||||
*/
|
||||
void SetStorageName(std::string_view name);
|
||||
|
||||
int* GetIntRef(std::string_view key, int defaultVal = 0);
|
||||
int64_t* GetInt64Ref(std::string_view key, int64_t defaultVal = 0);
|
||||
bool* GetBoolRef(std::string_view key, bool defaultVal = false);
|
||||
float* GetFloatRef(std::string_view key, float defaultVal = 0.0f);
|
||||
double* GetDoubleRef(std::string_view key, double defaultVal = 0.0);
|
||||
std::string* GetStringRef(std::string_view key,
|
||||
std::string_view defaultVal = {});
|
||||
/**
|
||||
* Sets storage load and auto-save directory. For more customized behavior, set
|
||||
* Context::storageLoadPath and Context::storageAutoSavePath directly.
|
||||
* Call this prior to calling wpi::gui::Initialize() for automatic startup
|
||||
* loading.
|
||||
*
|
||||
* @param dir path to directory
|
||||
*/
|
||||
void SetStorageDir(std::string_view dir);
|
||||
|
||||
Value& GetValue(std::string_view key);
|
||||
/**
|
||||
* Gets storage auto-save directory.
|
||||
*
|
||||
* @return Path to directory
|
||||
*/
|
||||
std::string GetStorageDir();
|
||||
|
||||
void SetData(std::shared_ptr<void>&& data) { m_data = std::move(data); }
|
||||
/**
|
||||
* Explicitly load storage. Set Context::storageLoadDir prior to calling
|
||||
* wpi::gui::Initialize() for automatic startup loading.
|
||||
*
|
||||
* Non-empty root names are not loaded unless GetStorageRoot() is called during
|
||||
* initialization (or before this function is called).
|
||||
*
|
||||
* @param dir path to directory
|
||||
*/
|
||||
bool LoadStorage(std::string_view dir);
|
||||
|
||||
template <typename T>
|
||||
T* GetData() const {
|
||||
return static_cast<T*>(m_data.get());
|
||||
}
|
||||
/**
|
||||
* Save storage to automatic on-change save location.
|
||||
*/
|
||||
bool SaveStorage();
|
||||
|
||||
Storage() = default;
|
||||
Storage(const Storage&) = delete;
|
||||
Storage& operator=(const Storage&) = delete;
|
||||
/**
|
||||
* Explicitly save storage. Set Context::storageAutoSaveDir prior to calling
|
||||
* wpi::gui::Initialize() for automatic on-change saving.
|
||||
*
|
||||
* @param dir path to directory
|
||||
*/
|
||||
bool SaveStorage(std::string_view dir);
|
||||
|
||||
std::vector<std::string>& GetKeys() { return m_keys; }
|
||||
const std::vector<std::string>& GetKeys() const { return m_keys; }
|
||||
std::vector<std::unique_ptr<Value>>& GetValues() { return m_values; }
|
||||
const std::vector<std::unique_ptr<Value>>& GetValues() const {
|
||||
return m_values;
|
||||
}
|
||||
/**
|
||||
* Gets the storage root for the current ID stack (e.g. the last call to
|
||||
* ResetStorageStack).
|
||||
*
|
||||
* @return Storage object
|
||||
*/
|
||||
Storage& GetCurStorageRoot();
|
||||
|
||||
private:
|
||||
mutable std::vector<std::string> m_keys;
|
||||
mutable std::vector<std::unique_ptr<Value>> m_values;
|
||||
std::shared_ptr<void> m_data;
|
||||
};
|
||||
/**
|
||||
* Gets an arbitrary storage root.
|
||||
*
|
||||
* Non-empty root names are saved but not loaded unless GetStorageRoot()
|
||||
* is called during initialization (or before LoadStorage is called).
|
||||
*
|
||||
* @param rootName root name
|
||||
* @return Storage object
|
||||
*/
|
||||
Storage& GetStorageRoot(std::string_view rootName = {});
|
||||
|
||||
/**
|
||||
* Resets storage stack. Should only be called at top level.
|
||||
*
|
||||
* @param rootName root name
|
||||
*/
|
||||
void ResetStorageStack(std::string_view rootName = {});
|
||||
|
||||
/**
|
||||
* Gets the storage object for the current point in the ID stack.
|
||||
*
|
||||
* @return Storage object
|
||||
*/
|
||||
Storage& GetStorage();
|
||||
Storage& GetStorage(std::string_view id);
|
||||
|
||||
/**
|
||||
* Pushes label/ID onto the storage stack, without pushing the imgui ID stack.
|
||||
*
|
||||
* @param label_id label or label###id
|
||||
*/
|
||||
void PushStorageStack(std::string_view label_id);
|
||||
|
||||
/**
|
||||
* Pushes specific storage onto the storage stack.
|
||||
*
|
||||
* @param storage storage
|
||||
*/
|
||||
void PushStorageStack(Storage& storage);
|
||||
|
||||
/**
|
||||
* Pops storage stack, without popping the imgui ID stack.
|
||||
*/
|
||||
void PopStorageStack();
|
||||
|
||||
bool Begin(const char* name, bool* p_open = nullptr,
|
||||
ImGuiWindowFlags flags = 0);
|
||||
|
||||