mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Compare commits
419 Commits
v2020.2.2
...
v2021.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b27d33675d | ||
|
|
00b9ae77f9 | ||
|
|
65219f3093 | ||
|
|
f78d1d4340 | ||
|
|
941edca597 | ||
|
|
a699435ede | ||
|
|
66d6417189 | ||
|
|
558e37c412 | ||
|
|
4f40d991ea | ||
|
|
549af99007 | ||
|
|
b336930093 | ||
|
|
c9a0edfb8b | ||
|
|
2c5668af46 | ||
|
|
751dea32ae | ||
|
|
cd8f4bfb1f | ||
|
|
a6cfcc6866 | ||
|
|
b8c4f603db | ||
|
|
0075e4b391 | ||
|
|
125af556ce | ||
|
|
963ad5c255 | ||
|
|
387f56cb7b | ||
|
|
b3deda38c9 | ||
|
|
2a5ca77454 | ||
|
|
727940d847 | ||
|
|
8cd42478e1 | ||
|
|
c11d34b26c | ||
|
|
339d7445b3 | ||
|
|
d16f05f2c8 | ||
|
|
5427b32a40 | ||
|
|
f73701239d | ||
|
|
f5a6fc0703 | ||
|
|
bdf5ba91a4 | ||
|
|
bc8f338771 | ||
|
|
3413bfc06a | ||
|
|
2056f0ce09 | ||
|
|
5eb8cfd691 | ||
|
|
e6a4254488 | ||
|
|
d478ad00d0 | ||
|
|
53eda861de | ||
|
|
cc1d86ba63 | ||
|
|
f0528f00e7 | ||
|
|
5cd2ad124d | ||
|
|
6c00e7a902 | ||
|
|
53170bbb58 | ||
|
|
467258e050 | ||
|
|
129be23c9e | ||
|
|
8e9290e86e | ||
|
|
7cf5bebf8e | ||
|
|
f7f9087fb5 | ||
|
|
256e7904fd | ||
|
|
c8ea1b6c38 | ||
|
|
2816b06c05 | ||
|
|
4c695ea088 | ||
|
|
a14d51806d | ||
|
|
0170977914 | ||
|
|
f61726b5ae | ||
|
|
fc27fdac57 | ||
|
|
47c59859ee | ||
|
|
6e76ab9c09 | ||
|
|
5f78b76702 | ||
|
|
5e0808c848 | ||
|
|
508f05a47e | ||
|
|
66b57f0323 | ||
|
|
cfac22b4c0 | ||
|
|
2ef67f20a7 | ||
|
|
7a73946ce1 | ||
|
|
6d22b5a3c6 | ||
|
|
50050a0e53 | ||
|
|
de17422793 | ||
|
|
6b5e83ce1d | ||
|
|
17d75d8a3b | ||
|
|
616405f7ae | ||
|
|
5c2dc043cd | ||
|
|
24a3c12f31 | ||
|
|
3e544282ff | ||
|
|
3c85a40648 | ||
|
|
ac3c336b98 | ||
|
|
f24f282442 | ||
|
|
0dfee4745c | ||
|
|
eb80f7a787 | ||
|
|
68fed2a1a6 | ||
|
|
10d118a8d0 | ||
|
|
e021c33191 | ||
|
|
7b7548196a | ||
|
|
e019c735e1 | ||
|
|
c253f2c7e2 | ||
|
|
0ce9133b55 | ||
|
|
6ac9683a32 | ||
|
|
1d7739d8da | ||
|
|
1de2a6d85c | ||
|
|
0a723a50dc | ||
|
|
34b91318f4 | ||
|
|
b11a7114a5 | ||
|
|
2ca5e1c8d6 | ||
|
|
7ae8c7b247 | ||
|
|
1e17e40868 | ||
|
|
1069019fd2 | ||
|
|
4422904a2e | ||
|
|
5d085b78bd | ||
|
|
0927c73b13 | ||
|
|
5fe8f9017f | ||
|
|
5cdffeaba1 | ||
|
|
6e7c7374fd | ||
|
|
fb7b41793b | ||
|
|
abbf9f01ab | ||
|
|
17698af5e3 | ||
|
|
b66fcdb3f7 | ||
|
|
7fc48b75dd | ||
|
|
07ac5370d8 | ||
|
|
7c8f1cf7af | ||
|
|
57a97e3fb3 | ||
|
|
061432147d | ||
|
|
8f3e5794b3 | ||
|
|
a112b5e231 | ||
|
|
67859aea44 | ||
|
|
37643ab0b2 | ||
|
|
b0ee11f7cc | ||
|
|
7647e29b21 | ||
|
|
a3e672f863 | ||
|
|
9058fe803d | ||
|
|
32f429a819 | ||
|
|
bf26656547 | ||
|
|
96e26247d7 | ||
|
|
8e538aa82f | ||
|
|
fa809b2c4b | ||
|
|
9a63cd36cd | ||
|
|
21d949daa8 | ||
|
|
330b90e046 | ||
|
|
693daafe29 | ||
|
|
c3b3fb8b74 | ||
|
|
62731bea20 | ||
|
|
c55fb583b8 | ||
|
|
9725aff83b | ||
|
|
1320691eb4 | ||
|
|
451f67c63d | ||
|
|
43b1b128b1 | ||
|
|
fc991cb59c | ||
|
|
17d3d2f754 | ||
|
|
73950b9857 | ||
|
|
61ee331f11 | ||
|
|
651319589c | ||
|
|
5479948bd4 | ||
|
|
1ec145ec87 | ||
|
|
8ab47cb075 | ||
|
|
b7b3dcf3ea | ||
|
|
f7da0b4525 | ||
|
|
f9a3380184 | ||
|
|
b61f08d3fa | ||
|
|
0503225928 | ||
|
|
d4d0b5501b | ||
|
|
6c5726c96f | ||
|
|
56972447b2 | ||
|
|
67e1796ef6 | ||
|
|
1ae8da3b3f | ||
|
|
3ed97f45fb | ||
|
|
ae2809cad4 | ||
|
|
b0a296477e | ||
|
|
629a4574db | ||
|
|
4be809499d | ||
|
|
b10063da9a | ||
|
|
5e54e13140 | ||
|
|
8f87c56312 | ||
|
|
1b18560e90 | ||
|
|
f86a5f9b09 | ||
|
|
f1b1bdb121 | ||
|
|
0365557b25 | ||
|
|
883d962838 | ||
|
|
35790a8990 | ||
|
|
8edc17dac9 | ||
|
|
502f5c8b5f | ||
|
|
bc28fb187c | ||
|
|
de0277713b | ||
|
|
1593eb4d47 | ||
|
|
70ba92f917 | ||
|
|
47cc71ea01 | ||
|
|
9453d67273 | ||
|
|
f758af826d | ||
|
|
6a1e5385e5 | ||
|
|
63487dca76 | ||
|
|
7d6f09f5c7 | ||
|
|
148eed3cdc | ||
|
|
05701317b4 | ||
|
|
7548fdae5d | ||
|
|
3e41d92c18 | ||
|
|
ad6c8b882e | ||
|
|
947ff655c5 | ||
|
|
183b7c85a1 | ||
|
|
4cf6947af7 | ||
|
|
b83709b269 | ||
|
|
c699d55175 | ||
|
|
781afaa852 | ||
|
|
007b03a2c2 | ||
|
|
ed18693345 | ||
|
|
7c99224bb7 | ||
|
|
c2c4090902 | ||
|
|
416288061a | ||
|
|
83376bc231 | ||
|
|
c0de98f9f2 | ||
|
|
70db0db221 | ||
|
|
526f26685d | ||
|
|
5d1220e629 | ||
|
|
b80fde4388 | ||
|
|
148f43b4a5 | ||
|
|
0d88213de5 | ||
|
|
36b8d74faa | ||
|
|
5021f28159 | ||
|
|
ee6a814576 | ||
|
|
807de9a0a9 | ||
|
|
9398b6b55b | ||
|
|
932bfcf374 | ||
|
|
e127bac7fd | ||
|
|
bccf13bf67 | ||
|
|
950bbd6dc2 | ||
|
|
e680ba85fa | ||
|
|
b23ede7d50 | ||
|
|
73047d8b35 | ||
|
|
ef5e0c2e75 | ||
|
|
dc9e560f9b | ||
|
|
ae5b07ba01 | ||
|
|
3384c23a56 | ||
|
|
c2259d42a8 | ||
|
|
370e9d089f | ||
|
|
cab8b18c68 | ||
|
|
3b283ab9aa | ||
|
|
e5b84e2f87 | ||
|
|
f86417d791 | ||
|
|
8dc3d23831 | ||
|
|
42993b15c6 | ||
|
|
ad817d4f23 | ||
|
|
77954bb3dd | ||
|
|
52f7a62e1e | ||
|
|
1b8ceb36fc | ||
|
|
ceea1f9d44 | ||
|
|
2f81f2b78a | ||
|
|
aba035eb3d | ||
|
|
5ca2702083 | ||
|
|
af588adce5 | ||
|
|
399684a58f | ||
|
|
5cf4c16f5b | ||
|
|
0fe2319dfc | ||
|
|
e759dad019 | ||
|
|
0c18abed33 | ||
|
|
89dad2fd84 | ||
|
|
a6a71f8c76 | ||
|
|
1fee190fd0 | ||
|
|
23ba3ca19e | ||
|
|
33d8363297 | ||
|
|
49dcf7cf59 | ||
|
|
9778445a74 | ||
|
|
c2cc90b27d | ||
|
|
b9feb81226 | ||
|
|
b06ddcdd86 | ||
|
|
16ef372b53 | ||
|
|
f0a34ea64e | ||
|
|
33f7dec5cf | ||
|
|
3005803598 | ||
|
|
eeaaf5d258 | ||
|
|
15819cc981 | ||
|
|
fe6bfb1ba2 | ||
|
|
5c8d7b2423 | ||
|
|
ff0801d783 | ||
|
|
ac4b0a3118 | ||
|
|
227084e92e | ||
|
|
a175f6e862 | ||
|
|
4043c461d7 | ||
|
|
b3b2aa6359 | ||
|
|
5bd2dca463 | ||
|
|
21740fd2c5 | ||
|
|
c11ef442fb | ||
|
|
b5a38001dd | ||
|
|
5b6f68cf72 | ||
|
|
3050e935a1 | ||
|
|
80a1fa9ece | ||
|
|
1851ba1434 | ||
|
|
c4b8a2505d | ||
|
|
a1d2d40ad3 | ||
|
|
a3881bb452 | ||
|
|
6e4ee8da2b | ||
|
|
2a0f79b90f | ||
|
|
5ccc98bc14 | ||
|
|
b1353e4d6e | ||
|
|
9f4de91554 | ||
|
|
d30d1088da | ||
|
|
c6e6346642 | ||
|
|
e08c8a1fc9 | ||
|
|
e50dbe0c43 | ||
|
|
d9c7bbd046 | ||
|
|
dac0e5b133 | ||
|
|
ce3bc91946 | ||
|
|
22c0e2813a | ||
|
|
9796987d59 | ||
|
|
4d275e4767 | ||
|
|
492d6c2826 | ||
|
|
765f009350 | ||
|
|
629e8b41f2 | ||
|
|
71c187a1ed | ||
|
|
00e991e2a0 | ||
|
|
8a80f97c06 | ||
|
|
3cbec411c7 | ||
|
|
3bcb89fb84 | ||
|
|
62b8a36ce9 | ||
|
|
27566abb06 | ||
|
|
dfb130270a | ||
|
|
1ba616f843 | ||
|
|
11fb0a4cb7 | ||
|
|
1557a4c3b0 | ||
|
|
ab28a7d65f | ||
|
|
d58a5e124a | ||
|
|
762347f005 | ||
|
|
4b76adf15b | ||
|
|
6be1b95241 | ||
|
|
38ad790612 | ||
|
|
86acb27e24 | ||
|
|
1b395fa21d | ||
|
|
92380485c8 | ||
|
|
27f9a21a2c | ||
|
|
cf7088a462 | ||
|
|
67554ef3b0 | ||
|
|
cf20b068ca | ||
|
|
0b9316d94a | ||
|
|
3011ebe547 | ||
|
|
4b77b0773e | ||
|
|
e5935a4737 | ||
|
|
a3a8472b82 | ||
|
|
212182d991 | ||
|
|
c82b8546bc | ||
|
|
fac4e3fcfc | ||
|
|
5b0122fed4 | ||
|
|
b46b5df16a | ||
|
|
cb51029335 | ||
|
|
e504b3ecbd | ||
|
|
0ad0ec0985 | ||
|
|
d1d32ada00 | ||
|
|
8058daa982 | ||
|
|
f4c5c0f5b5 | ||
|
|
2ecb8dab7d | ||
|
|
576d427f03 | ||
|
|
21aafea098 | ||
|
|
3ed2908563 | ||
|
|
2b188b54d8 | ||
|
|
306720e63e | ||
|
|
a308dd4471 | ||
|
|
8f33d21bc2 | ||
|
|
b9ee3ae030 | ||
|
|
c14b87b228 | ||
|
|
8a279aaf20 | ||
|
|
3a5e541b2d | ||
|
|
43574128b3 | ||
|
|
e4a9903844 | ||
|
|
0d30108dcb | ||
|
|
b7a79c70cc | ||
|
|
6e6f28d1ac | ||
|
|
d14978e549 | ||
|
|
1c28b729ad | ||
|
|
303194b08b | ||
|
|
2ee3bfaa25 | ||
|
|
029a94dd33 | ||
|
|
f6df9217b6 | ||
|
|
184fae4ba5 | ||
|
|
35b236651e | ||
|
|
c926770550 | ||
|
|
272eaf184f | ||
|
|
56fbb1fc33 | ||
|
|
33f6bf947e | ||
|
|
07326edb6b | ||
|
|
144610151c | ||
|
|
4228d3609e | ||
|
|
510936a2a0 | ||
|
|
5854e284ea | ||
|
|
a732606e55 | ||
|
|
84e300739c | ||
|
|
8edf9282c3 | ||
|
|
5a1acaaefc | ||
|
|
4fd7c210d8 | ||
|
|
a26a7d217d | ||
|
|
7b7f44d937 | ||
|
|
6cf89aa0f3 | ||
|
|
3be83784cd | ||
|
|
b6c163acd7 | ||
|
|
05a26b7279 | ||
|
|
35eb90c135 | ||
|
|
761f79385a | ||
|
|
554bda3332 | ||
|
|
2a968df779 | ||
|
|
30ccd13b69 | ||
|
|
60c09ea51f | ||
|
|
65eab93527 | ||
|
|
a226ad8509 | ||
|
|
31f4fd70ce | ||
|
|
7275ab9837 | ||
|
|
5b3facc63b | ||
|
|
0f313fb9ab | ||
|
|
05b7593e66 | ||
|
|
1b85066d26 | ||
|
|
e93b64f58d | ||
|
|
f0a18f31e7 | ||
|
|
29c82527a5 | ||
|
|
c165dc5e50 | ||
|
|
42da07396c | ||
|
|
20e6c04059 | ||
|
|
ff5d3e5b36 | ||
|
|
6cc68ab503 | ||
|
|
068465146b | ||
|
|
3bcf8057d4 | ||
|
|
8039a6c525 | ||
|
|
558c020cca | ||
|
|
7797da78f5 | ||
|
|
0ab81d768f | ||
|
|
1cee5ccb93 | ||
|
|
3ce01b5ac2 | ||
|
|
e6aa8f3ff4 | ||
|
|
9d7b087972 | ||
|
|
bb184ed481 | ||
|
|
b9b31069cc | ||
|
|
d0cf4e8882 | ||
|
|
02fb850761 | ||
|
|
ac8177e10d | ||
|
|
2eb5c54476 | ||
|
|
0e206e69cf |
@@ -3,24 +3,29 @@ Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
@@ -29,6 +34,7 @@ BraceWrapping:
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
@@ -38,6 +44,7 @@ BraceWrapping:
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
@@ -50,6 +57,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
@@ -58,15 +66,25 @@ ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: true
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
@@ -76,32 +94,73 @@ MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
38
.github/CODEOWNERS
vendored
Normal file
38
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
# More details are here: https://help.github.com/articles/about-codeowners/
|
||||
|
||||
# The '*' pattern is global owners.
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
|
||||
# The folders are ordered as a tree: by depth then alphabetically.
|
||||
# This should make it easy to add new rules without breaking existing ones.
|
||||
|
||||
# Global rule:
|
||||
* @wpilibsuite/wpilib
|
||||
|
||||
/cameraserver/ @wpilibsuite/camera-server
|
||||
|
||||
/cscore/ @wpilibsuite/camera-server
|
||||
|
||||
/hal/ @wpilibsuite/hardware
|
||||
/hal/src/main/java/**/sim/ @wpilibsuite/simulation
|
||||
/hal/src/main/native/include/mockdata/ @wpilibsuite/simulation
|
||||
/hal/src/main/native/include/simulation/ @wpilibsuite/simulation
|
||||
/hal/src/main/native/sim/ @wpilibsuite/simulation
|
||||
|
||||
/ntcore/ @wpilibsuite/network-tables
|
||||
|
||||
/simulation/ @wpilibsuite/simulation
|
||||
|
||||
/wpilibNewCommands/ @wpilibsuite/commandbased
|
||||
|
||||
/wpilibOldCommands/ @wpilibsuite/commandbased
|
||||
|
||||
/wpilibcExamples/ @wpilibsuite/wpilib @wpilibsuite/documentation
|
||||
|
||||
/wpilibjExamples/ @wpilibsuite/wpilib @wpilibsuite/documentation
|
||||
|
||||
/wpiutil/ @PeterJohnson
|
||||
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Java version [e.g. 1.10.2]
|
||||
- C++ version [e.g. 17]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
20
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask about features or parts of this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the question you have.**
|
||||
A clear and concise description of what you want clarification on.
|
||||
|
||||
**Describe the reason for your confusion.**
|
||||
A clear and concise description of why the question requires clarification and what's confusing.
|
||||
|
||||
**Is your question related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the question here.
|
||||
238
.github/workflows/ci.yml
vendored
Normal file
238
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
name: CI
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
build-docker:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2021-18.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:10-18.04
|
||||
artifact-name: Raspbian
|
||||
build-options: "-Ponlylinuxraspbian"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bionic-18.04
|
||||
artifact-name: Aarch64
|
||||
build-options: "-Ponlylinuxaarch64bionic"
|
||||
- container: wpilib/ubuntu-base:18.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Dorg.gradle.jvmargs=-Xmx2g"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -PbuildServer ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
|
||||
build-host:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
artifact-name: Win64
|
||||
architecture: x64
|
||||
- os: windows-latest
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
- os: macos-latest
|
||||
artifact-name: macOS
|
||||
architecture: x64
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Import Developer ID Certificate
|
||||
uses: wpilibsuite/import-signing-certificate@v1
|
||||
with:
|
||||
certificate-data: ${{ secrets.APPLE_CERTIFICATE_DATA }}
|
||||
certificate-passphrase: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- name: Set Keychain Lock Timeout
|
||||
run: security set-keychain-settings -lut 3600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Sign Libraries with Developer ID
|
||||
run: ./gradlew build -PbuildServer -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
|
||||
build-documentation:
|
||||
name: "Build - Documentation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 13
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Documentation
|
||||
path: docs/build/outputs
|
||||
|
||||
build-cmake:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2020-18.04
|
||||
flags: ""
|
||||
- os: macos-latest
|
||||
name: macOS
|
||||
container: ""
|
||||
flags: "-DWITH_JAVA=OFF"
|
||||
name: "Build - CMake ${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "macOS" ]; then
|
||||
brew install opencv
|
||||
fi
|
||||
- name: configure
|
||||
run: mkdir build && cd build && cmake ${{ matrix.flags }} ..
|
||||
- name: build
|
||||
working-directory: build
|
||||
run: make -j3
|
||||
- name: test
|
||||
working-directory: build
|
||||
run: make test
|
||||
|
||||
build-cmake-vcpkg:
|
||||
name: "Build - CMake Windows"
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Prepare vcpkg
|
||||
uses: lukka/run-vcpkg@v4
|
||||
with:
|
||||
vcpkgArguments: opencv
|
||||
vcpkgDirectory: ${{ runner.workspace }}/vcpkg/
|
||||
vcpkgGitCommitId: 544f8e4593764f78faa94bac2adb81cca5232943
|
||||
vcpkgTriplet: x64-windows
|
||||
- name: Configure & Build
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
buildDirectory: ${{ runner.workspace }}/build/
|
||||
cmakeAppendedArgs: -DWITH_JAVA=OFF
|
||||
cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
|
||||
useVcpkgToolchainFile: true
|
||||
- name: Run Tests
|
||||
run: ctest -C "Debug"
|
||||
working-directory: ${{ runner.workspace }}/build/
|
||||
|
||||
wpiformat:
|
||||
name: "wpiformat"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch all history and metadata
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f master origin/master
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install clang-format
|
||||
run: sudo apt-get update -q && sudo apt-get install clang-format-10
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run
|
||||
run: wpiformat -clang 10
|
||||
- name: Check Output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
|
||||
combine:
|
||||
name: Combine
|
||||
needs: [build-docker, build-host, build-documentation]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
|
||||
- name: Check version number exists
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Combine
|
||||
if: |
|
||||
!startsWith(github.ref, 'refs/tags/v') &&
|
||||
github.ref != 'refs/heads/master'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib
|
||||
- name: Combine (Master)
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
github.ref == 'refs/heads/master'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- name: Combine (Release)
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
|
||||
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -222,3 +222,5 @@ compile_commands.json
|
||||
# clang configuration and clangd cache
|
||||
.clang
|
||||
.clangd/
|
||||
|
||||
imgui.ini
|
||||
|
||||
@@ -2,6 +2,7 @@ cppHeaderFileInclude {
|
||||
\.h$
|
||||
\.hpp$
|
||||
\.inc$
|
||||
\.inl$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
@@ -18,16 +19,22 @@ repoRootNameOverride {
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
^Eigen/
|
||||
^cameraserver/
|
||||
^cscore
|
||||
^drake/
|
||||
^hal/
|
||||
^imgui
|
||||
^implot
|
||||
^mockdata/
|
||||
^networktables/
|
||||
^ntcore
|
||||
^opencv2/
|
||||
^support/
|
||||
^units/
|
||||
^unsupported/
|
||||
^vision/
|
||||
^wpi/
|
||||
^wpigui
|
||||
^wpimath/
|
||||
}
|
||||
|
||||
139
CMakeLists.txt
139
CMakeLists.txt
@@ -1,5 +1,5 @@
|
||||
# Disable in-source builds to prevent source tree corruption.
|
||||
if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")
|
||||
if(" ${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL " ${CMAKE_CURRENT_BINARY_DIR}")
|
||||
message(FATAL_ERROR "
|
||||
FATAL: In-source builds are not allowed.
|
||||
You should create a separate directory for build files.
|
||||
@@ -9,21 +9,19 @@ endif()
|
||||
|
||||
project(allwpilib)
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules")
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
|
||||
set(WPILIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
INCLUDE(CPack)
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND MSVC)
|
||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Default install dir on windows" FORCE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${CMAKE_BINARY_DIR}/jar)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${WPILIB_BINARY_DIR}/bin)
|
||||
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${WPILIB_BINARY_DIR}/jar)
|
||||
|
||||
# use, i.e. don't skip the full RPATH for the build tree
|
||||
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
@@ -44,23 +42,86 @@ IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
option(WITHOUT_JAVA "don't include java and JNI in the build" OFF)
|
||||
option(BUILD_SHARED_LIBS "build with shared libs (needed for JNI)" ON)
|
||||
option(WITHOUT_CSCORE "Don't build cscore (removes OpenCV requirement)" OFF)
|
||||
option(WITHOUT_ALLWPILIB "Don't build allwpilib (removes OpenCV requirement)" ON)
|
||||
option(WITH_TESTS "build unit tests (requires internet connection)" OFF)
|
||||
option(USE_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
# Options for building certain parts of the repo. Everything is built by default.
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
option(WITH_JAVA "Include java and JNI in the build" ON)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
|
||||
option(WITH_OLD_COMMANDS "Build old commands" OFF)
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
option(WITH_TESTS "Build unit tests (requires internet connection)" ON)
|
||||
option(WITH_GUI "Build GUI items" ON)
|
||||
option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
|
||||
|
||||
# Options for external HAL.
|
||||
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
# Options for using a package manager (vcpkg) for certain dependencies.
|
||||
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
|
||||
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
|
||||
option(FLAT_INSTALL_WPILIB "Use a flat install directory" OFF)
|
||||
option(WITH_SIMULATION_MODULES "build simulation modules" OFF)
|
||||
|
||||
if (NOT WITHOUT_JAVA AND NOT BUILD_SHARED_LIBS)
|
||||
# Options for installation.
|
||||
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
|
||||
|
||||
# Options for location of OpenCV Java.
|
||||
set(OPENCV_JAVA_INSTALL_DIR "" CACHE PATH "Location to search for the OpenCV jar file")
|
||||
|
||||
# Set default build type to release with debug info (i.e. release mode optimizations
|
||||
# are performed, but debug info still exists).
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set (CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# We always want flat install with MSVC.
|
||||
if (MSVC)
|
||||
set(WITH_FLAT_INSTALL ON)
|
||||
endif()
|
||||
|
||||
if (WITH_JAVA AND NOT BUILD_SHARED_LIBS)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build static libs with Java enabled.
|
||||
Static libs requires both BUILD_SHARED_LIBS=OFF and
|
||||
WITHOUT_JAVA=ON
|
||||
WITH_JAVA=OFF
|
||||
")
|
||||
endif()
|
||||
|
||||
if (WITH_SIMULATION_MODULES AND NOT BUILD_SHARED_LIBS)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build static libs with simulation modules enabled.
|
||||
Static libs requires both BUILD_SHARED_LIBS=OFF and
|
||||
WITH_SIMULATION_MODULES=OFF
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_JAVA OR NOT WITH_CSCORE)
|
||||
if(NOT "${OPENCV_JAVA_INSTALL_DIR}" STREQUAL "")
|
||||
message(WARNING "
|
||||
WARNING: OpenCV Java dir set but java is not enabled!
|
||||
It will be ignored.
|
||||
")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPILIB AND WITH_SIMULATION_MODULES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build simulation modules with wpilib disabled.
|
||||
Enable wpilib by setting WITH_WPILIB=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPIMATH AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without wpimath.
|
||||
Enable wpimath by setting WITH_WPIMATH=ON
|
||||
")
|
||||
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()
|
||||
|
||||
@@ -70,7 +131,7 @@ set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
|
||||
if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
@@ -84,20 +145,26 @@ if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set(WPIUTIL_DEP_REPLACE "include($\{SELF_DIR\}/wpiutil-config.cmake)")
|
||||
set(NTCORE_DEP_REPLACE "include($\{SELF_DIR\}/ntcore-config.cmake)")
|
||||
set(CSCORE_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cscore-config.cmake)")
|
||||
set(CAMERASERVER_DEP_REPLACE_IMPL "include(\${SELF_DIR}/cameraserver-config.cmake)")
|
||||
set(HAL_DEP_REPLACE_IMPL "include(\${SELF_DIR}/hal-config.cmake)")
|
||||
set(WPIMATH_DEP_REPLACE "include($\{SELF_DIR\}/wpimath-config.cmake)")
|
||||
set(WPILIBC_DEP_REPLACE_IMPL "include(\${SELF_DIR}/wpilibc-config.cmake)")
|
||||
set(WPILIBNEWCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibNewcommands-config.cmake)")
|
||||
set(WPILIBOLDCOMMANDS_DEP_REPLACE "include(\${SELF_DIR}/wpilibOldcommands-config.cmake)")
|
||||
else()
|
||||
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
|
||||
set(NTCORE_DEP_REPLACE "find_dependency(ntcore)")
|
||||
set(CSCORE_DEP_REPLACE_IMPL "find_dependency(cscore)")
|
||||
set(CAMERASERVER_DEP_REPLACE_IMPL "find_dependency(cameraserver)")
|
||||
set(HAL_DEP_REPLACE_IMPL "find_dependency(hal)")
|
||||
set(WPIMATH_DEP_REPLACE "find_dependency(wpimath)")
|
||||
set(WPILIBC_DEP_REPLACE_IMPL "find_dependency(wpilibc)")
|
||||
set(WPILIBNEWCOMMANDS_DEP_REPLACE "find_dependency(wpilibNewCommands)")
|
||||
set(WPILIBOLDCOMMANDS_DEP_REPLACE "find_dependency(wpilibOldCommands)")
|
||||
endif()
|
||||
|
||||
set(FILENAME_DEP_REPLACE "get_filename_component(SELF_DIR \"$\{CMAKE_CURRENT_LIST_FILE\}\" PATH)")
|
||||
@@ -112,25 +179,41 @@ endif()
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (NOT WITHOUT_CSCORE)
|
||||
if (WITH_WPIMATH)
|
||||
add_subdirectory(wpimath)
|
||||
endif()
|
||||
|
||||
if (WITH_GUI)
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(wpigui)
|
||||
add_subdirectory(glass)
|
||||
endif()
|
||||
|
||||
if (WITH_CSCORE)
|
||||
set(CSCORE_DEP_REPLACE ${CSCORE_DEP_REPLACE_IMPL})
|
||||
set(CAMERASERVER_DEP_REPLACE ${CAMERASERVER_DEP_REPLACE_IMPL})
|
||||
add_subdirectory(cscore)
|
||||
add_subdirectory(cameraserver)
|
||||
if (NOT WITHOUT_ALLWPILIB)
|
||||
if (WITH_WPILIB)
|
||||
set(HAL_DEP_REPLACE ${HAL_DEP_REPLACE_IMPL})
|
||||
set(WPILIBC_DEP_REPLACE ${WPILIBC_DEP_REPLACE_IMPL})
|
||||
add_subdirectory(hal)
|
||||
add_subdirectory(wpilibj)
|
||||
add_subdirectory(wpilibc)
|
||||
add_subdirectory(wpilibNewCommands)
|
||||
if (WITH_OLD_COMMANDS)
|
||||
add_subdirectory(wpilibOldCommands)
|
||||
endif()
|
||||
if (WITH_EXAMPLES)
|
||||
add_subdirectory(wpilibcExamples)
|
||||
endif()
|
||||
add_subdirectory(myRobot)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WITH_SIMULATION_MODULES AND NOT USE_EXTERNAL_HAL)
|
||||
add_subdirectory(imgui)
|
||||
if (WITH_SIMULATION_MODULES AND NOT WITH_EXTERNAL_HAL)
|
||||
add_subdirectory(simulation)
|
||||
endif()
|
||||
|
||||
configure_file(wpilib-config.cmake.in ${CMAKE_BINARY_DIR}/wpilib-config.cmake )
|
||||
install(FILES ${CMAKE_BINARY_DIR}/wpilib-config.cmake DESTINATION ${wpilib_config_dir})
|
||||
configure_file(wpilib-config.cmake.in ${WPILIB_BINARY_DIR}/wpilib-config.cmake )
|
||||
install(FILES ${WPILIB_BINARY_DIR}/wpilib-config.cmake DESTINATION ${wpilib_config_dir})
|
||||
|
||||
131
CODE_OF_CONDUCT.md
Normal file
131
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Exhibiting Gracious Professionalism® at all times. Gracious Professionalism
|
||||
is a way of doing things that encourages high-quality work, emphasizes the
|
||||
value of others, and respects individuals and the community.
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[conduct@wpilib.org](mailto:conduct@wpilib.org).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing to WPILib
|
||||
|
||||
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules written here, and behave with Gracious Professionalism.
|
||||
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules in the [code of conduct](https://github.com/wpilibsuite/allwpilib/blob/master/CODE_OF_CONDUCT.md), and behave with Gracious Professionalism.
|
||||
|
||||
- [General Contribution Rules](#general-contribution-rules)
|
||||
- [What to Contribute](#what-to-contribute)
|
||||
@@ -37,7 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 6.0 with wpiformat.
|
||||
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 10.0 with wpiformat.
|
||||
|
||||
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
|
||||
|
||||
@@ -45,12 +45,12 @@ While the library should be fully formatted according to the styles, additional
|
||||
|
||||
### Pull Request Format
|
||||
|
||||
Changes should be submitted as a Pull Request against the master branch of WPILib. For most changes, we ask that you squash your changes down to a single commit. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. No change will be merged unless it is up to date with the current master. We will also not merge any changes with merge commits in them; please rebase off of master before submitting a pull request. We do this to make sure that the git history isn't too cluttered.
|
||||
Changes should be submitted as a Pull Request against the master branch of WPILib. For most changes, commits will be squashed upon merge. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. We may ask you to break a PR into multiple standalone PRs or commits for rebase within one PR to separate unrelated changes. No change will be merged unless it is up to date with the current master. We do this to make sure that the git history isn't too cluttered.
|
||||
|
||||
### Merge Process
|
||||
|
||||
When you first submit changes, Travis-CI will attempt to run `./gradlew check` on your change. If this fails, you will need to fix any issues that it sees. Once Travis passes, we will begin the review process in more earnest. One or more WPILib team members will review your change. This will be a back-and-forth process with the WPILib team and the greater community. Once we are satisfied that your change is ready, we will allow our Jenkins instance to test it. This will run the full gamut of checks, including integration tests on actual hardware. Once all tests have passed and the team is satisfied, we will merge your change into the WPILib repository.
|
||||
When you first submit changes, GitHub Actions will attempt to run `./gradlew check` on your change. If this fails, you will need to fix any issues that it sees. Once Actions passes, we will begin the review process in more earnest. One or more WPILib team members will review your change. This will be a back-and-forth process with the WPILib team and the greater community. Once we are satisfied that your change is ready, we will allow our hosted instance to test it. This will run the full gamut of checks, including integration tests on actual hardware. Once all tests have passed and the team is satisfied, we will merge your change into the WPILib repository.
|
||||
|
||||
## Licensing
|
||||
|
||||
By contributing to WPILib, you agree that your code will be distributed with WPILib, and licensed under the license for the WPILib project. You should not contribute code that you do not have permission to relicense in this manner. This includes code that is licensed under the GPL that you do not have permission to relicense, as WPILib is not released under a copyleft license. Our license is the 3-clause BSD license, which you can find [here](LICENSE.txt).
|
||||
By contributing to WPILib, you agree that your code will be distributed with WPILib, and licensed under the license for the WPILib project. You should not contribute code that you do not have permission to relicense in this manner. This includes code that is licensed under the GPL that you do not have permission to relicense, as WPILib is not released under a copyleft license. Our license is the 3-clause BSD license, which you can find [here](LICENSE.md).
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Faster Builds for Developers
|
||||
|
||||
When you run `./gradlew build`, it builds EVERYTHING. This means debug and release builds for desktop and all installed cross compilers. For many developers, this is way too much, and causes much developer pain.
|
||||
|
||||
To help with some of these things, common tasks have shortcuts to only build necessary things for common development and testing tasks.
|
||||
|
||||
## Development (Desktop)
|
||||
|
||||
For projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibOldCommands`, `wpilibNewCommands` and `cameraserver`, a `testDesktopJava` and a `testDesktopCpp` task exists. These can be ran with `./gradlew :projectName:task`, and will only build the minimum things required to run those tests.
|
||||
|
||||
For `wpilibc`, a `testDesktopCpp` task exists. For `wpilibj`, a `testDesktopJava` task exists.
|
||||
|
||||
For `wpilibcExamples`, a `buildDesktopCpp` task exists (These can't be ran, but they can compile).
|
||||
|
||||
For `wpilibjExamples`, a `buildDesktopJava` task exists.
|
||||
@@ -1,18 +1,18 @@
|
||||
Copyright (c) 2009-2018 FIRST
|
||||
Copyright (c) 2009-2019 FIRST
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the FIRST nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the FIRST nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS``AS IS'' AND ANY
|
||||
THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS "AS IS" AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR
|
||||
27
MAINTAINERS.md
Normal file
27
MAINTAINERS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Publishing Third Party Dependencies
|
||||
Currently the 3rd party deps are imgui, opencv, and google test
|
||||
|
||||
For publishing these dependencies, the version needs to be manually updated in the publish.gradle file of their respective repository.
|
||||
Then, in the azure build for the dependency you want to build for, manually start a pipeline build (As of current, this is the `Run Pipeline` button).
|
||||
A variable needs to be added called `RUN_AZURE_ARTIFACTORY_RELEASE`, with a value of `true`. Then when the pipeline gets started, the final build outputs will be updated to artifactory.
|
||||
|
||||
To use newer versions of C++ dependencies, in `shared/config.gradle`, update the version related to the specific dependency.
|
||||
For Java dependencies, there is likely a file related to the specific dependency in the shared folder. Update the version in there.
|
||||
|
||||
Note, changing artifact locations (This includes changing the artifact year currently, I have an issue open to change this) requires updating the `native-utils` plugin
|
||||
|
||||
## Publishing allwpilib
|
||||
allwpilib publishes to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
|
||||
## Publishing desktop tools
|
||||
Desktop tools publish to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
|
||||
## Publishing VS Code
|
||||
Before publishing, make sure to update the gradlerio version in `vscode-wpilib/resources/gradle/version.txt` Also make sure the gradle wrapper version matches the wrapper required by gradlerio.
|
||||
Upon pushing a tag, a release will be built, and the files will be uploaded to the releases on GitHub. For publishing to the marketplace, you need a Microsoft account and to be added as a maintainer.
|
||||
|
||||
## Publishing GradleRIO
|
||||
Before publishing, make sure to update the version in build.gradle. Publishing must happen locally, using the command `./gradlew publishPlugin`. This does require your API key for publishing to be set.
|
||||
|
||||
## Building the installer
|
||||
Update the GradleRIO version in gradle.properties, and in the scripts folder in vscode, update the vscode extension. Then push, it will build the installer on azure.
|
||||
@@ -72,6 +72,10 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* hal
|
||||
* wpiutil
|
||||
|
||||
* halsim
|
||||
* imgui
|
||||
* wpiutil
|
||||
|
||||
* ntcore
|
||||
* wpiutil
|
||||
|
||||
@@ -85,7 +89,6 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* opencv
|
||||
* wpiutil
|
||||
|
||||
|
||||
* wpilibj
|
||||
* hal
|
||||
* cameraserver
|
||||
@@ -93,10 +96,35 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
* wpilibNewCommands
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
* wpilibNewCommands
|
||||
* wpilibc
|
||||
* hal
|
||||
* cameraserver
|
||||
* ntcore
|
||||
* cscore
|
||||
* wpiutil
|
||||
|
||||
### Third Party Artifacts
|
||||
|
||||
This repository provides the builds of the following third party software.
|
||||
|
||||
All artifacts are based at `edu.wpi.first.thirdparty.frcYEAR` in the repository.
|
||||
|
||||
* googletest
|
||||
* imgui
|
||||
* opencv
|
||||
|
||||
68
OtherVersions.md
Normal file
68
OtherVersions.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Installing Development Builds
|
||||
|
||||
This article contains instructions on building projects using a development build and a local WPILib build.
|
||||
|
||||
**Note:** This only applies to Java/C++ teams.
|
||||
|
||||
## Development Build
|
||||
|
||||
Development builds are the per-commit build hosted everytime a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
|
||||
|
||||
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.useDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.+'
|
||||
```
|
||||
|
||||
The top of your ``build.gradle`` file should now look similar to the code below. Ignore any differences in versions.
|
||||
|
||||
Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
}
|
||||
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.wpilibVersion = '2020.+'
|
||||
```
|
||||
|
||||
C++
|
||||
```groovy
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
}
|
||||
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.wpilibVersion = '2020.+'
|
||||
```
|
||||
|
||||
## Local Build
|
||||
|
||||
Building with a local build is very similar to building with a development build. Ensure you have built and published WPILib by following the instructions attached [here](https://github.com/wpilibsuite/allwpilib#building-wpilib). Next, find the ``build.gradle`` file in your robot project and open it. Then, add the following code below the plugin section and replace ``YEAR`` with the year of the local version.
|
||||
|
||||
Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
}
|
||||
|
||||
wpi.maven.useFrcMavenLocalDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.424242.+'
|
||||
```
|
||||
|
||||
C++
|
||||
```groovy
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2020.3.2"
|
||||
}
|
||||
|
||||
wpi.maven.useFrcMavenLocalDevelopment = true
|
||||
wpi.wpilibVersion = 'YEAR.424242.+'
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# WPILib CMake Support
|
||||
|
||||
WPILib is normally built with Gradle, however for some systems, such a linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over linux builds.
|
||||
WPILib is normally built with Gradle, however for some systems, such as Linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. Furthermore, the CMake build can be used for C++ development because it provides better build caching compared to Gradle. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over Linux builds.
|
||||
|
||||
## Libraries that get built
|
||||
* wpiutil
|
||||
@@ -9,56 +9,69 @@ WPILib is normally built with Gradle, however for some systems, such a linux bas
|
||||
* cameraserver
|
||||
* hal
|
||||
* wpilib
|
||||
* halsim
|
||||
* wpigui
|
||||
* wpimath
|
||||
|
||||
By default, all libraries except for the HAL and WPILib get built with a default cmake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java jars.
|
||||
By default, all libraries except for the HAL and WPILib get built with a default CMake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java JARs.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The most common prerequisite is going to be OpenCV. OpenCV needs to be findable by cmake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build cmake from source and install it.
|
||||
The most common prerequisite is going to be OpenCV. OpenCV needs to be findable by CMake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build OpenCV from source and install it.
|
||||
|
||||
In addition, if you want JNI and Java, you will need a JDK of at least version 8 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the jdk directory.
|
||||
In addition, if you want JNI and Java, you will need a JDK of at least version 11 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
|
||||
|
||||
If you are building with unit tests or simulation modules, you will also need an Internet connection for the initial setup process, as CMake will clone google-test and imgui from GitHub.
|
||||
|
||||
## Build Options
|
||||
|
||||
The following build options are available:
|
||||
|
||||
* WITHOUT_JAVA (OFF Default)
|
||||
* Enabling this option will disable Java and JNI builds. If this is off, `BUILD_SHARED_LIBS` must be on. Otherwise cmake will error.
|
||||
* BUILD_SHARED_LIBS (ON Default)
|
||||
* Disabling this option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITHOUT_JAVA` must be on. Otherwise cmake will error.
|
||||
* WITHOUT_CSCORE (OFF Default)
|
||||
* Enabling this option will cause cscore to not be built. This will also implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is on, the opencv build requirement is removed.
|
||||
* WITHOUT_ALLWPILIB (ON Default)
|
||||
* Disabling this option will build the hal and wpilib during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* USE_EXTERNAL_HAL (OFF Default)
|
||||
* `WITH_JAVA` (ON Default)
|
||||
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
|
||||
* `WITH_SHARED_LIBS` (ON Default)
|
||||
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_CSCORE` (ON Default)
|
||||
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_WPILIB` (ON Default)
|
||||
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* `WITH_SIMULATION_MODULES` (ON Default)
|
||||
* This option will build simulation modules, including wpigui and the HALSim plugins.
|
||||
* `WITH_EXTERNAL_HAL` (OFF Default)
|
||||
* TODO
|
||||
* EXTERNAL_HAL_FILE
|
||||
* `EXTERNAL_HAL_FILE`
|
||||
* TODO
|
||||
* `OPENCV_JAVA_INSTALL_DIR`
|
||||
* Set this option to the location of the archive of the OpenCV Java bindings (it should be called opencv-xxx.jar, with the x'es being version numbers). NOTE: set it to the LOCATION of the file, not the file itself!
|
||||
|
||||
## Build Setup
|
||||
|
||||
The WPILib CMake build does not allow in source builds. Because the `build` directory is used by gradle, we recommend a `buildcmake` directory in the root. This folder is included in the gitignore.
|
||||
The WPILib CMake build does not allow in source builds. Because the `build` directory is used by Gradle, we recommend a `buildcmake` directory in the root. This folder is included in the gitignore.
|
||||
|
||||
Once you have a build folder, run cmake configuration in that build directory with the following command.
|
||||
Once you have a build folder, run CMake configuration in that build directory with the following command.
|
||||
|
||||
```
|
||||
cmake path/to/allwpilib/root
|
||||
```
|
||||
|
||||
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the cmake command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
|
||||
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the `cmake` command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
|
||||
|
||||
If you want, you can also use `ccmake` in order to visually set these properties as well.
|
||||
https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html
|
||||
Here is the link to the documentation for that program.
|
||||
If you want, you can also use `ccmake` in order to visually set these properties as well. [Here](https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html) is the link to the documentation for that program.
|
||||
|
||||
## Building
|
||||
|
||||
Once you have cmake setup. run `make` from the directory you configured cmake in. This will build all libraries possible. If you have a multicore system, we recommend running make with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
|
||||
Once you have cmake setup. run `make` from the directory you configured CMake in. This will build all libraries possible. If you have a multicore system, we recommend running `make` with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
|
||||
|
||||
```
|
||||
make -jx
|
||||
```
|
||||
|
||||
The `ninja` generator is also supported, and can be enabled by passing `-GNinja` to the initial `cmake` command.
|
||||
|
||||
## Installing
|
||||
|
||||
After build, the easiest way to use the libraries is to install them. Run the following command to install the libraries. This will install them so that they can be used from external cmake projects.
|
||||
@@ -79,7 +92,7 @@ project(vision_app) # Project Name Here
|
||||
|
||||
find_package(wpilib REQUIRED)
|
||||
|
||||
add_executable(my_vision_app main.cpp) # exectuable name as first parameter
|
||||
add_executable(my_vision_app main.cpp) # executable name as first parameter
|
||||
target_link_libraries(my_vision_app cameraserver ntcore cscore wpiutil)
|
||||
```
|
||||
|
||||
@@ -135,4 +148,4 @@ CMake Error at /usr/share/cmake-3.5/Modules/FindPackageHandleStandardArgs.cmake:
|
||||
|
||||
If this happens, make sure you have a JDK of at least version 8 installed, and that your JAVA_HOME variable is set properly to point to the JDK.
|
||||
|
||||
In addition, if you do not need Java, you can disable it with `-DWITHOUT_JAVA=ON`.
|
||||
In addition, if you do not need Java, you can disable it with `-DWITH_JAVA=OFF`.
|
||||
|
||||
79
README.md
79
README.md
@@ -1,21 +1,28 @@
|
||||
# WPILib Project
|
||||
|
||||
[](https://dev.azure.com/wpilib/wpilib/_build/latest?definitionId=1)
|
||||

|
||||
|
||||
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
|
||||
|
||||
- [WPILib Mission](#wpilib-mission)
|
||||
- [WPILib Project](#wpilib-project)
|
||||
- [WPILib Mission](#wpilib-mission)
|
||||
- [Building WPILib](#building-wpilib)
|
||||
- [Requirements](#requirements)
|
||||
- [Setup](#setup)
|
||||
- [Building](#building)
|
||||
- [Publishing](#publishing)
|
||||
- [Structure and Organization](#structure-and-organization)
|
||||
- [Requirements](#requirements)
|
||||
- [Setup](#setup)
|
||||
- [Building](#building)
|
||||
- [Faster builds](#faster-builds)
|
||||
- [Using Development Builds](#using-development-builds)
|
||||
- [Custom toolchain location](#custom-toolchain-location)
|
||||
- [Gazebo simulation](#gazebo-simulation)
|
||||
- [Formatting/linting with wpiformat](#formattinglinting-with-wpiformat)
|
||||
- [CMake](#cmake)
|
||||
- [Publishing](#publishing)
|
||||
- [Structure and Organization](#structure-and-organization)
|
||||
- [Contributing to WPILib](#contributing-to-wpilib)
|
||||
|
||||
## WPILib Mission
|
||||
|
||||
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.txt).
|
||||
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.md).
|
||||
|
||||
# Building WPILib
|
||||
|
||||
@@ -23,17 +30,24 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
|
||||
## Requirements
|
||||
|
||||
- A C++ compiler
|
||||
- On Linux, GCC works fine
|
||||
- On Windows, you need Visual Studio 2019 (the free community edition works fine).
|
||||
Make sure to select the C++ Programming Language for installation
|
||||
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/toolchain-builder/releases)
|
||||
* Note that for 2020 and beyond, you should use version 7 or greater of GCC
|
||||
- Doxygen (Only required if you want to build the C++ documentation)
|
||||
- [JDK 11](https://adoptopenjdk.net/)
|
||||
- Note that the JRE is insufficient; the full JDK is required
|
||||
- On Ubuntu, run `sudo apt install openjdk-11-jdk`
|
||||
- On Windows, install the JDK 11 .msi from the link above
|
||||
- On macOS, install the JDK 11 .pkg from the link above
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 7 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 macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
- If the WPILib installer was used, this toolchain is already installed
|
||||
- Raspberry Pi toolchain (optional)
|
||||
- Run `./gradlew installRaspbianToolchain` after cloning this repository
|
||||
|
||||
## Setup
|
||||
|
||||
Clone the WPILib repository. If the toolchains are not installed, install them, and make sure they are available on the system PATH.
|
||||
Clone the WPILib repository and follow the instructions above for installing any required tooling.
|
||||
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/master/README.md) for wpiformat setup instructions.
|
||||
|
||||
@@ -51,12 +65,34 @@ To build a specific subproject, such as WPILibC, you must access the subproject
|
||||
./gradlew :wpilibc:build
|
||||
```
|
||||
|
||||
The gradlew wrapper only exists in the root of the main project, so be sure to run all commands from there. All of the subprojects have build tasks that can be run. Gradle automatically determines and rebuilds dependencies, so if you make a change in the HAL and then run `./gradlew :wpilibc:build`, the HAL will be rebuilt, then WPILibC.
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
`./gradlew testDesktopCpp` and `./gradlew testDesktopJava` will build and run the tests for `wpilibc` and `wpilibj` respectively. They will only build the minimum components required to run the tests.
|
||||
|
||||
`testDesktopCpp` and `testDesktopJava` tasks also exist for the projects `wpiutil`, `ntcore`, `cscore`, `hal` `wpilibOldCommands`, `wpilibNewCommands` and `cameraserver`. These can be ran with `./gradlew :projectName:task`.
|
||||
|
||||
`./gradlew buildDesktopCpp` and `./gradlew buildDesktopJava` will compile `wpilibcExamples` and `wpilibjExamples` respectively. The results can't be ran, but they can compile.
|
||||
|
||||
### Using Development Builds
|
||||
|
||||
Please read the documentation available [here](OtherVersions.md)
|
||||
|
||||
### Custom toolchain location
|
||||
|
||||
If you have installed the FRC Toolchain to a directory other than the default, or if the Toolchain location is not on your System PATH, you can pass the `toolChainPath` property to specify where it is located. Example:
|
||||
|
||||
```bash
|
||||
./gradlew build -PtoolChainPath=some/path/to/frc/toolchain/bin
|
||||
```
|
||||
|
||||
### Gazebo simulation
|
||||
|
||||
If you also want simulation to be built, add -PmakeSim. This requires gazebo_transport. We have tested on 14.04 and 15.05, but any correct install of Gazebo should work, even on Windows if you build Gazebo from source. Correct means CMake needs to be able to find gazebo-config.cmake. See [The Gazebo website](https://gazebosim.org/) for installation instructions.
|
||||
|
||||
```bash
|
||||
@@ -73,12 +109,19 @@ cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
The gradlew wrapper only exists in the root of the main project, so be sure to run all commands from there. All of the subprojects have build tasks that can be run. Gradle automatically determines and rebuilds dependencies, so if you make a change in the HAL and then run `./gradlew :wpilibc:build`, the HAL will be rebuilt, then WPILibC.
|
||||
|
||||
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.
|
||||
### Formatting/linting
|
||||
|
||||
#### wpiformat
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
|
||||
|
||||
#### Java Code Quality Tools
|
||||
|
||||
The Java code quality tools (checkstyle, pmd, etc.) can be run with the `./gradlew javaFormat` task.
|
||||
|
||||
### CMake
|
||||
|
||||
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
|
||||
|
||||
## Publishing
|
||||
|
||||
@@ -36,14 +36,18 @@ CoreUI wpiutil/src/main/native/resources/coreui-*
|
||||
Feather Icons wpiutil/src/main/native/resources/feather-*
|
||||
jQuery wpiutil/src/main/native/resources/jquery-*
|
||||
popper.js wpiutil/src/main/native/resources/popper-*
|
||||
units wpiutil/src/main/native/include/units/units.h
|
||||
Eigen wpiutil/src/main/native/eigeninclude/
|
||||
units wpimath/src/main/native/include/units/
|
||||
Eigen wpimath/src/main/native/eigeninclude/
|
||||
wpimath/src/main/native/include/unsupported/
|
||||
StackWalker wpiutil/src/main/native/windows/StackWalker.*
|
||||
Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineParameterizer.java
|
||||
wpilibj/src/main/java/edu/wpi/first/wpilibj/trajectory/TrajectoryParameterizer.java
|
||||
wpilibc/src/main/native/include/spline/SplineParameterizer.h
|
||||
wpilibc/src/main/native/include/trajectory/TrajectoryParameterizer.h
|
||||
wpilibc/src/main/native/cpp/trajectory/TrajectoryParameterizer.cpp
|
||||
Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
|
||||
Drake wpimath/src/main/native/cpp/drake/common/drake_assert_and_throw.cpp
|
||||
wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp
|
||||
|
||||
|
||||
==============================================================================
|
||||
@@ -806,3 +810,38 @@ 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.
|
||||
|
||||
=============
|
||||
Drake Library
|
||||
=============
|
||||
All components of Drake are licensed under the BSD 3-Clause License
|
||||
shown below. Where noted in the source code, some portions may
|
||||
be subject to other permissive, non-viral licenses.
|
||||
|
||||
Copyright 2012-2016 Robot Locomotion Group @ CSAIL
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer. Redistributions
|
||||
in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution. Neither the name of
|
||||
the Massachusetts Institute of Technology nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"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 COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS 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.
|
||||
|
||||
@@ -15,7 +15,7 @@ stages:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2020-18.04
|
||||
image: wpilib/roborio-cross-ubuntu:2021-18.04
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
|
||||
@@ -1,417 +0,0 @@
|
||||
# Gradle
|
||||
# Build your Java projects and run tests with Gradle using a Gradle wrapper script.
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/vsts/pipelines/languages/java
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: wpilib2020
|
||||
image: wpilib/roborio-cross-ubuntu:2020-18.04
|
||||
- container: raspbian
|
||||
image: wpilib/raspbian-cross-ubuntu:10-18.04
|
||||
- container: aarch64
|
||||
image: wpilib/aarch64-cross-ubuntu:bionic-18.04
|
||||
- container: ubuntu
|
||||
image: wpilib/ubuntu-base:18.04
|
||||
|
||||
variables:
|
||||
- group: Artifactory-Package-Publish
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: Linux_Arm
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: wpilib2020
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxathena -PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: false
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxathena -PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Athena'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Linux_Raspbian
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: raspbian
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxraspbian -PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxraspbian -PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Raspbian'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Linux_Aarch64
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: aarch64
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxaarch64bionic -PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-Ponlylinuxaarch64bionic -PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Aarch64'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: ubuntu
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Linux'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Styleguide
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: ubuntu
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
sudo pip3 install wpiformat
|
||||
displayName: 'Install wpiformat'
|
||||
- script: |
|
||||
git checkout -b master
|
||||
wpiformat -clang 6.0
|
||||
displayName: 'Run wpiformat'
|
||||
- script: |
|
||||
# Ensure formatter made no changes
|
||||
git --no-pager diff --exit-code HEAD
|
||||
displayName: 'Check wpiformat Output'
|
||||
|
||||
- job: CMakeBuild
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
container: wpilib2020
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- task: CMake@1
|
||||
inputs:
|
||||
cmakeArgs: '-DWITHOUT_ALLWPILIB=OFF ..'
|
||||
- script: |
|
||||
make -j3
|
||||
workingDirectory: 'build'
|
||||
displayName: 'Build'
|
||||
|
||||
- job: Windows_64_Bit
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
|
||||
timeoutInMinutes: 0
|
||||
steps:
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
jdkVersionOption: '1.11'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PskipPMD -PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
jdkVersionOption: '1.11'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PskipPMD -PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Win64'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Windows_32_Bit
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
|
||||
timeoutInMinutes: 0
|
||||
steps:
|
||||
- powershell: |
|
||||
mkdir build
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
wget "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.4%2B11/OpenJDK11U-jdk_x86-32_windows_hotspot_11.0.4_11.zip" -O "build\jdk.zip"
|
||||
displayName: 'Download JDK'
|
||||
- task: JavaToolInstaller@0
|
||||
inputs:
|
||||
jdkSourceOption: localDirectory
|
||||
jdkFile: 'build/jdk.zip'
|
||||
jdkDestinationDirectory: 'build/jdkinst'
|
||||
jdkArchitectureOption: x86
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx1024m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PskipPMD -PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx1024m'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PskipPMD -PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Win32'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- job: Mac
|
||||
pool:
|
||||
vmImage: 'macOS-10.14'
|
||||
|
||||
timeoutInMinutes: 0
|
||||
steps:
|
||||
- script: |
|
||||
mkdir build
|
||||
export JAVA_HOME=`/usr/libexec/java_home -v 11`
|
||||
displayName: 'Setup JDK'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
jdkVersionOption: '1.11'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PbuildServer'
|
||||
|
||||
- task: Gradle@2
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
inputs:
|
||||
workingDirectory: ''
|
||||
gradleWrapperFile: 'gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
jdkVersionOption: '1.11'
|
||||
publishJUnitResults: true
|
||||
testResultsFiles: '**/TEST-*.xml'
|
||||
tasks: 'build'
|
||||
options: '-PreleaseMode -PbuildServer'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Mac'
|
||||
targetPath: 'build/allOutputs'
|
||||
|
||||
- stage: Combine
|
||||
jobs:
|
||||
- job: CombineJob
|
||||
pool:
|
||||
vmImage: 'macOS-10.14'
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
- script: |
|
||||
git clone https://github.com/wpilibsuite/build-tools
|
||||
displayName: 'Clone Combiner'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Mac'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Win32'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Win64'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Linux'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Raspbian'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Athena'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
- task: DownloadPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Aarch64'
|
||||
targetPath: 'build-tools/combiner/products/build/allOutputs'
|
||||
|
||||
# PR Builds
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: 'build-tools/combiner'
|
||||
gradleWrapperFile: 'build-tools/combiner/gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
tasks: 'publish '
|
||||
options: '-Pallwpilib'
|
||||
condition: and(succeeded(), and(ne(variables['Build.SourceBranch'], 'refs/heads/master'), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))))
|
||||
|
||||
# Master Builds
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: 'build-tools/combiner'
|
||||
gradleWrapperFile: 'build-tools/combiner/gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
tasks: 'publish '
|
||||
options: '-Pallwpilib'
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
|
||||
ARTIFACTORY_PUBLISH_USERNAME: $(PublishUserName)
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: $(PublishPassword)
|
||||
|
||||
# Tagged Builds
|
||||
- task: Gradle@2
|
||||
inputs:
|
||||
workingDirectory: 'build-tools/combiner'
|
||||
gradleWrapperFile: 'build-tools/combiner/gradlew'
|
||||
gradleOptions: '-Xmx3072m'
|
||||
tasks: 'publish '
|
||||
options: '-Pallwpilib -PreleaseRepoPublish'
|
||||
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE'
|
||||
ARTIFACTORY_PUBLISH_USERNAME: $(PublishUserName)
|
||||
ARTIFACTORY_PUBLISH_PASSWORD: $(PublishPassword)
|
||||
|
||||
- script: |
|
||||
echo "##vso[task.setvariable variable=UserHome]$HOME"
|
||||
displayName: 'Set Home Variable'
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
artifactName: 'Maven'
|
||||
targetPath: $(UserHome)/releases
|
||||
28
build.gradle
28
build.gradle
@@ -2,7 +2,7 @@ import edu.wpi.first.toolchain.*
|
||||
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.0.1'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.0.2'
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
|
||||
id 'edu.wpi.first.NativeUtils' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '0.10.1'
|
||||
@@ -15,10 +15,12 @@ plugins {
|
||||
|
||||
if (project.hasProperty('buildServer')) {
|
||||
wpilibVersioning.buildServerMode = true
|
||||
wpilibVersioning.useAllTags = true
|
||||
}
|
||||
|
||||
if (project.hasProperty('releaseMode')) {
|
||||
wpilibVersioning.releaseMode = true
|
||||
wpilibVersioning.useAllTags = true
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -39,7 +41,7 @@ buildScan {
|
||||
publishAlways()
|
||||
}
|
||||
|
||||
ext.licenseFile = files("$rootDir/LICENSE.txt", "$rootDir/ThirdPartyNotices.txt")
|
||||
ext.licenseFile = files("$rootDir/LICENSE.md", "$rootDir/ThirdPartyNotices.txt")
|
||||
|
||||
if (project.hasProperty("publishVersion")) {
|
||||
wpilibVersioning.version.set(project.publishVersion)
|
||||
@@ -103,6 +105,26 @@ subprojects {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sign outputs with Developer ID
|
||||
if (project.hasProperty("developerID")) {
|
||||
tasks.withType(AbstractLinkTask) { task ->
|
||||
// Don't sign any executables because codesign complains
|
||||
// about relative rpath.
|
||||
if (!(task instanceof LinkExecutable)) {
|
||||
doLast {
|
||||
// Get path to binary.
|
||||
String path = task.getLinkedFile().getAsFile().get().getAbsolutePath()
|
||||
exec {
|
||||
workingDir rootDir
|
||||
def args = ["sh", "-c", "codesign --force --strict --timestamp --options=runtime " +
|
||||
"--verbose -s ${project.findProperty("developerID")} ${path}"]
|
||||
commandLine args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext.getCurrentArch = {
|
||||
@@ -110,5 +132,5 @@ ext.getCurrentArch = {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '6.0'
|
||||
gradleVersion = '6.0.1'
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2020.7.2"
|
||||
implementation "edu.wpi.first:native-utils:2021.0.6"
|
||||
}
|
||||
|
||||
@@ -6,10 +6,14 @@ import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.tasks.Delete
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.tasks.Copy;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.language.base.internal.ProjectLayout;
|
||||
@@ -32,6 +36,8 @@ import org.gradle.nativeplatform.toolchain.internal.ToolType;
|
||||
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
|
||||
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
|
||||
import org.gradle.nativeplatform.tasks.CreateStaticLibrary;
|
||||
import org.gradle.nativeplatform.tasks.AbstractLinkTask;
|
||||
import org.gradle.platform.base.BinarySpec;
|
||||
import org.gradle.platform.base.ComponentSpec;
|
||||
import org.gradle.platform.base.ComponentSpecContainer;
|
||||
@@ -79,10 +85,18 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
components.each { component ->
|
||||
if (component.name == "${nativeName}Base") {
|
||||
base = (NativeLibrarySpec) component
|
||||
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI") {
|
||||
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI" || component.name == "${nativeName}JNICvStatic") {
|
||||
subs << component
|
||||
}
|
||||
}
|
||||
Delete deleteObjects = null
|
||||
if (project.hasProperty('buildServer')) {
|
||||
deleteObjects = project.tasks.create('deleteObjects', Delete)
|
||||
project.tasks.named('build').configure { Task t ->
|
||||
t.dependsOn deleteObjects
|
||||
return
|
||||
}
|
||||
}
|
||||
subs.each {
|
||||
((NativeLibrarySpec) it).binaries.each { oBinary ->
|
||||
if (oBinary.buildable == false) {
|
||||
@@ -100,6 +114,23 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
baseBin = tmpBaseBin
|
||||
}
|
||||
}
|
||||
|
||||
if (binary instanceof StaticLibraryBinarySpec) {
|
||||
File intoDir = ((CreateStaticLibrary)((StaticLibraryBinarySpec)binary).tasks.createStaticLib).outputFile.get().asFile.parentFile
|
||||
File fromDir = ((CreateStaticLibrary)((StaticLibraryBinarySpec)baseBin).tasks.createStaticLib).outputFile.get().asFile.parentFile
|
||||
|
||||
def copyBasePdbName = "copyBasePdbFor" + binary.buildTask.name
|
||||
def copyTask = project.tasks.register(copyBasePdbName, Copy) { Copy t ->
|
||||
t.from (fromDir)
|
||||
t.include '*.pdb'
|
||||
t.into intoDir
|
||||
|
||||
t.dependsOn (((StaticLibraryBinarySpec)baseBin).tasks.createStaticLib)
|
||||
}
|
||||
((CreateStaticLibrary)((StaticLibraryBinarySpec)binary).tasks.createStaticLib).dependsOn(copyTask)
|
||||
|
||||
}
|
||||
|
||||
baseBin.tasks.withType(AbstractNativeSourceCompileTask) { oCompileTask ->
|
||||
def compileTask = (AbstractNativeSourceCompileTask) oCompileTask
|
||||
if (binary instanceof SharedLibraryBinarySpec) {
|
||||
@@ -115,6 +146,10 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
tree.include '**/*.o'
|
||||
tree.include '**/*.obj'
|
||||
link.source tree
|
||||
if (project.hasProperty('buildServer')) {
|
||||
deleteObjects.dependsOn link
|
||||
deleteObjects.delete tree
|
||||
}
|
||||
} else if (binary instanceof StaticLibraryBinarySpec) {
|
||||
def sBinary = (StaticLibraryBinarySpec) binary
|
||||
ObjectFilesToBinary assemble = (ObjectFilesToBinary) sBinary.tasks.createStaticLib
|
||||
@@ -124,6 +159,10 @@ class SingleNativeBuild implements Plugin<Project> {
|
||||
tree.include '**/*.o'
|
||||
tree.include '**/*.obj'
|
||||
assemble.source tree
|
||||
if (project.hasProperty('buildServer')) {
|
||||
deleteObjects.dependsOn assemble
|
||||
deleteObjects.delete tree
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ include(AddTest)
|
||||
find_package( OpenCV REQUIRED )
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
if (WITH_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
include(UseJava)
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked")
|
||||
@@ -43,18 +43,18 @@ set_property(TARGET cameraserver PROPERTY FOLDER "libraries")
|
||||
install(TARGETS cameraserver EXPORT cameraserver DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cameraserver")
|
||||
|
||||
if (NOT WITHOUT_JAVA AND MSVC)
|
||||
if (WITH_JAVA AND MSVC)
|
||||
install(TARGETS cameraserver RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (cameraserver_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cameraserver_config_dir share/cameraserver)
|
||||
endif()
|
||||
|
||||
configure_file(cameraserver-config.cmake.in ${CMAKE_BINARY_DIR}/cameraserver-config.cmake )
|
||||
install(FILES ${CMAKE_BINARY_DIR}/cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
|
||||
configure_file(cameraserver-config.cmake.in ${WPILIB_BINARY_DIR}/cameraserver-config.cmake )
|
||||
install(FILES ${WPILIB_BINARY_DIR}/cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
|
||||
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
|
||||
|
||||
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)
|
||||
|
||||
@@ -5,4 +5,5 @@ include(CMakeFindDependencyMacro)
|
||||
@CSCORE_DEP_REPLACE@
|
||||
find_dependency(OpenCV)
|
||||
|
||||
@FILENAME_DEP_REPLACE@
|
||||
include(${SELF_DIR}/cameraserver.cmake)
|
||||
|
||||
@@ -18,7 +18,7 @@ ext {
|
||||
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
mainClassName = 'Main'
|
||||
mainClassName = 'edu.wpi.Main'
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -37,7 +37,6 @@ import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
* Singleton class for creating and keeping camera servers.
|
||||
* Also publishes camera information to NetworkTables.
|
||||
*/
|
||||
@SuppressWarnings("PMD.TooManyMethods")
|
||||
public final class CameraServer {
|
||||
public static final int kBasePort = 1181;
|
||||
|
||||
|
||||
5
cmake/modules/LinkMacOSGUI.cmake
Normal file
5
cmake/modules/LinkMacOSGUI.cmake
Normal file
@@ -0,0 +1,5 @@
|
||||
macro(wpilib_link_macos_gui target)
|
||||
if (APPLE)
|
||||
set_target_properties(${target} PROPERTIES LINK_FLAGS "-framework Metal -framework QuartzCore")
|
||||
endif()
|
||||
endmacro()
|
||||
@@ -1,4 +1,4 @@
|
||||
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
|
||||
set(GNU_MACHINE "arm-frc2020-linux-gnueabi" CACHE STRING "GNU compiler triple")
|
||||
set(GNU_MACHINE "arm-frc2021-linux-gnueabi" CACHE STRING "GNU compiler triple")
|
||||
set(SOFTFP yes)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# load settings in case of "try compile"
|
||||
set(TOOLCHAIN_CONFIG_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
|
||||
set(TOOLCHAIN_CONFIG_FILE "${WPILIB_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
|
||||
get_property(__IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
|
||||
if(__IN_TRY_COMPILE)
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # CMAKE_BINARY_DIR is different
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # WPILIB_BINARY_DIR is different
|
||||
macro(toolchain_save_config)
|
||||
# nothing
|
||||
endmacro()
|
||||
|
||||
@@ -6,6 +6,7 @@ cppHeaderFileInclude {
|
||||
(?<!_c)\.h$
|
||||
\.hpp$
|
||||
\.inc$
|
||||
\.inl$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
|
||||
@@ -3,6 +3,7 @@ project(cscore)
|
||||
include(SubDirList)
|
||||
include(CompileWarnings)
|
||||
include(AddTest)
|
||||
include(LinkMacOSGUI)
|
||||
|
||||
find_package( OpenCV REQUIRED )
|
||||
|
||||
@@ -39,29 +40,42 @@ set_property(TARGET cscore PROPERTY FOLDER "libraries")
|
||||
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
|
||||
|
||||
if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (cscore_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (cscore_config_dir share/cscore)
|
||||
endif()
|
||||
|
||||
configure_file(cscore-config.cmake.in ${CMAKE_BINARY_DIR}/cscore-config.cmake )
|
||||
install(FILES ${CMAKE_BINARY_DIR}/cscore-config.cmake DESTINATION ${cscore_config_dir})
|
||||
configure_file(cscore-config.cmake.in ${WPILIB_BINARY_DIR}/cscore-config.cmake )
|
||||
install(FILES ${WPILIB_BINARY_DIR}/cscore-config.cmake DESTINATION ${cscore_config_dir})
|
||||
install(EXPORT cscore DESTINATION ${cscore_config_dir})
|
||||
|
||||
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
|
||||
foreach(example ${cscore_examples})
|
||||
file(GLOB cscore_example_src examples/${example}/*.cpp)
|
||||
unset(add_libs)
|
||||
if(${example} STREQUAL "usbviewer")
|
||||
if(TARGET wpigui)
|
||||
set(add_libs wpigui)
|
||||
else()
|
||||
unset(cscore_example_src)
|
||||
endif()
|
||||
endif()
|
||||
if(cscore_example_src)
|
||||
add_executable(cscore_${example} ${cscore_example_src})
|
||||
wpilib_target_warnings(cscore_${example})
|
||||
target_link_libraries(cscore_${example} cscore)
|
||||
|
||||
if (${example} STREQUAL "usbviewer")
|
||||
wpilib_link_macos_gui(cscore_${example})
|
||||
endif()
|
||||
|
||||
target_link_libraries(cscore_${example} cscore ${add_libs})
|
||||
set_property(TARGET cscore_${example} PROPERTY FOLDER "examples")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Java bindings
|
||||
if (NOT WITHOUT_JAVA)
|
||||
if (WITH_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
find_package(JNI REQUIRED)
|
||||
include(UseJava)
|
||||
@@ -69,7 +83,9 @@ if (NOT WITHOUT_JAVA)
|
||||
|
||||
#find java files, copy them locally
|
||||
|
||||
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
|
||||
if("${OPENCV_JAVA_INSTALL_DIR}" STREQUAL "")
|
||||
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
|
||||
endif()
|
||||
|
||||
find_file(OPENCV_JAR_FILE NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin NO_DEFAULT_PATH)
|
||||
find_file(OPENCV_JNI_FILE NAMES libopencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.so
|
||||
|
||||
@@ -15,15 +15,60 @@ ext {
|
||||
|
||||
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
|
||||
|
||||
model {
|
||||
components {
|
||||
cscoreJNICvStatic(JniNativeLibrarySpec) {
|
||||
baseName = 'cscorejnicvstatic'
|
||||
|
||||
enableCheckTask true
|
||||
javaCompileTasks << compileJava
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.raspbian)
|
||||
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.aarch64bionic)
|
||||
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'src/main/native/cpp'
|
||||
include '**/jni/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDir 'src/main/native/include'
|
||||
include '**/*.h'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it instanceof StaticLibraryBinarySpec) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
|
||||
if (it.targetPlatform.operatingSystem.linux) {
|
||||
it.linker.args '-Wl,--version-script=' + file('src/main/native/LinuxSymbolScript.txt')
|
||||
} else if (it.targetPlatform.operatingSystem.macOsX) {
|
||||
it.linker.args '-exported_symbols_list'
|
||||
it.linker.args file('src/main/native/MacSymbolScript.txt').toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ext {
|
||||
sharedCvConfigs = [cscore : [],
|
||||
cscoreBase: [],
|
||||
cscoreDev : [],
|
||||
cscoreTest: [],
|
||||
cscoreJNIShared: []]
|
||||
staticCvConfigs = [cscoreJNI: []]
|
||||
staticCvConfigs = [cscoreJNI: [],
|
||||
cscoreJNICvStatic: []]
|
||||
useJava = true
|
||||
useCpp = true
|
||||
cvStaticBuild = true
|
||||
splitSetup = {
|
||||
if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.sources {
|
||||
@@ -112,22 +157,63 @@ nativeUtils.exportsConfigs {
|
||||
symbols.removeIf({ !it.startsWith('CS_') })
|
||||
}
|
||||
}
|
||||
cscoreJNICvStatic {
|
||||
x86SymbolFilter = { symbols ->
|
||||
symbols.removeIf({ !it.startsWith('CS_') })
|
||||
}
|
||||
x64SymbolFilter = { symbols ->
|
||||
symbols.removeIf({ !it.startsWith('CS_') })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
if (key == "usbviewer") {
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
}
|
||||
}
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
include '**/*.cpp'
|
||||
} else {
|
||||
"${key}"(NativeExecutableSpec) {
|
||||
targetBuildTypes 'debug'
|
||||
binaries.all {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
}
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'examples/' + "${key}"
|
||||
include '**/*.cpp'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ include(CMakeFindDependencyMacro)
|
||||
@WPIUTIL_DEP_REPLACE@
|
||||
find_dependency(OpenCV)
|
||||
|
||||
@FILENAME_DEP_REPLACE@
|
||||
include(${SELF_DIR}/cscore.cmake)
|
||||
|
||||
114
cscore/examples/usbviewer/usbviewer.cpp
Normal file
114
cscore/examples/usbviewer/usbviewer.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/spinlock.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
int main() {
|
||||
std::atomic<cv::Mat*> latestFrame{nullptr};
|
||||
std::vector<cv::Mat*> sharedFreeList;
|
||||
wpi::spinlock sharedFreeListMutex;
|
||||
std::vector<cv::Mat*> sourceFreeList;
|
||||
std::atomic<bool> stopCamera{false};
|
||||
|
||||
cs::UsbCamera camera{"usbcam", 0};
|
||||
camera.SetVideoMode(cs::VideoMode::kMJPEG, 640, 480, 30);
|
||||
cs::CvSink cvsink{"cvsink"};
|
||||
cvsink.SetSource(camera);
|
||||
|
||||
std::thread thr([&] {
|
||||
cv::Mat frame;
|
||||
while (!stopCamera) {
|
||||
// get frame from camera
|
||||
uint64_t time = cvsink.GrabFrame(frame);
|
||||
if (time == 0) {
|
||||
wpi::outs() << "error: " << cvsink.GetError() << '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// get or create a mat, prefer sourceFreeList over sharedFreeList
|
||||
cv::Mat* out;
|
||||
if (!sourceFreeList.empty()) {
|
||||
out = sourceFreeList.back();
|
||||
sourceFreeList.pop_back();
|
||||
} else {
|
||||
{
|
||||
std::scoped_lock lock(sharedFreeListMutex);
|
||||
for (auto mat : sharedFreeList) sourceFreeList.emplace_back(mat);
|
||||
sharedFreeList.clear();
|
||||
}
|
||||
if (!sourceFreeList.empty()) {
|
||||
out = sourceFreeList.back();
|
||||
sourceFreeList.pop_back();
|
||||
} else {
|
||||
out = new cv::Mat;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to RGBA
|
||||
cv::cvtColor(frame, *out, cv::COLOR_BGR2RGBA);
|
||||
|
||||
// make available
|
||||
auto prev = latestFrame.exchange(out);
|
||||
|
||||
// put prev on free list
|
||||
if (prev) sourceFreeList.emplace_back(prev);
|
||||
}
|
||||
});
|
||||
|
||||
gui::CreateContext();
|
||||
gui::Initialize("Hello World", 1024, 768);
|
||||
gui::Texture tex;
|
||||
gui::AddEarlyExecute([&] {
|
||||
auto frame = latestFrame.exchange(nullptr);
|
||||
if (frame) {
|
||||
// create or update texture
|
||||
if (!tex || frame->cols != tex.GetWidth() ||
|
||||
frame->rows != tex.GetHeight()) {
|
||||
tex = gui::Texture(gui::kPixelRGBA, frame->cols, frame->rows,
|
||||
frame->data);
|
||||
} else {
|
||||
tex.Update(frame->data);
|
||||
}
|
||||
// put back on shared freelist
|
||||
std::scoped_lock lock(sharedFreeListMutex);
|
||||
sharedFreeList.emplace_back(frame);
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Video")) {
|
||||
// render to window (best fit)
|
||||
if (tex && tex.GetWidth() != 0 && tex.GetHeight() != 0) {
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
ImVec2 windowPos = ImGui::GetWindowPos();
|
||||
ImVec2 imageMin = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 imageMax = ImGui::GetWindowContentRegionMax();
|
||||
gui::MaxFit(&imageMin, &imageMax, tex.GetWidth(), tex.GetHeight());
|
||||
drawList->AddImage(tex, windowPos + imageMin, windowPos + imageMax);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
});
|
||||
gui::Main();
|
||||
stopCamera = true;
|
||||
thr.join();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -120,6 +120,7 @@ public class CameraServerJNI {
|
||||
//
|
||||
// UsbCamera Source Functions
|
||||
//
|
||||
public static native void setUsbCameraPath(int source, String path);
|
||||
public static native String getUsbCameraPath(int source);
|
||||
public static native UsbCameraInfo getUsbCameraInfo(int source);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -40,6 +40,13 @@ public class UsbCamera extends VideoCamera {
|
||||
return CameraServerJNI.enumerateUsbCameras();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the path to the device.
|
||||
*/
|
||||
void setPath(String path) {
|
||||
CameraServerJNI.setUsbCameraPath(m_handle, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the device.
|
||||
*/
|
||||
|
||||
4
cscore/src/main/native/LinuxSymbolScript.txt
Normal file
4
cscore/src/main/native/LinuxSymbolScript.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
cscorejnicvstatic {
|
||||
global: CS_*; JNI_*; Java_*; # explicitly list symbols to be exported
|
||||
local: *; # hide everything else
|
||||
};
|
||||
3
cscore/src/main/native/MacSymbolScript.txt
Normal file
3
cscore/src/main/native/MacSymbolScript.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
_CS_*
|
||||
_JNI_*
|
||||
_Java_*
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -62,21 +62,21 @@ int ConfigurableSourceImpl::CreateProperty(const wpi::Twine& name,
|
||||
int maximum, int step,
|
||||
int defaultValue, int value) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
int ndx = CreateOrUpdateProperty(name,
|
||||
[=] {
|
||||
return std::make_unique<PropertyImpl>(
|
||||
name, kind, minimum, maximum, step,
|
||||
defaultValue, value);
|
||||
},
|
||||
[&](PropertyImpl& prop) {
|
||||
// update all but value
|
||||
prop.propKind = kind;
|
||||
prop.minimum = minimum;
|
||||
prop.maximum = maximum;
|
||||
prop.step = step;
|
||||
prop.defaultValue = defaultValue;
|
||||
value = prop.value;
|
||||
});
|
||||
int ndx = CreateOrUpdateProperty(
|
||||
name,
|
||||
[=] {
|
||||
return std::make_unique<PropertyImpl>(name, kind, minimum, maximum,
|
||||
step, defaultValue, value);
|
||||
},
|
||||
[&](PropertyImpl& prop) {
|
||||
// update all but value
|
||||
prop.propKind = kind;
|
||||
prop.minimum = minimum;
|
||||
prop.maximum = maximum;
|
||||
prop.step = step;
|
||||
prop.defaultValue = defaultValue;
|
||||
value = prop.value;
|
||||
});
|
||||
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
|
||||
kind, value, wpi::Twine{});
|
||||
return ndx;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -44,6 +44,11 @@ CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
|
||||
return cs::CreateUsbCameraPath(name, path, status);
|
||||
}
|
||||
|
||||
void CS_SetUsbCameraPath(CS_Source source, const char* path,
|
||||
CS_Status* status) {
|
||||
cs::SetUsbCameraPath(source, path, status);
|
||||
}
|
||||
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
return ConvertToC(cs::GetUsbCameraPath(source, status));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -1026,6 +1026,20 @@ Java_edu_wpi_cscore_CameraServerJNI_setCameraExposureManual
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setUsbCameraPath
|
||||
* Signature: (ILjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_setUsbCameraPath
|
||||
(JNIEnv* env, jclass, jint source, jstring path)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
cs::SetUsbCameraPath(source, JStringRef{env, path}.str(), &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: getUsbCameraPath
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -228,7 +228,7 @@ struct CS_Event {
|
||||
};
|
||||
|
||||
/**
|
||||
* USB camera infomation
|
||||
* USB camera information
|
||||
*/
|
||||
typedef struct CS_UsbCameraInfo {
|
||||
int dev;
|
||||
@@ -338,6 +338,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @defgroup cscore_usbcamera_cfunc UsbCamera Source Functions
|
||||
* @{
|
||||
*/
|
||||
void CS_SetUsbCameraPath(CS_Source source, const char* path, CS_Status* status);
|
||||
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -274,6 +274,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
|
||||
* @defgroup cscore_usbcamera_func UsbCamera Source Functions
|
||||
* @{
|
||||
*/
|
||||
void SetUsbCameraPath(CS_Source, const wpi::Twine& path, CS_Status* status);
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
|
||||
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2015-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -444,6 +444,11 @@ class UsbCamera : public VideoCamera {
|
||||
*/
|
||||
static std::vector<UsbCameraInfo> EnumerateUsbCameras();
|
||||
|
||||
/**
|
||||
* Change the path to the device.
|
||||
*/
|
||||
void SetPath(const wpi::Twine& path);
|
||||
|
||||
/**
|
||||
* Get the path to the device.
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Copyright (c) 2015-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef CSCORE_OO_INL_
|
||||
#define CSCORE_OO_INL_
|
||||
#ifndef CSCORE_CSCORE_OO_INL_
|
||||
#define CSCORE_CSCORE_OO_INL_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace cs {
|
||||
|
||||
@@ -76,7 +80,7 @@ inline VideoProperty::VideoProperty(CS_Property handle) : m_handle(handle) {
|
||||
}
|
||||
|
||||
inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
|
||||
: m_status(0), m_handle(handle), m_kind(kind) {}
|
||||
: m_status(0), m_handle(handle), m_kind(kind) {}
|
||||
|
||||
inline VideoSource::VideoSource(const VideoSource& source)
|
||||
: m_handle(source.m_handle == 0 ? 0
|
||||
@@ -255,6 +259,11 @@ inline std::vector<UsbCameraInfo> UsbCamera::EnumerateUsbCameras() {
|
||||
return ::cs::EnumerateUsbCameras(&status);
|
||||
}
|
||||
|
||||
inline void UsbCamera::SetPath(const wpi::Twine& path) {
|
||||
m_status = 0;
|
||||
return ::cs::SetUsbCameraPath(m_handle, path, &m_status);
|
||||
}
|
||||
|
||||
inline std::string UsbCamera::GetPath() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetUsbCameraPath(m_handle, &m_status);
|
||||
@@ -394,10 +403,10 @@ inline void ImageSource::SetDescription(const wpi::Twine& description) {
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name,
|
||||
VideoProperty::Kind kind,
|
||||
int minimum, int maximum,
|
||||
int step, int defaultValue,
|
||||
int value) {
|
||||
VideoProperty::Kind kind,
|
||||
int minimum, int maximum,
|
||||
int step, int defaultValue,
|
||||
int value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
|
||||
@@ -405,35 +414,41 @@ inline VideoProperty ImageSource::CreateProperty(const wpi::Twine& name,
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateIntegerProperty(const wpi::Twine& name,
|
||||
int minimum, int maximum,
|
||||
int step, int defaultValue,
|
||||
int value) {
|
||||
int minimum,
|
||||
int maximum, int step,
|
||||
int defaultValue,
|
||||
int value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kInteger)),
|
||||
m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kInteger)),
|
||||
minimum, maximum, step, defaultValue, value, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateBooleanProperty(const wpi::Twine& name,
|
||||
bool defaultValue,
|
||||
bool value) {
|
||||
bool defaultValue,
|
||||
bool value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kBoolean)),
|
||||
m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kBoolean)),
|
||||
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateStringProperty(const wpi::Twine& name,
|
||||
const wpi::Twine& value) {
|
||||
inline VideoProperty ImageSource::CreateStringProperty(
|
||||
const wpi::Twine& name, const wpi::Twine& value) {
|
||||
m_status = 0;
|
||||
auto prop = VideoProperty{CreateSourceProperty(
|
||||
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(VideoProperty::Kind::kString)),
|
||||
0, 0, 0, 0, 0, &m_status)};
|
||||
auto prop = VideoProperty{
|
||||
CreateSourceProperty(m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kString)),
|
||||
0, 0, 0, 0, 0, &m_status)};
|
||||
prop.SetString(value);
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, wpi::ArrayRef<std::string> choices) {
|
||||
m_status = 0;
|
||||
@@ -441,8 +456,8 @@ inline void ImageSource::SetEnumPropertyChoices(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void ImageSource::SetEnumPropertyChoices(const VideoProperty& property,
|
||||
std::initializer_list<T> choices) {
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, std::initializer_list<T> choices) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(choices.size());
|
||||
for (const auto& choice : choices) vec.emplace_back(choice);
|
||||
@@ -618,4 +633,4 @@ inline VideoListener::~VideoListener() {
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif /* CSCORE_OO_INL_ */
|
||||
#endif // CSCORE_CSCORE_OO_INL_
|
||||
|
||||
@@ -223,6 +223,25 @@ static bool GetDescriptionIoctl(const char* cpath, std::string* desc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsVideoCaptureDevice(const char* cpath) {
|
||||
int fd = open(cpath, O_RDWR);
|
||||
if (fd < 0) return false;
|
||||
|
||||
struct v4l2_capability vcap;
|
||||
std::memset(&vcap, 0, sizeof(vcap));
|
||||
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) < 0) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
return (vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
|
||||
(vcap.capabilities & V4L2_CAP_STREAMING) != 0 &&
|
||||
((vcap.capabilities & V4L2_CAP_DEVICE_CAPS) == 0 ||
|
||||
((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) != 0 &&
|
||||
(vcap.device_caps & V4L2_CAP_STREAMING) != 0));
|
||||
}
|
||||
|
||||
static int GetDeviceNum(const char* cpath) {
|
||||
wpi::StringRef path{cpath};
|
||||
std::string pathBuf;
|
||||
@@ -264,10 +283,10 @@ UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
|
||||
Notifier& notifier, Telemetry& telemetry,
|
||||
const wpi::Twine& path)
|
||||
: SourceImpl{name, logger, notifier, telemetry},
|
||||
m_path{path.str()},
|
||||
m_fd{-1},
|
||||
m_command_fd{eventfd(0, 0)},
|
||||
m_active{true} {
|
||||
m_active{true},
|
||||
m_path{path.str()} {
|
||||
SetDescription(GetDescriptionImpl(m_path.c_str()));
|
||||
SetQuirks();
|
||||
|
||||
@@ -765,6 +784,22 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
|
||||
return CS_OK;
|
||||
}
|
||||
|
||||
CS_StatusValue UsbCameraImpl::DeviceCmdSetPath(
|
||||
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
|
||||
m_path = msg.dataStr;
|
||||
lock.unlock();
|
||||
// disconnect and reconnect
|
||||
bool wasStreaming = m_streaming;
|
||||
if (wasStreaming) DeviceStreamOff();
|
||||
if (m_fd >= 0) {
|
||||
DeviceDisconnect();
|
||||
DeviceConnect();
|
||||
}
|
||||
if (wasStreaming) DeviceStreamOn();
|
||||
lock.lock();
|
||||
return CS_OK;
|
||||
}
|
||||
|
||||
CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
|
||||
std::unique_lock<wpi::mutex>& lock, const Message& msg) {
|
||||
if (msg.kind == Message::kCmdSetMode ||
|
||||
@@ -778,6 +813,8 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
|
||||
} else if (msg.kind == Message::kNumSinksChanged ||
|
||||
msg.kind == Message::kNumSinksEnabledChanged) {
|
||||
return CS_OK;
|
||||
} else if (msg.kind == Message::kCmdSetPath) {
|
||||
return DeviceCmdSetPath(lock, msg);
|
||||
} else {
|
||||
return CS_OK;
|
||||
}
|
||||
@@ -1358,6 +1395,17 @@ void UsbCameraImpl::NumSinksEnabledChanged() {
|
||||
Send(Message{Message::kNumSinksEnabledChanged});
|
||||
}
|
||||
|
||||
void UsbCameraImpl::SetPath(const wpi::Twine& path, CS_Status* status) {
|
||||
Message msg{Message::kCmdSetPath};
|
||||
msg.dataStr = path.str();
|
||||
*status = SendAndWait(std::move(msg));
|
||||
}
|
||||
|
||||
std::string UsbCameraImpl::GetPath() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_path;
|
||||
}
|
||||
|
||||
namespace cs {
|
||||
|
||||
CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
|
||||
@@ -1376,6 +1424,16 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
|
||||
inst.telemetry, path));
|
||||
}
|
||||
|
||||
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<UsbCameraImpl&>(*data->source).SetPath(path, status);
|
||||
}
|
||||
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
@@ -1453,6 +1511,8 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
path += fname;
|
||||
info.path = path.str();
|
||||
|
||||
if (!IsVideoCaptureDevice(path.c_str())) continue;
|
||||
|
||||
info.name = GetDescriptionImpl(path.c_str());
|
||||
if (info.name.empty()) continue;
|
||||
|
||||
|
||||
@@ -66,12 +66,14 @@ class UsbCameraImpl : public SourceImpl {
|
||||
void NumSinksChanged() override;
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
std::string GetPath() { return m_path; }
|
||||
void SetPath(const wpi::Twine& path, CS_Status* status);
|
||||
std::string GetPath() const;
|
||||
|
||||
// Messages passed to/from camera thread
|
||||
struct Message {
|
||||
enum Kind {
|
||||
kNone = 0,
|
||||
kCmdSetPath,
|
||||
kCmdSetMode,
|
||||
kCmdSetPixelFormat,
|
||||
kCmdSetResolution,
|
||||
@@ -132,6 +134,8 @@ class UsbCameraImpl : public SourceImpl {
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
CS_StatusValue DeviceCmdSetPath(std::unique_lock<wpi::mutex>& lock,
|
||||
const Message& msg);
|
||||
|
||||
// Property helper functions
|
||||
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
|
||||
@@ -152,11 +156,6 @@ class UsbCameraImpl : public SourceImpl {
|
||||
static constexpr int kNumBuffers = 4;
|
||||
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
std::atomic_int m_fd;
|
||||
std::atomic_int m_command_fd; // for command eventfd
|
||||
|
||||
@@ -176,6 +175,9 @@ class UsbCameraImpl : public SourceImpl {
|
||||
mutable std::vector<Message> m_commands;
|
||||
mutable std::vector<std::pair<std::thread::id, CS_StatusValue>> m_responses;
|
||||
mutable wpi::condition_variable m_responseCv;
|
||||
|
||||
// Path
|
||||
std::string m_path;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2016-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -21,6 +21,11 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return std::string{};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -198,6 +198,20 @@ void UsbCameraImpl::NumSinksEnabledChanged() {
|
||||
SetCameraMessage, Message::kNumSinksEnabledChanged, nullptr);
|
||||
}
|
||||
|
||||
void UsbCameraImpl::SetPath(const wpi::Twine& path, CS_Status* status) {
|
||||
Message msg{Message::kCmdSetPath};
|
||||
msg.dataStr = path.str();
|
||||
auto result =
|
||||
m_messagePump->SendWindowMessage<CS_Status, Message::Kind, Message*>(
|
||||
SetCameraMessage, msg.kind, &msg);
|
||||
*status = result;
|
||||
}
|
||||
|
||||
std::string UsbCameraImpl::GetPath() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_path;
|
||||
}
|
||||
|
||||
void UsbCameraImpl::StartMessagePump() {
|
||||
m_messagePump = std::make_unique<WindowsMessagePump>(
|
||||
[this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
|
||||
@@ -705,6 +719,16 @@ CS_StatusValue UsbCameraImpl::DeviceProcessCommand(
|
||||
DeviceStreamOn();
|
||||
}
|
||||
return CS_OK;
|
||||
} else if (msgKind == Message::kCmdSetPath) {
|
||||
{
|
||||
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());
|
||||
}
|
||||
DeviceDisconnect();
|
||||
DeviceConnect();
|
||||
return CS_OK;
|
||||
} else {
|
||||
return CS_OK;
|
||||
}
|
||||
@@ -966,6 +990,27 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
|
||||
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
|
||||
}
|
||||
|
||||
static void ParseVidAndPid(wpi::StringRef path, int* pid, int* vid) {
|
||||
auto vidIndex = path.find_lower("vid_");
|
||||
auto pidIndex = path.find_lower("pid_");
|
||||
|
||||
if (vidIndex != wpi::StringRef::npos) {
|
||||
auto vidSlice = path.slice(vidIndex + 4, vidIndex + 8);
|
||||
uint16_t val = 0;
|
||||
if (!vidSlice.getAsInteger(16, val)) {
|
||||
*vid = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (pidIndex != wpi::StringRef::npos) {
|
||||
auto pidSlice = path.slice(pidIndex + 4, pidIndex + 8);
|
||||
uint16_t val = 0;
|
||||
if (!pidSlice.getAsInteger(16, val)) {
|
||||
*pid = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
std::vector<UsbCameraInfo> retval;
|
||||
|
||||
@@ -1012,6 +1057,10 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buf,
|
||||
sizeof(buf) / sizeof(WCHAR), NULL);
|
||||
info.path = utf8_conv.to_bytes(buf);
|
||||
|
||||
// Try to parse path from symbolic link
|
||||
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
||||
|
||||
retval.emplace_back(std::move(info));
|
||||
}
|
||||
|
||||
@@ -1052,6 +1101,16 @@ CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
|
||||
return inst.CreateSource(CS_SOURCE_USB, source);
|
||||
}
|
||||
|
||||
void SetUsbCameraPath(CS_Source source, const wpi::Twine& path,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
static_cast<UsbCameraImpl&>(*data->source).SetPath(path, status);
|
||||
}
|
||||
|
||||
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || data->kind != CS_SOURCE_USB) {
|
||||
@@ -1070,7 +1129,10 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
info.path = static_cast<UsbCameraImpl&>(*data->source).GetPath();
|
||||
// TODO: dev and name
|
||||
wpi::SmallVector<char, 64> buf;
|
||||
info.name = static_cast<UsbCameraImpl&>(*data->source).GetDescription(buf);
|
||||
ParseVidAndPid(info.path, &info.productId, &info.vendorId);
|
||||
info.dev = -1; // We have lost dev information by this point in time.
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -78,12 +78,14 @@ class UsbCameraImpl : public SourceImpl,
|
||||
void ProcessFrame(IMFSample* sample, const VideoMode& mode);
|
||||
void PostRequestNewFrame();
|
||||
|
||||
std::string GetPath() { return m_path; }
|
||||
void SetPath(const wpi::Twine& path, CS_Status* status);
|
||||
std::string GetPath() const;
|
||||
|
||||
// Messages passed to/from camera thread
|
||||
struct Message {
|
||||
enum Kind {
|
||||
kNone = 0,
|
||||
kCmdSetPath,
|
||||
kCmdSetMode,
|
||||
kCmdSetPixelFormat,
|
||||
kCmdSetResolution,
|
||||
@@ -116,9 +118,6 @@ class UsbCameraImpl : public SourceImpl,
|
||||
bool CacheProperties(CS_Status* status) const override;
|
||||
|
||||
private:
|
||||
// The camera processing thread
|
||||
void CameraThreadMain();
|
||||
|
||||
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
|
||||
@@ -171,9 +170,6 @@ class UsbCameraImpl : public SourceImpl,
|
||||
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
|
||||
ComPtr<IMFMediaType> m_currentMode;
|
||||
|
||||
//
|
||||
// Path never changes, so not protected by mutex.
|
||||
//
|
||||
std::string m_path;
|
||||
|
||||
std::wstring m_widePath;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2018-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
@@ -56,6 +56,6 @@ class UsbCameraTest {
|
||||
private static int getNonexistentCameraDev() {
|
||||
return Arrays.stream(CameraServerJNI.enumerateUsbCameras())
|
||||
.mapToInt(info -> info.dev)
|
||||
.max().orElse(-1) + 1;
|
||||
.max().orElse(-1) + 20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':cscore')
|
||||
evaluationDependsOn(':hal')
|
||||
evaluationDependsOn(':cameraserver')
|
||||
evaluationDependsOn(':wpimath')
|
||||
evaluationDependsOn(':wpilibc')
|
||||
evaluationDependsOn(':wpilibj')
|
||||
evaluationDependsOn(':wpilibOldCommands')
|
||||
@@ -30,14 +31,16 @@ cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
cppProjectZips.add(project(':ntcore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cscore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpimath').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibOldCommands').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibNewCommands').cppHeadersZip)
|
||||
|
||||
doxygen {
|
||||
executables {
|
||||
doxygen version : '1.8.16'
|
||||
}
|
||||
executables {
|
||||
doxygen version : '1.8.18',
|
||||
baseURI : 'https://frcmaven.wpi.edu/artifactory/generic-release-mirror/doxygen'
|
||||
}
|
||||
}
|
||||
|
||||
doxygen {
|
||||
@@ -101,17 +104,19 @@ ext {
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
task generateJavaDocs(type: Javadoc) {
|
||||
classpath += project(":wpiutil").sourceSets.main.compileClasspath
|
||||
classpath += project(":wpimath").sourceSets.main.compileClasspath
|
||||
options.links("https://docs.oracle.com/en/java/javase/11/docs/api/")
|
||||
options.addStringOption "tag", "pre:a:Pre-Condition"
|
||||
options.addBooleanOption "Xdoclint:html,missing,reference,syntax", true
|
||||
options.addBooleanOption('html5', true)
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':hal').generateUsageReporting
|
||||
dependsOn project(':wpimath').generateNat
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
source project(':ntcore').sourceSets.main.java
|
||||
source project(':wpimath').sourceSets.main.java
|
||||
source project(':wpilibj').sourceSets.main.java
|
||||
source project(':cameraserver').sourceSets.main.java
|
||||
source project(':wpilibOldCommands').sourceSets.main.java
|
||||
@@ -125,7 +130,9 @@ task generateJavaDocs(type: Javadoc) {
|
||||
ext.entryPoint = "$destinationDir/index.html"
|
||||
|
||||
if (JavaVersion.current().isJava11Compatible()) {
|
||||
options.addBooleanOption('-no-module-directories', true)
|
||||
if (!JavaVersion.current().isJava12Compatible()) {
|
||||
options.addBooleanOption('-no-module-directories', true)
|
||||
}
|
||||
doLast {
|
||||
// This is a work-around for https://bugs.openjdk.java.net/browse/JDK-8211194. Can be removed once that issue is fixed on JDK's side
|
||||
// Since JDK 11, package-list is missing from javadoc output files and superseded by element-list file, but a lot of external tools still need it
|
||||
@@ -143,11 +150,10 @@ tasks.register("zipJavaDocs", Zip) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
addTaskToCopyAllOutputs(zipCppDocs)
|
||||
addTaskToCopyAllOutputs(zipJavaDocs)
|
||||
|
||||
build.dependsOn zipCppDocs
|
||||
build.dependsOn zipJavaDocs
|
||||
tasks.register("zipDocs") {
|
||||
dependsOn zipCppDocs
|
||||
dependsOn zipJavaDocs
|
||||
}
|
||||
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
|
||||
69
glass/CMakeLists.txt
Normal file
69
glass/CMakeLists.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
project(glass)
|
||||
|
||||
include(CompileWarnings)
|
||||
include(LinkMacOSGUI)
|
||||
|
||||
#
|
||||
# libglass
|
||||
#
|
||||
file(GLOB_RECURSE libglass_src src/lib/native/cpp/*.cpp)
|
||||
|
||||
add_library(libglass STATIC ${libglass_src})
|
||||
set_target_properties(libglass PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glass")
|
||||
set_property(TARGET libglass PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
set_property(TARGET libglass PROPERTY FOLDER "libraries")
|
||||
|
||||
wpilib_target_warnings(libglass)
|
||||
target_link_libraries(libglass PUBLIC wpigui wpimath wpiutil)
|
||||
|
||||
target_include_directories(libglass PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/lib/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/glass>)
|
||||
|
||||
install(TARGETS libglass EXPORT libglass DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/lib/native/include/ DESTINATION "${include_dest}/glass")
|
||||
|
||||
#
|
||||
# libglassnt
|
||||
#
|
||||
file(GLOB_RECURSE libglassnt_src src/libnt/native/cpp/*.cpp)
|
||||
|
||||
add_library(libglassnt STATIC ${libglassnt_src})
|
||||
set_target_properties(libglassnt PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glassnt")
|
||||
set_property(TARGET libglassnt PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
set_property(TARGET libglassnt PROPERTY FOLDER "libraries")
|
||||
|
||||
wpilib_target_warnings(libglassnt)
|
||||
target_link_libraries(libglassnt PUBLIC ntcore libglass)
|
||||
|
||||
target_include_directories(libglassnt PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/libnt/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/glass>)
|
||||
|
||||
install(TARGETS libglassnt EXPORT libglassnt DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/libnt/native/include/ DESTINATION "${include_dest}/glass")
|
||||
|
||||
#
|
||||
# glass application
|
||||
#
|
||||
|
||||
file(GLOB glass_src src/app/native/cpp/*.cpp)
|
||||
|
||||
add_executable(glass ${glass_src})
|
||||
wpilib_link_macos_gui(glass)
|
||||
target_link_libraries(glass libglassnt libglass)
|
||||
if (WIN32)
|
||||
set_target_properties(glass PROPERTIES WIN32_EXECUTABLE YES)
|
||||
endif()
|
||||
|
||||
#if (MSVC OR FLAT_INSTALL_WPILIB)
|
||||
# set (wpigui_config_dir ${wpilib_dest})
|
||||
#else()
|
||||
# set (wpigui_config_dir share/wpigui)
|
||||
#endif()
|
||||
|
||||
#configure_file(wpigui-config.cmake.in ${CMAKE_BINARY_DIR}/wpigui-config.cmake )
|
||||
#install(FILES ${CMAKE_BINARY_DIR}/wpigui-config.cmake DESTINATION ${wpigui_config_dir})
|
||||
#install(EXPORT wpigui DESTINATION ${wpigui_config_dir})
|
||||
30
glass/Info.plist
Normal file
30
glass/Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>Glass</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>glass</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Glass</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>edu.wpi.first.tools.Glass</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2021</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2021</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.11</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
132
glass/build.gradle
Normal file
132
glass/build.gradle
Normal file
@@ -0,0 +1,132 @@
|
||||
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
|
||||
|
||||
description = "A different kind of dashboard"
|
||||
|
||||
apply plugin: 'cpp'
|
||||
apply plugin: 'c'
|
||||
apply plugin: 'google-test-test-suite'
|
||||
apply plugin: 'visual-studio'
|
||||
apply plugin: 'edu.wpi.first.NativeUtils'
|
||||
|
||||
ext {
|
||||
nativeName = 'glass'
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/config.gradle"
|
||||
|
||||
project(':').libraryBuild.dependsOn build
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
glass {
|
||||
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
|
||||
'_TI5?AVfailure', '_CT??_R0?AVout_of_range', '_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range', '_CT??_R0?AVbad_cast']
|
||||
}
|
||||
}
|
||||
|
||||
model {
|
||||
components {
|
||||
"${nativeName}"(NativeLibrarySpec) {
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ['src/lib/native/cpp']
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/lib/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it instanceof SharedLibraryBinarySpec) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
}
|
||||
appendDebugPathToBinaries(binaries)
|
||||
}
|
||||
"${nativeName}nt"(NativeLibrarySpec) {
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs = ['src/libnt/native/cpp']
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/libnt/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it instanceof SharedLibraryBinarySpec) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
}
|
||||
appendDebugPathToBinaries(binaries)
|
||||
}
|
||||
// By default, a development executable will be generated. This is to help the case of
|
||||
// testing specific functionality of the library.
|
||||
"${nativeName}App"(NativeExecutableSpec) {
|
||||
baseName = 'glass'
|
||||
sources {
|
||||
cpp {
|
||||
source {
|
||||
srcDirs 'src/app/native/cpp'
|
||||
include '**/*.cpp'
|
||||
}
|
||||
exportedHeaders {
|
||||
srcDirs 'src/app/native/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
binaries.all {
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
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, 'imgui_static')
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
88
glass/publish.gradle
Normal file
88
glass/publish.gradle
Normal file
@@ -0,0 +1,88 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
def baseArtifactId = 'Glass'
|
||||
def artifactGroupId = 'edu.wpi.first.tools'
|
||||
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_Glass_CLS'
|
||||
|
||||
model {
|
||||
publishing {
|
||||
def tasks = []
|
||||
$.components.each { component ->
|
||||
component.binaries.each { binary ->
|
||||
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("glassApp")) {
|
||||
if (binary.buildable && binary.name.contains("Release")) {
|
||||
// We are now in the binary that we want.
|
||||
// This is the default application path for the ZIP task.
|
||||
def applicationPath = binary.executable.file
|
||||
|
||||
// Create the macOS bundle.
|
||||
def bundleTask = project.tasks.create("bundleGlassOsxApp", Copy) {
|
||||
description("Creates a macOS application bundle for Glass")
|
||||
from(file("$project.projectDir/Info.plist"))
|
||||
into(file("$project.buildDir/outputs/bundles/Glass.app/Contents"))
|
||||
into("MacOS") { with copySpec { from binary.executable.file } }
|
||||
|
||||
doLast {
|
||||
if (project.hasProperty("developerID")) {
|
||||
// Get path to binary.
|
||||
exec {
|
||||
workingDir rootDir
|
||||
def args = ["sh", "-c", "codesign --force --strict --deep " +
|
||||
"--timestamp --options=runtime " +
|
||||
"--verbose -s ${project.findProperty("developerID")} " +
|
||||
"$project.buildDir/outputs/bundles/Glass.app/"]
|
||||
commandLine args
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the application path if we are creating a bundle.
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
applicationPath = file("$project.buildDir/outputs/bundles")
|
||||
project.build.dependsOn bundleTask
|
||||
}
|
||||
|
||||
// Create the ZIP.
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
def task = project.tasks.create("copyGlassExecutable", Zip) {
|
||||
description("Copies the Glass executable to the outputs directory.")
|
||||
destinationDir(outputsFolder)
|
||||
|
||||
archiveBaseName = '_M_' + zipBaseName
|
||||
duplicatesStrategy = 'exclude'
|
||||
classifier = nativeUtils.getPublishClassifier(binary)
|
||||
|
||||
from(licenseFile) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from(applicationPath)
|
||||
into(nativeUtils.getPlatformPath(binary))
|
||||
}
|
||||
|
||||
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
bundleTask.dependsOn binary.tasks.link
|
||||
task.dependsOn(bundleTask)
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
tasks.add(task)
|
||||
project.build.dependsOn task
|
||||
project.artifacts { task }
|
||||
addTaskToCopyAllOutputs(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
cpp(MavenPublication) {
|
||||
tasks.each { artifact it }
|
||||
artifactId = baseArtifactId
|
||||
groupId = artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
glass/src/app/native/cpp/NetworkTablesSettings.cpp
Normal file
71
glass/src/app/native/cpp/NetworkTablesSettings.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "NetworkTablesSettings.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
|
||||
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
|
||||
const char* storageName)
|
||||
: m_inst{inst} {
|
||||
auto& storage = glass::GetStorage(storageName);
|
||||
m_pMode = storage.GetIntRef("mode");
|
||||
m_pIniName = storage.GetStringRef("iniName", "networktables.ini");
|
||||
m_pServerTeam = storage.GetStringRef("serverTeam");
|
||||
m_pListenAddress = storage.GetStringRef("listenAddress");
|
||||
}
|
||||
|
||||
void NetworkTablesSettings::Update() {
|
||||
if (!m_restart) return;
|
||||
m_restart = false;
|
||||
nt::StopClient(m_inst);
|
||||
nt::StopServer(m_inst);
|
||||
nt::StopLocal(m_inst);
|
||||
if (*m_pMode == 1) {
|
||||
wpi::StringRef serverTeam{*m_pServerTeam};
|
||||
unsigned int team;
|
||||
if (!serverTeam.contains('.') && !serverTeam.getAsInteger(10, team)) {
|
||||
nt::StartClientTeam(m_inst, team, NT_DEFAULT_PORT);
|
||||
} else {
|
||||
wpi::SmallVector<wpi::StringRef, 4> serverNames;
|
||||
wpi::SmallVector<std::pair<wpi::StringRef, unsigned int>, 4> servers;
|
||||
serverTeam.split(serverNames, ',', -1, false);
|
||||
for (auto&& serverName : serverNames)
|
||||
servers.emplace_back(serverName, NT_DEFAULT_PORT);
|
||||
nt::StartClient(m_inst, servers);
|
||||
}
|
||||
} else if (*m_pMode == 2) {
|
||||
nt::StartServer(m_inst, m_pIniName->c_str(), m_pListenAddress->c_str(),
|
||||
NT_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTablesSettings::Display() {
|
||||
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
|
||||
ImGui::Combo("Mode", m_pMode, modeOptions, 3);
|
||||
switch (*m_pMode) {
|
||||
case 1:
|
||||
ImGui::InputText("Team/IP", m_pServerTeam);
|
||||
break;
|
||||
case 2:
|
||||
ImGui::InputText("Listen Address", m_pListenAddress);
|
||||
ImGui::InputText("ini Filename", m_pIniName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ImGui::Button("Apply")) m_restart = true;
|
||||
}
|
||||
35
glass/src/app/native/cpp/NetworkTablesSettings.h
Normal file
35
glass/src/app/native/cpp/NetworkTablesSettings.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
|
||||
namespace wpi {
|
||||
template <typename T>
|
||||
class SmallVectorImpl;
|
||||
} // namespace wpi
|
||||
|
||||
class NetworkTablesSettings {
|
||||
public:
|
||||
explicit NetworkTablesSettings(
|
||||
NT_Inst inst = nt::GetDefaultInstance(),
|
||||
const char* storageName = "NetworkTables Settings");
|
||||
|
||||
void Update();
|
||||
void Display();
|
||||
|
||||
private:
|
||||
NT_Inst m_inst;
|
||||
bool m_restart = true;
|
||||
int* m_pMode;
|
||||
std::string* m_pIniName;
|
||||
std::string* m_pServerTeam;
|
||||
std::string* m_pListenAddress;
|
||||
};
|
||||
137
glass/src/app/native/cpp/main.cpp
Normal file
137
glass/src/app/native/cpp/main.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "NetworkTablesSettings.h"
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
#include "glass/networktables/NetworkTables.h"
|
||||
#include "glass/networktables/NetworkTablesProvider.h"
|
||||
#include "glass/other/Plot.h"
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
static std::unique_ptr<glass::PlotProvider> gPlotProvider;
|
||||
static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
|
||||
|
||||
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
|
||||
static std::unique_ptr<NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static glass::Window* gNetworkTablesWindow;
|
||||
static glass::Window* gNetworkTablesSettingsWindow;
|
||||
|
||||
static void NtInitialize() {
|
||||
// update window title when connection status changes
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateConnectionListenerPoller(inst);
|
||||
nt::AddPolledConnectionListener(poller, true);
|
||||
gui::AddEarlyExecute([poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) return;
|
||||
bool timedOut;
|
||||
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
|
||||
if (event.connected) {
|
||||
wpi::SmallString<64> title;
|
||||
title = "Glass - Connected (";
|
||||
title += event.conn.remote_ip;
|
||||
title += ')';
|
||||
glfwSetWindowTitle(win, title.c_str());
|
||||
} else {
|
||||
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// NetworkTables table window
|
||||
gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
|
||||
gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
|
||||
|
||||
gNetworkTablesWindow = gNtProvider->AddWindow(
|
||||
"NetworkTables",
|
||||
std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
|
||||
if (gNetworkTablesWindow) {
|
||||
gNetworkTablesWindow->SetDefaultPos(250, 277);
|
||||
gNetworkTablesWindow->SetDefaultSize(750, 185);
|
||||
gNetworkTablesWindow->DisableRenamePopup();
|
||||
}
|
||||
|
||||
// NetworkTables settings window
|
||||
gNetworkTablesSettings = std::make_unique<NetworkTablesSettings>();
|
||||
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
|
||||
|
||||
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
|
||||
"NetworkTables Settings", [] { gNetworkTablesSettings->Display(); });
|
||||
if (gNetworkTablesSettingsWindow) {
|
||||
gNetworkTablesSettingsWindow->SetDefaultPos(30, 30);
|
||||
gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
gNetworkTablesSettingsWindow->DisableRenamePopup();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
#else
|
||||
int main() {
|
||||
#endif
|
||||
gui::CreateContext();
|
||||
glass::CreateContext();
|
||||
|
||||
gPlotProvider = std::make_unique<glass::PlotProvider>("Plot");
|
||||
gNtProvider = std::make_unique<glass::NetworkTablesProvider>("NTProvider");
|
||||
|
||||
gui::ConfigurePlatformSaveFile("glass.ini");
|
||||
gPlotProvider->GlobalInit();
|
||||
gui::AddInit([] { gPlotProvider->ResetTime(); });
|
||||
gNtProvider->GlobalInit();
|
||||
gui::AddInit(NtInitialize);
|
||||
|
||||
glass::AddStandardNetworkTablesViews(*gNtProvider);
|
||||
|
||||
gui::AddLateExecute([] {
|
||||
ImGui::BeginMainMenuBar();
|
||||
gui::EmitViewMenu();
|
||||
if (ImGui::BeginMenu("NetworkTables")) {
|
||||
if (gNetworkTablesSettingsWindow)
|
||||
gNetworkTablesSettingsWindow->DisplayMenuItem("NetworkTables Settings");
|
||||
if (gNetworkTablesWindow)
|
||||
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
|
||||
ImGui::Separator();
|
||||
gNtProvider->DisplayMenu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Plot")) {
|
||||
bool paused = gPlotProvider->IsPaused();
|
||||
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
|
||||
gPlotProvider->SetPaused(paused);
|
||||
}
|
||||
if (ImGui::MenuItem("Reset Plot Time")) gPlotProvider->ResetTime();
|
||||
ImGui::Separator();
|
||||
gPlotProvider->DisplayMenu();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
});
|
||||
|
||||
gui::Initialize("Glass - DISCONNECTED", 1024, 768);
|
||||
gui::Main();
|
||||
|
||||
gNetworkTablesModel.reset();
|
||||
gNetworkTablesSettings.reset();
|
||||
gNtProvider.reset();
|
||||
gPlotProvider.reset();
|
||||
|
||||
glass::DestroyContext();
|
||||
gui::DestroyContext();
|
||||
}
|
||||
422
glass/src/lib/native/cpp/Context.cpp
Normal file
422
glass/src/lib/native/cpp/Context.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/Context.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
Context* glass::gContext;
|
||||
|
||||
static bool ConvertInt(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt;
|
||||
if (value->stringVal.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->intVal))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertInt64(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt64;
|
||||
if (value->stringVal.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
if (wpi::StringRef{value->stringVal}.getAsInteger(10, value->int64Val))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertBool(Storage::Value* value) {
|
||||
value->type = Storage::Value::kBool;
|
||||
if (value->stringVal.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
int val;
|
||||
if (wpi::StringRef{value->stringVal}.getAsInteger(10, val)) {
|
||||
return false;
|
||||
}
|
||||
value->boolVal = (val != 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertFloat(Storage::Value* value) {
|
||||
value->type = Storage::Value::kFloat;
|
||||
if (value->stringVal.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
if (std::sscanf(value->stringVal.c_str(), "%f", &value->floatVal) != 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertDouble(Storage::Value* value) {
|
||||
value->type = Storage::Value::kDouble;
|
||||
if (value->stringVal.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
if (std::sscanf(value->stringVal.c_str(), "%lf", &value->doubleVal) != 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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::StringRef{line}.split('=');
|
||||
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));
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
|
||||
return a->getKey() < b->getKey();
|
||||
});
|
||||
|
||||
for (auto&& entryIt : sorted) {
|
||||
auto& entry = *entryIt;
|
||||
out_buf->append("[GlassStorage][");
|
||||
out_buf->append(entry.first().begin(), entry.first().end());
|
||||
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;
|
||||
}
|
||||
}
|
||||
out_buf->append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ctx->sources.Initialize();
|
||||
});
|
||||
}
|
||||
|
||||
static void Shutdown(Context* ctx) {}
|
||||
|
||||
Context* glass::CreateContext() {
|
||||
Context* ctx = new Context;
|
||||
if (!gContext) SetCurrentContext(ctx);
|
||||
Initialize(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void glass::DestroyContext(Context* ctx) {
|
||||
if (!ctx) ctx = gContext;
|
||||
Shutdown(ctx);
|
||||
if (gContext == ctx) SetCurrentContext(nullptr);
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
Context* glass::GetCurrentContext() { return gContext; }
|
||||
|
||||
void glass::SetCurrentContext(Context* ctx) { gContext = ctx; }
|
||||
|
||||
Storage::Value& Storage::GetValue(wpi::StringRef 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()];
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFUN(CapsName, LowerName, CType) \
|
||||
CType Storage::Get##CapsName(wpi::StringRef 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(wpi::StringRef 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(wpi::StringRef 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(wpi::StringRef key,
|
||||
const std::string& 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()];
|
||||
value.type = Value::kString;
|
||||
return value.stringVal;
|
||||
}
|
||||
|
||||
void Storage::SetString(wpi::StringRef key, const wpi::Twine& 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.str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string* Storage::GetStringRef(wpi::StringRef key,
|
||||
wpi::StringRef 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;
|
||||
} else {
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
return &value.stringVal;
|
||||
}
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage() {
|
||||
auto& storage = gContext->storage[gContext->curId];
|
||||
if (!storage) storage = std::make_unique<Storage>();
|
||||
return *storage;
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage(wpi::StringRef id) {
|
||||
auto& storage = gContext->storage[id];
|
||||
if (!storage) storage = std::make_unique<Storage>();
|
||||
return *storage;
|
||||
}
|
||||
|
||||
static void PushIDStack(wpi::StringRef label_id) {
|
||||
gContext->idStack.emplace_back(gContext->curId.size());
|
||||
|
||||
auto [label, id] = wpi::StringRef{label_id}.split("###");
|
||||
// if no ###id, use label as id
|
||||
if (id.empty()) id = label;
|
||||
if (!gContext->curId.empty()) gContext->curId += "###";
|
||||
gContext->curId += id;
|
||||
}
|
||||
|
||||
static void PopIDStack() {
|
||||
gContext->curId.resize(gContext->idStack.back());
|
||||
gContext->idStack.pop_back();
|
||||
}
|
||||
|
||||
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
|
||||
PushIDStack(name);
|
||||
return ImGui::Begin(name, p_open, flags);
|
||||
}
|
||||
|
||||
void glass::End() {
|
||||
ImGui::End();
|
||||
PopIDStack();
|
||||
}
|
||||
|
||||
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
|
||||
ImGuiWindowFlags flags) {
|
||||
PushIDStack(str_id);
|
||||
return ImGui::BeginChild(str_id, size, border, flags);
|
||||
}
|
||||
|
||||
void glass::EndChild() {
|
||||
ImGui::EndChild();
|
||||
PopIDStack();
|
||||
}
|
||||
|
||||
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
wpi::SmallString<64> openKey;
|
||||
auto [name, id] = wpi::StringRef{label}.split("###");
|
||||
// if no ###id, use name as id
|
||||
if (id.empty()) id = name;
|
||||
openKey = id;
|
||||
openKey += "###open";
|
||||
|
||||
bool* open = GetStorage().GetBoolRef(openKey);
|
||||
*open = ImGui::CollapsingHeader(
|
||||
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
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();
|
||||
return *open;
|
||||
}
|
||||
|
||||
void glass::TreePop() {
|
||||
ImGui::TreePop();
|
||||
PopIDStack();
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id) {
|
||||
PushIDStack(str_id);
|
||||
ImGui::PushID(str_id);
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
|
||||
PushIDStack(wpi::StringRef(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);
|
||||
ImGui::PushID(int_id);
|
||||
}
|
||||
|
||||
void glass::PopID() {
|
||||
ImGui::PopID();
|
||||
PopIDStack();
|
||||
}
|
||||
|
||||
bool glass::PopupEditName(const char* label, std::string* name) {
|
||||
bool rv = false;
|
||||
if (ImGui::BeginPopupContextItem(label)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (ImGui::InputText("##editname", name)) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
142
glass/src/lib/native/cpp/DataSource.cpp
Normal file
142
glass/src/lib/native/cpp/DataSource.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
|
||||
|
||||
DataSource::DataSource(const wpi::Twine& id) : m_id{id.str()} {
|
||||
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;
|
||||
sourceCreated(m_id.c_str(), this);
|
||||
}
|
||||
|
||||
DataSource::DataSource(const wpi::Twine& id, int index)
|
||||
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(']')} {}
|
||||
|
||||
DataSource::DataSource(const wpi::Twine& id, int index, int index2)
|
||||
: DataSource{id + wpi::Twine('[') + wpi::Twine(index) + wpi::Twine(',') +
|
||||
wpi::Twine(index2) + wpi::Twine(']')} {}
|
||||
|
||||
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(const wpi::Twine& 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);
|
||||
}
|
||||
|
||||
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
LabelTextV(label, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Add a label+text combo aligned to other label+value widgets
|
||||
void DataSource::LabelTextV(const char* label, const char* fmt,
|
||||
va_list args) const {
|
||||
ImGui::PushID(label);
|
||||
ImGui::LabelTextV("##input", fmt, args);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
ImGui::PopID();
|
||||
EmitDrag();
|
||||
}
|
||||
|
||||
bool DataSource::Combo(const char* label, int* current_item,
|
||||
const char* const items[], int items_count,
|
||||
int popup_max_height_in_items) const {
|
||||
ImGui::PushID(label);
|
||||
bool rv = ImGui::Combo("##input", current_item, items, items_count,
|
||||
popup_max_height_in_items);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool DataSource::SliderFloat(const char* label, float* v, float v_min,
|
||||
float v_max, const char* format,
|
||||
float power) const {
|
||||
ImGui::PushID(label);
|
||||
bool rv = ImGui::SliderFloat("##input", v, v_min, v_max, format, power);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool DataSource::InputDouble(const char* label, double* v, double step,
|
||||
double step_fast, const char* format,
|
||||
ImGuiInputTextFlags flags) const {
|
||||
ImGui::PushID(label);
|
||||
bool rv = ImGui::InputDouble("##input", v, step, step_fast, format, flags);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool DataSource::InputInt(const char* label, int* v, int step, int step_fast,
|
||||
ImGuiInputTextFlags flags) const {
|
||||
ImGui::PushID(label);
|
||||
bool rv = ImGui::InputInt("##input", v, step, step_fast, flags);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
|
||||
void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
|
||||
if (ImGui::BeginDragDropSource(flags)) {
|
||||
auto self = this;
|
||||
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self));
|
||||
const char* name = GetName();
|
||||
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
}
|
||||
|
||||
DataSource* DataSource::Find(wpi::StringRef id) {
|
||||
auto it = gContext->sources.find(id);
|
||||
if (it == gContext->sources.end()) return nullptr;
|
||||
return it->getValue().source;
|
||||
}
|
||||
52
glass/src/lib/native/cpp/MainMenuBar.cpp
Normal file
52
glass/src/lib/native/cpp/MainMenuBar.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/MainMenuBar.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <wpigui.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
|
||||
if (menu) m_menus.emplace_back(std::move(menu));
|
||||
}
|
||||
|
||||
void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
|
||||
if (menu) m_optionMenus.emplace_back(std::move(menu));
|
||||
}
|
||||
|
||||
void MainMenuBar::Display() {
|
||||
ImGui::BeginMainMenuBar();
|
||||
|
||||
if (!m_optionMenus.empty()) {
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
for (auto&& menu : m_optionMenus) {
|
||||
if (menu) menu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
wpi::gui::EmitViewMenu();
|
||||
|
||||
for (auto&& menu : m_menus) {
|
||||
if (menu) menu();
|
||||
}
|
||||
|
||||
#if 0
|
||||
char str[64];
|
||||
std::snprintf(str, sizeof(str), "%.3f ms/frame (%.1f FPS)",
|
||||
1000.0f / ImGui::GetIO().Framerate,
|
||||
ImGui::GetIO().Framerate);
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize(str).x -
|
||||
10);
|
||||
ImGui::Text("%s", str);
|
||||
#endif
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
int main() {}
|
||||
#include "glass/Model.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
bool Model::IsReadOnly() { return false; }
|
||||
30
glass/src/lib/native/cpp/View.cpp
Normal file
30
glass/src/lib/native/cpp/View.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/View.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace {
|
||||
class FunctionView : public View {
|
||||
public:
|
||||
explicit FunctionView(wpi::unique_function<void()> display)
|
||||
: m_display(std::move(display)) {}
|
||||
|
||||
void Display() override { m_display(); }
|
||||
|
||||
private:
|
||||
wpi::unique_function<void()> m_display;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<View> glass::MakeFunctionView(
|
||||
wpi::unique_function<void()> display) {
|
||||
return std::make_unique<FunctionView>(std::move(display));
|
||||
}
|
||||
|
||||
void View::Hidden() {}
|
||||
102
glass/src/lib/native/cpp/Window.cpp
Normal file
102
glass/src/lib/native/cpp/Window.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/Window.h"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/StringRef.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::Display() {
|
||||
if (!m_view) return;
|
||||
if (!m_visible || !m_enabled) {
|
||||
PushID(m_id);
|
||||
m_view->Hidden();
|
||||
PopID();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_posCond != 0) ImGui::SetNextWindowPos(m_pos, m_posCond);
|
||||
if (m_sizeCond != 0) ImGui::SetNextWindowSize(m_size, m_sizeCond);
|
||||
if (m_setPadding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, m_padding);
|
||||
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###%s",
|
||||
m_name.empty() ? m_id.c_str() : m_name.c_str(), m_id.c_str());
|
||||
|
||||
if (Begin(label, &m_visible, m_flags)) {
|
||||
if (m_renamePopupEnabled) PopupEditName(nullptr, &m_name);
|
||||
m_view->Display();
|
||||
} else {
|
||||
m_view->Hidden();
|
||||
}
|
||||
End();
|
||||
if (m_setPadding) ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
bool Window::DisplayMenuItem(const char* label) {
|
||||
bool wasVisible = m_visible;
|
||||
ImGui::MenuItem(
|
||||
label ? label : (m_name.empty() ? m_id.c_str() : m_name.c_str()), nullptr,
|
||||
&m_visible, m_enabled);
|
||||
return !wasVisible && m_visible;
|
||||
}
|
||||
|
||||
void Window::ScaleDefault(float scale) {
|
||||
if ((m_posCond & ImGuiCond_FirstUseEver) != 0) {
|
||||
m_pos.x *= scale;
|
||||
m_pos.y *= scale;
|
||||
}
|
||||
if ((m_sizeCond & ImGuiCond_FirstUseEver) != 0) {
|
||||
m_size.x *= scale;
|
||||
m_size.y *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::IniReadLine(const char* lineStr) {
|
||||
wpi::StringRef line{lineStr};
|
||||
auto [name, value] = line.split('=');
|
||||
name = name.trim();
|
||||
value = value.trim();
|
||||
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
} else if (name == "visible") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return;
|
||||
m_visible = num;
|
||||
} else if (name == "enabled") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return;
|
||||
m_enabled = num;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
107
glass/src/lib/native/cpp/WindowManager.cpp
Normal file
107
glass/src/lib/native/cpp/WindowManager.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/WindowManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
WindowManager::WindowManager(const wpi::Twine& 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);
|
||||
}
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(wpi::StringRef id,
|
||||
wpi::unique_function<void()> display) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
if (!win) return nullptr;
|
||||
if (win->HasView()) {
|
||||
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
|
||||
return nullptr;
|
||||
}
|
||||
win->SetView(MakeFunctionView(std::move(display)));
|
||||
return win;
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(wpi::StringRef id,
|
||||
std::unique_ptr<View> view) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
if (!win) return nullptr;
|
||||
if (win->HasView()) {
|
||||
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
|
||||
return nullptr;
|
||||
}
|
||||
win->SetView(std::move(view));
|
||||
return win;
|
||||
}
|
||||
|
||||
Window* WindowManager::GetOrAddWindow(wpi::StringRef id, bool duplicateOk) {
|
||||
// binary search
|
||||
auto it = std::lower_bound(
|
||||
m_windows.begin(), m_windows.end(), id,
|
||||
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
|
||||
if (it != m_windows.end() && (*it)->GetId() == id) {
|
||||
if (!duplicateOk) {
|
||||
wpi::errs() << "GUI: ignoring duplicate window '" << id << "'\n";
|
||||
return nullptr;
|
||||
}
|
||||
return it->get();
|
||||
}
|
||||
// insert before (keeps sort)
|
||||
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
|
||||
}
|
||||
|
||||
Window* WindowManager::GetWindow(wpi::StringRef id) {
|
||||
// binary search
|
||||
auto it = std::lower_bound(
|
||||
m_windows.begin(), m_windows.end(), id,
|
||||
[](const auto& elem, wpi::StringRef s) { return elem->GetId() < s; });
|
||||
if (it == m_windows.end() || (*it)->GetId() != id) return nullptr;
|
||||
return it->get();
|
||||
}
|
||||
|
||||
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) {
|
||||
window->ScaleDefault(scale);
|
||||
}
|
||||
});
|
||||
wpi::gui::AddLateExecute([this] { DisplayWindows(); });
|
||||
}
|
||||
|
||||
void WindowManager::DisplayMenu() {
|
||||
for (auto&& window : m_windows) {
|
||||
window->DisplayMenuItem();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::DisplayWindows() {
|
||||
for (auto&& window : m_windows) {
|
||||
window->Display();
|
||||
}
|
||||
}
|
||||
51
glass/src/lib/native/cpp/hardware/Accelerometer.cpp
Normal file
51
glass/src/lib/native/cpp/hardware/Accelerometer.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/Accelerometer.h"
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayAccelerometerDevice(AccelerometerModel* model) {
|
||||
if (!model->Exists()) return;
|
||||
if (BeginDevice("BuiltInAccel")) {
|
||||
// Range
|
||||
{
|
||||
int value = model->GetRange();
|
||||
static const char* rangeOptions[] = {"2G", "4G", "8G"};
|
||||
DeviceEnum("Range", true, &value, rangeOptions, 3);
|
||||
}
|
||||
|
||||
// X Accel
|
||||
if (auto xData = model->GetXData()) {
|
||||
double value = xData->GetValue();
|
||||
if (DeviceDouble("X Accel", false, &value, xData)) {
|
||||
model->SetX(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Y Accel
|
||||
if (auto yData = model->GetYData()) {
|
||||
double value = yData->GetValue();
|
||||
if (DeviceDouble("Y Accel", false, &value, yData)) {
|
||||
model->SetY(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Z Accel
|
||||
if (auto zData = model->GetZData()) {
|
||||
double value = zData->GetValue();
|
||||
if (DeviceDouble("Z Accel", false, &value, zData)) {
|
||||
model->SetZ(value);
|
||||
}
|
||||
}
|
||||
|
||||
EndDevice();
|
||||
}
|
||||
}
|
||||
41
glass/src/lib/native/cpp/hardware/AnalogGyro.cpp
Normal file
41
glass/src/lib/native/cpp/hardware/AnalogGyro.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/AnalogGyro.h"
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayAnalogGyroDevice(AnalogGyroModel* model, int index) {
|
||||
char name[32];
|
||||
std::snprintf(name, sizeof(name), "AnalogGyro[%d]", index);
|
||||
if (BeginDevice(name)) {
|
||||
// angle
|
||||
if (auto angleData = model->GetAngleData()) {
|
||||
double value = angleData->GetValue();
|
||||
if (DeviceDouble("Angle", false, &value, angleData)) {
|
||||
model->SetAngle(value);
|
||||
}
|
||||
}
|
||||
|
||||
// rate
|
||||
if (auto rateData = model->GetRateData()) {
|
||||
double value = rateData->GetValue();
|
||||
if (DeviceDouble("Rate", false, &value, rateData)) {
|
||||
model->SetRate(value);
|
||||
}
|
||||
}
|
||||
EndDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayAnalogGyrosDevice(AnalogGyrosModel* model) {
|
||||
model->ForEachAnalogGyro(
|
||||
[&](AnalogGyroModel& gyro, int i) { DisplayAnalogGyroDevice(&gyro, i); });
|
||||
}
|
||||
66
glass/src/lib/native/cpp/hardware/AnalogInput.cpp
Normal file
66
glass/src/lib/native/cpp/hardware/AnalogInput.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/AnalogInput.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
|
||||
auto voltageData = model->GetVoltageData();
|
||||
if (!voltageData) return;
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
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);
|
||||
}
|
||||
|
||||
if (model->IsGyro()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::LabelText(label, "AnalogGyro[%d]", index);
|
||||
ImGui::PopStyleColor();
|
||||
} else if (auto simDevice = model->GetSimDevice()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::LabelText(label, "%s", simDevice);
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
float val = voltageData->GetValue();
|
||||
if (voltageData->SliderFloat(label, &val, 0.0, 5.0)) model->SetVoltage(val);
|
||||
}
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) voltageData->SetName(name->c_str());
|
||||
}
|
||||
|
||||
void glass::DisplayAnalogInputs(AnalogInputsModel* model,
|
||||
wpi::StringRef noneMsg) {
|
||||
ImGui::Text("(Use Ctrl+Click to edit value)");
|
||||
bool hasAny = false;
|
||||
bool first = true;
|
||||
model->ForEachAnalogInput([&](AnalogInputModel& input, int i) {
|
||||
if (!first) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
PushID(i);
|
||||
DisplayAnalogInput(&input, i);
|
||||
PopID();
|
||||
hasAny = true;
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
47
glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
Normal file
47
glass/src/lib/native/cpp/hardware/AnalogOutput.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/AnalogOutput.h"
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
|
||||
int count = 0;
|
||||
model->ForEachAnalogOutput([&](auto&, int) { ++count; });
|
||||
if (count == 0) return;
|
||||
|
||||
if (BeginDevice("Analog Outputs")) {
|
||||
model->ForEachAnalogOutput([&](auto& analogOut, int i) {
|
||||
auto analogOutData = analogOut.GetVoltageData();
|
||||
if (!analogOutData) return;
|
||||
PushID(i);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
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);
|
||||
}
|
||||
|
||||
double value = analogOutData->GetValue();
|
||||
DeviceDouble(label, true, &value, analogOutData);
|
||||
|
||||
if (PopupEditName("name", name)) {
|
||||
if (analogOutData) analogOutData->SetName(name->c_str());
|
||||
}
|
||||
PopID();
|
||||
});
|
||||
|
||||
EndDevice();
|
||||
}
|
||||
}
|
||||
118
glass/src/lib/native/cpp/hardware/DIO.cpp
Normal file
118
glass/src/lib/native/cpp/hardware/DIO.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/DIO.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/Encoder.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
static void LabelSimDevice(const char* name, const char* simDeviceName) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::LabelText(name, "%s", simDeviceName);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
auto dpwm = model->GetDPWM();
|
||||
auto dutyCycle = model->GetDutyCycle();
|
||||
auto encoder = model->GetEncoder();
|
||||
|
||||
auto dioData = model->GetValueData();
|
||||
auto dpwmData = dpwm ? dpwm->GetValueData() : nullptr;
|
||||
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
|
||||
|
||||
bool exists = model->Exists();
|
||||
auto& info = dioData->GetNameInfo();
|
||||
char label[128];
|
||||
if (exists && dpwmData) {
|
||||
dpwmData->GetNameInfo().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);
|
||||
if (auto simDevice = encoder->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::LabelText(label, "Encoder[%d,%d]", encoder->GetChannelA(),
|
||||
encoder->GetChannelB());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
} else if (exists && dutyCycleData) {
|
||||
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
|
||||
if (auto simDevice = dutyCycle->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
double val = dutyCycleData->GetValue();
|
||||
if (dutyCycleData->InputDouble(label, &val)) {
|
||||
dutyCycle->SetValue(val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const char* name = model->GetName();
|
||||
if (name[0] != '\0')
|
||||
info.GetLabel(label, sizeof(label), name);
|
||||
else
|
||||
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
|
||||
index);
|
||||
if (auto simDevice = model->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
if (!exists) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
dioData->LabelText(label, "unknown");
|
||||
ImGui::PopStyleColor();
|
||||
} else if (model->IsReadOnly()) {
|
||||
dioData->LabelText(
|
||||
label, "%s",
|
||||
outputsEnabled ? (dioData->GetValue() != 0 ? "1 (high)" : "0 (low)")
|
||||
: "1 (disabled)");
|
||||
|
||||
} else {
|
||||
static const char* options[] = {"0 (low)", "1 (high)"};
|
||||
int val = dioData->GetValue() != 0 ? 1 : 0;
|
||||
if (dioData->Combo(label, &val, options, 2)) {
|
||||
model->SetValue(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.PopupEditName(index)) {
|
||||
if (dpwmData) dpwmData->SetName(info.GetName());
|
||||
if (dutyCycleData) dutyCycleData->SetName(info.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayDIO(DIOModel* model, int index, bool outputsEnabled) {
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
DisplayDIOImpl(model, index, outputsEnabled);
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void glass::DisplayDIOs(DIOsModel* model, bool outputsEnabled,
|
||||
wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
model->ForEachDIO([&](DIOModel& dio, int i) {
|
||||
hasAny = true;
|
||||
ImGui::PushID(i);
|
||||
DisplayDIOImpl(&dio, i, outputsEnabled);
|
||||
ImGui::PopID();
|
||||
});
|
||||
ImGui::PopItemWidth();
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
165
glass/src/lib/native/cpp/hardware/Encoder.cpp
Normal file
165
glass/src/lib/native/cpp/hardware/Encoder.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/Encoder.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void EncoderModel::SetName(const wpi::Twine& name) {
|
||||
if (name.str().empty()) {
|
||||
if (auto distancePerPulse = GetDistancePerPulseData()) {
|
||||
distancePerPulse->SetName("");
|
||||
}
|
||||
if (auto count = GetCountData()) {
|
||||
count->SetName("");
|
||||
}
|
||||
if (auto period = GetPeriodData()) {
|
||||
period->SetName("");
|
||||
}
|
||||
if (auto direction = GetDirectionData()) {
|
||||
direction->SetName("");
|
||||
}
|
||||
if (auto distance = GetDistanceData()) {
|
||||
distance->SetName("");
|
||||
}
|
||||
if (auto rate = GetRateData()) {
|
||||
rate->SetName("");
|
||||
}
|
||||
} else {
|
||||
if (auto distancePerPulse = GetDistancePerPulseData()) {
|
||||
distancePerPulse->SetName(name + " Distance/Count");
|
||||
}
|
||||
if (auto count = GetCountData()) {
|
||||
count->SetName(name + " Count");
|
||||
}
|
||||
if (auto period = GetPeriodData()) {
|
||||
period->SetName(name + " Period");
|
||||
}
|
||||
if (auto direction = GetDirectionData()) {
|
||||
direction->SetName(name + " Direction");
|
||||
}
|
||||
if (auto distance = GetDistanceData()) {
|
||||
distance->SetName(name + " Distance");
|
||||
}
|
||||
if (auto rate = GetRateData()) {
|
||||
rate->SetName(name + " Rate");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayEncoder(EncoderModel* model) {
|
||||
if (auto simDevice = model->GetSimDevice()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::TextUnformatted(simDevice);
|
||||
ImGui::PopStyleColor();
|
||||
return;
|
||||
}
|
||||
|
||||
int chA = model->GetChannelA();
|
||||
int chB = model->GetChannelB();
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
|
||||
chB);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
|
||||
}
|
||||
|
||||
// header
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) {
|
||||
model->SetName(name->c_str());
|
||||
}
|
||||
|
||||
if (!open) return;
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
// distance per pulse
|
||||
if (auto distancePerPulseData = model->GetDistancePerPulseData()) {
|
||||
double value = distancePerPulseData->GetValue();
|
||||
distancePerPulseData->LabelText("Dist/Count", "%.6f", value);
|
||||
}
|
||||
|
||||
// count
|
||||
if (auto countData = model->GetCountData()) {
|
||||
int value = countData->GetValue();
|
||||
if (ImGui::InputInt("##input", &value)) model->SetCount(value);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset")) {
|
||||
model->SetCount(0);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Selectable("Count");
|
||||
countData->EmitDrag();
|
||||
}
|
||||
|
||||
// max period
|
||||
{
|
||||
double maxPeriod = model->GetMaxPeriod();
|
||||
ImGui::LabelText("Max Period", "%.6f", maxPeriod);
|
||||
}
|
||||
|
||||
// period
|
||||
if (auto periodData = model->GetPeriodData()) {
|
||||
double value = periodData->GetValue();
|
||||
if (periodData->InputDouble("Period", &value, 0, 0, "%.6g")) {
|
||||
model->SetPeriod(value);
|
||||
}
|
||||
}
|
||||
|
||||
// reverse direction
|
||||
ImGui::LabelText("Reverse Direction", "%s",
|
||||
model->GetReverseDirection() ? "true" : "false");
|
||||
|
||||
// direction
|
||||
if (auto directionData = model->GetDirectionData()) {
|
||||
static const char* options[] = {"reverse", "forward"};
|
||||
int value = directionData->GetValue() ? 1 : 0;
|
||||
if (directionData->Combo("Direction", &value, options, 2)) {
|
||||
model->SetDirection(value != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// distance
|
||||
if (auto distanceData = model->GetDistanceData()) {
|
||||
double value = distanceData->GetValue();
|
||||
if (distanceData->InputDouble("Distance", &value, 0, 0, "%.6g")) {
|
||||
model->SetDistance(value);
|
||||
}
|
||||
}
|
||||
|
||||
// rate
|
||||
if (auto rateData = model->GetRateData()) {
|
||||
double value = rateData->GetValue();
|
||||
if (rateData->InputDouble("Rate", &value, 0, 0, "%.6g")) {
|
||||
model->SetRate(value);
|
||||
}
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void glass::DisplayEncoders(EncodersModel* model, wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
model->ForEachEncoder([&](EncoderModel& encoder, int i) {
|
||||
hasAny = true;
|
||||
PushID(i);
|
||||
DisplayEncoder(&encoder);
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
91
glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
Normal file
91
glass/src/lib/native/cpp/hardware/LEDDisplay.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/LEDDisplay.h"
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace {
|
||||
struct IndicatorData {
|
||||
std::vector<int> values;
|
||||
std::vector<ImU32> colors;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
|
||||
wpi::SmallVector<LEDDisplayModel::Data, 64> dataBuf;
|
||||
auto data = model->GetData(dataBuf);
|
||||
int length = data.size();
|
||||
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);
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::LabelText("Length", "%d", length);
|
||||
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
|
||||
ImGui::InputInt("Columns", numColumns);
|
||||
{
|
||||
static const char* options[] = {"Row Major", "Column Major"};
|
||||
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::Checkbox("Serpentine", serpentine);
|
||||
if (*numColumns < 1) *numColumns = 1;
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// show as LED indicators
|
||||
auto iData = storage.GetData<IndicatorData>();
|
||||
if (!iData) {
|
||||
storage.SetData(std::make_shared<IndicatorData>());
|
||||
iData = storage.GetData<IndicatorData>();
|
||||
}
|
||||
if (length > static_cast<int>(iData->values.size()))
|
||||
iData->values.resize(length);
|
||||
if (length > static_cast<int>(iData->colors.size()))
|
||||
iData->colors.resize(length);
|
||||
if (!running) {
|
||||
iData->colors[0] = IM_COL32(128, 128, 128, 255);
|
||||
for (int j = 0; j < length; ++j) iData->values[j] = -1;
|
||||
} else {
|
||||
for (int j = 0; j < length; ++j) {
|
||||
iData->values[j] = j + 1;
|
||||
iData->colors[j] = IM_COL32(data[j].r, data[j].g, data[j].b, 255);
|
||||
}
|
||||
}
|
||||
|
||||
LEDConfig config;
|
||||
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);
|
||||
}
|
||||
|
||||
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
|
||||
bool hasAny = false;
|
||||
|
||||
model->ForEachLEDDisplay([&](LEDDisplayModel& display, int i) {
|
||||
hasAny = true;
|
||||
if (model->GetNumLEDDisplays() > 1) ImGui::Text("LEDs[%d]", i);
|
||||
PushID(i);
|
||||
DisplayLEDDisplay(&display, i);
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny) ImGui::Text("No addressable LEDs");
|
||||
}
|
||||
150
glass/src/lib/native/cpp/hardware/PCM.cpp
Normal file
150
glass/src/lib/native/cpp/hardware/PCM.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/PCM.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
bool outputsEnabled) {
|
||||
wpi::SmallVector<int, 16> channels;
|
||||
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
|
||||
if (auto data = solenoid.GetOutputData()) {
|
||||
if (j >= static_cast<int>(channels.size())) channels.resize(j + 1);
|
||||
channels[j] = (outputsEnabled && data->GetValue()) ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
if (channels.empty()) return false;
|
||||
|
||||
// show nonexistent channels as empty
|
||||
for (auto&& ch : channels) {
|
||||
if (ch == 0) ch = -2;
|
||||
}
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
|
||||
}
|
||||
|
||||
// header
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
PopupEditName("name", name);
|
||||
|
||||
ImGui::SetItemAllowOverlap();
|
||||
ImGui::SameLine();
|
||||
|
||||
// show channels as LED indicators
|
||||
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
|
||||
IM_COL32(128, 128, 128, 255)};
|
||||
DrawLEDs(channels.data(), channels.size(), channels.size(), colors);
|
||||
|
||||
if (open) {
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
|
||||
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);
|
||||
PopID();
|
||||
}
|
||||
});
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void glass::DisplayPCMsSolenoids(PCMsModel* model, bool outputsEnabled,
|
||||
wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
model->ForEachPCM([&](PCMModel& pcm, int i) {
|
||||
PushID(i);
|
||||
if (DisplayPCMSolenoids(&pcm, i, outputsEnabled)) hasAny = true;
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
|
||||
void glass::DisplayCompressorDevice(PCMModel* model, int index,
|
||||
bool outputsEnabled) {
|
||||
auto compressor = model->GetCompressor();
|
||||
if (!compressor || !compressor->Exists()) return;
|
||||
DisplayCompressorDevice(compressor, index, outputsEnabled);
|
||||
}
|
||||
|
||||
void glass::DisplayCompressorDevice(CompressorModel* model, int index,
|
||||
bool outputsEnabled) {
|
||||
char name[32];
|
||||
std::snprintf(name, sizeof(name), "Compressor[%d]", index);
|
||||
if (BeginDevice(name)) {
|
||||
// output enabled
|
||||
if (auto runningData = model->GetRunningData()) {
|
||||
bool value = outputsEnabled && runningData->GetValue();
|
||||
if (DeviceBoolean("Running", false, &value, runningData)) {
|
||||
model->SetRunning(value);
|
||||
}
|
||||
}
|
||||
|
||||
// closed loop enabled
|
||||
if (auto enabledData = model->GetEnabledData()) {
|
||||
int value = enabledData->GetValue() ? 1 : 0;
|
||||
static const char* enabledOptions[] = {"disabled", "enabled"};
|
||||
if (DeviceEnum("Closed Loop", true, &value, enabledOptions, 2,
|
||||
enabledData)) {
|
||||
model->SetEnabled(value != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// pressure switch
|
||||
if (auto pressureSwitchData = model->GetPressureSwitchData()) {
|
||||
int value = pressureSwitchData->GetValue() ? 1 : 0;
|
||||
static const char* switchOptions[] = {"full", "low"};
|
||||
if (DeviceEnum("Pressure", false, &value, switchOptions, 2,
|
||||
pressureSwitchData)) {
|
||||
model->SetPressureSwitch(value != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// compressor current
|
||||
if (auto currentData = model->GetCurrentData()) {
|
||||
double value = currentData->GetValue();
|
||||
if (DeviceDouble("Current (A)", false, &value, currentData)) {
|
||||
model->SetCurrent(value);
|
||||
}
|
||||
}
|
||||
|
||||
EndDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayCompressorsDevice(PCMsModel* model, bool outputsEnabled) {
|
||||
model->ForEachPCM([&](PCMModel& pcm, int i) {
|
||||
DisplayCompressorDevice(&pcm, i, outputsEnabled);
|
||||
});
|
||||
}
|
||||
92
glass/src/lib/native/cpp/hardware/PDP.cpp
Normal file
92
glass/src/lib/native/cpp/hardware/PDP.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/PDP.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
static float DisplayChannel(PDPModel& pdp, int channel) {
|
||||
float width = 0;
|
||||
if (auto currentData = pdp.GetCurrentData(channel)) {
|
||||
ImGui::PushID(channel);
|
||||
auto& leftInfo = currentData->GetNameInfo();
|
||||
char name[64];
|
||||
leftInfo.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);
|
||||
ImGui::PopID();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
void glass::DisplayPDP(PDPModel* model, int index) {
|
||||
char name[128];
|
||||
std::snprintf(name, sizeof(name), "PDP[%d]", index);
|
||||
if (CollapsingHeader(name)) {
|
||||
// temperature
|
||||
if (auto tempData = model->GetTemperatureData()) {
|
||||
double value = tempData->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (tempData->InputDouble("Temp", &value, 0, 0, "%.3f")) {
|
||||
model->SetTemperature(value);
|
||||
}
|
||||
}
|
||||
|
||||
// voltage
|
||||
if (auto voltageData = model->GetVoltageData()) {
|
||||
double value = voltageData->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (voltageData->InputDouble("Voltage", &value, 0, 0, "%.3f")) {
|
||||
model->SetVoltage(value);
|
||||
}
|
||||
}
|
||||
|
||||
// channel currents; show as two columns laid out like PDP
|
||||
const int numChannels = model->GetNumChannels();
|
||||
ImGui::Text("Channel Current (A)");
|
||||
ImGui::Columns(2, "channels", false);
|
||||
float maxWidth = ImGui::GetFontSize() * 13;
|
||||
for (int left = 0, right = numChannels - 1; left < right; ++left, --right) {
|
||||
float leftWidth = DisplayChannel(*model, left);
|
||||
ImGui::NextColumn();
|
||||
|
||||
float rightWidth = DisplayChannel(*model, right);
|
||||
ImGui::NextColumn();
|
||||
|
||||
float width =
|
||||
(std::max)(leftWidth, rightWidth) * 2 + ImGui::GetFontSize() * 4;
|
||||
if (width > maxWidth) maxWidth = width;
|
||||
}
|
||||
ImGui::Columns(1);
|
||||
ImGui::Dummy(ImVec2(maxWidth, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayPDPs(PDPsModel* model, wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
model->ForEachPDP([&](PDPModel& pdp, int i) {
|
||||
hasAny = true;
|
||||
PushID(i);
|
||||
DisplayPDP(&pdp, i);
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
62
glass/src/lib/native/cpp/hardware/PWM.cpp
Normal file
62
glass/src/lib/native/cpp/hardware/PWM.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/PWM.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
|
||||
auto data = model->GetSpeedData();
|
||||
if (!data) return;
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
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);
|
||||
}
|
||||
|
||||
int led = model->GetAddressableLED();
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (led >= 0) {
|
||||
ImGui::LabelText(label, "LED[%d]", led);
|
||||
} else {
|
||||
float val = outputsEnabled ? data->GetValue() : 0;
|
||||
data->LabelText(label, "%0.3f", val);
|
||||
}
|
||||
if (PopupEditName("name", name)) {
|
||||
data->SetName(name->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayPWMs(PWMsModel* model, bool outputsEnabled,
|
||||
wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
bool first = true;
|
||||
model->ForEachPWM([&](PWMModel& pwm, int i) {
|
||||
hasAny = true;
|
||||
PushID(i);
|
||||
|
||||
if (!first)
|
||||
ImGui::Separator();
|
||||
else
|
||||
first = false;
|
||||
|
||||
DisplayPWM(&pwm, i, outputsEnabled);
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
74
glass/src/lib/native/cpp/hardware/Relay.cpp
Normal file
74
glass/src/lib/native/cpp/hardware/Relay.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/Relay.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
|
||||
auto forwardData = model->GetForwardData();
|
||||
auto reverseData = model->GetReverseData();
|
||||
|
||||
if (!forwardData && !reverseData) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool forward = false;
|
||||
bool reverse = false;
|
||||
if (outputsEnabled) {
|
||||
if (forwardData) forward = forwardData->GetValue();
|
||||
if (reverseData) reverse = reverseData->GetValue();
|
||||
}
|
||||
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
ImGui::PushID("name");
|
||||
if (!name->empty())
|
||||
ImGui::Text("%s [%d]", name->c_str(), index);
|
||||
else
|
||||
ImGui::Text("Relay[%d]", index);
|
||||
ImGui::PopID();
|
||||
if (PopupEditName("name", name)) {
|
||||
if (forwardData) forwardData->SetName(name->c_str());
|
||||
if (reverseData) reverseData->SetName(name->c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
// show forward and reverse as LED indicators
|
||||
static const ImU32 colors[] = {IM_COL32(255, 255, 102, 255),
|
||||
IM_COL32(255, 0, 0, 255),
|
||||
IM_COL32(128, 128, 128, 255)};
|
||||
int values[2] = {reverseData ? (reverse ? 2 : -2) : -3,
|
||||
forwardData ? (forward ? 1 : -1) : -3};
|
||||
DataSource* sources[2] = {reverseData, forwardData};
|
||||
DrawLEDSources(values, sources, 2, 2, colors);
|
||||
}
|
||||
|
||||
void glass::DisplayRelays(RelaysModel* model, bool outputsEnabled,
|
||||
wpi::StringRef noneMsg) {
|
||||
bool hasAny = false;
|
||||
bool first = true;
|
||||
model->ForEachRelay([&](RelayModel& relay, int i) {
|
||||
hasAny = true;
|
||||
|
||||
if (!first)
|
||||
ImGui::Separator();
|
||||
else
|
||||
first = false;
|
||||
|
||||
PushID(i);
|
||||
DisplayRelay(&relay, i, outputsEnabled);
|
||||
PopID();
|
||||
});
|
||||
if (!hasAny && !noneMsg.empty())
|
||||
ImGui::TextUnformatted(noneMsg.begin(), noneMsg.end());
|
||||
}
|
||||
87
glass/src/lib/native/cpp/hardware/RoboRio.cpp
Normal file
87
glass/src/lib/native/cpp/hardware/RoboRio.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/hardware/RoboRio.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
static void DisplayRail(RoboRioRailModel& rail, const char* name) {
|
||||
if (CollapsingHeader(name)) {
|
||||
ImGui::PushID(name);
|
||||
if (auto data = rail.GetVoltageData()) {
|
||||
double val = data->GetValue();
|
||||
if (data->InputDouble("Voltage (V)", &val)) {
|
||||
rail.SetVoltage(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto data = rail.GetCurrentData()) {
|
||||
double val = data->GetValue();
|
||||
if (data->InputDouble("Current (A)", &val)) {
|
||||
rail.SetCurrent(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto data = rail.GetActiveData()) {
|
||||
static const char* options[] = {"inactive", "active"};
|
||||
int val = data->GetValue() ? 1 : 0;
|
||||
if (data->Combo("Active", &val, options, 2)) {
|
||||
rail.SetActive(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto data = rail.GetFaultsData()) {
|
||||
int val = data->GetValue();
|
||||
if (data->InputInt("Faults", &val)) {
|
||||
rail.SetFaults(val);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayRoboRio(RoboRioModel* model) {
|
||||
ImGui::Button("User Button");
|
||||
model->SetUserButton(ImGui::IsItemActive());
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
|
||||
if (CollapsingHeader("RoboRIO Input")) {
|
||||
ImGui::PushID("RoboRIO Input");
|
||||
if (auto data = model->GetVInVoltageData()) {
|
||||
double val = data->GetValue();
|
||||
if (data->InputDouble("Voltage (V)", &val)) {
|
||||
model->SetVInVoltage(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto data = model->GetVInCurrentData()) {
|
||||
double val = data->GetValue();
|
||||
if (data->InputDouble("Current (A)", &val)) {
|
||||
model->SetVInCurrent(val);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (auto rail = model->GetUser6VRail()) {
|
||||
DisplayRail(*rail, "6V Rail");
|
||||
}
|
||||
if (auto rail = model->GetUser5VRail()) {
|
||||
DisplayRail(*rail, "5V Rail");
|
||||
}
|
||||
if (auto rail = model->GetUser3V3Rail()) {
|
||||
DisplayRail(*rail, "3.3V Rail");
|
||||
}
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
161
glass/src/lib/native/cpp/other/DeviceTree.cpp
Normal file
161
glass/src/lib/native/cpp/other/DeviceTree.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/ContextInternal.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void DeviceTreeModel::Update() {
|
||||
for (auto&& display : m_displays) {
|
||||
if (display.first) display.first->Update();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::Exists() {
|
||||
for (auto&& display : m_displays) {
|
||||
if (display.first && display.first->Exists()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeviceTreeModel::Display() {
|
||||
for (auto&& display : m_displays) {
|
||||
if (display.second) display.second(display.first);
|
||||
}
|
||||
}
|
||||
|
||||
void glass::HideDevice(const char* id) { gContext->deviceHidden[id] = true; }
|
||||
|
||||
bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
|
||||
if (gContext->deviceHidden[id]) return false;
|
||||
|
||||
PushID(id);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###name",
|
||||
name->empty() ? id : name->c_str());
|
||||
|
||||
bool open = CollapsingHeader(label, flags);
|
||||
PopupEditName("name", name);
|
||||
|
||||
if (!open) PopID();
|
||||
return open;
|
||||
}
|
||||
|
||||
void glass::EndDevice() { PopID(); }
|
||||
|
||||
static bool DeviceBooleanImpl(const char* name, bool readonly, bool* value) {
|
||||
if (readonly) {
|
||||
ImGui::LabelText(name, "%s", *value ? "true" : "false");
|
||||
} else {
|
||||
static const char* boolOptions[] = {"false", "true"};
|
||||
int val = *value ? 1 : 0;
|
||||
if (ImGui::Combo(name, &val, boolOptions, 2)) {
|
||||
*value = val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DeviceDoubleImpl(const char* name, bool readonly, double* value) {
|
||||
if (readonly) {
|
||||
ImGui::LabelText(name, "%.6f", *value);
|
||||
return false;
|
||||
} else {
|
||||
return ImGui::InputDouble(name, value, 0, 0, "%.6f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DeviceEnumImpl(const char* name, bool readonly, int* value,
|
||||
const char** options, int32_t numOptions) {
|
||||
if (readonly) {
|
||||
if (*value < 0 || *value >= numOptions)
|
||||
ImGui::LabelText(name, "%d (unknown)", *value);
|
||||
else
|
||||
ImGui::LabelText(name, "%s", options[*value]);
|
||||
return false;
|
||||
} else {
|
||||
return ImGui::Combo(name, value, options, numOptions);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DeviceIntImpl(const char* name, bool readonly, int32_t* value) {
|
||||
if (readonly) {
|
||||
ImGui::LabelText(name, "%" PRId32, *value);
|
||||
return false;
|
||||
} else {
|
||||
return ImGui::InputScalar(name, ImGuiDataType_S32, value, nullptr, nullptr,
|
||||
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DeviceLongImpl(const char* name, bool readonly, int64_t* value) {
|
||||
if (readonly) {
|
||||
ImGui::LabelText(name, "%" PRId64, *value);
|
||||
return false;
|
||||
} else {
|
||||
return ImGui::InputScalar(name, ImGuiDataType_S64, value, nullptr, nullptr,
|
||||
nullptr, ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F, typename... Args>
|
||||
static inline bool DeviceValueImpl(const char* name, bool readonly,
|
||||
const DataSource* source, F&& func,
|
||||
Args... args) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f);
|
||||
if (!source) {
|
||||
return func(name, readonly, args...);
|
||||
} else {
|
||||
ImGui::PushID(name);
|
||||
bool rv = func("", readonly, args...);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(name);
|
||||
source->EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
bool glass::DeviceBoolean(const char* name, bool readonly, bool* value,
|
||||
const DataSource* source) {
|
||||
return DeviceValueImpl(name, readonly, source, DeviceBooleanImpl, value);
|
||||
}
|
||||
|
||||
bool glass::DeviceDouble(const char* name, bool readonly, double* value,
|
||||
const DataSource* source) {
|
||||
return DeviceValueImpl(name, readonly, source, DeviceDoubleImpl, value);
|
||||
}
|
||||
|
||||
bool glass::DeviceEnum(const char* name, bool readonly, int* value,
|
||||
const char** options, int32_t numOptions,
|
||||
const DataSource* source) {
|
||||
return DeviceValueImpl(name, readonly, source, DeviceEnumImpl, value, options,
|
||||
numOptions);
|
||||
}
|
||||
|
||||
bool glass::DeviceInt(const char* name, bool readonly, int32_t* value,
|
||||
const DataSource* source) {
|
||||
return DeviceValueImpl(name, readonly, source, DeviceIntImpl, value);
|
||||
}
|
||||
|
||||
bool glass::DeviceLong(const char* name, bool readonly, int64_t* value,
|
||||
const DataSource* source) {
|
||||
return DeviceValueImpl(name, readonly, source, DeviceLongImpl, value);
|
||||
}
|
||||
140
glass/src/lib/native/cpp/other/FMS.cpp
Normal file
140
glass/src/lib/native/cpp/other/FMS.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/other/FMS.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
|
||||
"Blue 1", "Blue 2", "Blue 3"};
|
||||
|
||||
void glass::DisplayFMS(FMSModel* model, bool* matchTimeEnabled) {
|
||||
if (!model->Exists() || model->IsReadOnly()) return DisplayFMSReadOnly(model);
|
||||
|
||||
// FMS Attached
|
||||
if (auto data = model->GetFmsAttachedData()) {
|
||||
bool val = data->GetValue();
|
||||
if (ImGui::Checkbox("FMS Attached", &val)) model->SetFmsAttached(val);
|
||||
data->EmitDrag();
|
||||
}
|
||||
|
||||
// DS Attached
|
||||
if (auto data = model->GetDsAttachedData()) {
|
||||
bool val = data->GetValue();
|
||||
if (ImGui::Checkbox("DS Attached", &val)) model->SetDsAttached(val);
|
||||
data->EmitDrag();
|
||||
}
|
||||
|
||||
// Alliance Station
|
||||
if (auto data = model->GetAllianceStationIdData()) {
|
||||
int val = data->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::Combo("Alliance Station", &val, stations, 6))
|
||||
model->SetAllianceStationId(val);
|
||||
data->EmitDrag();
|
||||
}
|
||||
|
||||
// Match Time
|
||||
if (auto data = model->GetMatchTimeData()) {
|
||||
if (matchTimeEnabled)
|
||||
ImGui::Checkbox("Match Time Enabled", matchTimeEnabled);
|
||||
|
||||
double val = data->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputDouble("Match Time", &val, 0, 0, "%.1f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
model->SetMatchTime(val);
|
||||
}
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset")) {
|
||||
model->SetMatchTime(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Game Specific Message
|
||||
// make buffer full 64 width, null terminated, for editability
|
||||
wpi::SmallString<64> gameSpecificMessage;
|
||||
model->GetGameSpecificMessage(gameSpecificMessage);
|
||||
gameSpecificMessage.resize(63);
|
||||
gameSpecificMessage.push_back('\0');
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputText("Game Specific", gameSpecificMessage.data(),
|
||||
gameSpecificMessage.size(),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
model->SetGameSpecificMessage(gameSpecificMessage.data());
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayFMSReadOnly(FMSModel* model) {
|
||||
bool exists = model->Exists();
|
||||
if (!exists) ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
|
||||
if (auto data = model->GetEStopData()) {
|
||||
ImGui::Selectable("E-Stopped: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetEnabledData()) {
|
||||
ImGui::Selectable("Robot Enabled: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetTestData()) {
|
||||
ImGui::Selectable("Test Mode: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetAutonomousData()) {
|
||||
ImGui::Selectable("Autonomous Mode: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetFmsAttachedData()) {
|
||||
ImGui::Selectable("FMS Attached: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetDsAttachedData()) {
|
||||
ImGui::Selectable("DS Attached: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? (data->GetValue() ? "Yes" : "No") : "?");
|
||||
}
|
||||
if (auto data = model->GetAllianceStationIdData()) {
|
||||
ImGui::Selectable("Alliance Station: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(exists ? stations[static_cast<int>(data->GetValue())]
|
||||
: "?");
|
||||
}
|
||||
if (auto data = model->GetMatchTimeData()) {
|
||||
ImGui::Selectable("Match Time: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
if (exists)
|
||||
ImGui::Text("%.1f", data->GetValue());
|
||||
else
|
||||
ImGui::TextUnformatted("?");
|
||||
}
|
||||
|
||||
wpi::SmallString<64> gameSpecificMessage;
|
||||
model->GetGameSpecificMessage(gameSpecificMessage);
|
||||
ImGui::Text("Game Specific: %s", exists ? gameSpecificMessage.c_str() : "?");
|
||||
|
||||
if (!exists) ImGui::PopStyleColor();
|
||||
}
|
||||
617
glass/src/lib/native/cpp/other/Field2D.cpp
Normal file
617
glass/src/lib/native/cpp/other/Field2D.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/other/Field2D.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <portable-file-dialogs.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <wpi/Path.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
namespace {
|
||||
|
||||
// Per-frame field data (not persistent)
|
||||
struct FieldFrameData {
|
||||
// in screen coordinates
|
||||
ImVec2 imageMin;
|
||||
ImVec2 imageMax;
|
||||
ImVec2 min;
|
||||
ImVec2 max;
|
||||
|
||||
float scale; // scaling from field units to screen units
|
||||
};
|
||||
|
||||
// Object drag state
|
||||
struct ObjectDragState {
|
||||
int object = 0;
|
||||
int corner = 0;
|
||||
ImVec2 initialOffset;
|
||||
double initialAngle = 0;
|
||||
};
|
||||
|
||||
// Per-frame object data (not persistent)
|
||||
class ObjectFrameData {
|
||||
public:
|
||||
explicit ObjectFrameData(FieldObjectModel& model, const FieldFrameData& ffd,
|
||||
float width, float length);
|
||||
void SetPosition(double x, double y);
|
||||
// set and get rotation in radians
|
||||
void SetRotation(double rot);
|
||||
double GetRotation() const {
|
||||
return units::convert<units::degrees, units::radians>(m_rot);
|
||||
}
|
||||
void UpdateFrameData();
|
||||
int IsHovered(const ImVec2& cursor) const;
|
||||
bool HandleDrag(const ImVec2& cursor, int hitCorner, ObjectDragState* drag);
|
||||
void Draw(ImDrawList* drawList, const gui::Texture& texture,
|
||||
int hitCorner) const;
|
||||
|
||||
// in window coordinates
|
||||
ImVec2 m_center;
|
||||
ImVec2 m_corners[4];
|
||||
ImVec2 m_arrow[3];
|
||||
|
||||
private:
|
||||
FieldObjectModel& m_model;
|
||||
const FieldFrameData& m_ffd;
|
||||
|
||||
// scaled width/2 and length/2, in screen units
|
||||
float m_width2;
|
||||
float m_length2;
|
||||
|
||||
float m_hitRadius;
|
||||
|
||||
double m_x = 0;
|
||||
double m_y = 0;
|
||||
double m_rot = 0;
|
||||
};
|
||||
|
||||
class ObjectGroupInfo {
|
||||
public:
|
||||
static constexpr float kDefaultWidth = 0.6858f;
|
||||
static constexpr float kDefaultLength = 0.8204f;
|
||||
|
||||
ObjectGroupInfo();
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
float* m_pWidth;
|
||||
float* m_pLength;
|
||||
ObjectDragState m_dragState;
|
||||
|
||||
void Reset();
|
||||
void LoadImage();
|
||||
const gui::Texture& GetTexture() const { return m_texture; }
|
||||
|
||||
private:
|
||||
bool LoadImageImpl(const char* fn);
|
||||
|
||||
std::string* m_pFilename;
|
||||
gui::Texture m_texture;
|
||||
};
|
||||
|
||||
class FieldInfo {
|
||||
public:
|
||||
static constexpr float kDefaultWidth = 15.98f;
|
||||
static constexpr float kDefaultHeight = 8.21f;
|
||||
|
||||
FieldInfo();
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
float* m_pWidth;
|
||||
float* m_pHeight;
|
||||
|
||||
void Reset();
|
||||
void LoadImage();
|
||||
void LoadJson(const wpi::Twine& jsonfile);
|
||||
FieldFrameData GetFrameData(ImVec2 min, ImVec2 max) const;
|
||||
void Draw(ImDrawList* drawList, const FieldFrameData& frameData) const;
|
||||
|
||||
wpi::StringMap<std::unique_ptr<ObjectGroupInfo>> m_objectGroups;
|
||||
|
||||
private:
|
||||
bool LoadImageImpl(const char* fn);
|
||||
|
||||
std::string* m_pFilename;
|
||||
gui::Texture m_texture;
|
||||
int m_imageWidth;
|
||||
int m_imageHeight;
|
||||
int* m_pTop;
|
||||
int* m_pLeft;
|
||||
int* m_pBottom;
|
||||
int* m_pRight;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
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);
|
||||
m_pHeight = storage.GetFloatRef("height", kDefaultHeight);
|
||||
}
|
||||
|
||||
void FieldInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_imageWidth = 0;
|
||||
m_imageHeight = 0;
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
}
|
||||
|
||||
void FieldInfo::LoadImage() {
|
||||
if (m_fileOpener && m_fileOpener->ready(0)) {
|
||||
auto result = m_fileOpener->result();
|
||||
if (!result.empty()) {
|
||||
if (wpi::StringRef(result[0]).endswith(".json")) {
|
||||
LoadJson(result[0]);
|
||||
} else {
|
||||
LoadImageImpl(result[0].c_str());
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
}
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream f(jsonfile, ec);
|
||||
if (ec) {
|
||||
wpi::errs() << "GUI: could not open field JSON file\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// parse file
|
||||
wpi::json j;
|
||||
try {
|
||||
j = wpi::json::parse(f);
|
||||
} catch (const wpi::json::parse_error& e) {
|
||||
wpi::errs() << "GUI: JSON: could not parse: " << e.what() << '\n';
|
||||
}
|
||||
|
||||
// top level must be an object
|
||||
if (!j.is_object()) {
|
||||
wpi::errs() << "GUI: JSON: does not contain a top object\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// image filename
|
||||
std::string image;
|
||||
try {
|
||||
image = j.at("field-image").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
wpi::errs() << "GUI: JSON: could not read field-image: " << e.what()
|
||||
<< '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// corners
|
||||
int top, left, bottom, right;
|
||||
try {
|
||||
top = j.at("field-corners").at("top-left").at(1).get<int>();
|
||||
left = j.at("field-corners").at("top-left").at(0).get<int>();
|
||||
bottom = j.at("field-corners").at("bottom-right").at(1).get<int>();
|
||||
right = j.at("field-corners").at("bottom-right").at(0).get<int>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
wpi::errs() << "GUI: JSON: could not read field-corners: " << e.what()
|
||||
<< '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// size
|
||||
float width;
|
||||
float height;
|
||||
try {
|
||||
width = j.at("field-size").at(0).get<float>();
|
||||
height = j.at("field-size").at(1).get<float>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
wpi::errs() << "GUI: JSON: could not read field-size: " << e.what() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// units for size
|
||||
std::string unit;
|
||||
try {
|
||||
unit = j.at("field-unit").get<std::string>();
|
||||
} catch (const wpi::json::exception& e) {
|
||||
wpi::errs() << "GUI: JSON: could not read field-unit: " << e.what() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// convert size units to meters
|
||||
if (unit == "foot" || unit == "feet") {
|
||||
width = units::convert<units::feet, units::meters>(width);
|
||||
height = units::convert<units::feet, units::meters>(height);
|
||||
}
|
||||
|
||||
// the image filename is relative to the json file
|
||||
wpi::SmallString<128> pathname;
|
||||
jsonfile.toVector(pathname);
|
||||
wpi::sys::path::remove_filename(pathname);
|
||||
wpi::sys::path::append(pathname, image);
|
||||
|
||||
// load field image
|
||||
if (!LoadImageImpl(pathname.c_str())) return;
|
||||
|
||||
// save to field info
|
||||
*m_pFilename = pathname.str();
|
||||
*m_pTop = top;
|
||||
*m_pLeft = left;
|
||||
*m_pBottom = bottom;
|
||||
*m_pRight = right;
|
||||
*m_pWidth = width;
|
||||
*m_pHeight = height;
|
||||
}
|
||||
|
||||
bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
wpi::outs() << "GUI: loading field image '" << fn << "'\n";
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
if (!texture) {
|
||||
wpi::errs() << "GUI: could not read field image\n";
|
||||
return false;
|
||||
}
|
||||
m_texture = std::move(texture);
|
||||
m_imageWidth = m_texture.GetWidth();
|
||||
m_imageHeight = m_texture.GetHeight();
|
||||
*m_pFilename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
|
||||
// fit the image into the window
|
||||
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0)
|
||||
gui::MaxFit(&min, &max, m_imageWidth, m_imageHeight);
|
||||
|
||||
FieldFrameData ffd;
|
||||
ffd.imageMin = min;
|
||||
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;
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
|
||||
|
||||
ffd.min = min;
|
||||
ffd.max = max;
|
||||
ffd.scale = (max.x - min.x) / *m_pWidth;
|
||||
return ffd;
|
||||
}
|
||||
|
||||
void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
|
||||
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
|
||||
drawList->AddImage(m_texture, ffd.imageMin, ffd.imageMax);
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
|
||||
}
|
||||
|
||||
ObjectGroupInfo::ObjectGroupInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
m_pWidth = storage.GetFloatRef("width", kDefaultWidth);
|
||||
m_pLength = storage.GetFloatRef("length", kDefaultLength);
|
||||
}
|
||||
|
||||
void ObjectGroupInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
}
|
||||
|
||||
void ObjectGroupInfo::LoadImage() {
|
||||
if (m_fileOpener && m_fileOpener->ready(0)) {
|
||||
auto result = m_fileOpener->result();
|
||||
if (!result.empty()) LoadImageImpl(result[0].c_str());
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) m_pFilename->clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectGroupInfo::LoadImageImpl(const char* fn) {
|
||||
wpi::outs() << "GUI: loading object image '" << fn << "'\n";
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
if (!texture) {
|
||||
wpi::errs() << "GUI: could not read object image\n";
|
||||
return false;
|
||||
}
|
||||
m_texture = std::move(texture);
|
||||
*m_pFilename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
ObjectFrameData::ObjectFrameData(FieldObjectModel& model,
|
||||
const FieldFrameData& ffd, float width,
|
||||
float length)
|
||||
: m_model{model},
|
||||
m_ffd{ffd},
|
||||
m_width2(ffd.scale * width / 2),
|
||||
m_length2(ffd.scale * length / 2),
|
||||
m_hitRadius((std::min)(m_width2, m_length2) / 2) {
|
||||
if (auto xData = model.GetXData()) m_x = xData->GetValue();
|
||||
if (auto yData = model.GetYData()) m_y = yData->GetValue();
|
||||
if (auto rotationData = model.GetRotationData())
|
||||
m_rot = rotationData->GetValue();
|
||||
UpdateFrameData();
|
||||
}
|
||||
|
||||
void ObjectFrameData::SetPosition(double x, double y) {
|
||||
m_x = x;
|
||||
m_y = y;
|
||||
m_model.SetPosition(x, y);
|
||||
}
|
||||
|
||||
void ObjectFrameData::SetRotation(double rot) {
|
||||
double rotDegrees = units::convert<units::radians, units::degrees>(rot);
|
||||
// force to -180 to +180 range
|
||||
rotDegrees = rotDegrees + std::ceil((-rotDegrees - 180) / 360) * 360;
|
||||
m_rot = rotDegrees;
|
||||
m_model.SetRotation(rotDegrees);
|
||||
}
|
||||
|
||||
void ObjectFrameData::UpdateFrameData() {
|
||||
// (0,0) origin is bottom left
|
||||
ImVec2 center(m_ffd.min.x + m_ffd.scale * m_x,
|
||||
m_ffd.max.y - m_ffd.scale * m_y);
|
||||
|
||||
// build rotated points around center
|
||||
float length2 = m_length2;
|
||||
float width2 = m_width2;
|
||||
double rot = GetRotation();
|
||||
float cos_a = std::cos(-rot);
|
||||
float sin_a = std::sin(-rot);
|
||||
|
||||
m_corners[0] = center + ImRotate(ImVec2(-length2, -width2), cos_a, sin_a);
|
||||
m_corners[1] = center + ImRotate(ImVec2(length2, -width2), cos_a, sin_a);
|
||||
m_corners[2] = center + ImRotate(ImVec2(length2, width2), cos_a, sin_a);
|
||||
m_corners[3] = center + ImRotate(ImVec2(-length2, width2), cos_a, sin_a);
|
||||
m_arrow[0] =
|
||||
center + ImRotate(ImVec2(-length2 / 2, -width2 / 2), cos_a, sin_a);
|
||||
m_arrow[1] = center + ImRotate(ImVec2(length2 / 2, 0), cos_a, sin_a);
|
||||
m_arrow[2] =
|
||||
center + ImRotate(ImVec2(-length2 / 2, width2 / 2), cos_a, sin_a);
|
||||
|
||||
m_center = center;
|
||||
}
|
||||
|
||||
int ObjectFrameData::IsHovered(const ImVec2& cursor) const {
|
||||
// only allow initiation of dragging when invisible button is hovered;
|
||||
// this prevents the window resize handles from simultaneously activating
|
||||
// the drag functionality
|
||||
if (!ImGui::IsItemHovered()) return 0;
|
||||
|
||||
float hitRadiusSquared = m_hitRadius * m_hitRadius;
|
||||
// it's within the hit radius of the center?
|
||||
if (gui::GetDistSquared(cursor, m_center) < hitRadiusSquared)
|
||||
return 1;
|
||||
else if (gui::GetDistSquared(cursor, m_corners[0]) < hitRadiusSquared)
|
||||
return 2;
|
||||
else if (gui::GetDistSquared(cursor, m_corners[1]) < hitRadiusSquared)
|
||||
return 3;
|
||||
else if (gui::GetDistSquared(cursor, m_corners[2]) < hitRadiusSquared)
|
||||
return 4;
|
||||
else if (gui::GetDistSquared(cursor, m_corners[3]) < hitRadiusSquared)
|
||||
return 5;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ObjectFrameData::HandleDrag(const ImVec2& cursor, int hitCorner,
|
||||
ObjectDragState* drag) {
|
||||
bool rv = false;
|
||||
if (hitCorner > 0 && ImGui::IsMouseClicked(0)) {
|
||||
if (hitCorner == 1) {
|
||||
drag->corner = hitCorner;
|
||||
drag->initialOffset = cursor - m_center;
|
||||
} else {
|
||||
drag->corner = hitCorner;
|
||||
ImVec2 off = cursor - m_center;
|
||||
drag->initialAngle = std::atan2(off.y, off.x) + GetRotation();
|
||||
}
|
||||
rv = true;
|
||||
}
|
||||
|
||||
if (drag->corner > 0 && ImGui::IsMouseDown(0)) {
|
||||
if (drag->corner == 1) {
|
||||
ImVec2 newPos = cursor - drag->initialOffset;
|
||||
SetPosition(
|
||||
(std::clamp(newPos.x, m_ffd.min.x, m_ffd.max.x) - m_ffd.min.x) /
|
||||
m_ffd.scale,
|
||||
(m_ffd.max.y - std::clamp(newPos.y, m_ffd.min.y, m_ffd.max.y)) /
|
||||
m_ffd.scale);
|
||||
UpdateFrameData();
|
||||
} else {
|
||||
ImVec2 off = cursor - m_center;
|
||||
SetRotation(drag->initialAngle - std::atan2(off.y, off.x));
|
||||
}
|
||||
} else {
|
||||
drag->corner = 0;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void ObjectFrameData::Draw(ImDrawList* drawList, const gui::Texture& texture,
|
||||
int hitCorner) const {
|
||||
if (texture) {
|
||||
drawList->AddImageQuad(texture, m_corners[0], m_corners[1], m_corners[2],
|
||||
m_corners[3]);
|
||||
} else {
|
||||
drawList->AddQuad(m_corners[0], m_corners[1], m_corners[2], m_corners[3],
|
||||
IM_COL32(255, 0, 0, 255), 4.0);
|
||||
drawList->AddTriangle(m_arrow[0], m_arrow[1], m_arrow[2],
|
||||
IM_COL32(0, 255, 0, 255), 4.0);
|
||||
}
|
||||
|
||||
if (hitCorner > 0) {
|
||||
if (hitCorner == 1) {
|
||||
drawList->AddCircle(m_center, m_hitRadius, IM_COL32(0, 255, 0, 255));
|
||||
} else {
|
||||
drawList->AddCircle(m_corners[hitCorner - 2], m_hitRadius,
|
||||
IM_COL32(0, 255, 0, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayField2DSettings(Field2DModel* model) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::CollapsingHeader("Field")) {
|
||||
ImGui::PushID("Field");
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
field->m_fileOpener = std::make_unique<pfd::open_file>(
|
||||
"Choose field image", "",
|
||||
std::vector<std::string>{"Image File",
|
||||
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
|
||||
"*.hdr *.pic *.ppm *.pgm",
|
||||
"PathWeaver JSON File", "*.json"});
|
||||
}
|
||||
if (ImGui::Button("Reset image")) {
|
||||
field->Reset();
|
||||
}
|
||||
ImGui::InputFloat("Field Width", field->m_pWidth);
|
||||
ImGui::InputFloat("Field Height", field->m_pHeight);
|
||||
// ImGui::InputInt("Field Top", field->m_pTop);
|
||||
// ImGui::InputInt("Field Left", field->m_pLeft);
|
||||
// ImGui::InputInt("Field Right", field->m_pRight);
|
||||
// ImGui::InputInt("Field Bottom", field->m_pBottom);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
|
||||
if (!groupModel.Exists()) return;
|
||||
PushID(name);
|
||||
auto& objGroupRef = field->m_objectGroups[name];
|
||||
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
|
||||
auto objGroup = objGroupRef.get();
|
||||
|
||||
wpi::SmallString<64> nameBuf = name;
|
||||
if (ImGui::CollapsingHeader(nameBuf.c_str())) {
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
objGroup->m_fileOpener = std::make_unique<pfd::open_file>(
|
||||
"Choose object image", "",
|
||||
std::vector<std::string>{
|
||||
"Image File",
|
||||
"*.jpg *.jpeg *.png *.bmp *.psd *.tga *.gif "
|
||||
"*.hdr *.pic *.ppm *.pgm"});
|
||||
}
|
||||
if (ImGui::Button("Reset image")) {
|
||||
objGroup->Reset();
|
||||
}
|
||||
ImGui::InputFloat("Width", objGroup->m_pWidth);
|
||||
ImGui::InputFloat("Length", objGroup->m_pLength);
|
||||
}
|
||||
PopID();
|
||||
});
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
ImVec2 windowPos = ImGui::GetWindowPos();
|
||||
ImVec2 mousePos = ImGui::GetIO().MousePos;
|
||||
|
||||
// for dragging to work, there needs to be a button (otherwise the window is
|
||||
// dragged)
|
||||
if (contentSize.x <= 0 || contentSize.y <= 0) return;
|
||||
ImVec2 cursorPos = windowPos + ImGui::GetCursorPos(); // screen coords
|
||||
ImGui::InvisibleButton("field", contentSize);
|
||||
|
||||
// field
|
||||
field->LoadImage();
|
||||
FieldFrameData ffd = field->GetFrameData(cursorPos, cursorPos + contentSize);
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
field->Draw(drawList, ffd);
|
||||
|
||||
model->ForEachFieldObjectGroup([&](auto& groupModel, auto name) {
|
||||
if (!groupModel.Exists()) return;
|
||||
PushID(name);
|
||||
auto& objGroupRef = field->m_objectGroups[name];
|
||||
if (!objGroupRef) objGroupRef = std::make_unique<ObjectGroupInfo>();
|
||||
auto objGroup = objGroupRef.get();
|
||||
objGroup->LoadImage();
|
||||
|
||||
int i = 0;
|
||||
groupModel.ForEachFieldObject([&](auto& objModel) {
|
||||
++i;
|
||||
ObjectFrameData ofd{objModel, ffd, *objGroup->m_pWidth,
|
||||
*objGroup->m_pLength};
|
||||
|
||||
int hitCorner = 0;
|
||||
if (objGroup->m_dragState.object == 0 ||
|
||||
objGroup->m_dragState.object == i) {
|
||||
hitCorner = ofd.IsHovered(mousePos);
|
||||
if (ofd.HandleDrag(mousePos, hitCorner, &objGroup->m_dragState))
|
||||
objGroup->m_dragState.object = i;
|
||||
}
|
||||
|
||||
// draw
|
||||
ofd.Draw(drawList, objGroup->GetTexture(), hitCorner);
|
||||
});
|
||||
PopID();
|
||||
});
|
||||
}
|
||||
|
||||
void Field2DView::Display() {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
DisplayField2DSettings(m_model);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
DisplayField2D(m_model, ImGui::GetWindowContentRegionMax() -
|
||||
ImGui::GetWindowContentRegionMin());
|
||||
}
|
||||
932
glass/src/lib/native/cpp/other/Plot.cpp
Normal file
932
glass/src/lib/native/cpp/other/Plot.cpp
Normal file
@@ -0,0 +1,932 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/other/Plot.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <implot.h>
|
||||
#include <wpigui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace {
|
||||
class PlotView;
|
||||
|
||||
struct PlotSeriesRef {
|
||||
PlotView* view;
|
||||
size_t plotIndex;
|
||||
size_t seriesIndex;
|
||||
};
|
||||
|
||||
class PlotSeries {
|
||||
public:
|
||||
explicit PlotSeries(wpi::StringRef id);
|
||||
explicit PlotSeries(DataSource* source, int yAxis = 0);
|
||||
|
||||
const std::string& GetId() const { return m_id; }
|
||||
|
||||
void CheckSource();
|
||||
|
||||
void SetSource(DataSource* source);
|
||||
DataSource* GetSource() const { return m_source; }
|
||||
|
||||
void Clear() { m_size = 0; }
|
||||
|
||||
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
|
||||
void WriteIni(ImGuiTextBuffer* out);
|
||||
|
||||
enum Action { kNone, kMoveUp, kMoveDown, kDelete };
|
||||
Action EmitPlot(PlotView& view, double now, size_t i, size_t plotIndex);
|
||||
void EmitSettings(size_t i);
|
||||
void EmitDragDropPayload(PlotView& view, size_t i, size_t plotIndex);
|
||||
|
||||
const char* GetName() const;
|
||||
|
||||
int GetYAxis() const { return m_yAxis; }
|
||||
void SetYAxis(int yAxis) { m_yAxis = yAxis; }
|
||||
|
||||
private:
|
||||
bool IsDigital() const {
|
||||
return m_digital == kDigital ||
|
||||
(m_digital == kAuto && m_source && m_source->IsDigital());
|
||||
}
|
||||
void AppendValue(double value, uint64_t time);
|
||||
|
||||
// source linkage
|
||||
DataSource* m_source = nullptr;
|
||||
wpi::sig::ScopedConnection m_sourceCreatedConn;
|
||||
wpi::sig::ScopedConnection m_newValueConn;
|
||||
std::string m_id;
|
||||
|
||||
// user settings
|
||||
std::string m_name;
|
||||
int m_yAxis = 0;
|
||||
ImVec4 m_color = IMPLOT_AUTO_COL;
|
||||
int m_marker = 0;
|
||||
float m_weight = IMPLOT_AUTO;
|
||||
|
||||
enum Digital { kAuto, kDigital, kAnalog };
|
||||
int m_digital = 0;
|
||||
int m_digitalBitHeight = 8;
|
||||
int m_digitalBitGap = 4;
|
||||
|
||||
// value storage
|
||||
static constexpr int kMaxSize = 2000;
|
||||
static constexpr double kTimeGap = 0.05;
|
||||
std::atomic<int> m_size = 0;
|
||||
std::atomic<int> m_offset = 0;
|
||||
ImPlotPoint m_data[kMaxSize];
|
||||
};
|
||||
|
||||
class Plot {
|
||||
public:
|
||||
Plot();
|
||||
|
||||
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
|
||||
void WriteIni(ImGuiTextBuffer* out);
|
||||
|
||||
void Clear();
|
||||
|
||||
void DragDropTarget(PlotView& view, size_t i, bool inPlot);
|
||||
void EmitPlot(PlotView& view, double now, bool paused, size_t i);
|
||||
void EmitSettings(size_t i);
|
||||
|
||||
const std::string& GetName() const { return m_name; }
|
||||
|
||||
std::vector<std::unique_ptr<PlotSeries>> m_series;
|
||||
|
||||
private:
|
||||
void EmitSettingsLimits(int axis);
|
||||
|
||||
std::string m_name;
|
||||
bool m_visible = true;
|
||||
bool m_showPause = true;
|
||||
unsigned int m_plotFlags = ImPlotFlags_Default;
|
||||
bool m_lockPrevX = false;
|
||||
bool m_paused = false;
|
||||
float m_viewTime = 10;
|
||||
int m_height = 300;
|
||||
struct PlotRange {
|
||||
double min = 0;
|
||||
double max = 1;
|
||||
bool lockMin = false;
|
||||
bool lockMax = false;
|
||||
bool apply = false;
|
||||
};
|
||||
PlotRange m_axisRange[3];
|
||||
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
|
||||
};
|
||||
|
||||
class PlotView : public View {
|
||||
public:
|
||||
explicit PlotView(PlotProvider* provider) : m_provider{provider} {}
|
||||
|
||||
void Clear();
|
||||
|
||||
void Display() override;
|
||||
|
||||
void MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex);
|
||||
|
||||
void MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
|
||||
size_t fromSeriesIndex, size_t toPlotIndex,
|
||||
size_t toSeriesIndex, int yAxis = -1);
|
||||
|
||||
PlotProvider* m_provider;
|
||||
std::vector<std::unique_ptr<Plot>> m_plots;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PlotSeries::PlotSeries(wpi::StringRef id) : m_id(id) {
|
||||
if (DataSource* source = DataSource::Find(id)) {
|
||||
SetSource(source);
|
||||
return;
|
||||
}
|
||||
CheckSource();
|
||||
}
|
||||
|
||||
PlotSeries::PlotSeries(DataSource* source, int yAxis) : m_yAxis(yAxis) {
|
||||
SetSource(source);
|
||||
}
|
||||
|
||||
void PlotSeries::CheckSource() {
|
||||
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
|
||||
m_source = nullptr;
|
||||
m_sourceCreatedConn = DataSource::sourceCreated.connect_connection(
|
||||
[this](const char* id, DataSource* source) {
|
||||
if (m_id == id) {
|
||||
SetSource(source);
|
||||
m_sourceCreatedConn.disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PlotSeries::SetSource(DataSource* source) {
|
||||
m_source = source;
|
||||
m_id = source->GetId();
|
||||
|
||||
// add initial value
|
||||
m_data[m_size++] = ImPlotPoint{wpi::Now() * 1.0e-6, source->GetValue()};
|
||||
|
||||
m_newValueConn = source->valueChanged.connect_connection(
|
||||
[this](double value, uint64_t time) { AppendValue(value, time); });
|
||||
}
|
||||
|
||||
void PlotSeries::AppendValue(double value, uint64_t timeUs) {
|
||||
double time = (timeUs != 0 ? timeUs : wpi::Now()) * 1.0e-6;
|
||||
if (IsDigital()) {
|
||||
if (m_size < kMaxSize) {
|
||||
m_data[m_size] = ImPlotPoint{time, value};
|
||||
++m_size;
|
||||
} else {
|
||||
m_data[m_offset] = ImPlotPoint{time, value};
|
||||
m_offset = (m_offset + 1) % kMaxSize;
|
||||
}
|
||||
} else {
|
||||
// as an analog graph draws linear lines in between each value,
|
||||
// insert duplicate value if "long" time between updates so it
|
||||
// looks appropriately flat
|
||||
if (m_size < kMaxSize) {
|
||||
if (m_size > 0) {
|
||||
if ((time - m_data[m_size - 1].x) > kTimeGap) {
|
||||
m_data[m_size] = ImPlotPoint{time, m_data[m_size - 1].y};
|
||||
++m_size;
|
||||
}
|
||||
}
|
||||
m_data[m_size] = ImPlotPoint{time, value};
|
||||
++m_size;
|
||||
} else {
|
||||
if (m_offset == 0) {
|
||||
if ((time - m_data[kMaxSize - 1].x) > kTimeGap) {
|
||||
m_data[m_offset] = ImPlotPoint{time, m_data[kMaxSize - 1].y};
|
||||
++m_offset;
|
||||
}
|
||||
} else {
|
||||
if ((time - m_data[m_offset - 1].x) > kTimeGap) {
|
||||
m_data[m_offset] = ImPlotPoint{time, m_data[m_offset - 1].y};
|
||||
m_offset = (m_offset + 1) % kMaxSize;
|
||||
}
|
||||
}
|
||||
m_data[m_offset] = ImPlotPoint{time, value};
|
||||
m_offset = (m_offset + 1) % kMaxSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PlotSeries::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
return true;
|
||||
}
|
||||
if (name == "yAxis") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_yAxis = num;
|
||||
return true;
|
||||
} else if (name == "color") {
|
||||
unsigned int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_color = ImColor(num);
|
||||
return true;
|
||||
} else if (name == "marker") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_marker = num;
|
||||
return true;
|
||||
} else if (name == "weight") {
|
||||
std::sscanf(value.data(), "%f", &m_weight);
|
||||
return true;
|
||||
} else if (name == "digital") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_digital = num;
|
||||
return true;
|
||||
} else if (name == "digitalBitHeight") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_digitalBitHeight = num;
|
||||
return true;
|
||||
} else if (name == "digitalBitGap") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_digitalBitGap = num;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlotSeries::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf(
|
||||
"name=%s\nyAxis=%d\ncolor=%u\nmarker=%d\nweight=%f\ndigital=%d\n"
|
||||
"digitalBitHeight=%d\ndigitalBitGap=%d\n",
|
||||
m_name.c_str(), m_yAxis, static_cast<ImU32>(ImColor(m_color)), m_marker,
|
||||
m_weight, m_digital, m_digitalBitHeight, m_digitalBitGap);
|
||||
}
|
||||
|
||||
const char* PlotSeries::GetName() const {
|
||||
if (!m_name.empty()) return m_name.c_str();
|
||||
if (m_newValueConn.connected()) {
|
||||
auto sourceName = m_source->GetName();
|
||||
if (sourceName[0] != '\0') return sourceName;
|
||||
}
|
||||
return m_id.c_str();
|
||||
}
|
||||
|
||||
PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
|
||||
size_t plotIndex) {
|
||||
CheckSource();
|
||||
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###name", GetName());
|
||||
|
||||
int size = m_size;
|
||||
int offset = m_offset;
|
||||
|
||||
// need to have last value at current time, so need to create fake last value
|
||||
// we handle the offset logic ourselves to avoid wrap issues with size + 1
|
||||
struct GetterData {
|
||||
double now;
|
||||
ImPlotPoint* data;
|
||||
int size;
|
||||
int offset;
|
||||
};
|
||||
GetterData getterData = {now, m_data, size, offset};
|
||||
auto getter = [](void* data, int idx) {
|
||||
auto d = static_cast<GetterData*>(data);
|
||||
if (idx == d->size)
|
||||
return ImPlotPoint{
|
||||
d->now, d->data[d->offset == 0 ? d->size - 1 : d->offset - 1].y};
|
||||
if (d->offset + idx < d->size)
|
||||
return d->data[d->offset + idx];
|
||||
else
|
||||
return d->data[d->offset + idx - d->size];
|
||||
};
|
||||
|
||||
if (m_color.w == IMPLOT_AUTO_COL.w) m_color = ImPlot::GetColormapColor(i);
|
||||
ImPlot::SetNextLineStyle(m_color, m_weight);
|
||||
if (IsDigital()) {
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
|
||||
ImPlot::PlotDigital(label, getter, &getterData, size + 1);
|
||||
ImPlot::PopStyleVar();
|
||||
ImPlot::PopStyleVar();
|
||||
} else {
|
||||
ImPlot::SetPlotYAxis(m_yAxis);
|
||||
ImPlot::SetNextMarkerStyle(m_marker - 1);
|
||||
ImPlot::PlotLine(label, getter, &getterData, size + 1);
|
||||
}
|
||||
|
||||
// DND source for PlotSeries
|
||||
if (ImPlot::BeginLegendDragDropSource(label)) {
|
||||
EmitDragDropPayload(view, i, plotIndex);
|
||||
ImPlot::EndLegendDragDropSource();
|
||||
}
|
||||
|
||||
// Edit settings via popup
|
||||
Action rv = kNone;
|
||||
if (ImPlot::BeginLegendPopup(label)) {
|
||||
if (ImGui::Button("Close")) ImGui::CloseCurrentPopup();
|
||||
ImGui::Text("Edit series name:");
|
||||
ImGui::InputText("##editname", &m_name);
|
||||
if (ImGui::Button("Move Up")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
rv = kMoveUp;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Move Down")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
rv = kMoveDown;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
rv = kDelete;
|
||||
}
|
||||
EmitSettings(i);
|
||||
ImPlot::EndLegendPopup();
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void PlotSeries::EmitDragDropPayload(PlotView& view, size_t i,
|
||||
size_t plotIndex) {
|
||||
PlotSeriesRef ref = {&view, plotIndex, i};
|
||||
ImGui::SetDragDropPayload("PlotSeries", &ref, sizeof(ref));
|
||||
ImGui::TextUnformatted(GetName());
|
||||
}
|
||||
|
||||
void PlotSeries::EmitSettings(size_t i) {
|
||||
// Line color
|
||||
{
|
||||
ImGui::ColorEdit3("Color", &m_color.x, ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Default")) m_color = ImPlot::GetColormapColor(i);
|
||||
}
|
||||
|
||||
// Line weight
|
||||
{
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::InputFloat("Weight", &m_weight, 0.1f, 1.0f, "%.1f");
|
||||
}
|
||||
|
||||
// Digital
|
||||
{
|
||||
static const char* const options[] = {"Auto", "Digital", "Analog"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::Combo("Digital", &m_digital, options,
|
||||
sizeof(options) / sizeof(options[0]));
|
||||
}
|
||||
|
||||
if (IsDigital()) {
|
||||
// Bit Height
|
||||
{
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
ImGui::InputInt("Bit Height", &m_digitalBitHeight);
|
||||
}
|
||||
|
||||
// Bit Gap
|
||||
{
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
ImGui::InputInt("Bit Gap", &m_digitalBitGap);
|
||||
}
|
||||
} else {
|
||||
// Y-axis
|
||||
{
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
static const char* const options[] = {"1", "2", "3"};
|
||||
ImGui::Combo("Y-Axis", &m_yAxis, options, 3);
|
||||
}
|
||||
|
||||
// Marker
|
||||
{
|
||||
static const char* const options[] = {
|
||||
"None", "Circle", "Square", "Diamond", "Up", "Down",
|
||||
"Left", "Right", "Cross", "Plus", "Asterisk"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Marker", &m_marker, options,
|
||||
sizeof(options) / sizeof(options[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Plot::Plot() {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_axisRange[i] = PlotRange{};
|
||||
}
|
||||
}
|
||||
|
||||
bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
return true;
|
||||
} else if (name == "visible") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_visible = num != 0;
|
||||
return true;
|
||||
} else if (name == "showPause") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_showPause = num != 0;
|
||||
return true;
|
||||
} else if (name == "lockPrevX") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_lockPrevX = num != 0;
|
||||
return true;
|
||||
} else if (name == "legend") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
if (num == 0)
|
||||
m_plotFlags &= ~ImPlotFlags_Legend;
|
||||
else
|
||||
m_plotFlags |= ImPlotFlags_Legend;
|
||||
return true;
|
||||
} else if (name == "yaxis2") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
if (num == 0)
|
||||
m_plotFlags &= ~ImPlotFlags_YAxis2;
|
||||
else
|
||||
m_plotFlags |= ImPlotFlags_YAxis2;
|
||||
return true;
|
||||
} else if (name == "yaxis3") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
if (num == 0)
|
||||
m_plotFlags &= ~ImPlotFlags_YAxis3;
|
||||
else
|
||||
m_plotFlags |= ImPlotFlags_YAxis3;
|
||||
return true;
|
||||
} else if (name == "viewTime") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_viewTime = num / 1000.0;
|
||||
return true;
|
||||
} else if (name == "height") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_height = num;
|
||||
return true;
|
||||
} else if (name.startswith("y")) {
|
||||
auto [yAxisStr, yName] = name.split('_');
|
||||
int yAxis;
|
||||
if (yAxisStr.substr(1).getAsInteger(10, yAxis)) return false;
|
||||
if (yAxis < 0 || yAxis > 3) return false;
|
||||
if (yName == "min") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_axisRange[yAxis].min = num / 1000.0;
|
||||
return true;
|
||||
} else if (yName == "max") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_axisRange[yAxis].max = num / 1000.0;
|
||||
return true;
|
||||
} else if (yName == "lockMin") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_axisRange[yAxis].lockMin = num != 0;
|
||||
return true;
|
||||
} else if (yName == "lockMax") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_axisRange[yAxis].lockMax = num != 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Plot::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf(
|
||||
"name=%s\nvisible=%d\nshowPause=%d\nlockPrevX=%d\nlegend=%d\n"
|
||||
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nheight=%d\n",
|
||||
m_name.c_str(), m_visible ? 1 : 0, m_showPause ? 1 : 0,
|
||||
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_Legend) ? 1 : 0,
|
||||
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
|
||||
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
|
||||
static_cast<int>(m_viewTime * 1000), m_height);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
out->appendf("y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n", i,
|
||||
static_cast<int>(m_axisRange[i].min * 1000), i,
|
||||
static_cast<int>(m_axisRange[i].max * 1000), i,
|
||||
m_axisRange[i].lockMin ? 1 : 0, i,
|
||||
m_axisRange[i].lockMax ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::Clear() {
|
||||
for (auto&& series : m_series) series->Clear();
|
||||
}
|
||||
|
||||
void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
|
||||
if (!ImGui::BeginDragDropTarget()) return;
|
||||
// handle dragging onto a specific Y axis
|
||||
int yAxis = -1;
|
||||
if (inPlot) {
|
||||
for (int y = 0; y < 3; ++y) {
|
||||
if (ImPlot::IsPlotYAxisHovered(y)) {
|
||||
yAxis = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload("DataSource")) {
|
||||
auto source = *static_cast<DataSource**>(payload->Data);
|
||||
// don't add duplicates unless it's onto a different Y axis
|
||||
auto it =
|
||||
std::find_if(m_series.begin(), m_series.end(), [=](const auto& elem) {
|
||||
return elem->GetId() == source->GetId() &&
|
||||
(yAxis == -1 || elem->GetYAxis() == yAxis);
|
||||
});
|
||||
if (it == m_series.end()) {
|
||||
m_series.emplace_back(
|
||||
std::make_unique<PlotSeries>(source, yAxis == -1 ? 0 : yAxis));
|
||||
}
|
||||
} else if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload("PlotSeries")) {
|
||||
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
|
||||
view.MovePlotSeries(ref->view, ref->plotIndex, ref->seriesIndex, i,
|
||||
m_series.size(), yAxis);
|
||||
} else if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload("Plot")) {
|
||||
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
|
||||
view.MovePlot(ref->view, ref->plotIndex, i);
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
|
||||
if (!m_visible) return;
|
||||
|
||||
bool lockX = (i != 0 && m_lockPrevX);
|
||||
|
||||
if (!lockX && m_showPause && ImGui::Button(m_paused ? "Resume" : "Pause"))
|
||||
m_paused = !m_paused;
|
||||
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s##plot", m_name.c_str());
|
||||
|
||||
if (lockX) {
|
||||
ImPlot::SetNextPlotLimitsX(view.m_plots[i - 1]->m_xaxisRange.Min,
|
||||
view.m_plots[i - 1]->m_xaxisRange.Max,
|
||||
ImGuiCond_Always);
|
||||
} else {
|
||||
// also force-pause plots if overall timing is paused
|
||||
ImPlot::SetNextPlotLimitsX(
|
||||
now - m_viewTime, now,
|
||||
(paused || m_paused) ? ImGuiCond_Once : ImGuiCond_Always);
|
||||
}
|
||||
|
||||
ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_Default,
|
||||
ImPlotAxisFlags_Auxiliary,
|
||||
ImPlotAxisFlags_Auxiliary};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ImPlot::SetNextPlotLimitsY(
|
||||
m_axisRange[i].min, m_axisRange[i].max,
|
||||
m_axisRange[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
|
||||
m_axisRange[i].apply = false;
|
||||
if (m_axisRange[i].lockMin) yFlags[i] |= ImPlotAxisFlags_LockMin;
|
||||
if (m_axisRange[i].lockMax) yFlags[i] |= ImPlotAxisFlags_LockMax;
|
||||
}
|
||||
|
||||
if (ImPlot::BeginPlot(label, nullptr, nullptr, ImVec2(-1, m_height),
|
||||
m_plotFlags, ImPlotAxisFlags_Default, yFlags[0],
|
||||
yFlags[1], yFlags[2])) {
|
||||
for (size_t j = 0; j < m_series.size(); ++j) {
|
||||
ImGui::PushID(j);
|
||||
switch (m_series[j]->EmitPlot(view, now, j, i)) {
|
||||
case PlotSeries::kMoveUp:
|
||||
if (j > 0) std::swap(m_series[j - 1], m_series[j]);
|
||||
break;
|
||||
case PlotSeries::kMoveDown:
|
||||
if (j < (m_series.size() - 1))
|
||||
std::swap(m_series[j], m_series[j + 1]);
|
||||
break;
|
||||
case PlotSeries::kDelete:
|
||||
m_series.erase(m_series.begin() + j);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
DragDropTarget(view, i, true);
|
||||
m_xaxisRange = ImPlot::GetPlotLimits().X;
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::EmitSettingsLimits(int axis) {
|
||||
ImGui::Indent();
|
||||
ImGui::PushID(axis);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
|
||||
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
|
||||
ImGui::InputDouble("Max", &m_axisRange[axis].max, 0, 0, "%.3f");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply")) m_axisRange[axis].apply = true;
|
||||
|
||||
ImGui::TextUnformatted("Lock Axis");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Min##minlock", &m_axisRange[axis].lockMin);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Max##maxlock", &m_axisRange[axis].lockMax);
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
void Plot::EmitSettings(size_t i) {
|
||||
ImGui::Text("Edit plot name:");
|
||||
ImGui::InputText("##editname", &m_name);
|
||||
ImGui::Checkbox("Visible", &m_visible);
|
||||
ImGui::Checkbox("Show Pause Button", &m_showPause);
|
||||
ImGui::CheckboxFlags("Show Legend", &m_plotFlags, ImPlotFlags_Legend);
|
||||
if (i != 0) ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
|
||||
ImGui::TextUnformatted("Primary Y-Axis");
|
||||
EmitSettingsLimits(0);
|
||||
ImGui::CheckboxFlags("2nd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis2);
|
||||
if ((m_plotFlags & ImPlotFlags_YAxis2) != 0) EmitSettingsLimits(1);
|
||||
ImGui::CheckboxFlags("3rd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis3);
|
||||
if ((m_plotFlags & ImPlotFlags_YAxis3) != 0) EmitSettingsLimits(2);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::InputFloat("View Time (s)", &m_viewTime, 0.1f, 1.0f, "%.1f");
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
if (ImGui::InputInt("Height", &m_height, 10)) {
|
||||
if (m_height < 0) m_height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::Clear() {
|
||||
for (auto&& plot : m_plots) plot->Clear();
|
||||
}
|
||||
|
||||
void PlotView::Display() {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::Button("Add plot"))
|
||||
m_plots.emplace_back(std::make_unique<Plot>());
|
||||
|
||||
for (size_t i = 0; i < m_plots.size(); ++i) {
|
||||
auto& plot = m_plots[i];
|
||||
ImGui::PushID(i);
|
||||
|
||||
char name[64];
|
||||
if (!plot->GetName().empty())
|
||||
std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str());
|
||||
else
|
||||
std::snprintf(name, sizeof(name), "Plot %d", static_cast<int>(i));
|
||||
|
||||
char label[90];
|
||||
std::snprintf(label, sizeof(label), "%s###header%d", name,
|
||||
static_cast<int>(i));
|
||||
|
||||
bool open = ImGui::CollapsingHeader(label);
|
||||
|
||||
// DND source and target for Plot
|
||||
if (ImGui::BeginDragDropSource()) {
|
||||
PlotSeriesRef ref = {this, i, 0};
|
||||
ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref));
|
||||
ImGui::TextUnformatted(name);
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
plot->DragDropTarget(*this, i, false);
|
||||
|
||||
if (open) {
|
||||
if (ImGui::Button("Move Up")) {
|
||||
if (i > 0) std::swap(m_plots[i - 1], plot);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Move Down")) {
|
||||
if (i < (m_plots.size() - 1)) std::swap(plot, m_plots[i + 1]);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
m_plots.erase(m_plots.begin() + i);
|
||||
ImGui::PopID();
|
||||
continue;
|
||||
}
|
||||
|
||||
plot->EmitSettings(i);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (m_plots.empty()) {
|
||||
if (ImGui::Button("Add plot"))
|
||||
m_plots.emplace_back(std::make_unique<Plot>());
|
||||
|
||||
// Make "add plot" button a DND target for Plot
|
||||
if (!ImGui::BeginDragDropTarget()) return;
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("Plot")) {
|
||||
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
|
||||
MovePlot(ref->view, ref->plotIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
double now = (wpi::Now() - m_provider->GetStartTime()) * 1.0e-6;
|
||||
for (size_t i = 0; i < m_plots.size(); ++i) {
|
||||
ImGui::PushID(i);
|
||||
m_plots[i]->EmitPlot(*this, now, m_provider->IsPaused(), i);
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex) {
|
||||
if (fromView == this) {
|
||||
if (fromIndex == toIndex) return;
|
||||
auto val = std::move(m_plots[fromIndex]);
|
||||
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
|
||||
m_plots.erase(m_plots.begin() + fromIndex + (fromIndex > toIndex ? 1 : 0));
|
||||
} else {
|
||||
auto val = std::move(fromView->m_plots[fromIndex]);
|
||||
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
|
||||
fromView->m_plots.erase(fromView->m_plots.begin() + fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
|
||||
size_t fromSeriesIndex, size_t toPlotIndex,
|
||||
size_t toSeriesIndex, int yAxis) {
|
||||
if (fromView == this && fromPlotIndex == toPlotIndex) {
|
||||
// need to handle this specially as the index of the old location changes
|
||||
if (fromSeriesIndex != toSeriesIndex) {
|
||||
auto& plotSeries = m_plots[fromPlotIndex]->m_series;
|
||||
auto val = std::move(plotSeries[fromSeriesIndex]);
|
||||
// only set Y-axis if actually set
|
||||
if (yAxis != -1) val->SetYAxis(yAxis);
|
||||
plotSeries.insert(plotSeries.begin() + toSeriesIndex, std::move(val));
|
||||
plotSeries.erase(plotSeries.begin() + fromSeriesIndex +
|
||||
(fromSeriesIndex > toSeriesIndex ? 1 : 0));
|
||||
}
|
||||
} else {
|
||||
auto& fromPlot = *fromView->m_plots[fromPlotIndex];
|
||||
auto& toPlot = *m_plots[toPlotIndex];
|
||||
// always set Y-axis if moving plots
|
||||
fromPlot.m_series[fromSeriesIndex]->SetYAxis(yAxis == -1 ? 0 : yAxis);
|
||||
toPlot.m_series.insert(toPlot.m_series.begin() + toSeriesIndex,
|
||||
std::move(fromPlot.m_series[fromSeriesIndex]));
|
||||
fromPlot.m_series.erase(fromPlot.m_series.begin() + fromSeriesIndex);
|
||||
}
|
||||
}
|
||||
|
||||
PlotProvider::PlotProvider(const wpi::Twine& iniName)
|
||||
: WindowManager{iniName + "Window"},
|
||||
m_plotSaver{iniName, this, false},
|
||||
m_seriesSaver{iniName + "Series", this, true} {}
|
||||
|
||||
PlotProvider::~PlotProvider() {}
|
||||
|
||||
void PlotProvider::GlobalInit() {
|
||||
WindowManager::GlobalInit();
|
||||
wpi::gui::AddInit([this] {
|
||||
m_plotSaver.Initialize();
|
||||
m_seriesSaver.Initialize();
|
||||
});
|
||||
}
|
||||
|
||||
void PlotProvider::ResetTime() {
|
||||
m_startTime = wpi::Now();
|
||||
for (auto&& window : m_windows) {
|
||||
if (auto view = static_cast<PlotView*>(window->GetView())) {
|
||||
view->Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlotProvider::DisplayMenu() {
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
m_windows[i]->DisplayMenuItem();
|
||||
// provide method to destroy the plot window
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::Selectable("Destroy Plot Window")) {
|
||||
m_windows.erase(m_windows.begin() + i);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("New Plot Window")) {
|
||||
char id[32];
|
||||
std::snprintf(id, sizeof(id), "Plot <%d>",
|
||||
static_cast<int>(m_windows.size()));
|
||||
AddWindow(id, std::make_unique<PlotView>(this));
|
||||
}
|
||||
}
|
||||
|
||||
void PlotProvider::DisplayWindows() {
|
||||
// create views if not already created
|
||||
for (auto&& window : m_windows) {
|
||||
if (!window->HasView()) window->SetView(std::make_unique<PlotView>(this));
|
||||
}
|
||||
WindowManager::DisplayWindows();
|
||||
}
|
||||
|
||||
PlotProvider::IniSaver::IniSaver(const wpi::Twine& typeName,
|
||||
PlotProvider* provider, bool forSeries)
|
||||
: IniSaverBase{typeName}, m_provider{provider}, m_forSeries{forSeries} {}
|
||||
|
||||
void* PlotProvider::IniSaver::IniReadOpen(const char* name) {
|
||||
auto [viewId, plotNumStr] = wpi::StringRef{name}.split('#');
|
||||
wpi::StringRef seriesId;
|
||||
if (m_forSeries) {
|
||||
std::tie(plotNumStr, seriesId) = plotNumStr.split('#');
|
||||
if (seriesId.empty()) return nullptr;
|
||||
}
|
||||
unsigned int plotNum;
|
||||
if (plotNumStr.getAsInteger(10, plotNum)) return nullptr;
|
||||
|
||||
// get or create window
|
||||
auto win = m_provider->GetOrAddWindow(viewId, true);
|
||||
if (!win) return nullptr;
|
||||
|
||||
// get or create view
|
||||
auto view = static_cast<PlotView*>(win->GetView());
|
||||
if (!view) {
|
||||
win->SetView(std::make_unique<PlotView>(m_provider));
|
||||
view = static_cast<PlotView*>(win->GetView());
|
||||
}
|
||||
|
||||
// get or create plot
|
||||
if (view->m_plots.size() <= plotNum) view->m_plots.resize(plotNum + 1);
|
||||
auto& plot = view->m_plots[plotNum];
|
||||
if (!plot) plot = std::make_unique<Plot>();
|
||||
|
||||
// early exit for plot data
|
||||
if (!m_forSeries) return plot.get();
|
||||
|
||||
// get or create series
|
||||
return plot->m_series.emplace_back(std::make_unique<PlotSeries>(seriesId))
|
||||
.get();
|
||||
}
|
||||
|
||||
void PlotProvider::IniSaver::IniReadLine(void* entry, const char* lineStr) {
|
||||
auto [name, value] = wpi::StringRef{lineStr}.split('=');
|
||||
name = name.trim();
|
||||
value = value.trim();
|
||||
if (m_forSeries)
|
||||
static_cast<PlotSeries*>(entry)->ReadIni(name, value);
|
||||
else
|
||||
static_cast<Plot*>(entry)->ReadIni(name, value);
|
||||
}
|
||||
|
||||
void PlotProvider::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
|
||||
for (auto&& win : m_provider->m_windows) {
|
||||
auto view = static_cast<PlotView*>(win->GetView());
|
||||
auto id = win->GetId();
|
||||
for (size_t i = 0; i < view->m_plots.size(); ++i) {
|
||||
if (m_forSeries) {
|
||||
// Loop over series
|
||||
for (auto&& series : view->m_plots[i]->m_series) {
|
||||
out_buf->appendf("[%s][%s#%d#%s]\n", GetTypeName(), id.data(),
|
||||
static_cast<int>(i), series->GetId().c_str());
|
||||
series->WriteIni(out_buf);
|
||||
out_buf->append("\n");
|
||||
}
|
||||
} else {
|
||||
// Just the plot
|
||||
out_buf->appendf("[%s][%s#%d]\n", GetTypeName(), id.data(),
|
||||
static_cast<int>(i));
|
||||
view->m_plots[i]->WriteIni(out_buf);
|
||||
out_buf->append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
glass/src/lib/native/cpp/other/StringChooser.cpp
Normal file
44
glass/src/lib/native/cpp/other/StringChooser.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/other/StringChooser.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplayStringChooser(StringChooserModel* model) {
|
||||
auto& defaultValue = model->GetDefault();
|
||||
auto& selected = model->GetSelected();
|
||||
auto& active = model->GetActive();
|
||||
auto& options = model->GetOptions();
|
||||
|
||||
const char* preview =
|
||||
selected.empty() ? defaultValue.c_str() : selected.c_str();
|
||||
|
||||
const char* label;
|
||||
if (active == preview) {
|
||||
label = "GOOD##select";
|
||||
} else {
|
||||
label = "BAD ##select";
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo(label, preview)) {
|
||||
for (auto&& option : options) {
|
||||
ImGui::PushID(option.c_str());
|
||||
bool isSelected = (option == selected);
|
||||
if (ImGui::Selectable(option.c_str(), isSelected)) {
|
||||
model->SetSelected(option);
|
||||
}
|
||||
if (isSelected) ImGui::SetItemDefaultFocus();
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
}
|
||||
168
glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
Normal file
168
glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
void DrawLEDSources(const int* values, DataSource** sources, int numValues,
|
||||
int cols, const ImU32* colors, float size, float spacing,
|
||||
const LEDConfig& config) {
|
||||
if (numValues == 0 || cols < 1) return;
|
||||
if (size == 0) size = ImGui::GetFontSize() / 2.0;
|
||||
if (spacing == 0) spacing = ImGui::GetFontSize() / 3.0;
|
||||
|
||||
int rows = (numValues + cols - 1) / cols;
|
||||
float inc = size + spacing;
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
|
||||
float sized2 = size / 2;
|
||||
float ystart, yinc;
|
||||
if (config.start & 1) {
|
||||
// lower
|
||||
ystart = p.y + sized2 + inc * (rows - 1);
|
||||
yinc = -inc;
|
||||
} else {
|
||||
// upper
|
||||
ystart = p.y + sized2;
|
||||
yinc = inc;
|
||||
}
|
||||
|
||||
float xstart, xinc;
|
||||
if (config.start & 2) {
|
||||
// right
|
||||
xstart = p.x + sized2 + inc * (cols - 1);
|
||||
xinc = -inc;
|
||||
} else {
|
||||
// left
|
||||
xstart = p.x + sized2;
|
||||
xinc = inc;
|
||||
}
|
||||
|
||||
float x = xstart, y = ystart;
|
||||
int rowcol = 1; // row for row-major, column for column-major
|
||||
for (int i = 0; i < numValues; ++i) {
|
||||
if (config.order == LEDConfig::RowMajor) {
|
||||
if (i >= (rowcol * cols)) {
|
||||
++rowcol;
|
||||
if (config.serpentine) {
|
||||
x -= xinc;
|
||||
xinc = -xinc;
|
||||
} else {
|
||||
x = xstart;
|
||||
}
|
||||
y += yinc;
|
||||
}
|
||||
} else {
|
||||
if (i >= (rowcol * rows)) {
|
||||
++rowcol;
|
||||
if (config.serpentine) {
|
||||
y -= yinc;
|
||||
yinc = -yinc;
|
||||
} else {
|
||||
y = ystart;
|
||||
}
|
||||
x += xinc;
|
||||
}
|
||||
}
|
||||
if (values[i] > 0)
|
||||
drawList->AddRectFilled(ImVec2(x, y), ImVec2(x + size, y + size),
|
||||
colors[values[i] - 1]);
|
||||
else if (values[i] < 0)
|
||||
drawList->AddRect(ImVec2(x, y), ImVec2(x + size, y + size),
|
||||
colors[-values[i] - 1], 0.0f, 0, 1.0);
|
||||
if (sources) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(x - sized2, y - sized2));
|
||||
if (sources[i]) {
|
||||
ImGui::PushID(i);
|
||||
ImGui::Selectable("", false, 0, ImVec2(inc, inc));
|
||||
sources[i]->EmitDrag();
|
||||
ImGui::PopID();
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(inc, inc));
|
||||
}
|
||||
}
|
||||
if (config.order == LEDConfig::RowMajor) {
|
||||
x += xinc;
|
||||
} else {
|
||||
y += yinc;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sources) ImGui::Dummy(ImVec2(inc * cols, inc * rows));
|
||||
}
|
||||
|
||||
void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
|
||||
float size, float spacing, const LEDConfig& config) {
|
||||
DrawLEDSources(values, nullptr, numValues, cols, colors, size, spacing,
|
||||
config);
|
||||
}
|
||||
|
||||
bool DeleteButton(ImGuiID id, const ImVec2& pos) {
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiWindow* window = g.CurrentWindow;
|
||||
|
||||
// We intentionally allow interaction when clipped so that a mechanical
|
||||
// Alt,Right,Validate sequence close a window. (this isn't the regular
|
||||
// behavior of buttons, but it doesn't affect the user much because navigation
|
||||
// tends to keep items visible).
|
||||
const ImRect bb(
|
||||
pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
|
||||
bool is_clipped = !ImGui::ItemAdd(bb, id);
|
||||
|
||||
bool hovered, held;
|
||||
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held);
|
||||
if (is_clipped) return pressed;
|
||||
|
||||
// Render
|
||||
ImU32 col =
|
||||
ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
|
||||
ImVec2 center = bb.GetCenter();
|
||||
if (hovered)
|
||||
window->DrawList->AddCircleFilled(
|
||||
center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
|
||||
|
||||
ImU32 cross_col = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
window->DrawList->AddCircle(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f),
|
||||
cross_col, 12);
|
||||
float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f;
|
||||
center -= ImVec2(0.5f, 0.5f);
|
||||
window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent),
|
||||
center + ImVec2(-cross_extent, -cross_extent),
|
||||
cross_col, 1.0f);
|
||||
window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent),
|
||||
center + ImVec2(-cross_extent, +cross_extent),
|
||||
cross_col, 1.0f);
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
bool HeaderDeleteButton(const char* label) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiItemHoveredDataBackup last_item_backup;
|
||||
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;
|
||||
bool rv = DeleteButton(
|
||||
window->GetID(reinterpret_cast<void*>(static_cast<intptr_t>(id) + 1)),
|
||||
ImVec2(button_x, button_y));
|
||||
last_item_backup.Restore();
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace glass
|
||||
64
glass/src/lib/native/cpp/support/IniSaverBase.cpp
Normal file
64
glass/src/lib/native/cpp/support/IniSaverBase.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the 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(const wpi::Twine& typeName, IniSaverBackend* backend)
|
||||
: m_typeName(typeName.str()),
|
||||
m_backend{backend ? backend : GetSaverInstance()} {}
|
||||
|
||||
IniSaverBase::~IniSaverBase() { m_backend->Unregister(this); }
|
||||
165
glass/src/lib/native/cpp/support/IniSaverInfo.cpp
Normal file
165
glass/src/lib/native/cpp/support/IniSaverInfo.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NameInfo::SetName(const wpi::Twine& name) {
|
||||
wpi::SmallString<64> nameBuf;
|
||||
auto nameStr = name.toStringRef(nameBuf);
|
||||
size_t len = (std::min)(nameStr.size(), sizeof(m_name) - 1);
|
||||
std::memcpy(m_name, nameStr.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(wpi::StringRef name, wpi::StringRef 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(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (name != "open") return false;
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return true;
|
||||
m_open = num;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf("open=%d\n", m_open ? 1 : 0);
|
||||
}
|
||||
|
||||
bool NameOpenInfo::ReadIni(wpi::StringRef name, wpi::StringRef 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);
|
||||
}
|
||||
147
glass/src/lib/native/include/glass/Context.h
Normal file
147
glass/src/lib/native/include/glass/Context.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
struct Context;
|
||||
|
||||
Context* CreateContext();
|
||||
void DestroyContext(Context* ctx = nullptr);
|
||||
Context* GetCurrentContext();
|
||||
void SetCurrentContext(Context* ctx);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
class Storage {
|
||||
public:
|
||||
struct Value {
|
||||
Value() = default;
|
||||
explicit Value(const wpi::Twine& str) : stringVal{str.str()} {}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
int GetInt(wpi::StringRef key, int defaultVal = 0) const;
|
||||
int64_t GetInt64(wpi::StringRef key, int64_t defaultVal = 0) const;
|
||||
bool GetBool(wpi::StringRef key, bool defaultVal = false) const;
|
||||
float GetFloat(wpi::StringRef key, float defaultVal = 0.0f) const;
|
||||
double GetDouble(wpi::StringRef key, double defaultVal = 0.0) const;
|
||||
std::string GetString(wpi::StringRef key,
|
||||
const std::string& defaultVal = {}) const;
|
||||
|
||||
void SetInt(wpi::StringRef key, int val);
|
||||
void SetInt64(wpi::StringRef key, int64_t val);
|
||||
void SetBool(wpi::StringRef key, bool val);
|
||||
void SetFloat(wpi::StringRef key, float val);
|
||||
void SetDouble(wpi::StringRef key, double val);
|
||||
void SetString(wpi::StringRef key, const wpi::Twine& val);
|
||||
|
||||
int* GetIntRef(wpi::StringRef key, int defaultVal = 0);
|
||||
int64_t* GetInt64Ref(wpi::StringRef key, int64_t defaultVal = 0);
|
||||
bool* GetBoolRef(wpi::StringRef key, bool defaultVal = false);
|
||||
float* GetFloatRef(wpi::StringRef key, float defaultVal = 0.0f);
|
||||
double* GetDoubleRef(wpi::StringRef key, double defaultVal = 0.0);
|
||||
std::string* GetStringRef(wpi::StringRef key, wpi::StringRef defaultVal = {});
|
||||
|
||||
Value& GetValue(wpi::StringRef key);
|
||||
|
||||
void SetData(std::shared_ptr<void>&& data) { m_data = std::move(data); }
|
||||
|
||||
template <typename T>
|
||||
T* GetData() const {
|
||||
return static_cast<T*>(m_data.get());
|
||||
}
|
||||
|
||||
Storage() = default;
|
||||
Storage(const Storage&) = delete;
|
||||
Storage& operator=(const Storage&) = delete;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::vector<std::string> m_keys;
|
||||
mutable std::vector<std::unique_ptr<Value>> m_values;
|
||||
std::shared_ptr<void> m_data;
|
||||
};
|
||||
|
||||
Storage& GetStorage();
|
||||
Storage& GetStorage(wpi::StringRef id);
|
||||
|
||||
bool Begin(const char* name, bool* p_open = nullptr,
|
||||
ImGuiWindowFlags flags = 0);
|
||||
|
||||
void End();
|
||||
|
||||
bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0),
|
||||
bool border = false, ImGuiWindowFlags flags = 0);
|
||||
|
||||
void EndChild();
|
||||
|
||||
/**
|
||||
* Saves open status to storage "open" key.
|
||||
* If returning 'true' the header is open. doesn't indent nor push on ID stack.
|
||||
* user doesn't have to call TreePop().
|
||||
*/
|
||||
bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);
|
||||
|
||||
bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0);
|
||||
|
||||
void TreePop();
|
||||
|
||||
// push string into the ID stack (will hash string).
|
||||
void PushID(const char* str_id);
|
||||
|
||||
// push string into the ID stack (will hash string).
|
||||
void PushID(const char* str_id_begin, const char* str_id_end);
|
||||
|
||||
// push string into the ID stack (will hash string).
|
||||
inline void PushID(wpi::StringRef str) { PushID(str.begin(), str.end()); }
|
||||
|
||||
// push integer into the ID stack (will hash integer).
|
||||
void PushID(int int_id);
|
||||
|
||||
// pop from the ID stack.
|
||||
void PopID();
|
||||
|
||||
bool PopupEditName(const char* label, std::string* name);
|
||||
|
||||
} // namespace glass
|
||||
49
glass/src/lib/native/include/glass/ContextInternal.h
Normal file
49
glass/src/lib/native/include/glass/ContextInternal.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringMap.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/IniSaverString.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
class DataSource;
|
||||
|
||||
class DataSourceName {
|
||||
public:
|
||||
DataSourceName() = default;
|
||||
explicit DataSourceName(DataSource* source) : source{source} {}
|
||||
|
||||
bool ReadIni(wpi::StringRef name_, wpi::StringRef value) {
|
||||
return name->ReadIni(name_, value);
|
||||
}
|
||||
void WriteIni(ImGuiTextBuffer* out) { name->WriteIni(out); }
|
||||
|
||||
std::unique_ptr<NameInfo> name{new NameInfo};
|
||||
DataSource* source = nullptr;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
wpi::SmallString<128> curId;
|
||||
wpi::SmallVector<size_t, 32> idStack;
|
||||
wpi::StringMap<std::unique_ptr<Storage>> storage;
|
||||
wpi::StringMap<bool> deviceHidden;
|
||||
IniSaverString<DataSourceName> sources{"Data Sources"};
|
||||
};
|
||||
|
||||
extern Context* gContext;
|
||||
|
||||
} // namespace glass
|
||||
86
glass/src/lib/native/include/glass/DataSource.h
Normal file
86
glass/src/lib/native/include/glass/DataSource.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpi/spinlock.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
class NameInfo;
|
||||
|
||||
/**
|
||||
* A data source for numeric/boolean data.
|
||||
*/
|
||||
class DataSource {
|
||||
public:
|
||||
explicit DataSource(const wpi::Twine& id);
|
||||
DataSource(const wpi::Twine& id, int index);
|
||||
DataSource(const wpi::Twine& id, int index, int index2);
|
||||
virtual ~DataSource();
|
||||
|
||||
DataSource(const DataSource&) = delete;
|
||||
DataSource& operator=(const DataSource&) = delete;
|
||||
|
||||
const char* GetId() const { return m_id.c_str(); }
|
||||
|
||||
void SetName(const wpi::Twine& name);
|
||||
const char* GetName() const;
|
||||
NameInfo& GetNameInfo() { return *m_name; }
|
||||
|
||||
void PushEditNameId(int index);
|
||||
void PushEditNameId(const char* name);
|
||||
bool PopupEditName(int index);
|
||||
bool PopupEditName(const char* name);
|
||||
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
|
||||
|
||||
void SetDigital(bool digital) { m_digital = digital; }
|
||||
bool IsDigital() const { return m_digital; }
|
||||
|
||||
void SetValue(double value, uint64_t time = 0) {
|
||||
m_value = value;
|
||||
valueChanged(value, time);
|
||||
}
|
||||
double GetValue() const { return m_value; }
|
||||
|
||||
// drag source helpers
|
||||
void LabelText(const char* label, const char* fmt, ...) const;
|
||||
void LabelTextV(const char* label, const char* fmt, va_list args) const;
|
||||
bool Combo(const char* label, int* current_item, const char* const items[],
|
||||
int items_count, int popup_max_height_in_items = -1) const;
|
||||
bool SliderFloat(const char* label, float* v, float v_min, float v_max,
|
||||
const char* format = "%.3f", float power = 1.0f) const;
|
||||
bool InputDouble(const char* label, double* v, double step = 0.0,
|
||||
double step_fast = 0.0, const char* format = "%.6f",
|
||||
ImGuiInputTextFlags flags = 0) const;
|
||||
bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100,
|
||||
ImGuiInputTextFlags flags = 0) const;
|
||||
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
|
||||
|
||||
wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
|
||||
|
||||
static DataSource* Find(wpi::StringRef id);
|
||||
|
||||
static wpi::sig::Signal<const char*, DataSource*> sourceCreated;
|
||||
|
||||
private:
|
||||
std::string m_id;
|
||||
NameInfo* m_name;
|
||||
bool m_digital = false;
|
||||
std::atomic<double> m_value = 0;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
51
glass/src/lib/native/include/glass/MainMenuBar.h
Normal file
51
glass/src/lib/native/include/glass/MainMenuBar.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace glass {
|
||||
|
||||
class WindowManager;
|
||||
|
||||
/**
|
||||
* GUI main menu bar.
|
||||
*/
|
||||
class MainMenuBar {
|
||||
public:
|
||||
/**
|
||||
* Displays the main menu bar. Should be added to GUI LateExecute.
|
||||
*/
|
||||
void Display();
|
||||
|
||||
/**
|
||||
* Adds to GUI's main menu bar. The menu function is called from within a
|
||||
* ImGui::BeginMainMenuBar()/EndMainMenuBar() block. Usually it's only
|
||||
* appropriate to create a menu with ImGui::BeginMenu()/EndMenu() inside of
|
||||
* this function.
|
||||
*
|
||||
* @param menu menu display function
|
||||
*/
|
||||
void AddMainMenu(std::function<void()> menu);
|
||||
|
||||
/**
|
||||
* Adds to GUI's option menu. The menu function is called from within a
|
||||
* ImGui::BeginMenu()/EndMenu() block. Usually it's only appropriate to
|
||||
* create menu items inside of this function.
|
||||
*
|
||||
* @param menu menu display function
|
||||
*/
|
||||
void AddOptionMenu(std::function<void()> menu);
|
||||
|
||||
private:
|
||||
std::vector<std::function<void()>> m_optionMenus;
|
||||
std::vector<std::function<void()>> m_menus;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
25
glass/src/lib/native/include/glass/Model.h
Normal file
25
glass/src/lib/native/include/glass/Model.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace glass {
|
||||
|
||||
class Model {
|
||||
public:
|
||||
Model() = default;
|
||||
virtual ~Model() = default;
|
||||
|
||||
Model(const Model&) = delete;
|
||||
Model& operator=(const Model&) = delete;
|
||||
|
||||
virtual void Update() = 0;
|
||||
virtual bool Exists() = 0;
|
||||
virtual bool IsReadOnly();
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
171
glass/src/lib/native/include/glass/Provider.h
Normal file
171
glass/src/lib/native/include/glass/Provider.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/WindowManager.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
namespace detail {
|
||||
struct ProviderFunctions {
|
||||
using Exists = std::function<bool()>;
|
||||
using CreateModel = std::function<std::unique_ptr<Model>()>;
|
||||
using ViewExists = std::function<bool(Model*)>;
|
||||
using CreateView = std::function<std::unique_ptr<View>(Window*, Model*)>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Providers are registries of models and views. They have ownership over
|
||||
* their created Models, Windows, and Views.
|
||||
*
|
||||
* GlobalInit() configures Update() to be called during EarlyExecute.
|
||||
* Calling Update() calls Update() on all created models (Provider
|
||||
* implementations must ensure this occurs).
|
||||
*
|
||||
* @tparam Functions defines functor interface types
|
||||
*/
|
||||
template <typename Functions = detail::ProviderFunctions>
|
||||
class Provider : public WindowManager {
|
||||
public:
|
||||
using ExistsFunc = typename Functions::Exists;
|
||||
using CreateModelFunc = typename Functions::CreateModel;
|
||||
using ViewExistsFunc = typename Functions::ViewExists;
|
||||
using CreateViewFunc = typename Functions::CreateView;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param iniName Group name to use in ini file
|
||||
*/
|
||||
explicit Provider(const wpi::Twine& iniName) : WindowManager{iniName} {}
|
||||
|
||||
Provider(const Provider&) = delete;
|
||||
Provider& operator=(const Provider&) = delete;
|
||||
|
||||
/**
|
||||
* Perform global initialization. This should be called prior to
|
||||
* wpi::gui::Initialize().
|
||||
*/
|
||||
void GlobalInit() override;
|
||||
|
||||
/**
|
||||
* Show the specified view by default on first load. Has no effect if
|
||||
* the user previously hid the window (e.g. in a saved prior execution).
|
||||
*
|
||||
* @param name View name
|
||||
*/
|
||||
void ShowDefault(wpi::StringRef name);
|
||||
|
||||
/**
|
||||
* Register a model and view combination. Equivalent to calling both
|
||||
* RegisterModel() and RegisterView() with no ViewExistsFunc.
|
||||
*
|
||||
* @param name View/model name
|
||||
* @param exists Functor, returns true if model can be created
|
||||
* @param createModel Functor for creating model
|
||||
* @param createView Functor for creating view
|
||||
*/
|
||||
void Register(wpi::StringRef name, ExistsFunc exists,
|
||||
CreateModelFunc createModel, CreateViewFunc createView);
|
||||
|
||||
/**
|
||||
* Register a model.
|
||||
*
|
||||
* @param name Model name
|
||||
* @param exists Functor, returns true if model can be created
|
||||
* @param createModel Functor for creating model
|
||||
*/
|
||||
void RegisterModel(wpi::StringRef name, ExistsFunc exists,
|
||||
CreateModelFunc createModel);
|
||||
|
||||
/**
|
||||
* Register a view.
|
||||
*
|
||||
* @param name View name
|
||||
* @param modelName Model name
|
||||
* @param exists Functor, returns true if view can be created
|
||||
* @param createView Functor for creating view
|
||||
*/
|
||||
void RegisterView(wpi::StringRef name, wpi::StringRef modelName,
|
||||
ViewExistsFunc exists, CreateViewFunc createView);
|
||||
|
||||
protected:
|
||||
virtual void Update();
|
||||
|
||||
struct ModelEntry {
|
||||
ModelEntry(wpi::StringRef name, ExistsFunc exists,
|
||||
CreateModelFunc createModel)
|
||||
: name{name},
|
||||
exists{std::move(exists)},
|
||||
createModel{std::move(createModel)} {}
|
||||
virtual ~ModelEntry() = default;
|
||||
|
||||
std::string name;
|
||||
ExistsFunc exists;
|
||||
CreateModelFunc createModel;
|
||||
std::unique_ptr<Model> model;
|
||||
};
|
||||
|
||||
struct ViewEntry {
|
||||
ViewEntry(wpi::StringRef name, ModelEntry* modelEntry,
|
||||
ViewExistsFunc exists, CreateViewFunc createView)
|
||||
: name{name},
|
||||
modelEntry{modelEntry},
|
||||
exists{std::move(exists)},
|
||||
createView{std::move(createView)} {}
|
||||
virtual ~ViewEntry() = default;
|
||||
|
||||
std::string name;
|
||||
ModelEntry* modelEntry;
|
||||
ViewExistsFunc exists;
|
||||
CreateViewFunc createView;
|
||||
Window* window = nullptr;
|
||||
};
|
||||
|
||||
// sorted by name
|
||||
using ModelEntries = std::vector<std::unique_ptr<ModelEntry>>;
|
||||
ModelEntries m_modelEntries;
|
||||
using ViewEntries = std::vector<std::unique_ptr<ViewEntry>>;
|
||||
ViewEntries m_viewEntries;
|
||||
|
||||
typename ModelEntries::iterator FindModelEntry(wpi::StringRef name);
|
||||
typename ViewEntries::iterator FindViewEntry(wpi::StringRef name);
|
||||
|
||||
virtual std::unique_ptr<ModelEntry> MakeModelEntry(
|
||||
wpi::StringRef name, ExistsFunc exists, CreateModelFunc createModel) {
|
||||
return std::make_unique<ModelEntry>(name, std::move(exists),
|
||||
std::move(createModel));
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<ViewEntry> MakeViewEntry(wpi::StringRef name,
|
||||
ModelEntry* modelEntry,
|
||||
ViewExistsFunc exists,
|
||||
CreateViewFunc createView) {
|
||||
return std::make_unique<ViewEntry>(name, modelEntry, std::move(exists),
|
||||
std::move(createView));
|
||||
}
|
||||
|
||||
virtual void Show(ViewEntry* entry, Window* window) = 0;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
#include "Provider.inc"
|
||||
90
glass/src/lib/native/include/glass/Provider.inc
Normal file
90
glass/src/lib/native/include/glass/Provider.inc
Normal file
@@ -0,0 +1,90 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace glass {
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::GlobalInit() {
|
||||
WindowManager::GlobalInit();
|
||||
wpi::gui::AddEarlyExecute([this] { Update(); });
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::ShowDefault(wpi::StringRef name) {
|
||||
auto win = GetWindow(name);
|
||||
if (win) return;
|
||||
auto it = FindViewEntry(name);
|
||||
if (it == m_viewEntries.end() || (*it)->name != name) return;
|
||||
this->Show(it->get(), (*it)->window);
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::Register(wpi::StringRef name, ExistsFunc exists,
|
||||
CreateModelFunc createModel,
|
||||
CreateViewFunc createView) {
|
||||
RegisterModel(name, std::move(exists), std::move(createModel));
|
||||
RegisterView(name, name, nullptr, std::move(createView));
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::RegisterModel(wpi::StringRef name, ExistsFunc exists,
|
||||
CreateModelFunc createModel) {
|
||||
auto it = FindModelEntry(name);
|
||||
// ignore if exists
|
||||
if (it != m_modelEntries.end() && (*it)->name == name) return;
|
||||
// insert in sorted location
|
||||
m_modelEntries.emplace(
|
||||
it, MakeModelEntry(name, std::move(exists), std::move(createModel)));
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::RegisterView(wpi::StringRef name,
|
||||
wpi::StringRef modelName,
|
||||
ViewExistsFunc exists,
|
||||
CreateViewFunc createView) {
|
||||
// find model; if model doesn't exist, ignore
|
||||
auto modelIt = FindModelEntry(modelName);
|
||||
if (modelIt == m_modelEntries.end() || (*modelIt)->name != modelName) return;
|
||||
|
||||
auto viewIt = FindViewEntry(name);
|
||||
// ignore if exists
|
||||
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) return;
|
||||
// insert in sorted location
|
||||
m_viewEntries.emplace(viewIt,
|
||||
MakeViewEntry(name, modelIt->get(), std::move(exists),
|
||||
std::move(createView)));
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
void Provider<Functions>::Update() {
|
||||
// update entries
|
||||
for (auto&& entry : m_modelEntries) {
|
||||
if (entry->model) entry->model->Update();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
typename Provider<Functions>::ModelEntries::iterator
|
||||
Provider<Functions>::FindModelEntry(wpi::StringRef name) {
|
||||
return std::lower_bound(
|
||||
m_modelEntries.begin(), m_modelEntries.end(), name,
|
||||
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
|
||||
}
|
||||
|
||||
template <typename Functions>
|
||||
typename Provider<Functions>::ViewEntries::iterator
|
||||
Provider<Functions>::FindViewEntry(wpi::StringRef name) {
|
||||
return std::lower_bound(
|
||||
m_viewEntries.begin(), m_viewEntries.end(), name,
|
||||
[](const auto& elem, wpi::StringRef s) { return elem->name < s; });
|
||||
}
|
||||
|
||||
} // namespace glass
|
||||
51
glass/src/lib/native/include/glass/View.h
Normal file
51
glass/src/lib/native/include/glass/View.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wpi/FunctionExtras.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
/**
|
||||
* A view is the contents of a window (1:1 mapping).
|
||||
* It may reference multiple models.
|
||||
*
|
||||
* Typically a view is constructed by a Provider and the View's constructor
|
||||
* is given the corresponding Model(s).
|
||||
*
|
||||
* A view may retain a reference to its parent window for dynamic
|
||||
* window configuration.
|
||||
*/
|
||||
class View {
|
||||
public:
|
||||
virtual ~View() = default;
|
||||
|
||||
/**
|
||||
* Displays the window contents. Called by Window::Display() from within an
|
||||
* ImGui::Begin() / ImGui::End() block.
|
||||
*/
|
||||
virtual void Display() = 0;
|
||||
|
||||
/**
|
||||
* Called instead of Display() when the window is hidden (e.g. when
|
||||
* ImGui::Begin() returns false).
|
||||
*/
|
||||
virtual void Hidden();
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a View for a display functor.
|
||||
*
|
||||
* @param display Display function
|
||||
* @return unique_ptr to View
|
||||
*/
|
||||
std::unique_ptr<View> MakeFunctionView(wpi::unique_function<void()> display);
|
||||
|
||||
} // namespace glass
|
||||
134
glass/src/lib/native/include/glass/Window.h
Normal file
134
glass/src/lib/native/include/glass/Window.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "glass/View.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
/**
|
||||
* Managed window information.
|
||||
* A Window owns the View that displays the window's contents.
|
||||
*/
|
||||
class Window {
|
||||
public:
|
||||
Window() = default;
|
||||
explicit Window(wpi::StringRef id) : m_id{id} {}
|
||||
|
||||
wpi::StringRef GetId() const { return m_id; }
|
||||
|
||||
enum Visibility { kHide = 0, kShow, kDisabled };
|
||||
|
||||
bool HasView() { return static_cast<bool>(m_view); }
|
||||
|
||||
void SetView(std::unique_ptr<View> view) { m_view = std::move(view); }
|
||||
|
||||
View* GetView() { return m_view.get(); }
|
||||
const View* GetView() const { return m_view.get(); }
|
||||
|
||||
bool IsVisible() const { return m_visible; }
|
||||
void SetVisible(bool visible) { m_visible = visible; }
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||
|
||||
void SetFlags(ImGuiWindowFlags flags) { m_flags = flags; }
|
||||
|
||||
void SetName(const wpi::Twine& name) { m_name = name.str(); }
|
||||
|
||||
/**
|
||||
* Normally windows provide a right-click popup menu on the title bar to
|
||||
* rename the window. Calling this disables that functionality so the
|
||||
* view can provide its own popup.
|
||||
*/
|
||||
void DisableRenamePopup() { m_renamePopupEnabled = false; }
|
||||
|
||||
/**
|
||||
* Sets visibility of window.
|
||||
*
|
||||
* @param visibility 0=hide, 1=show, 2=disabled (force-hide)
|
||||
*/
|
||||
void SetVisibility(Visibility visibility);
|
||||
|
||||
/**
|
||||
* Sets default position of window.
|
||||
*
|
||||
* @param x x location of upper left corner
|
||||
* @param y y location of upper left corner
|
||||
*/
|
||||
void SetDefaultPos(float x, float y) {
|
||||
m_posCond = ImGuiCond_FirstUseEver;
|
||||
m_pos = ImVec2{x, y};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default size of window.
|
||||
*
|
||||
* @param width width
|
||||
* @param height height
|
||||
*/
|
||||
void SetDefaultSize(float width, float height) {
|
||||
m_sizeCond = ImGuiCond_FirstUseEver;
|
||||
m_size = ImVec2{width, height};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets internal padding of window.
|
||||
* @param x horizontal padding
|
||||
* @param y vertical padding
|
||||
*/
|
||||
void SetPadding(float x, float y) {
|
||||
m_setPadding = true;
|
||||
m_padding = ImVec2{x, y};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays window.
|
||||
*/
|
||||
void Display();
|
||||
|
||||
/**
|
||||
* Displays menu item for the window.
|
||||
* @param label what to display as the menu item label; defaults to
|
||||
* window ID if nullptr
|
||||
* @return True if window went from invisible to visible.
|
||||
*/
|
||||
bool DisplayMenuItem(const char* label = nullptr);
|
||||
|
||||
/**
|
||||
* Scale default window position and size.
|
||||
*/
|
||||
void ScaleDefault(float scale);
|
||||
|
||||
void IniReadLine(const char* lineStr);
|
||||
void IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf);
|
||||
|
||||
private:
|
||||
std::string m_id;
|
||||
std::string m_name;
|
||||
std::unique_ptr<View> m_view;
|
||||
ImGuiWindowFlags m_flags = 0;
|
||||
bool m_visible = true;
|
||||
bool m_enabled = true;
|
||||
bool m_renamePopupEnabled = true;
|
||||
ImGuiCond m_posCond = 0;
|
||||
ImGuiCond m_sizeCond = 0;
|
||||
ImVec2 m_pos;
|
||||
ImVec2 m_size;
|
||||
bool m_setPadding = false;
|
||||
ImVec2 m_padding;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
141
glass/src/lib/native/include/glass/WindowManager.h
Normal file
141
glass/src/lib/native/include/glass/WindowManager.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/FunctionExtras.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "glass/Window.h"
|
||||
#include "glass/support/IniSaverBase.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
/**
|
||||
* Window manager.
|
||||
*
|
||||
* To properly integrate into an application:
|
||||
* - Call GlobalInit() from the application main, after calling
|
||||
* wpi::gui::CreateContext(), but before calling wpi::gui::Initialize().
|
||||
* - Add DisplayMenu() to the application's MainMenuBar.
|
||||
*/
|
||||
class WindowManager {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param iniName Group name to use in ini file
|
||||
*/
|
||||
explicit WindowManager(const wpi::Twine& iniName);
|
||||
virtual ~WindowManager() = default;
|
||||
|
||||
WindowManager(const WindowManager&) = delete;
|
||||
WindowManager& operator=(const WindowManager&) = delete;
|
||||
|
||||
/**
|
||||
* Perform global initialization. This should be called prior to
|
||||
* wpi::gui::Initialize().
|
||||
*/
|
||||
virtual void GlobalInit();
|
||||
|
||||
/**
|
||||
* Displays menu contents, one item for each window.
|
||||
* See Window::DisplayMenuItem().
|
||||
*/
|
||||
virtual void DisplayMenu();
|
||||
|
||||
/**
|
||||
* Adds window to GUI. The display function is called from within a
|
||||
* ImGui::Begin()/End() block. While windows can be created within the
|
||||
* execute function passed to gui::AddExecute(), using this function ensures
|
||||
* the windows are consistently integrated with the rest of the GUI.
|
||||
*
|
||||
* On each Dear ImGui frame, gui::AddExecute() functions are always called
|
||||
* prior to AddWindow display functions. Note that windows may be shaded or
|
||||
* completely hidden, in which case this function will not be called.
|
||||
* It's important to perform any processing steps that must be performed
|
||||
* every frame in the gui::AddExecute() function.
|
||||
*
|
||||
* @param id unique identifier of the window (title bar)
|
||||
* @param display window contents display function
|
||||
*/
|
||||
Window* AddWindow(wpi::StringRef id, wpi::unique_function<void()> display);
|
||||
|
||||
/**
|
||||
* Adds window to GUI. The view's display function is called from within a
|
||||
* ImGui::Begin()/End() block. While windows can be created within the
|
||||
* execute function passed to gui::AddExecute(), using this function ensures
|
||||
* the windows are consistently integrated with the rest of the GUI.
|
||||
*
|
||||
* On each Dear ImGui frame, gui::AddExecute() functions are always called
|
||||
* prior to AddWindow display functions. Note that windows may be shaded or
|
||||
* completely hidden, in which case this function will not be called.
|
||||
* It's important to perform any processing steps that must be performed
|
||||
* every frame in the gui::AddExecute() function.
|
||||
*
|
||||
* @param id unique identifier of the window (title bar)
|
||||
* @param view view object
|
||||
* @return Window, or nullptr on duplicate window
|
||||
*/
|
||||
Window* AddWindow(wpi::StringRef id, std::unique_ptr<View> view);
|
||||
|
||||
/**
|
||||
* Adds window to GUI. A View must be assigned to the returned Window
|
||||
* to display the window contents. While windows can be created within the
|
||||
* execute function passed to gui::AddExecute(), using this function ensures
|
||||
* the windows are consistently integrated with the rest of the GUI.
|
||||
*
|
||||
* On each Dear ImGui frame, gui::AddExecute() functions are always called
|
||||
* prior to AddWindow display functions. Note that windows may be shaded or
|
||||
* completely hidden, in which case this function will not be called.
|
||||
* It's important to perform any processing steps that must be performed
|
||||
* every frame in the gui::AddExecute() function.
|
||||
*
|
||||
* @param id unique identifier of the window (default title bar)
|
||||
* @return Window, or nullptr on duplicate window
|
||||
*/
|
||||
Window* GetOrAddWindow(wpi::StringRef id, bool duplicateOk = false);
|
||||
|
||||
/**
|
||||
* Gets existing window. If none exists, returns nullptr.
|
||||
*
|
||||
* @param id unique identifier of the window (default title bar)
|
||||
* @return Window, or nullptr if window does not exist
|
||||
*/
|
||||
Window* GetWindow(wpi::StringRef id);
|
||||
|
||||
protected:
|
||||
virtual void DisplayWindows();
|
||||
|
||||
// kept sorted by id
|
||||
std::vector<std::unique_ptr<Window>> m_windows;
|
||||
|
||||
private:
|
||||
class IniSaver : public IniSaverBase {
|
||||
public:
|
||||
explicit IniSaver(const wpi::Twine& typeName, WindowManager* manager)
|
||||
: IniSaverBase{typeName}, m_manager{manager} {}
|
||||
|
||||
void* IniReadOpen(const char* name) override;
|
||||
void IniReadLine(void* entry, const char* lineStr) override;
|
||||
void IniWriteAll(ImGuiTextBuffer* out_buf) override;
|
||||
|
||||
private:
|
||||
WindowManager* m_manager;
|
||||
};
|
||||
|
||||
IniSaver m_iniSaver;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user