mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-11 04:48:46 +00:00
Compare commits
404 Commits
prerelease
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd35176e1 | ||
|
|
10a10b9149 | ||
|
|
37b8c2c3e9 | ||
|
|
3367280576 | ||
|
|
ff74376ca2 | ||
|
|
c82f86aed3 | ||
|
|
17e6c655ff | ||
|
|
fcf00af130 | ||
|
|
7176aba0d6 | ||
|
|
14c7940a21 | ||
|
|
0a26319914 | ||
|
|
eb4a6ceafb | ||
|
|
e09389c6db | ||
|
|
1ff509aac9 | ||
|
|
2acac792f2 | ||
|
|
cfdcbe44c9 | ||
|
|
df95e64567 | ||
|
|
4c6ca9b511 | ||
|
|
25786c6951 | ||
|
|
d725234d56 | ||
|
|
37f973b093 | ||
|
|
7691af95b9 | ||
|
|
907c758b83 | ||
|
|
d2afc77bb8 | ||
|
|
a6e75d7a40 | ||
| bedf1f5f38 | |||
|
|
ec5fbba87c | ||
|
|
1e8c68bbeb | ||
| 5d82c159e1 | |||
| db84f1dc75 | |||
|
|
972f1c638c | ||
|
|
7f5bb589af | ||
|
|
4debfee6c1 | ||
|
|
9001a12625 | ||
|
|
a793a08213 | ||
|
|
a45d98c171 | ||
|
|
b00f5ca73a | ||
|
|
3b2d799bfc | ||
|
|
99d11e1921 | ||
|
|
0e911d68ba | ||
|
|
4552b3ba17 | ||
|
|
bd904178c8 | ||
|
|
00948de9d6 | ||
|
|
648b7765ef | ||
|
|
86f78940b1 | ||
|
|
966ca47335 | ||
|
|
35d5ef1743 | ||
|
|
7052ee38b6 | ||
|
|
3022c7df65 | ||
|
|
db6fb33c96 | ||
|
|
d9a3a238af | ||
|
|
10ad1d6a58 | ||
|
|
957bfde0af | ||
|
|
c18f542a61 | ||
|
|
058513fd2a | ||
|
|
a9d4bd2385 | ||
|
|
c37c031dc2 | ||
|
|
74c24c84ae | ||
|
|
7b71ee05da | ||
|
|
62057d657a | ||
|
|
cac44d748d | ||
|
|
7030c7af2a | ||
|
|
ff2e57aeaa | ||
|
|
f152812064 | ||
|
|
6b4ed48d05 | ||
|
|
d6f1ba9aad | ||
|
|
9e994840f6 | ||
|
|
107b604c86 | ||
|
|
257fa8ae0d | ||
|
|
9c04932afa | ||
|
|
e5cb058152 | ||
|
|
da3d47d7b8 | ||
|
|
4c2eca29cc | ||
|
|
8d7af6bb85 | ||
|
|
7e981a61d3 | ||
|
|
91d1d16f24 | ||
|
|
18d75318d9 | ||
|
|
e588a06516 | ||
|
|
fa8b03104f | ||
|
|
8c1fef2883 | ||
|
|
90ddc3006c | ||
|
|
1201820799 | ||
|
|
ef8359dd2e | ||
|
|
59c9538e65 | ||
|
|
551921db9f | ||
|
|
9fcd20d762 | ||
|
|
9132301dbf | ||
|
|
36bff3c5f3 | ||
|
|
e5a5f10286 | ||
|
|
28640f9502 | ||
|
|
5c7497a859 | ||
|
|
554f3e1ac7 | ||
|
|
a113497941 | ||
|
|
13c4820738 | ||
|
|
765f413326 | ||
|
|
ba3838489f | ||
|
|
baae4af153 | ||
|
|
1d23c69d39 | ||
|
|
fb59443f9a | ||
|
|
a48e0d2d90 | ||
|
|
e6565a2896 | ||
|
|
a340f8822e | ||
|
|
39c0b6c51b | ||
|
|
e112ca9da8 | ||
|
|
be92701c67 | ||
|
|
24f470a480 | ||
|
|
6956f09bb6 | ||
|
|
5203e95141 | ||
|
|
2e600e76b0 | ||
|
|
110b6b90a0 | ||
|
|
aaaae5be52 | ||
|
|
e5edc97077 | ||
|
|
108b881fd6 | ||
|
|
d48d0acb5b | ||
|
|
585a2fbe18 | ||
|
|
27ead861a7 | ||
|
|
ef08000f56 | ||
|
|
9d955bd983 | ||
|
|
d0e354952d | ||
|
|
93e74ef8d3 | ||
|
|
bcf0b74fcc | ||
|
|
49ed0205b7 | ||
|
|
313b4a0dea | ||
|
|
6adcae94b4 | ||
|
|
248d4c8e18 | ||
|
|
bfd4a436de | ||
|
|
39d7d8132f | ||
|
|
f88459fddd | ||
|
|
f0e9581fe1 | ||
|
|
2d00a64a7d | ||
|
|
c11d12ded4 | ||
|
|
f51aa3ca06 | ||
|
|
1ed13d8b68 | ||
|
|
78d3b58f99 | ||
|
|
c901bce626 | ||
|
|
9126601ae7 | ||
|
|
00c510cad3 | ||
|
|
0b6bf5685e | ||
|
|
c61e3d289a | ||
|
|
cf10193bd2 | ||
|
|
f2c8de1746 | ||
|
|
21b8112de5 | ||
|
|
d9df443970 | ||
|
|
5184d53d97 | ||
|
|
38a1318170 | ||
|
|
a2fe5d5d08 | ||
|
|
3952a268d1 | ||
|
|
654ea94e16 | ||
|
|
158c380747 | ||
|
|
6364ce3fc4 | ||
|
|
960991e77b | ||
|
|
17bd62e673 | ||
|
|
9438bba386 | ||
|
|
c29d30d222 | ||
|
|
ab64d1332b | ||
|
|
cea8c1b796 | ||
|
|
78b96deeab | ||
|
|
65f62486cc | ||
|
|
19082481c3 | ||
|
|
f0ab5fa250 | ||
|
|
e73ae961ba | ||
|
|
ff6efe9456 | ||
|
|
7edaf709e6 | ||
|
|
833ab7ac78 | ||
|
|
3ca3b6a18a | ||
|
|
50b5f13051 | ||
|
|
b3dc12790c | ||
|
|
0c107410d5 | ||
|
|
b01c5b16fc | ||
|
|
8b4b5d8810 | ||
|
|
28edabae89 | ||
|
|
dad6a56238 | ||
|
|
e8590abd61 | ||
|
|
9393bf2b68 | ||
|
|
e304d67035 | ||
|
|
e33ae8a066 | ||
|
|
0667d58ed8 | ||
|
|
6b4e84c95a | ||
|
|
59fe1df40f | ||
|
|
c1e0dadce8 | ||
|
|
f4b9b67f1a | ||
|
|
c77d51fb58 | ||
|
|
190d797f1a | ||
|
|
d656c6960d | ||
|
|
8f1509fc28 | ||
|
|
1bd4efbbc5 | ||
|
|
6921561317 | ||
|
|
fb6845be5d | ||
|
|
4e8d532c63 | ||
|
|
3894d9f9fe | ||
|
|
1f3f213405 | ||
|
|
4737173c2a | ||
|
|
c55cb5266b | ||
|
|
0f8c8a6e11 | ||
|
|
d73312c4f4 | ||
|
|
43e396bf6d | ||
|
|
38f18613c1 | ||
|
|
76d424012a | ||
|
|
46c035851a | ||
|
|
38eb0ff460 | ||
|
|
7e72e22295 | ||
|
|
41c1458b5a | ||
|
|
ccd79de67c | ||
|
|
3e88aa37ff | ||
|
|
ebf40992bc | ||
|
|
be217ffc02 | ||
|
|
543b62a715 | ||
|
|
f985682e78 | ||
|
|
e866afbfb0 | ||
|
|
97d18d38cc | ||
|
|
2a0a0f3cfd | ||
|
|
524296e4e9 | ||
|
|
919199a8d0 | ||
|
|
279761abd3 | ||
|
|
1b0796c68b | ||
|
|
f990641724 | ||
|
|
0a5cc6955e | ||
|
|
d055f4818c | ||
|
|
9bf38f54a8 | ||
|
|
4994b3fb44 | ||
|
|
aa8519b33c | ||
|
|
a8dadfc0c8 | ||
|
|
94a1939c69 | ||
|
|
68eae085e4 | ||
|
|
c466d03988 | ||
|
|
fbf908c032 | ||
|
|
3394786d31 | ||
|
|
ce927d320f | ||
|
|
19984b87f7 | ||
|
|
8a4390fb80 | ||
|
|
8d5640aba3 | ||
|
|
bd723dfc1c | ||
|
|
d57e90956f | ||
|
|
888ed3743e | ||
|
|
d96581f11c | ||
|
|
bbadef7a9a | ||
|
|
ca9ce948c5 | ||
|
|
4b15d3d976 | ||
|
|
898ed19f29 | ||
|
|
0fdb40bd35 | ||
|
|
a798980eec | ||
|
|
0ac973d4bc | ||
|
|
3d1e53e395 | ||
|
|
876465cfdb | ||
|
|
c69be2836e | ||
|
|
b7c0435648 | ||
|
|
fcde1d4ecc | ||
|
|
2ee170b803 | ||
|
|
625fff4b73 | ||
|
|
8a1b302e48 | ||
|
|
bf3d5fc75d | ||
|
|
a0536b4302 | ||
|
|
c132edeb51 | ||
|
|
318ec99ad4 | ||
|
|
7b7ef2045a | ||
|
|
730881b6d0 | ||
|
|
d26f62223b | ||
|
|
7f75a89d97 | ||
|
|
d3b0da0b1f | ||
|
|
742496b13d | ||
|
|
9c6d7329a6 | ||
|
|
51e1ecea68 | ||
| ef3e349da4 | |||
| abeb6885ce | |||
|
|
e545d2f3da | ||
|
|
402242b79c | ||
|
|
d605afe6cc | ||
|
|
c75e839756 | ||
|
|
23f80895b6 | ||
|
|
e5c42ee228 | ||
|
|
3158e4247a | ||
|
|
811917c1c9 | ||
|
|
606a34c7be | ||
|
|
f3a4ccea8b | ||
|
|
eed6ff7a2b | ||
|
|
671c587df6 | ||
|
|
4143cc2de8 | ||
|
|
e21edd0ed6 | ||
|
|
2a34381926 | ||
|
|
f6f6f0fe6a | ||
|
|
11392f6043 | ||
|
|
a51ab70298 | ||
|
|
5ccd1a9761 | ||
|
|
e4c487e2de | ||
|
|
9cedeea2f0 | ||
|
|
138d168cf2 | ||
|
|
af5bae52ec | ||
|
|
2dff152e90 | ||
|
|
bb3bc576db | ||
|
|
6a02d5031b | ||
|
|
77bed1bf94 | ||
|
|
176a28761f | ||
|
|
4223879e72 | ||
|
|
6014bb9e3c | ||
|
|
65c071e0bd | ||
|
|
89a910d9e2 | ||
|
|
4a152f4e95 | ||
|
|
a1fd8d5099 | ||
|
|
ad4da3032d | ||
|
|
e52faf1c9f | ||
|
|
56abc48ab3 | ||
|
|
e9f2b77fd6 | ||
|
|
3a6171726c | ||
|
|
e1f0ccbcc1 | ||
|
|
d2b8123a30 | ||
|
|
c6a318b6e3 | ||
|
|
e0869e4f28 | ||
|
|
78cb95aabc | ||
|
|
661242ebf9 | ||
|
|
eeaf3999cf | ||
|
|
2621542479 | ||
|
|
990f55b7c5 | ||
|
|
7eccdf079a | ||
|
|
704d649fba | ||
|
|
8639cde83a | ||
|
|
86a22a9d39 | ||
|
|
3d0916b17c | ||
|
|
1c438be158 | ||
|
|
44691dd5a8 | ||
|
|
3030312df9 | ||
|
|
0225457b1b | ||
|
|
02798b6d6e | ||
|
|
66f263ff2a | ||
|
|
ee6d295fd8 | ||
|
|
3830e8566e | ||
|
|
e03d6b20e9 | ||
|
|
0e077e31b3 | ||
|
|
552879ca77 | ||
|
|
fc598d88ad | ||
|
|
7c19937bce | ||
|
|
cfb39b986d | ||
|
|
4936213249 | ||
|
|
27d69bc1bc | ||
|
|
de7c09f047 | ||
|
|
07d4539add | ||
|
|
a991917ea9 | ||
|
|
2a3fd30313 | ||
|
|
329eeb8a5c | ||
|
|
b298900ed2 | ||
|
|
1c07bef3cf | ||
|
|
428bbd73e3 | ||
|
|
af1920141c | ||
|
|
65f81ab768 | ||
|
|
99a365be0b | ||
|
|
229d93901b | ||
|
|
d18ef4f5f4 | ||
|
|
850038ffd1 | ||
|
|
962b13c6ed | ||
|
|
4726190355 | ||
|
|
b5de3b65b2 | ||
|
|
79309dcee7 | ||
|
|
f43ebd2153 | ||
|
|
3860ef0f3d | ||
|
|
6ba2aa176c | ||
|
|
91364158c0 | ||
|
|
6156da0161 | ||
|
|
8181127977 | ||
|
|
d9c63d63e4 | ||
|
|
4224875fb0 | ||
|
|
5ef6c4b8a7 | ||
|
|
52a769be96 | ||
|
|
480e0b9b12 | ||
|
|
49d229cd2b | ||
|
|
af03f291d3 | ||
|
|
db069ea8bf | ||
|
|
cdfa14441e | ||
|
|
0122ab00d9 | ||
|
|
ff55bf0d26 | ||
|
|
35f8ee5f77 | ||
|
|
a903771be3 | ||
|
|
24a241f91f | ||
|
|
f5688db630 | ||
|
|
63591ec3a6 | ||
|
|
61e4886869 | ||
|
|
bde4e861c2 | ||
|
|
b6762d074a | ||
|
|
deaee707f1 | ||
|
|
2e9b3d59d6 | ||
|
|
34824dde42 | ||
|
|
aa4a740b78 | ||
|
|
88322baafd | ||
|
|
567ca193a4 | ||
|
|
06e6075c4d | ||
|
|
f249f80d19 | ||
|
|
70b6035b0d | ||
|
|
c0323f7248 | ||
|
|
c24e57e556 | ||
|
|
9cfa45972d | ||
|
|
dc8147d5a7 | ||
|
|
ec594f02b1 | ||
|
|
6136861215 | ||
|
|
a846bd592e | ||
|
|
26ad9a3385 | ||
|
|
e190059623 | ||
|
|
51f08379eb | ||
|
|
fa710777d7 | ||
|
|
0b4588ea58 | ||
|
|
4ffe8d8848 | ||
|
|
8ae066935e | ||
|
|
cb98f25780 | ||
|
|
ed3a770881 | ||
|
|
07f8209b7b | ||
|
|
c86dba30fc | ||
|
|
a76ecec97d |
13
.github/workflows/docfx.yml
vendored
13
.github/workflows/docfx.yml
vendored
@ -12,11 +12,11 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "7.0.x"
|
||||
dotnet-version: "8.0.x"
|
||||
- name: Setup DocFX
|
||||
run: dotnet tool update -g docfx
|
||||
- name: Build Core
|
||||
@ -26,10 +26,11 @@ jobs:
|
||||
- name: Build DocFX
|
||||
run: docfx docfx/docfx_project/docfx.json
|
||||
- name: Upload to FTP
|
||||
uses: SamKirkland/FTP-Deploy-Action@4.3.2
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: artemis-rgb.com
|
||||
server: www360.your-server.de
|
||||
protocol: ftps
|
||||
username: ${{ secrets.FTP_USER }}
|
||||
password: ${{ secrets.FTP_PASSWORD }}
|
||||
local-dir: docfx/docfx_project/_site/
|
||||
server-dir: /httpdocs/docs/
|
||||
server-dir: /docs/
|
||||
|
||||
14
.github/workflows/master.yml
vendored
14
.github/workflows/master.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
version-number: ${{ steps.get-version.outputs.version-number }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Version String
|
||||
@ -35,7 +35,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
rid: win10-x64
|
||||
rid: win-x64
|
||||
csproj: Windows
|
||||
|
||||
- os: ubuntu-latest
|
||||
@ -49,18 +49,18 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Artemis
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: Artemis
|
||||
- name: Checkout Plugins
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Artemis-RGB/Artemis.Plugins
|
||||
path: Artemis.Plugins
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
dotnet-version: '9.0.x'
|
||||
- name: Publish Artemis
|
||||
run: dotnet publish --configuration Release -p:Version=${{ needs.version.outputs.version-number }} --runtime ${{ matrix.rid }} --output build/${{ matrix.rid }} --self-contained Artemis/src/Artemis.UI.${{ matrix.csproj }}/Artemis.UI.${{ matrix.csproj }}.csproj
|
||||
- name: Publish Plugins
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
}
|
||||
shell: pwsh
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artemis-${{ matrix.rid }}-${{ needs.version.outputs.version-number }}
|
||||
path: build/${{ matrix.rid }}
|
||||
|
||||
8
.github/workflows/nuget.yml
vendored
8
.github/workflows/nuget.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
version-number: ${{ steps.get-version.outputs.version-number }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Version String
|
||||
@ -35,11 +35,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
dotnet-version: '9.0.x'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Pack Artemis.Core
|
||||
run: dotnet pack -c Release -p:Version=${{ needs.version.outputs.version-number }} -p:BuildingNuget=True src/Artemis.Core/Artemis.Core.csproj
|
||||
- name: Pack Artemis.UI.Shared
|
||||
|
||||
17
.github/workflows/pr-branch.yml
vendored
Normal file
17
.github/workflows/pr-branch.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
name: Check pull request branch
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
jobs:
|
||||
check-branches:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check branches
|
||||
run: |
|
||||
if [ ${{ github.head_ref }} != "development" ] && [ ${{ github.base_ref }} == "master" ]; then
|
||||
echo "Merge requests to master are only allowed from development."
|
||||
exit 1
|
||||
fi
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -198,4 +198,5 @@ src/Artemis\.Storage/Storage\.db
|
||||
docfx/docfx_project/_site/
|
||||
|
||||
src/.idea/
|
||||
packages.lock.json
|
||||
packages.lock.json
|
||||
docfx/docfx_project/api/
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
Artemis adds highly configurable support for several games to a range of RGB keyboards, mice and headsets.
|
||||
|
||||
### Check out our [Wiki](https://wiki.artemis-rgb.com) and more specifically, the [getting started guide](https://wiki.artemis-rgb.com/en/guides/user).
|
||||
**Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!)
|
||||
**Pre-release download**: https://artemis-rgb.com/
|
||||
**Plugin documentation**: https://artemis-rgb.com/docs/
|
||||
|
||||
**Please note that even though we have plugins for each brand supported by RGB.NET, they have not been thoroughly tested due to a lack of hardware. If you run into any issues please let us know on Discord.**
|
||||
|
||||
@ -44,12 +44,12 @@ steps:
|
||||
displayName: "DockFX FTP upload"
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: 'ftp://artemis-rgb.com'
|
||||
username: 'devops'
|
||||
serverUrl: 'ftp://www360.your-server.de'
|
||||
username: '$(ftp_user)'
|
||||
password: '$(ftp_password)'
|
||||
rootDirectory: '$(Pipeline.Workspace)/s/Artemis/docfx/docfx_project/_site'
|
||||
filePatterns: '**'
|
||||
remoteDirectory: '/httpdocs/docs'
|
||||
remoteDirectory: '/docs'
|
||||
clean: true
|
||||
preservePaths: true
|
||||
trustSSL: false
|
||||
5
docfx/docfx_project/api/.gitignore
vendored
5
docfx/docfx_project/api/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
###############
|
||||
# temp file #
|
||||
###############
|
||||
*.yml
|
||||
.manifest
|
||||
@ -1,26 +0,0 @@
|
||||
|
||||
# Welcome to the **Artemis API documentation**
|
||||
On this website you can browse the Artemis Core and Shared UI API.
|
||||
A large part of this documentation is being generated based on code but over time the plan is to expand the documentation with written guides.
|
||||
|
||||
|
||||
## Plugins
|
||||
Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented:
|
||||
- [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html)
|
||||
- [LayerBrush\<T\>](api/Artemis.Core.LayerBrushes.LayerBrush-1.html)
|
||||
- [PerLedLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html)
|
||||
- [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html)
|
||||
- [Module](api/Artemis.Core.Modules.Module.html)
|
||||
- [Module\<T\>](api/Artemis.Core.Modules.Module-1.html)
|
||||
|
||||
These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates).
|
||||
|
||||
Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins).
|
||||
|
||||
## Services
|
||||
Artemis provides plugins with an API through a range of services.
|
||||
All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide.
|
||||
|
||||
- [Core Services](api/Artemis.Core.Services.html#interfaces)
|
||||
- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces)
|
||||
|
||||
4
docfx/docfx_project/artemis-template/public/main.css
Normal file
4
docfx/docfx_project/artemis-template/public/main.css
Normal file
@ -0,0 +1,4 @@
|
||||
#logo {
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@ -3,30 +3,26 @@
|
||||
{
|
||||
"src": [
|
||||
{
|
||||
"src": "../../src",
|
||||
"files": [
|
||||
"Artemis.Core/bin/net7.0/Artemis.Core.dll",
|
||||
"Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll"
|
||||
],
|
||||
"src": "../../src"
|
||||
"**/Artemis.Core.csproj",
|
||||
"**/Artemis.UI.Shared.csproj"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dest": "api",
|
||||
"disableGitFeatures": false,
|
||||
"disableDefaultFilter": false,
|
||||
"filter": "filterConfig.yml"
|
||||
"filter": "filterConfig.yml",
|
||||
"namespaceLayout": "nested"
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"api/**.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"toc.yml",
|
||||
"*.md"
|
||||
"**/*.{md,yml}"
|
||||
],
|
||||
"exclude": [
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -37,42 +33,23 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"overwrite": [
|
||||
{
|
||||
"files": [
|
||||
"apidoc/**.md"
|
||||
],
|
||||
"exclude": [
|
||||
"obj/**",
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
"output": "_site",
|
||||
"template": [
|
||||
"default",
|
||||
"modern",
|
||||
"artemis-template"
|
||||
],
|
||||
"globalMetadata": {
|
||||
"_appTitle": "Artemis API Documentation",
|
||||
"_appName": "Artemis",
|
||||
"_appFaviconPath": "images/application.ico",
|
||||
"_appLogoPath": "images/application.ico",
|
||||
"_appFooter": "<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi-globe\" role=\"img\" aria-label=\"GitHub\" style=\"margin: 0 10\"></i>\r</a>\r<a href=\"https://github.com/Artemis-RGB/Artemis\" style=\"margin: 0 5px;\">\r <i class=\"bi-github\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi bi-layout-text-sidebar-reverse\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/\" style=\"margin: 0 5px;\">\r <i class=\"bi-chat-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/en/donating\" style=\"margin: 0 5px;\">\r <i class=\"bi-gift-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>",
|
||||
"_appFaviconPath": "images/application.ico",
|
||||
"_copyrightFooter": "Content is available under the PolyForm Noncommercial License, by Artemis RGB.",
|
||||
"_enableSearch": true,
|
||||
"_disableSideFilter": false,
|
||||
"_enableNewTab": true,
|
||||
"_disableContribution": false,
|
||||
"_disableBreadcrumb": false
|
||||
},
|
||||
"dest": "_site",
|
||||
"globalMetadataFiles": [],
|
||||
"fileMetadataFiles": [],
|
||||
"template": [
|
||||
"default",
|
||||
"templates/singulinkfx"
|
||||
],
|
||||
"postProcessors": [],
|
||||
"markdownEngineName": "markdig",
|
||||
"noLangKeyword": false,
|
||||
"keepFileLink": false,
|
||||
"cleanupCacheHistory": false,
|
||||
"disableGitFeatures": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,5 +24,5 @@ apiRules:
|
||||
uidRegex: ^Artemis\.UI\.Shared\.Properties
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: ^Artemis\.Core\.TypeExtensions\.IsNumber
|
||||
type: Method
|
||||
uidRegex: ^Artemis\.Core\.TypeExtensions
|
||||
type: Type
|
||||
@ -1,26 +1,7 @@
|
||||
---
|
||||
_layout: landing
|
||||
---
|
||||
|
||||
# Welcome to the **Artemis API documentation**
|
||||
On this website you can browse the Artemis Core and Shared UI API.
|
||||
A large part of this documentation is being generated based on code but over time the plan is to expand the documentation with written guides.
|
||||
|
||||
|
||||
## Plugins
|
||||
Artemis 2.0 has been developed from the ground up with plugins in mind. This means almost all functionality can be expanded. The following plugin types are currently available and fully implemented:
|
||||
- [DeviceProvider](api/Artemis.Core.DeviceProviders.DeviceProvider.html)
|
||||
- [LayerBrush\<T\>](api/Artemis.Core.LayerBrushes.LayerBrush-1.html)
|
||||
- [PerLedLayerBrush\<T\>](api/Artemis.Core.LayerBrushes.PerLedLayerBrush-1.html)
|
||||
- [LayerEffect](api/Artemis.Core.LayerEffects.LayerEffect-1.html)
|
||||
- [Module](api/Artemis.Core.Modules.Module.html)
|
||||
- [Module\<T\>](api/Artemis.Core.Modules.Module-1.html)
|
||||
|
||||
These allow you to expand on Artemis's functionality. For quick and interactive plugin creation, use the [Visual Studio template extension](https://marketplace.visualstudio.com/items?itemName=SpoinkyNL.ArtemisTemplates).
|
||||
|
||||
Example implementations of these plugins can be found on [GitHub](https://github.com/Artemis-RGB/Artemis/tree/master/src/Plugins).
|
||||
|
||||
## Services
|
||||
Artemis provides plugins with an API through a range of services.
|
||||
All the services are available to plugins by using dependency injection in your plugin's constructor. Dependency injection is also available for the different view models plugins may provide.
|
||||
|
||||
- [Core Services](api/Artemis.Core.Services.html#interfaces)
|
||||
- [UI Services](api/Artemis.UI.Shared.Services.html#interfaces)
|
||||
|
||||
# Artemis Technical Documentation
|
||||
Welcome to the technical docs of Artemis. This page contains all the public types available when writing Artemis plugins.
|
||||
For more details, please refer to the [Artemis wiki](https://wiki.artemis-rgb.com/en/guides/developer).
|
||||
@ -1,62 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
{{!include(/^styles/.*/)}}
|
||||
{{!include(/^fonts/.*/)}}
|
||||
{{!include(favicon.ico)}}
|
||||
{{!include(logo.svg)}}
|
||||
{{!include(search-stopwords.json)}}
|
||||
<!DOCTYPE html>
|
||||
<!--[if IE]><![endif]-->
|
||||
<html>
|
||||
{{>partials/head}}
|
||||
|
||||
<body>
|
||||
<div class="top-navbar">
|
||||
<a class="burger-icon" onclick="toggleMenu()">
|
||||
<svg name="Hamburger"
|
||||
style="vertical-align: middle;"
|
||||
width="34" height="34" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M20 6H4V9H20V6ZM4 10.999H20V13.999H4V10.999ZM4 15.999H20V18.999H4V15.999Z"></path></svg>
|
||||
</a>
|
||||
|
||||
{{>partials/logo}}
|
||||
</div>
|
||||
|
||||
<div class="body-content">
|
||||
<div id="blackout" class="blackout" onclick="toggleMenu()"></div>
|
||||
|
||||
<nav id="sidebar" role="navigation">
|
||||
<div class="sidebar">
|
||||
{{>partials/navbar}}
|
||||
<div class="sidebar-item-separator"></div>
|
||||
{{^_disableToc}}
|
||||
{{>partials/toc}}
|
||||
{{/_disableToc}}
|
||||
</div>
|
||||
{{>partials/footer}}
|
||||
</nav>
|
||||
|
||||
<main class="main-panel">
|
||||
{{#_enableSearch}}
|
||||
{{>partials/searchResults}}
|
||||
{{/_enableSearch}}
|
||||
|
||||
<div role="main" class="hide-when-search" >
|
||||
{{^_disableBreadcrumb}}
|
||||
{{>partials/breadcrumb}}
|
||||
{{/_disableBreadcrumb}}
|
||||
|
||||
<article class="content wrap" id="_content" data-uid="{{uid}}">
|
||||
{{!body}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{#_copyrightFooter}}
|
||||
<div class="copyright-footer">
|
||||
<span>{{_copyrightFooter}}</span>
|
||||
</div>
|
||||
{{/_copyrightFooter}}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{{>partials/scripts}}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,4 +0,0 @@
|
||||
<div class="footer">
|
||||
{{{_appFooter}}}
|
||||
{{^_appFooter}}<strong><a href='https://dotnet.github.io/docfx/'>DocFX</a> + <a href='https://www.singulink.com'>Singulink</a> = ♥</strong>{{/_appFooter}}
|
||||
</div>
|
||||
@ -1,24 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
|
||||
<meta name="generator" content="docfx {{_docfxVersion}}">
|
||||
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
|
||||
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/night-owl.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" integrity="sha384-EvBWSlnoFgZlXJvpzS+MAUEjvN7+gcCwH+qh7GRFOGgZO0PuwOFro7qPOJnLfe7l" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/config.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/discord.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/singulink.css">
|
||||
<link rel="stylesheet" href="{{_rel}}styles/main.css">
|
||||
<meta property="docfx:navrel" content="{{_navRel}}">
|
||||
<meta property="docfx:tocrel" content="{{_tocRel}}">
|
||||
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
|
||||
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
|
||||
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
|
||||
</head>
|
||||
@ -1,31 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<ul class="nav level{{level}}">
|
||||
{{#items}}
|
||||
{{^dropdown}}
|
||||
<li>
|
||||
{{^leaf}}
|
||||
<span class="expand-stub"></span>
|
||||
{{/leaf}}
|
||||
{{#topicHref}}
|
||||
<a href="{{topicHref}}" class="sidebar-item" name="{{tocHref}}" title="{{name}}">{{name}}</a>
|
||||
{{/topicHref}}
|
||||
{{^topicHref}}
|
||||
<a>{{{name}}}</a>
|
||||
{{/topicHref}}
|
||||
|
||||
{{^leaf}}
|
||||
{{>partials/li}}
|
||||
{{/leaf}}
|
||||
</li>
|
||||
{{/dropdown}}
|
||||
{{#dropdown}}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{name}} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu level{{level}}">
|
||||
{{>partials/dd-li}}
|
||||
</ul>
|
||||
</li>
|
||||
{{/dropdown}}
|
||||
{{/items}}
|
||||
</ul>
|
||||
@ -1,6 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<a class="brand" href="{{_rel}}index.html">
|
||||
<img src="{{_rel}}{{{_appLogoPath}}}{{^_appLogoPath}}logo.svg{{/_appLogoPath}}" alt="{{_appName}}" class="logomark">
|
||||
<span class="brand-title">{{_appName}}</span>
|
||||
</a>
|
||||
@ -1,13 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<h1 id="{{id}}" data-uid="{{uid}}" class="text-break">{{>partials/title}}</h1>
|
||||
<div class="markdown level0 summary">{{{summary}}}</div>
|
||||
<div class="markdown level0 conceptual">{{{conceptual}}}</div>
|
||||
<div class="markdown level0 remarks">{{{remarks}}}</div>
|
||||
{{#children}}
|
||||
<h3 id="{{id}}">{{>partials/namespaceSubtitle}}</h3>
|
||||
{{#children}}
|
||||
<h5><xref uid="{{uid}}" altProperty="fullName" displayProperty="name"/></h5>
|
||||
<section>{{{summary}}}</section>
|
||||
{{/children}}
|
||||
{{/children}}
|
||||
@ -1,19 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div>
|
||||
<div class="mobile-hide">
|
||||
{{>partials/logo}}
|
||||
</div>
|
||||
|
||||
{{#_enableSearch}}
|
||||
<div class="sidesearch">
|
||||
<form id="search" role="search" class="search">
|
||||
<i class="bi bi-search search-icon"></i>
|
||||
<input type="text" id="search-query" placeholder="{{__global.search}}" autocomplete="off">
|
||||
</form>
|
||||
</div>
|
||||
{{/_enableSearch}}
|
||||
|
||||
<div id="navbar">
|
||||
</div>
|
||||
</div>
|
||||
@ -1,12 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js"></script>
|
||||
<script type="text/javascript" src="{{_rel}}styles/jquery.twbsPagination.js"></script>
|
||||
<script type="text/javascript" src="{{_rel}}styles/url.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js"></script>
|
||||
<script type="text/javascript" src="{{_rel}}styles/docfx.js"></script>
|
||||
<script type="text/javascript" src="{{_rel}}styles/singulink.js"></script>
|
||||
<script type="text/javascript" src="{{_rel}}styles/main.js"></script>
|
||||
@ -1,9 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div id="search-results" style="display: none;">
|
||||
<h1 class="search-list">{{__global.searchResults}} <span></span></h1>
|
||||
<div class="sr-items">
|
||||
<p><i class="bi bi-hourglass-split index-loading"></i></p>
|
||||
</div>
|
||||
<ul id="pagination" data-first={{__global.pageFirst}} data-prev={{__global.pagePrev}} data-next={{__global.pageNext}} data-last={{__global.pageLast}}></ul>
|
||||
</div>
|
||||
@ -1,5 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div id="sidetoggle">
|
||||
<div id="sidetoc"></div>
|
||||
</div>
|
||||
@ -1,114 +0,0 @@
|
||||
/* Theme Configuration Options */
|
||||
|
||||
:root
|
||||
{
|
||||
/* General */
|
||||
|
||||
--base-font-size: 16px;
|
||||
--smalldevice-base-font-size: 14px; /* Base font size for devices < 1024px */
|
||||
|
||||
--main-bg-color: #1f1f23;
|
||||
--footer-bg-color: rgba(0,0,0,.4);
|
||||
--separator-color: #42474f;
|
||||
|
||||
--table-strip-bg-color: #151515;
|
||||
--table-header-bg-color: black;
|
||||
--table-header-color: hsla(0,0%,100%,.8);
|
||||
--table-header-border-color: #040405;
|
||||
|
||||
/* Text */
|
||||
|
||||
--appname-color: white;
|
||||
|
||||
--h1-color: white;
|
||||
--h2-color: #f2f2f2;
|
||||
--h3-color: #e3e3e3;
|
||||
--h4-color: #ffffff;
|
||||
--h5-color: #e0e0e0;
|
||||
|
||||
--text-color: #e1e1e1;
|
||||
--link-color: #00b0f4;
|
||||
--link-hover-color: #2ec4ff;
|
||||
|
||||
/* Mobile Topbar */
|
||||
|
||||
--topbar-bg-color: #18191c;
|
||||
|
||||
/* Button */
|
||||
|
||||
--button-color: #747f8d;
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
--sidebar-width: 400px;
|
||||
--sidebar-bg-color: #292B30;
|
||||
|
||||
--search-color: #bdbdbd;
|
||||
--search-bg-color: #1b1e21;
|
||||
--search-searchicon-color: #e3e3e3;
|
||||
--search-border-color: black;
|
||||
|
||||
--sidebar-item-color: white;
|
||||
--sidebar-active-item-color: #00b0f4;
|
||||
--sidebar-level1-item-bg-color: #222429;
|
||||
--sidebar-level1-item-hover-bg-color: #1D1F22;
|
||||
|
||||
--toc-filter-color: #bdbdbd;
|
||||
--toc-filter-bg-color: #1b1e21;
|
||||
--toc-filter-filtericon-color: #e3e3e3;
|
||||
--toc-filter-clearicon-color: #e68585;
|
||||
--toc-filter-border-color: black;
|
||||
|
||||
/* Scrollbars */
|
||||
|
||||
--scrollbar-bg-color: transparent;
|
||||
--scrollbar-thumb-bg-color: rgba(0,0,0,.4);
|
||||
--scrollbar-thumb-border-color: transparent;
|
||||
|
||||
/* Alerts and Blocks */
|
||||
|
||||
--alert-info-border-color: rgba(114,137,218,.5);
|
||||
--alert-info-bg-color: rgba(114,137,218,.1);
|
||||
|
||||
--alert-warning-border-color: rgba(250,166,26,.5);
|
||||
--alert-warning-bg-color: rgba(250,166,26,.1);
|
||||
|
||||
--alert-danger-border-color: rgba(240,71,71,.5);
|
||||
--alert-danger-bg-color: rgba(240,71,71,.1);
|
||||
|
||||
--alert-tip-border-color: rgba(255,255,255,.5);
|
||||
--alert-tip-bg-color: rgba(255,255,255,.1);
|
||||
|
||||
--blockquote-border-color: rgba(255,255,255,.5);
|
||||
--blockquote-bg-color: rgba(255,255,255,.1);
|
||||
|
||||
--breadcrumb-bg-color: #2f3136;
|
||||
|
||||
/* Inline Code */
|
||||
|
||||
--ref-bg-color: black;
|
||||
--ref-color: #89d4f1;
|
||||
|
||||
/* Code Blocks */
|
||||
|
||||
--code-bg-color: #151515;
|
||||
--code-color: #d6deeb;
|
||||
--code-keyword-color: #569cd6;
|
||||
--code-comment-color: #57a64a;
|
||||
--code-macro-color: #beb7ff;
|
||||
--code-string-color: #d69d85;
|
||||
--code-string-escape-color: #ffd68f;
|
||||
--code-field-color: #c8c8c8;
|
||||
--code-function-color: #dcdcaa;
|
||||
--code-control-color: #d8a0df;
|
||||
--code-class-color: #4ec9b0;
|
||||
--code-number-color: #b5cea8;
|
||||
--code-params-color: #9a9a9a;
|
||||
--code-breakpoint-color: #8c2f2f;
|
||||
}
|
||||
|
||||
/* Code Block Overrides */
|
||||
|
||||
pre, legend {
|
||||
--scrollbar-thumb-bg-color: #333;
|
||||
}
|
||||
@ -1,681 +0,0 @@
|
||||
/* Discord Style */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-bg-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb-bg-color);
|
||||
border-color: var(--scrollbar-thumb-border-color);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::marker {
|
||||
unicode-bidi: isolate;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-transform: none;
|
||||
text-indent: 0px !important;
|
||||
text-align: start !important;
|
||||
text-align-last: start !important;
|
||||
}
|
||||
|
||||
*, :after, :before
|
||||
{
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body
|
||||
{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: 15px/150% 'Roboto', sans-serif;
|
||||
overflow: hidden;
|
||||
color: var(--text-color);
|
||||
background-color: var(--main-bg-color);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
ul > li, ol > li {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1, h2
|
||||
{
|
||||
margin-block-start: 2em;
|
||||
}
|
||||
|
||||
h3
|
||||
{
|
||||
margin-block-start: 1em;
|
||||
font-weight: 300;
|
||||
font-size: 1.5em;
|
||||
color: var(--h3-color);
|
||||
margin-block-start: 3em;
|
||||
}
|
||||
|
||||
h4
|
||||
{
|
||||
opacity: 1;
|
||||
color: var(--h4-color);
|
||||
font-size: large;
|
||||
border-bottom: 2px solid var(--separator-color);
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
|
||||
|
||||
h5 {
|
||||
margin-block-end: .8em;
|
||||
margin-block-start: 1em;
|
||||
font-size: .85em;
|
||||
font-weight: 500;
|
||||
color: var(--h5-color);
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: .75em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p
|
||||
{
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
ul
|
||||
{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul, ol
|
||||
{
|
||||
padding-inline-start: 3em;
|
||||
}
|
||||
|
||||
ul.level1
|
||||
{
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
ul.level2, ul.level3
|
||||
{
|
||||
padding-inline-start: 1em;
|
||||
list-style-type: none;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
transition: color .25s;
|
||||
}
|
||||
|
||||
a:focus, a:hover
|
||||
{
|
||||
color: var(--link-hover-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.anchorjs-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.active, a:active
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.body-content
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.page-title
|
||||
{
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
nav
|
||||
{
|
||||
width: 300px;
|
||||
transition: left .5s ease-out;
|
||||
position: fixed;
|
||||
left: -350px;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
background-color: var(--sidebar-bg-color);
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1:first-child
|
||||
{
|
||||
margin-block-start: 1.1em;
|
||||
margin-top: 1.1em;
|
||||
}
|
||||
|
||||
.sidebar
|
||||
{
|
||||
padding: 32px 17px 32px 32px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar-item
|
||||
{
|
||||
font-size: 1em;
|
||||
font-weight: 400;
|
||||
display: block;
|
||||
padding: 4px 16px;
|
||||
color: var(--sidebar-item-color);
|
||||
}
|
||||
|
||||
.sidebar-item.large, #navbar .sidebar-item
|
||||
{
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
a.sidebar-item:hover, a.sidebar-item:focus
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.sidebar-item.active
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
ul.level1 > li > a.sidebar-item
|
||||
{
|
||||
background-color: transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#toc ul.level1 > li > a.sidebar-item.active
|
||||
{
|
||||
background-color: var(--link-active-bg-color);
|
||||
}
|
||||
|
||||
.sidebar-item-separator
|
||||
{
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: var(--separator-color);
|
||||
margin: 2em 0;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
span.sidebar-item
|
||||
{
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
font-size: .8em;
|
||||
color: var(--text-color);
|
||||
margin-block-start: 1.25em;
|
||||
}
|
||||
|
||||
.main-panel
|
||||
{
|
||||
background-color: var(--main-bg-color);
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px 40px;
|
||||
}
|
||||
|
||||
.top-navbar
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 40px;
|
||||
height: 40px;
|
||||
background-color: var(--topbar-bg-color);
|
||||
}
|
||||
|
||||
.burger-icon
|
||||
{
|
||||
margin-right: 1em;
|
||||
color: var(--button-color);
|
||||
}
|
||||
|
||||
.burger-icon:hover, .burger-icon:focus
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.burger-icon.active, .burger-icon:active
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.brand
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.logomark
|
||||
{
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.brand-title
|
||||
{
|
||||
padding: 0 .5em;
|
||||
font-size: .9em;
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.footer
|
||||
{
|
||||
background-color: var(--footer-bg-color);
|
||||
padding: 20px;
|
||||
margin: 0 20px 20px 20px;
|
||||
border-radius: 8px;
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.footer > h4
|
||||
{
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.blackout
|
||||
{
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--footer-bg-color);
|
||||
}
|
||||
|
||||
@keyframes showThat {
|
||||
0% { opacity: 0; visibility: hidden; }
|
||||
1% { opacity: 0; visibility: visible; }
|
||||
100% { opacity: 1; visibility: visible;}
|
||||
}
|
||||
|
||||
@keyframes hideThat {
|
||||
0% { opacity: 1; visibility: visible; }
|
||||
99% { opacity: 0; visibility: visible; }
|
||||
100% { opacity: 0; visibility: hidden;}
|
||||
}
|
||||
|
||||
.showThat
|
||||
{
|
||||
animation: showThat .5s forwards;
|
||||
}
|
||||
|
||||
.hideThat
|
||||
{
|
||||
animation: hideThat .5s forwards;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width: 1024px)
|
||||
{
|
||||
nav
|
||||
{
|
||||
position: relative;
|
||||
left: 0!important;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.top-navbar
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blackout
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table */
|
||||
|
||||
.table-responsive
|
||||
{
|
||||
overflow-x: auto;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
table
|
||||
{
|
||||
background-color: var(--code-bg-color);
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
table.table-striped tbody tr:nth-child(2n)
|
||||
{
|
||||
background-color: var(--table-strip-bg-color);
|
||||
}
|
||||
|
||||
table thead
|
||||
{
|
||||
background: var(--table-header-bg-color);
|
||||
}
|
||||
|
||||
table th
|
||||
{
|
||||
color: var(--table-header-color);
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
border-bottom: 1px solid var(--table-header-border-color);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.table-condensed th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table td
|
||||
{
|
||||
padding: 8px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
table td > p
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin: 25px 0;
|
||||
}
|
||||
|
||||
.alert > h5
|
||||
{
|
||||
display: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alert > p
|
||||
{
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.alert.alert-info
|
||||
{
|
||||
border: 2px solid var(--alert-info-border-color);
|
||||
background: var(--alert-info-bg-color);
|
||||
}
|
||||
|
||||
.alert.alert-warning
|
||||
{
|
||||
border: 2px solid var(--alert-warning-border-color);
|
||||
background: var(--alert-warning-bg-color);
|
||||
}
|
||||
|
||||
.alert.alert-danger
|
||||
{
|
||||
border: 2px solid var(--alert-danger-border-color);
|
||||
background: var(--alert-danger-bg-color);
|
||||
}
|
||||
|
||||
.TIP.alert.alert-info
|
||||
{
|
||||
border: 2px solid var(--alert-tip-border-color);
|
||||
background: var(--alert-tip-bg-color);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 8px 0;
|
||||
border-left: 4px solid var(--blockquote-border-color);
|
||||
padding: 8px;
|
||||
background: var(--blockquote-bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
blockquote > p {
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
/* Breadcrumb */
|
||||
|
||||
#breadcrumb
|
||||
{
|
||||
padding: 8px 16px;
|
||||
background: var(--breadcrumb-bg-color);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#breadcrumb:empty
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.breadcrumb
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul.breadcrumb > li {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
ul.breadcrumb > li::before
|
||||
{
|
||||
content: "/";
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
ul.breadcrumb > li:first-child::before
|
||||
{
|
||||
content: "";
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Code */
|
||||
|
||||
legend, pre
|
||||
{
|
||||
display: block;
|
||||
background-color: var(--code-bg-color);
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
code
|
||||
{
|
||||
background-color: var(--code-bg-color);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.hljs
|
||||
{
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* DocFX related */
|
||||
|
||||
.small {
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.pull-right
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hide
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px)
|
||||
{
|
||||
.mobile-hide
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
li
|
||||
{
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.expand-stub
|
||||
{
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
ul.level1 > li > .expand-stub
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before
|
||||
{
|
||||
content: " ";
|
||||
position: absolute;
|
||||
transform: rotate(-90deg);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background-repeat: no-repeat;
|
||||
background: url(down-arrow.svg);
|
||||
}
|
||||
|
||||
.toc .nav > li.active > .expand-stub::before, .toc .nav > li.in > .expand-stub::before, .toc .nav > li.in.active > .expand-stub::before, .toc .nav > li.filtered > .expand-stub::before
|
||||
{
|
||||
transform: none;
|
||||
}
|
||||
|
||||
li > ul
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
li.in > ul
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul.level2 > li > a.sidebar-item,
|
||||
ul.level3 > li > a.sidebar-item
|
||||
{
|
||||
font-weight: 500;
|
||||
font-size: .95em;
|
||||
padding: 0;
|
||||
margin: 2px 16px;
|
||||
}
|
||||
|
||||
ul.level2 > li > a.sidebar-item
|
||||
{
|
||||
color: var(--sidebar-item-2nd-color);
|
||||
}
|
||||
|
||||
ul.level3 > li > a.sidebar-item
|
||||
{
|
||||
color: var(--sidebar-item-3rd-color);
|
||||
}
|
||||
|
||||
ul.level2 > li > a.sidebar-item:hover,
|
||||
ul.level2 > li > a.sidebar-item:focus,
|
||||
ul.level3 > li > a.sidebar-item:hover,
|
||||
ul.level3 > li > a.sidebar-item:focus
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul.level2 > li > a.sidebar-item.active,
|
||||
ul.level3 > li > a.sidebar-item.active
|
||||
{
|
||||
color: var(--link-active-color);
|
||||
}
|
||||
|
||||
.inheritance .level0:before,
|
||||
.inheritance .level1:before,
|
||||
.inheritance .level2:before,
|
||||
.inheritance .level3:before,
|
||||
.inheritance .level4:before,
|
||||
.inheritance .level5:before {
|
||||
content: '↳';
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.inheritance .level0 {
|
||||
margin-left: 0em;
|
||||
}
|
||||
|
||||
.inheritance .level1 {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.inheritance .level2 {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.inheritance .level3 {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
.inheritance .level4 {
|
||||
margin-left: 4em;
|
||||
}
|
||||
|
||||
.inheritance .level5 {
|
||||
margin-left: 5em;
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 491.996 491.996" style="enable-background:new 0 0 491.996 491.996;" xml:space="preserve">
|
||||
<g>
|
||||
<g color="white">
|
||||
<path d="M484.132,124.986l-16.116-16.228c-5.072-5.068-11.82-7.86-19.032-7.86c-7.208,0-13.964,2.792-19.036,7.86l-183.84,183.848
|
||||
L62.056,108.554c-5.064-5.068-11.82-7.856-19.028-7.856s-13.968,2.788-19.036,7.856l-16.12,16.128
|
||||
c-10.496,10.488-10.496,27.572,0,38.06l219.136,219.924c5.064,5.064,11.812,8.632,19.084,8.632h0.084
|
||||
c7.212,0,13.96-3.572,19.024-8.632l218.932-219.328c5.072-5.064,7.856-12.016,7.864-19.224
|
||||
C491.996,136.902,489.204,130.046,484.132,124.986z" fill="currentcolor"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1,317 +0,0 @@
|
||||
/*!
|
||||
* jQuery pagination plugin v1.4.1
|
||||
* http://esimakin.github.io/twbs-pagination/
|
||||
*
|
||||
* Copyright 2014-2016, Eugene Simakin
|
||||
* Released under Apache 2.0 license
|
||||
* http://apache.org/licenses/LICENSE-2.0.html
|
||||
*/
|
||||
(function ($, window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var old = $.fn.twbsPagination;
|
||||
|
||||
// PROTOTYPE AND CONSTRUCTOR
|
||||
|
||||
var TwbsPagination = function (element, options) {
|
||||
this.$element = $(element);
|
||||
this.options = $.extend({}, $.fn.twbsPagination.defaults, options);
|
||||
|
||||
if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) {
|
||||
throw new Error('Start page option is incorrect');
|
||||
}
|
||||
|
||||
this.options.totalPages = parseInt(this.options.totalPages);
|
||||
if (isNaN(this.options.totalPages)) {
|
||||
throw new Error('Total pages option is not correct!');
|
||||
}
|
||||
|
||||
this.options.visiblePages = parseInt(this.options.visiblePages);
|
||||
if (isNaN(this.options.visiblePages)) {
|
||||
throw new Error('Visible pages option is not correct!');
|
||||
}
|
||||
|
||||
if (this.options.onPageClick instanceof Function) {
|
||||
this.$element.first().on('page', this.options.onPageClick);
|
||||
}
|
||||
|
||||
// hide if only one page exists
|
||||
if (this.options.hideOnlyOnePage && this.options.totalPages == 1) {
|
||||
this.$element.trigger('page', 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.options.totalPages < this.options.visiblePages) {
|
||||
this.options.visiblePages = this.options.totalPages;
|
||||
}
|
||||
|
||||
if (this.options.href) {
|
||||
this.options.startPage = this.getPageFromQueryString();
|
||||
if (!this.options.startPage) {
|
||||
this.options.startPage = 1;
|
||||
}
|
||||
}
|
||||
|
||||
var tagName = (typeof this.$element.prop === 'function') ?
|
||||
this.$element.prop('tagName') : this.$element.attr('tagName');
|
||||
|
||||
if (tagName === 'UL') {
|
||||
this.$listContainer = this.$element;
|
||||
} else {
|
||||
this.$listContainer = $('<ul></ul>');
|
||||
}
|
||||
|
||||
this.$listContainer.addClass(this.options.paginationClass);
|
||||
|
||||
if (tagName !== 'UL') {
|
||||
this.$element.append(this.$listContainer);
|
||||
}
|
||||
|
||||
if (this.options.initiateStartPageClick) {
|
||||
this.show(this.options.startPage);
|
||||
} else {
|
||||
this.render(this.getPages(this.options.startPage));
|
||||
this.setupEvents();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
TwbsPagination.prototype = {
|
||||
|
||||
constructor: TwbsPagination,
|
||||
|
||||
destroy: function () {
|
||||
this.$element.empty();
|
||||
this.$element.removeData('twbs-pagination');
|
||||
this.$element.off('page');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
show: function (page) {
|
||||
if (page < 1 || page > this.options.totalPages) {
|
||||
throw new Error('Page is incorrect.');
|
||||
}
|
||||
this.currentPage = page;
|
||||
|
||||
this.render(this.getPages(page));
|
||||
this.setupEvents();
|
||||
|
||||
this.$element.trigger('page', page);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
buildListItems: function (pages) {
|
||||
var listItems = [];
|
||||
|
||||
if (this.options.first) {
|
||||
listItems.push(this.buildItem('first', 1));
|
||||
}
|
||||
|
||||
if (this.options.prev) {
|
||||
var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1;
|
||||
listItems.push(this.buildItem('prev', prev));
|
||||
}
|
||||
|
||||
for (var i = 0; i < pages.numeric.length; i++) {
|
||||
listItems.push(this.buildItem('page', pages.numeric[i]));
|
||||
}
|
||||
|
||||
if (this.options.next) {
|
||||
var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages;
|
||||
listItems.push(this.buildItem('next', next));
|
||||
}
|
||||
|
||||
if (this.options.last) {
|
||||
listItems.push(this.buildItem('last', this.options.totalPages));
|
||||
}
|
||||
|
||||
return listItems;
|
||||
},
|
||||
|
||||
buildItem: function (type, page) {
|
||||
var $itemContainer = $('<li></li>'),
|
||||
$itemContent = $('<a></a>'),
|
||||
itemText = this.options[type] ? this.makeText(this.options[type], page) : page;
|
||||
|
||||
$itemContainer.addClass(this.options[type + 'Class']);
|
||||
$itemContainer.data('page', page);
|
||||
$itemContainer.data('page-type', type);
|
||||
$itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText));
|
||||
|
||||
return $itemContainer;
|
||||
},
|
||||
|
||||
getPages: function (currentPage) {
|
||||
var pages = [];
|
||||
|
||||
var half = Math.floor(this.options.visiblePages / 2);
|
||||
var start = currentPage - half + 1 - this.options.visiblePages % 2;
|
||||
var end = currentPage + half;
|
||||
|
||||
// handle boundary case
|
||||
if (start <= 0) {
|
||||
start = 1;
|
||||
end = this.options.visiblePages;
|
||||
}
|
||||
if (end > this.options.totalPages) {
|
||||
start = this.options.totalPages - this.options.visiblePages + 1;
|
||||
end = this.options.totalPages;
|
||||
}
|
||||
|
||||
var itPage = start;
|
||||
while (itPage <= end) {
|
||||
pages.push(itPage);
|
||||
itPage++;
|
||||
}
|
||||
|
||||
return {"currentPage": currentPage, "numeric": pages};
|
||||
},
|
||||
|
||||
render: function (pages) {
|
||||
var _this = this;
|
||||
this.$listContainer.children().remove();
|
||||
var items = this.buildListItems(pages);
|
||||
jQuery.each(items, function(key, item){
|
||||
_this.$listContainer.append(item);
|
||||
});
|
||||
|
||||
this.$listContainer.children().each(function () {
|
||||
var $this = $(this),
|
||||
pageType = $this.data('page-type');
|
||||
|
||||
switch (pageType) {
|
||||
case 'page':
|
||||
if ($this.data('page') === pages.currentPage) {
|
||||
$this.addClass(_this.options.activeClass);
|
||||
}
|
||||
break;
|
||||
case 'first':
|
||||
$this.toggleClass(_this.options.disabledClass, pages.currentPage === 1);
|
||||
break;
|
||||
case 'last':
|
||||
$this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages);
|
||||
break;
|
||||
case 'prev':
|
||||
$this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1);
|
||||
break;
|
||||
case 'next':
|
||||
$this.toggleClass(_this.options.disabledClass,
|
||||
!_this.options.loop && pages.currentPage === _this.options.totalPages);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
setupEvents: function () {
|
||||
var _this = this;
|
||||
this.$listContainer.off('click').on('click', 'li', function (evt) {
|
||||
var $this = $(this);
|
||||
if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) {
|
||||
return false;
|
||||
}
|
||||
// Prevent click event if href is not set.
|
||||
!_this.options.href && evt.preventDefault();
|
||||
_this.show(parseInt($this.data('page')));
|
||||
});
|
||||
},
|
||||
|
||||
makeHref: function (page) {
|
||||
return this.options.href ? this.generateQueryString(page) : "#";
|
||||
},
|
||||
|
||||
makeText: function (text, page) {
|
||||
return text.replace(this.options.pageVariable, page)
|
||||
.replace(this.options.totalPagesVariable, this.options.totalPages)
|
||||
},
|
||||
getPageFromQueryString: function (searchStr) {
|
||||
var search = this.getSearchString(searchStr),
|
||||
regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'),
|
||||
page = regex.exec(search);
|
||||
if (!page || !page[2]) {
|
||||
return null;
|
||||
}
|
||||
page = decodeURIComponent(page[2]);
|
||||
page = parseInt(page);
|
||||
if (isNaN(page)) {
|
||||
return null;
|
||||
}
|
||||
return page;
|
||||
},
|
||||
generateQueryString: function (pageNumber, searchStr) {
|
||||
var search = this.getSearchString(searchStr),
|
||||
regex = new RegExp(this.options.pageVariable + '=*[^&#]*');
|
||||
if (!search) return '';
|
||||
return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber);
|
||||
|
||||
},
|
||||
getSearchString: function (searchStr) {
|
||||
var search = searchStr || window.location.search;
|
||||
if (search === '') {
|
||||
return null;
|
||||
}
|
||||
if (search.indexOf('?') === 0) search = search.substr(1);
|
||||
return search;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// PLUGIN DEFINITION
|
||||
|
||||
$.fn.twbsPagination = function (option) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
var methodReturn;
|
||||
|
||||
var $this = $(this);
|
||||
var data = $this.data('twbs-pagination');
|
||||
var options = typeof option === 'object' ? option : {};
|
||||
|
||||
if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) ));
|
||||
if (typeof option === 'string') methodReturn = data[ option ].apply(data, args);
|
||||
|
||||
return ( methodReturn === undefined ) ? $this : methodReturn;
|
||||
};
|
||||
|
||||
$.fn.twbsPagination.defaults = {
|
||||
totalPages: 1,
|
||||
startPage: 1,
|
||||
visiblePages: 5,
|
||||
initiateStartPageClick: true,
|
||||
hideOnlyOnePage: false,
|
||||
href: false,
|
||||
pageVariable: '{{page}}',
|
||||
totalPagesVariable: '{{total_pages}}',
|
||||
page: null,
|
||||
first: 'First',
|
||||
prev: 'Previous',
|
||||
next: 'Next',
|
||||
last: 'Last',
|
||||
loop: false,
|
||||
onPageClick: null,
|
||||
paginationClass: 'pagination',
|
||||
nextClass: 'page-item next',
|
||||
prevClass: 'page-item prev',
|
||||
lastClass: 'page-item last',
|
||||
firstClass: 'page-item first',
|
||||
pageClass: 'page-item',
|
||||
activeClass: 'active',
|
||||
disabledClass: 'disabled',
|
||||
anchorClass: 'page-link'
|
||||
};
|
||||
|
||||
$.fn.twbsPagination.Constructor = TwbsPagination;
|
||||
|
||||
$.fn.twbsPagination.noConflict = function () {
|
||||
$.fn.twbsPagination = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
$.fn.twbsPagination.version = "1.4.1";
|
||||
|
||||
})(window.jQuery, window, document);
|
||||
@ -1,471 +0,0 @@
|
||||
body {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
body {
|
||||
font-size: var(--smalldevice-base-font-size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
h1, h1:first-child {
|
||||
font-size: 2.25em;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--h1-color);
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: -0.05em;
|
||||
}
|
||||
|
||||
.article h1 {
|
||||
margin-block-end: -0.2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.1em;
|
||||
color: var(--h2-color);
|
||||
}
|
||||
|
||||
.article h2 {
|
||||
margin-block-start: 1.3em;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--separator-color);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.95em;
|
||||
font-weight: 500;
|
||||
margin-block-start: 1.7em;
|
||||
}
|
||||
|
||||
.article h3 {
|
||||
font-size: 1.85em;
|
||||
font-weight: 500;
|
||||
margin-block-start: 1.2em;
|
||||
margin-block-end: 0.9em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.8em;
|
||||
font-weight: 400;
|
||||
margin-block-start: 2em;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.article h4 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 300;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.article h5 {
|
||||
font-size: 1.13em;
|
||||
font-weight: 400;
|
||||
text-decoration: underline;
|
||||
margin-block-start: 1.5em;
|
||||
margin-block-end: 1.0em;
|
||||
}
|
||||
|
||||
a.brand:hover
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.brand .brand-title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--appname-color);
|
||||
margin-top: 1px;
|
||||
padding: 0 0 0 0.4em;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
a.brand .brand-title {
|
||||
font-size: 1.55em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
a.brand .logomark {
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
/* Top bar */
|
||||
|
||||
.top-navbar {
|
||||
height: 60px;
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.burger-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Side Bar */
|
||||
|
||||
.sidebar {
|
||||
padding: 25px 17px 32px 17px;
|
||||
}
|
||||
|
||||
.blackout {
|
||||
top: 60px;
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px) {
|
||||
.navbar-nav {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 94%;
|
||||
max-width: var(--sidebar-width);
|
||||
left: calc(var(--sidebar-width) * -1);
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px) {
|
||||
nav {
|
||||
top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
nav .nav a, nav .nav a:hover {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav a.sidebar-item {
|
||||
padding: 4px 0 4px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nav a:focus, nav a.sidebar-item:hover, nav a.sidebar-item:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
nav a, nav a:hover, nav a:focus {
|
||||
color: var(--sidebar-item-color) !important;
|
||||
}
|
||||
|
||||
nav a.active, nav a.active:hover, nav a.active:focus {
|
||||
color: var(--sidebar-active-item-color) !important;
|
||||
}
|
||||
|
||||
.sidebar-item-separator {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#toc ul li a {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.search {
|
||||
background: var(--search-bg-color);
|
||||
border: 1px solid var(--search-border-color);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
margin-block-start: 25px;
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px) {
|
||||
.search {
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.search > input {
|
||||
font-size: 0.95em;
|
||||
color: var(--search-color);
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 11px 30px 10px 37px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search > input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.search > .search-icon {
|
||||
font-size: 1.2em;
|
||||
color: var(--search-searchicon-color);
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.toc-filter {
|
||||
background: var(--toc-filter-bg-color);
|
||||
border: 1px solid var(--toc-filter-border-color);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toc-filter > input {
|
||||
font-size: 0.95em;
|
||||
color: var(--toc-filter-color);
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 11px 30px 10px 37px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toc-filter > input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.toc-filter > .filter-icon {
|
||||
font-size: 1.2em;
|
||||
color: var(--toc-filter-filtericon-color);
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.toc-filter > .clear-icon {
|
||||
color: var(--toc-filter-clearicon-color);
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: 9px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toc .nav > li > .expand-stub::before, .toc .nav > li.active > .expand-stub::before
|
||||
{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
#toc ul.level2
|
||||
{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#toc ul.level1 > li > a {
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#toc ul.level1 > li > a, #toc ul.level1 > li > a.active {
|
||||
background-color: var(--sidebar-level1-item-bg-color) !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#toc ul.level1 > li > a:hover, #toc ul.level1 > li > a.active:hover,
|
||||
#toc ul.level1 > li > a:focus, #toc ul.level1 > li > a.active:focus {
|
||||
background-color: var(--sidebar-level1-item-hover-bg-color) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul.level2 {
|
||||
padding-inline-start: 0.7em;
|
||||
}
|
||||
|
||||
ul.level2 .expand-stub {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
ul.level2 > li > a, ul.level2 > li > a.sidebar-item {
|
||||
font-weight: 400;
|
||||
color: var(--sidebar-item-color);
|
||||
margin: 4px 0 4px;
|
||||
}
|
||||
|
||||
ul.level3 {
|
||||
padding-inline-start: 1em;
|
||||
}
|
||||
|
||||
ul.level3 > li > a, ul.level3 > li > a.sidebar-item {
|
||||
font-size: 1.05em;
|
||||
color: var(--sidebar-item-color);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
ul.level4 {
|
||||
padding-inline-start: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ul.level4 > li > a, ul.level4 > li > a.sidebar-item {
|
||||
font-size: 1.05em;
|
||||
color: var(--sidebar-item-color);
|
||||
margin: 5px 0 5px 10px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.subnav.navbar {
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
#breadcrumb {
|
||||
overflow: scroll;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#breadcrumb::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#breadcrumb a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#breadcrumb wbr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Search Results */
|
||||
|
||||
#search-results h1 {
|
||||
margin-block-start: 0.5em;
|
||||
}
|
||||
|
||||
#search-results .item-title {
|
||||
font-size: 1.3em;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
#search-results .item-href {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
#search-results .item-brief {
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
#search-results ul.pagination {
|
||||
text-align: center;
|
||||
padding: 10px 0 0 0;
|
||||
margin-block-start: 40px;
|
||||
border-top: 1px solid var(--separator-color);
|
||||
}
|
||||
|
||||
#search-results ul.pagination > li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#search-results ul.pagination > li.disabled a, #search-results ul.pagination > li.disabled a:hover {
|
||||
color: var(--text-color);
|
||||
cursor: txt;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
|
||||
.main-panel {
|
||||
margin-bottom: 60px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.main-panel {
|
||||
margin-bottom: 0;
|
||||
padding: 20px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
margin-top: 70px;
|
||||
/* Fix unclickable links */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
article ul li, article ol li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
legend, pre {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: var(--code-color);
|
||||
}
|
||||
|
||||
.hljs::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.hljs-keyword, .hljs-title, .hljs-built_in {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
p .xref, code {
|
||||
background-color: var(--ref-bg-color);
|
||||
color: var(--ref-color);
|
||||
padding: 2px 3px;
|
||||
font-family: monospace;
|
||||
font-size: 0.95em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
span.parametername {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-size: 14px;
|
||||
padding: 9px 10px;
|
||||
}
|
||||
|
||||
table td p {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.footer a:hover, .footer a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.copyright-footer {
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-block-start: 30px;
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
function toggleMenu() {
|
||||
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var blackout = document.getElementById("blackout");
|
||||
|
||||
if (sidebar.style.left === "0px")
|
||||
{
|
||||
sidebar.style.left = "-" + sidebar.offsetWidth + "px";
|
||||
blackout.classList.remove("showThat");
|
||||
blackout.classList.add("hideThat");
|
||||
}
|
||||
else
|
||||
{
|
||||
sidebar.style.left = "0px";
|
||||
blackout.classList.remove("hideThat");
|
||||
blackout.classList.add("showThat");
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('table').each(function(a, tbl) {
|
||||
var currentTableRows = $(tbl).find('tbody tr').length;
|
||||
$(tbl).find('th').each(function(i) {
|
||||
var remove = 0;
|
||||
var currentTable = $(this).parents('table');
|
||||
|
||||
var tds = currentTable.find('tr td:nth-child(' + (i + 1) + ')');
|
||||
tds.each(function(j) { if ($(this).text().trim() === '') remove++; });
|
||||
|
||||
if (remove == currentTableRows) {
|
||||
$(this).hide();
|
||||
tds.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
/*! url - v1.8.6 - 2013-11-22 */window.url=function(){function a(a){return!isNaN(parseFloat(a))&&isFinite(a)}return function(b,c){var d=c||window.location.toString();if(!b)return d;b=b.toString(),"//"===d.substring(0,2)?d="http:"+d:1===d.split("://").length&&(d="http://"+d),c=d.split("/");var e={auth:""},f=c[2].split("@");1===f.length?f=f[0].split(":"):(e.auth=f[0],f=f[1].split(":")),e.protocol=c[0],e.hostname=f[0],e.port=f[1]||("https"===e.protocol.split(":")[0].toLowerCase()?"443":"80"),e.pathname=(c.length>3?"/":"")+c.slice(3,c.length).join("/").split("?")[0].split("#")[0];var g=e.pathname;"/"===g.charAt(g.length-1)&&(g=g.substring(0,g.length-1));var h=e.hostname,i=h.split("."),j=g.split("/");if("hostname"===b)return h;if("domain"===b)return/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(h)?h:i.slice(-2).join(".");if("sub"===b)return i.slice(0,i.length-2).join(".");if("port"===b)return e.port;if("protocol"===b)return e.protocol.split(":")[0];if("auth"===b)return e.auth;if("user"===b)return e.auth.split(":")[0];if("pass"===b)return e.auth.split(":")[1]||"";if("path"===b)return e.pathname;if("."===b.charAt(0)){if(b=b.substring(1),a(b))return b=parseInt(b,10),i[0>b?i.length+b:b-1]||""}else{if(a(b))return b=parseInt(b,10),j[0>b?j.length+b:b]||"";if("file"===b)return j.slice(-1)[0];if("filename"===b)return j.slice(-1)[0].split(".")[0];if("fileext"===b)return j.slice(-1)[0].split(".")[1]||"";if("?"===b.charAt(0)||"#"===b.charAt(0)){var k=d,l=null;if("?"===b.charAt(0)?k=(k.split("?")[1]||"").split("#")[0]:"#"===b.charAt(0)&&(k=k.split("#")[1]||""),!b.charAt(1))return k;b=b.substring(1),k=k.split("&");for(var m=0,n=k.length;n>m;m++)if(l=k[m].split("="),l[0]===b)return l[1]||"";return null}}return""}}(),"undefined"!=typeof jQuery&&jQuery.extend({url:function(a,b){return window.url(a,b)}});
|
||||
@ -1,22 +0,0 @@
|
||||
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
|
||||
|
||||
<div id="sidetoggle">
|
||||
<div>
|
||||
{{^_disableSideFilter}}
|
||||
<div class="sidefilter">
|
||||
<form class="toc-filter">
|
||||
<i class="bi bi-funnel-fill filter-icon"></i>
|
||||
<i id="toc_filter_clear" class="bi bi-x-lg clear-icon"></i>
|
||||
<input type="text" id="toc_filter_input" placeholder="{{__global.tocFilter}}" autocomplete="off" onkeypress="if(event.keyCode==13) {return false;}">
|
||||
</form>
|
||||
</div>
|
||||
{{/_disableSideFilter}}
|
||||
<div class="sidetoc">
|
||||
<div class="toc" id="toc">
|
||||
{{^leaf}}
|
||||
{{>partials/li}}
|
||||
{{/leaf}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,2 +1,2 @@
|
||||
- name: API Documentation
|
||||
href: api/
|
||||
- name: Plugin API
|
||||
href: api/
|
||||
1
docfx/docs/getting-started.md
Normal file
1
docfx/docs/getting-started.md
Normal file
@ -0,0 +1 @@
|
||||
# Getting Started
|
||||
1
docfx/docs/introduction.md
Normal file
1
docfx/docs/introduction.md
Normal file
@ -0,0 +1 @@
|
||||
# Introduction
|
||||
4
docfx/docs/toc.yml
Normal file
4
docfx/docs/toc.yml
Normal file
@ -0,0 +1,4 @@
|
||||
- name: Introduction
|
||||
href: introduction.md
|
||||
- name: Getting Started
|
||||
href: getting-started.md
|
||||
@ -1,10 +0,0 @@
|
||||
To generate the [Artemis API docs](https://artemis-rgb.com/docs/) we use [DocFX](https://dotnet.github.io/docfx/).
|
||||
|
||||
To build locally run the following commands from this folder.
|
||||
|
||||
#### Want to build? Follow these instructions (Windows)
|
||||
1. Ensure you can build the Artemis solution as per the [main build instructions](https://github.com/Artemis-RGB/Artemis#want-to-build-follow-these-instructions)
|
||||
2. Install DocFX (with Chocolatey: `choco install docfx`)
|
||||
3. Open PowerShell in `<repo>\docfx` (the same folder as this readme file)
|
||||
4. Run `docfx .\docfx_project\docfx.json` to build static files
|
||||
Run `docfx .\docfx_project\docfx.json --serve` to serve locally
|
||||
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
||||
<AssemblyTitle>Artemis.Core</AssemblyTitle>
|
||||
@ -12,6 +12,7 @@
|
||||
<PackageId>ArtemisRGB.Core</PackageId>
|
||||
<PluginApiVersion>1</PluginApiVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -35,27 +36,22 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DryIoc.dll" Version="5.4.0" />
|
||||
<PackageReference Include="EmbedIO" Version="3.5.2" />
|
||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.16" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.53" />
|
||||
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.53" />
|
||||
<PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.53" />
|
||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="Unosquare.Swan.Lite" Version="3.1.0" />
|
||||
<PackageReference Include="DryIoc.dll" />
|
||||
<PackageReference Include="GenHTTP.Core" />
|
||||
<PackageReference Include="GenHTTP.Modules.Webservices" />
|
||||
<PackageReference Include="HidSharp" />
|
||||
<PackageReference Include="HPPH.SkiaSharp" />
|
||||
<PackageReference Include="Humanizer.Core" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" />
|
||||
<PackageReference Include="RGB.NET.Core" />
|
||||
<PackageReference Include="RGB.NET.Layout" />
|
||||
<PackageReference Include="RGB.NET.Presets" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="SkiaSharp" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Cquantization/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Csorting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -80,6 +79,8 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitoring/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitoring_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@ -1,167 +0,0 @@
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
internal readonly struct ColorRanges
|
||||
{
|
||||
public readonly byte RedRange;
|
||||
public readonly byte GreenRange;
|
||||
public readonly byte BlueRange;
|
||||
|
||||
public ColorRanges(byte redRange, byte greenRange, byte blueRange)
|
||||
{
|
||||
this.RedRange = redRange;
|
||||
this.GreenRange = greenRange;
|
||||
this.BlueRange = blueRange;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct ColorCube
|
||||
{
|
||||
private const int BYTES_PER_COLOR = 4;
|
||||
private static readonly int ELEMENTS_PER_VECTOR = Vector<byte>.Count / BYTES_PER_COLOR;
|
||||
private static readonly int BYTES_PER_VECTOR = ELEMENTS_PER_VECTOR * BYTES_PER_COLOR;
|
||||
|
||||
private readonly int _from;
|
||||
private readonly int _length;
|
||||
private readonly SortTarget _currentOrder = SortTarget.None;
|
||||
|
||||
public ColorCube(in Span<SKColor> fullColorList, int from, int length, SortTarget preOrdered)
|
||||
{
|
||||
this._from = from;
|
||||
this._length = length;
|
||||
|
||||
if (length < 2) return;
|
||||
|
||||
Span<SKColor> colors = fullColorList.Slice(from, length);
|
||||
ColorRanges colorRanges = GetColorRanges(colors);
|
||||
|
||||
if ((colorRanges.RedRange > colorRanges.GreenRange) && (colorRanges.RedRange > colorRanges.BlueRange))
|
||||
{
|
||||
if (preOrdered != SortTarget.Red)
|
||||
QuantizerSort.SortRed(colors);
|
||||
|
||||
_currentOrder = SortTarget.Red;
|
||||
}
|
||||
else if (colorRanges.GreenRange > colorRanges.BlueRange)
|
||||
{
|
||||
if (preOrdered != SortTarget.Green)
|
||||
QuantizerSort.SortGreen(colors);
|
||||
|
||||
_currentOrder = SortTarget.Green;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (preOrdered != SortTarget.Blue)
|
||||
QuantizerSort.SortBlue(colors);
|
||||
|
||||
_currentOrder = SortTarget.Blue;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ColorRanges GetColorRanges(in ReadOnlySpan<SKColor> colors)
|
||||
{
|
||||
if (Vector.IsHardwareAccelerated && (colors.Length >= Vector<byte>.Count))
|
||||
{
|
||||
int chunks = colors.Length / ELEMENTS_PER_VECTOR;
|
||||
int vectorElements = (chunks * ELEMENTS_PER_VECTOR);
|
||||
int missingElements = colors.Length - vectorElements;
|
||||
|
||||
Vector<byte> max = Vector<byte>.Zero;
|
||||
Vector<byte> min = new(byte.MaxValue);
|
||||
foreach (Vector<byte> currentVector in MemoryMarshal.Cast<SKColor, Vector<byte>>(colors[..vectorElements]))
|
||||
{
|
||||
max = Vector.Max(max, currentVector);
|
||||
min = Vector.Min(min, currentVector);
|
||||
}
|
||||
|
||||
byte redMin = byte.MaxValue;
|
||||
byte redMax = byte.MinValue;
|
||||
byte greenMin = byte.MaxValue;
|
||||
byte greenMax = byte.MinValue;
|
||||
byte blueMin = byte.MaxValue;
|
||||
byte blueMax = byte.MinValue;
|
||||
|
||||
for (int i = 0; i < BYTES_PER_VECTOR; i += BYTES_PER_COLOR)
|
||||
{
|
||||
if (min[i + 2] < redMin) redMin = min[i + 2];
|
||||
if (max[i + 2] > redMax) redMax = max[i + 2];
|
||||
if (min[i + 1] < greenMin) greenMin = min[i + 1];
|
||||
if (max[i + 1] > greenMax) greenMax = max[i + 1];
|
||||
if (min[i] < blueMin) blueMin = min[i];
|
||||
if (max[i] > blueMax) blueMax = max[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < missingElements; i++)
|
||||
{
|
||||
SKColor color = colors[^(i + 1)];
|
||||
|
||||
if (color.Red < redMin) redMin = color.Red;
|
||||
if (color.Red > redMax) redMax = color.Red;
|
||||
if (color.Green < greenMin) greenMin = color.Green;
|
||||
if (color.Green > greenMax) greenMax = color.Green;
|
||||
if (color.Blue < blueMin) blueMin = color.Blue;
|
||||
if (color.Blue > blueMax) blueMax = color.Blue;
|
||||
}
|
||||
|
||||
return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin));
|
||||
}
|
||||
else
|
||||
{
|
||||
byte redMin = byte.MaxValue;
|
||||
byte redMax = byte.MinValue;
|
||||
byte greenMin = byte.MaxValue;
|
||||
byte greenMax = byte.MinValue;
|
||||
byte blueMin = byte.MaxValue;
|
||||
byte blueMax = byte.MinValue;
|
||||
|
||||
foreach (SKColor color in colors)
|
||||
{
|
||||
if (color.Red < redMin) redMin = color.Red;
|
||||
if (color.Red > redMax) redMax = color.Red;
|
||||
if (color.Green < greenMin) greenMin = color.Green;
|
||||
if (color.Green > greenMax) greenMax = color.Green;
|
||||
if (color.Blue < blueMin) blueMin = color.Blue;
|
||||
if (color.Blue > blueMax) blueMax = color.Blue;
|
||||
}
|
||||
|
||||
return new ColorRanges((byte)(redMax - redMin), (byte)(greenMax - greenMin), (byte)(blueMax - blueMin));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Split(in Span<SKColor> fullColorList, out ColorCube a, out ColorCube b)
|
||||
{
|
||||
Span<SKColor> colors = fullColorList.Slice(_from, _length);
|
||||
|
||||
int median = colors.Length / 2;
|
||||
|
||||
a = new ColorCube(fullColorList, _from, median, _currentOrder);
|
||||
b = new ColorCube(fullColorList, _from + median, colors.Length - median, _currentOrder);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal SKColor GetAverageColor(in ReadOnlySpan<SKColor> fullColorList)
|
||||
{
|
||||
ReadOnlySpan<SKColor> colors = fullColorList.Slice(_from, _length);
|
||||
|
||||
int r = 0, g = 0, b = 0;
|
||||
foreach (SKColor color in colors)
|
||||
{
|
||||
r += color.Red;
|
||||
g += color.Green;
|
||||
b += color.Blue;
|
||||
}
|
||||
|
||||
return new SKColor(
|
||||
(byte)(r / colors.Length),
|
||||
(byte)(g / colors.Length),
|
||||
(byte)(b / colors.Length)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
using SkiaSharp;
|
||||
using HPPH;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
@ -10,13 +12,27 @@ namespace Artemis.Core.ColorScience;
|
||||
/// </summary>
|
||||
public static class ColorQuantizer
|
||||
{
|
||||
/// <inheritdoc cref="Quantize(Span{SKColor}, int)"/>
|
||||
[Obsolete("Use Quantize(Span<SKColor> colors, int amount) in-parameter instead")]
|
||||
public static SKColor[] Quantize(in Span<SKColor> colors, int amount)
|
||||
{
|
||||
return Quantize(colors, amount);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="QuantizeSplit(Span{SKColor}, int)"/>
|
||||
[Obsolete("Use QuantizeSplit(Span<SKColor> colors, int splits) without the in-parameter instead")]
|
||||
public static SKColor[] QuantizeSplit(in Span<SKColor> colors, int splits)
|
||||
{
|
||||
return QuantizeSplit(colors, splits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes a span of colors into the desired amount of representative colors.
|
||||
/// </summary>
|
||||
/// <param name="colors">The colors to quantize</param>
|
||||
/// <param name="amount">How many colors to return. Must be a power of two.</param>
|
||||
/// <returns><paramref name="amount"/> colors.</returns>
|
||||
public static SKColor[] Quantize(in Span<SKColor> colors, int amount)
|
||||
public static SKColor[] Quantize(Span<SKColor> colors, int amount)
|
||||
{
|
||||
if (!BitOperations.IsPow2(amount))
|
||||
throw new ArgumentException("Must be power of two", nameof(amount));
|
||||
@ -24,38 +40,19 @@ public static class ColorQuantizer
|
||||
int splits = BitOperations.Log2((uint)amount);
|
||||
return QuantizeSplit(colors, splits);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Quantizes a span of colors, splitting the average <paramref name="splits"/> number of times.
|
||||
/// </summary>
|
||||
/// <param name="colors">The colors to quantize</param>
|
||||
/// <param name="splits">How many splits to execute. Each split doubles the number of colors returned.</param>
|
||||
/// <returns>Up to (2 ^ <paramref name="splits"/>) number of colors.</returns>
|
||||
public static SKColor[] QuantizeSplit(in Span<SKColor> colors, int splits)
|
||||
public static SKColor[] QuantizeSplit(Span<SKColor> colors, int splits)
|
||||
{
|
||||
if (colors.Length < (1 << splits)) throw new ArgumentException($"The color array must at least contain ({(1 << splits)}) to perform {splits} splits.");
|
||||
|
||||
Span<ColorCube> cubes = new ColorCube[1 << splits];
|
||||
cubes[0] = new ColorCube(colors, 0, colors.Length, SortTarget.None);
|
||||
|
||||
int currentIndex = 0;
|
||||
for (int i = 0; i < splits; i++)
|
||||
{
|
||||
int currentCubeCount = 1 << i;
|
||||
Span<ColorCube> currentCubes = cubes.Slice(0, currentCubeCount);
|
||||
for (int j = 0; j < currentCubes.Length; j++)
|
||||
{
|
||||
currentCubes[j].Split(colors, out ColorCube a, out ColorCube b);
|
||||
currentCubes[j] = a;
|
||||
cubes[++currentIndex] = b;
|
||||
}
|
||||
}
|
||||
|
||||
SKColor[] result = new SKColor[cubes.Length];
|
||||
for (int i = 0; i < cubes.Length; i++)
|
||||
result[i] = cubes[i].GetAverageColor(colors);
|
||||
|
||||
return result;
|
||||
// DarthAffe 22.07.2024: This is not ideal as it allocates an additional array, but i don't see a way to get SKColors out here
|
||||
return MemoryMarshal.Cast<ColorBGRA, SKColor>(MemoryMarshal.Cast<SKColor, ColorBGRA>(colors).CreateSimpleColorPalette(1 << splits)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using SkiaSharp;
|
||||
using System.Text;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
@ -36,4 +37,14 @@ public readonly record struct ColorSwatch
|
||||
/// The <see cref="ColorType.DarkMuted" /> component.
|
||||
/// </summary>
|
||||
public SKColor DarkMuted { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Override the record ToString method,
|
||||
/// so we get a cleaner datamodel viewer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string? ToString()
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
//HACK DarthAffe 17.11.2022: Sorting is a really hot path in the quantizer, therefore abstracting this into cleaner code (one method with parameter or something like that) sadly has a well measurable performance impact.
|
||||
internal static class QuantizerSort
|
||||
{
|
||||
#region Methods
|
||||
|
||||
public static void SortRed(in Span<SKColor> colors)
|
||||
{
|
||||
Span<int> counts = stackalloc int[256];
|
||||
foreach (SKColor t in colors)
|
||||
counts[t.Red]++;
|
||||
|
||||
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
|
||||
Span<int> currentBucketIndex = stackalloc int[256];
|
||||
|
||||
int offset = 0;
|
||||
for (int i = 0; i < counts.Length; i++)
|
||||
{
|
||||
currentBucketIndex[i] = offset;
|
||||
offset += counts[i];
|
||||
}
|
||||
|
||||
foreach (SKColor color in colors)
|
||||
{
|
||||
int index = color.Red;
|
||||
int bucketIndex = currentBucketIndex[index];
|
||||
currentBucketIndex[index]++;
|
||||
buckets[bucketIndex] = color;
|
||||
}
|
||||
|
||||
buckets.CopyTo(colors);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<SKColor>.Shared.Return(bucketsArray);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SortGreen(in Span<SKColor> colors)
|
||||
{
|
||||
Span<int> counts = stackalloc int[256];
|
||||
foreach (SKColor t in colors)
|
||||
counts[t.Green]++;
|
||||
|
||||
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
|
||||
Span<int> currentBucketIndex = stackalloc int[256];
|
||||
|
||||
int offset = 0;
|
||||
for (int i = 0; i < counts.Length; i++)
|
||||
{
|
||||
currentBucketIndex[i] = offset;
|
||||
offset += counts[i];
|
||||
}
|
||||
|
||||
foreach (SKColor color in colors)
|
||||
{
|
||||
int index = color.Green;
|
||||
int bucketIndex = currentBucketIndex[index];
|
||||
currentBucketIndex[index]++;
|
||||
buckets[bucketIndex] = color;
|
||||
}
|
||||
|
||||
buckets.CopyTo(colors);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<SKColor>.Shared.Return(bucketsArray);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SortBlue(in Span<SKColor> colors)
|
||||
{
|
||||
Span<int> counts = stackalloc int[256];
|
||||
foreach (SKColor t in colors)
|
||||
counts[t.Blue]++;
|
||||
|
||||
SKColor[] bucketsArray = ArrayPool<SKColor>.Shared.Rent(colors.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Span<SKColor> buckets = bucketsArray.AsSpan().Slice(0, colors.Length);
|
||||
Span<int> currentBucketIndex = stackalloc int[256];
|
||||
|
||||
int offset = 0;
|
||||
for (int i = 0; i < counts.Length; i++)
|
||||
{
|
||||
currentBucketIndex[i] = offset;
|
||||
offset += counts[i];
|
||||
}
|
||||
|
||||
foreach (SKColor color in colors)
|
||||
{
|
||||
int index = color.Blue;
|
||||
int bucketIndex = currentBucketIndex[index];
|
||||
currentBucketIndex[index]++;
|
||||
buckets[bucketIndex] = color;
|
||||
}
|
||||
|
||||
buckets.CopyTo(colors);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<SKColor>.Shared.Return(bucketsArray);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace Artemis.Core.ColorScience;
|
||||
|
||||
internal enum SortTarget
|
||||
{
|
||||
None, Red, Green, Blue
|
||||
}
|
||||
@ -4,10 +4,9 @@ using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Artemis.Core.JsonConverters;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.SkiaSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@ -47,6 +46,7 @@ public static class Constants
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
@ -62,6 +62,11 @@ public static class Constants
|
||||
/// </summary>
|
||||
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis user layouts folder
|
||||
/// </summary>
|
||||
public static readonly string WorkshopFolder = Path.Combine(DataFolder, "workshop");
|
||||
|
||||
/// <summary>
|
||||
/// The current API version for plugins
|
||||
/// </summary>
|
||||
@ -71,9 +76,9 @@ public static class Constants
|
||||
/// <summary>
|
||||
/// The current version of the application
|
||||
/// </summary>
|
||||
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion != "1.0.0"
|
||||
? CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion
|
||||
: "local";
|
||||
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.StartsWith("1.0.0")
|
||||
? "local"
|
||||
: CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin info used by core components of Artemis
|
||||
@ -86,21 +91,7 @@ public static class Constants
|
||||
/// <summary>
|
||||
/// The plugin used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
||||
|
||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||
{
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), new PluginEntity(){PluginGuid = CorePluginInfo.Guid}, false);
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive numeric types
|
||||
@ -149,10 +140,8 @@ public static class Constants
|
||||
/// Gets the startup arguments provided to the application
|
||||
/// </summary>
|
||||
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via
|
||||
/// <see cref="IRgbService.UpdateGraphicsContext" />.
|
||||
/// </summary>
|
||||
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
|
||||
|
||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||
internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}};
|
||||
}
|
||||
@ -2,9 +2,10 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DryIoc.Factories;
|
||||
using Artemis.Core.Providers;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage;
|
||||
using Artemis.Storage.Migrations.Interfaces;
|
||||
using Artemis.Storage.Migrations;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using DryIoc;
|
||||
|
||||
@ -21,21 +22,21 @@ public static class ContainerExtensions
|
||||
/// <param name="container">The builder building the current container</param>
|
||||
public static void RegisterCore(this IContainer container)
|
||||
{
|
||||
Assembly[] coreAssembly = {typeof(IArtemisService).Assembly};
|
||||
Assembly[] storageAssembly = {typeof(IRepository).Assembly};
|
||||
Assembly[] coreAssembly = [typeof(IArtemisService).Assembly];
|
||||
Assembly[] storageAssembly = [typeof(IRepository).Assembly];
|
||||
|
||||
// Bind all services as singletons
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IArtemisService>(), Reuse.Singleton);
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IProtectedArtemisService>(), Reuse.Singleton, setup: Setup.With(condition: HasAccessToProtectedService));
|
||||
|
||||
// Bind storage
|
||||
container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton);
|
||||
container.Register<StorageMigrationService>(Reuse.Singleton);
|
||||
container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient);
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IRepository>(), Reuse.Singleton);
|
||||
|
||||
|
||||
// Bind migrations
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
|
||||
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
|
||||
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
|
||||
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
|
||||
container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
|
||||
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);
|
||||
|
||||
@ -13,7 +13,10 @@ internal class LoggerFactory : ILoggerFactory
|
||||
internal static readonly ILogger Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.File(Path.Combine(Constants.LogsFolder, "Artemis log-.log"),
|
||||
fileSizeLimitBytes: 5 * 1024 * 1024,
|
||||
rollOnFileSizeLimit: true,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 14,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Console()
|
||||
#if DEBUG
|
||||
|
||||
27
src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
Normal file
27
src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data about device provider related events
|
||||
/// </summary>
|
||||
public class DeviceProviderEventArgs : EventArgs
|
||||
{
|
||||
internal DeviceProviderEventArgs(DeviceProvider deviceProvider, List<ArtemisDevice> devices)
|
||||
{
|
||||
DeviceProvider = deviceProvider;
|
||||
Devices = devices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device provider the event is related to.
|
||||
/// </summary>
|
||||
public DeviceProvider DeviceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the affected devices.
|
||||
/// </summary>
|
||||
public List<ArtemisDevice> Devices { get; set; }
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class DirectoryInfoExtensions
|
||||
{
|
||||
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
|
||||
{
|
||||
foreach (DirectoryInfo dir in source.GetDirectories())
|
||||
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
|
||||
foreach (FileInfo file in source.GetFiles())
|
||||
file.CopyTo(Path.Combine(target.FullName, file.Name));
|
||||
}
|
||||
|
||||
public static void DeleteRecursively(this DirectoryInfo baseDir)
|
||||
{
|
||||
if (!baseDir.Exists)
|
||||
return;
|
||||
|
||||
foreach (DirectoryInfo dir in baseDir.EnumerateDirectories())
|
||||
DeleteRecursively(dir);
|
||||
FileInfo[] files = baseDir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
file.IsReadOnly = false;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
baseDir.Delete();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -18,6 +19,18 @@ internal static class RgbDeviceExtensions
|
||||
builder.Append(rgbDevice.DeviceInfo.DeviceType);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static void EnsureValidDimensions(this IRGBDevice rgbDevice)
|
||||
{
|
||||
if (rgbDevice.Location == Point.Invalid)
|
||||
rgbDevice.Location = new Point(0, 0);
|
||||
|
||||
if (rgbDevice.Size == Size.Invalid)
|
||||
{
|
||||
Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
|
||||
rgbDevice.Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class RgbRectangleExtensions
|
||||
|
||||
@ -2,9 +2,16 @@
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal static class SKPaintExtensions
|
||||
/// <summary>
|
||||
/// A static class providing <see cref="SKPaint" /> extensions
|
||||
/// </summary>
|
||||
public static class SKPaintExtensions
|
||||
{
|
||||
internal static void DisposeSelfAndProperties(this SKPaint paint)
|
||||
/// <summary>
|
||||
/// Disposes the paint and its disposable properties such as shaders and filters.
|
||||
/// </summary>
|
||||
/// <param name="paint">The pain to dispose.</param>
|
||||
public static void DisposeSelfAndProperties(this SKPaint paint)
|
||||
{
|
||||
paint.ImageFilter?.Dispose();
|
||||
paint.ColorFilter?.Dispose();
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
/// <summary>
|
||||
/// An int converter that, if required, will round float values
|
||||
/// </summary>
|
||||
internal class ForgivingIntConverter : JsonConverter<int>
|
||||
{
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
JValue? jsonValue = serializer.Deserialize<JValue>(reader);
|
||||
if (jsonValue == null)
|
||||
throw new JsonReaderException("Failed to deserialize forgiving int value");
|
||||
|
||||
if (jsonValue.Type == JTokenType.Float)
|
||||
return (int) Math.Round(jsonValue.Value<double>());
|
||||
if (jsonValue.Type == JTokenType.Integer)
|
||||
return jsonValue.Value<int>();
|
||||
|
||||
throw new JsonReaderException("Failed to deserialize forgiving int value");
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// Version converter that is forgiving of missing parts of the version string,
|
||||
/// setting them to zero instead of -1.
|
||||
/// </summary>
|
||||
internal class ForgivingVersionConverter : VersionConverter
|
||||
{
|
||||
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||
{
|
||||
object? obj = base.ReadJson(reader, objectType, existingValue, serializer);
|
||||
if (obj is not Version v)
|
||||
return obj;
|
||||
|
||||
int major = v.Major == -1 ? 0 : v.Major;
|
||||
int minor = v.Minor == -1 ? 0 : v.Minor;
|
||||
int build = v.Build == -1 ? 0 : v.Build;
|
||||
int revision = v.Revision == -1 ? 0 : v.Revision;
|
||||
return new Version(major, minor, build, revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,26 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||
namespace Artemis.Core.JsonConverters
|
||||
{
|
||||
#region Overrides of JsonConverter<Numeric>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
|
||||
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||
{
|
||||
float floatValue = value;
|
||||
writer.WriteValue(floatValue);
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, Numeric value, JsonSerializerOptions options)
|
||||
{
|
||||
float floatValue = value;
|
||||
writer.WriteNumberValue(floatValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return new Numeric(reader.Value);
|
||||
}
|
||||
public override Numeric Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.Number)
|
||||
{
|
||||
throw new JsonException($"Expected a number token, but got {reader.TokenType}.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
float floatValue = reader.GetSingle();
|
||||
return new Numeric(floatValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,26 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
internal class SKColorConverter : JsonConverter<SKColor>
|
||||
namespace Artemis.Core.JsonConverters
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, SKColor value, JsonSerializer serializer)
|
||||
internal class SKColorConverter : JsonConverter<SKColor>
|
||||
{
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
public override void Write(Utf8JsonWriter writer, SKColor value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
|
||||
public override SKColor ReadJson(JsonReader reader, Type objectType, SKColor existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is string value && !string.IsNullOrWhiteSpace(value))
|
||||
return SKColor.Parse(value);
|
||||
public override SKColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.String)
|
||||
{
|
||||
throw new JsonException($"Expected a string token, but got {reader.TokenType}.");
|
||||
}
|
||||
|
||||
return SKColor.Empty;
|
||||
string colorString = reader.GetString() ?? string.Empty;
|
||||
return SKColor.TryParse(colorString, out SKColor color) ? color : SKColor.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.JsonConverters;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class StreamConverter : JsonConverter<Stream>
|
||||
{
|
||||
#region Overrides of JsonConverter<Stream>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
value.Position = 0;
|
||||
value.CopyTo(memoryStream);
|
||||
writer.WriteValue(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is not string base64)
|
||||
return null;
|
||||
|
||||
if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
|
||||
return new MemoryStream(Convert.FromBase64String(base64));
|
||||
|
||||
using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
|
||||
existingValue.Position = 0;
|
||||
memoryStream.CopyTo(existingValue);
|
||||
existingValue.Position = 0;
|
||||
return existingValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -61,6 +61,9 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
|
||||
/// <inheritdoc />
|
||||
public void SetBrokenState(string state, Exception? exception = null)
|
||||
{
|
||||
if (state == BrokenState && BrokenStateException?.StackTrace == exception?.StackTrace)
|
||||
return;
|
||||
|
||||
BrokenState = state ?? throw new ArgumentNullException(nameof(state));
|
||||
BrokenStateException = exception;
|
||||
OnBrokenStateChanged();
|
||||
|
||||
15
src/Artemis.Core/Models/IPluginFeatureDependent.cs
Normal file
15
src/Artemis.Core/Models/IPluginFeatureDependent.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that depends on plugin features
|
||||
/// </summary>
|
||||
public interface IPluginFeatureDependent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the plugin features this class depends on, may contain the same plugin feature twice if depending on it in multiple ways.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="List{T}"/> of <see cref="PluginFeature"/> this class depends on.</returns>
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies();
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
using RGB.NET.Core;
|
||||
@ -15,7 +16,12 @@ public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
{KeyboardSection.MacroKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Programmable1 && l <= LedId.Keyboard_Programmable32).ToList()},
|
||||
{KeyboardSection.LedStrips, Enum.GetValues<LedId>().Where(l => l >= LedId.LedStripe1 && l <= LedId.LedStripe128).ToList()},
|
||||
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()}
|
||||
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()},
|
||||
{KeyboardSection.FunctionKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_F1 && l <= LedId.Keyboard_F12).ToList()},
|
||||
{KeyboardSection.NumberKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_1 && l <= LedId.Keyboard_0).ToList()},
|
||||
{KeyboardSection.NumPad, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_NumLock && l <= LedId.Keyboard_NumPeriodAndDelete).ToList()},
|
||||
{KeyboardSection.ArrowKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_PageDown && l <= LedId.Keyboard_ArrowRight).ToList()},
|
||||
{KeyboardSection.MediaKeys, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_MediaMute && l <= LedId.Keyboard_MediaNextTrack).ToList()},
|
||||
};
|
||||
|
||||
private KeyboardSection _section;
|
||||
@ -46,6 +52,12 @@ public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
if (Section == KeyboardSection.Movement)
|
||||
{
|
||||
ApplyMovement(layer, devices);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only keyboards should have the LEDs we care about
|
||||
foreach (ArtemisDevice keyboard in devices.Where(d => d.DeviceType == RGBDeviceType.Keyboard))
|
||||
{
|
||||
@ -54,6 +66,26 @@ public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMovement(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
// Only keyboards should have the LEDs we care about
|
||||
foreach (ArtemisDevice keyboard in devices.Where(d => d.DeviceType == RGBDeviceType.Keyboard))
|
||||
{
|
||||
ArtemisLed? qLed = keyboard.Leds.FirstOrDefault(l => l.RgbLed.Id == LedId.Keyboard_Q);
|
||||
ArtemisLed? aLed = keyboard.Leds.FirstOrDefault(l => l.RgbLed.Id == LedId.Keyboard_A);
|
||||
if (qLed == null || aLed == null)
|
||||
continue;
|
||||
|
||||
// AZERTY keyboards will have their A above their Q
|
||||
bool isAzerty = aLed.Rectangle.MidX < qLed.Rectangle.MidX;
|
||||
|
||||
if (isAzerty)
|
||||
layer.AddLeds(keyboard.Leds.Where(l => l.RgbLed.Id is LedId.Keyboard_Z or LedId.Keyboard_Q or LedId.Keyboard_S or LedId.Keyboard_D));
|
||||
else
|
||||
layer.AddLeds(keyboard.Leds.Where(l => l.RgbLed.Id is LedId.Keyboard_W or LedId.Keyboard_A or LedId.Keyboard_S or LedId.Keyboard_D));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
@ -77,15 +109,45 @@ public enum KeyboardSection
|
||||
/// <summary>
|
||||
/// A region containing the macro keys of a keyboard
|
||||
/// </summary>
|
||||
MacroKeys,
|
||||
[Description("Macro Keys")] MacroKeys,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the LED strips of a keyboard
|
||||
/// </summary>
|
||||
LedStrips,
|
||||
[Description("LED Strips")] LedStrips,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing extra non-standard LEDs of a keyboard
|
||||
/// A region containing the extra non-standard LEDs of a keyboard
|
||||
/// </summary>
|
||||
Extra
|
||||
[Description("Extra LEDs")] Extra,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the movement keys of a keyboard (WASD for QWERTY and ZQSD for AZERTY)
|
||||
/// </summary>
|
||||
[Description("Movement (WASD/ZQSD)")] Movement,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the F-keys of a keyboard
|
||||
/// </summary>
|
||||
[Description("F-keys")] FunctionKeys,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the numeric keys of a keyboard
|
||||
/// </summary>
|
||||
[Description("Numeric keys")] NumberKeys,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the Numpad of a keyboard
|
||||
/// </summary>
|
||||
[Description("Numpad")] NumPad,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the arrow keys of a keyboard
|
||||
/// </summary>
|
||||
[Description("Arrow keys")] ArrowKeys,
|
||||
|
||||
/// <summary>
|
||||
/// A region containing the media keys of a keyboard
|
||||
/// </summary>
|
||||
[Description("Media keys")] MediaKeys
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.AdaptionHints;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a single LED of one or more devices
|
||||
/// </summary>
|
||||
public class SingleLedAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private LedId _ledId;
|
||||
private int _skip;
|
||||
private bool _limitAmount;
|
||||
private int _amount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SingleLedAdaptionHint" /> class
|
||||
/// </summary>
|
||||
public SingleLedAdaptionHint()
|
||||
{
|
||||
}
|
||||
|
||||
internal SingleLedAdaptionHint(SingleLedAdaptionHintEntity entity)
|
||||
{
|
||||
LedId = (LedId) entity.LedId;
|
||||
Skip = entity.Skip;
|
||||
LimitAmount = entity.LimitAmount;
|
||||
Amount = entity.Amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LED ID to apply to.
|
||||
/// </summary>
|
||||
public LedId LedId
|
||||
{
|
||||
get => _ledId;
|
||||
set => SetAndNotify(ref _ledId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => d.Leds.Any(l => l.RgbLed.Id == LedId))
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
if (LimitAmount)
|
||||
matches = matches.Take(Amount);
|
||||
|
||||
foreach (ArtemisDevice artemisDevice in matches)
|
||||
{
|
||||
ArtemisLed led = artemisDevice.Leds.First(l => l.RgbLed.Id == LedId);
|
||||
layer.AddLed(led);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdaptionHintEntity GetEntry()
|
||||
{
|
||||
return new SingleLedAdaptionHintEntity {Amount = Amount, LimitAmount = LimitAmount, Skip = Skip, LedId = (int) LedId};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Single LED adaption - {nameof(LedId)}: {LedId}, {nameof(Skip)}: {Skip}, {nameof(LimitAmount)}: {LimitAmount}, {nameof(Amount)}: {Amount}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -241,7 +241,7 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
||||
return _stops[^1].Color;
|
||||
|
||||
//find the first stop after the position
|
||||
int stop2Index = 0;
|
||||
int stop2Index = -1;
|
||||
|
||||
for (int i = 0; i < _stops.Count; i++)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
@ -82,4 +83,14 @@ public class AlwaysOnCondition : ICondition
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -325,4 +325,14 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return Script.GetFeatureDependencies().Concat(EventPath?.GetFeatureDependencies() ?? []);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -6,7 +6,7 @@ namespace Artemis.Core;
|
||||
/// <summary>
|
||||
/// Represents a condition applied to a <see cref="ProfileElement" />
|
||||
/// </summary>
|
||||
public interface ICondition : IDisposable, IStorageModel
|
||||
public interface ICondition : IDisposable, IStorageModel, IPluginFeatureDependent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the entity used to store this condition
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
@ -82,4 +83,14 @@ public class PlayOnceCondition : ICondition
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
@ -159,6 +160,16 @@ public class StaticCondition : CorePropertyChanged, INodeScriptCondition
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return Script.GetFeatureDependencies();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -243,4 +243,14 @@ public class DataBinding<TLayerProperty> : IDataBinding
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return Script.GetFeatureDependencies();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -8,7 +8,7 @@ namespace Artemis.Core;
|
||||
/// Represents a data binding that binds a certain <see cref="LayerProperty{T}" /> to a value inside a
|
||||
/// <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public interface IDataBinding : IStorageModel, IDisposable
|
||||
public interface IDataBinding : IStorageModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the layer property the data binding is applied to
|
||||
|
||||
@ -11,7 +11,7 @@ namespace Artemis.Core;
|
||||
/// <summary>
|
||||
/// Represents a path that points to a property in data model
|
||||
/// </summary>
|
||||
public class DataModelPath : IStorageModel, IDisposable
|
||||
public class DataModelPath : IStorageModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
private readonly LinkedList<DataModelPathSegment> _segments;
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
@ -158,7 +158,16 @@ public class DataModelPath : IStorageModel, IDisposable
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelPath");
|
||||
|
||||
return Segments.LastOrDefault()?.GetPropertyType();
|
||||
// Prefer the actual type from the segments
|
||||
Type? segmentType = Segments.LastOrDefault()?.GetPropertyType();
|
||||
if (segmentType != null)
|
||||
return segmentType;
|
||||
|
||||
// Fall back to stored type
|
||||
if (!string.IsNullOrWhiteSpace(Entity.Type))
|
||||
return Type.GetType(Entity.Type);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -179,6 +188,14 @@ public class DataModelPath : IStorageModel, IDisposable
|
||||
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
if (Target == null)
|
||||
return [];
|
||||
return [Target.Module];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the path becomes invalid
|
||||
/// </summary>
|
||||
@ -358,36 +375,15 @@ public class DataModelPath : IStorageModel, IDisposable
|
||||
// Do not save an invalid state
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
|
||||
Entity.Path = Path;
|
||||
Entity.DataModelId = DataModelId;
|
||||
|
||||
// Store the type name but only if available
|
||||
Type? pathType = Segments.LastOrDefault()?.GetPropertyType();
|
||||
if (pathType != null)
|
||||
Entity.Type = pathType.FullName;
|
||||
}
|
||||
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="Equals(object)" />
|
||||
/// >
|
||||
protected bool Equals(DataModelPath other)
|
||||
{
|
||||
return ReferenceEquals(Target, other.Target) && Path == other.Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((DataModelPath) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Target, Path);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -164,7 +164,7 @@ public sealed class Folder : RenderProfileElement
|
||||
if (Parent == null)
|
||||
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
|
||||
|
||||
FolderEntity entityCopy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(FolderEntity, true), true)!;
|
||||
FolderEntity entityCopy = CoreJson.Deserialize<FolderEntity>(CoreJson.Serialize(FolderEntity))!;
|
||||
entityCopy.Id = Guid.NewGuid();
|
||||
entityCopy.Name += " - Copy";
|
||||
|
||||
@ -179,6 +179,14 @@ public sealed class Folder : RenderProfileElement
|
||||
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
|
||||
.Concat(Children.SelectMany(c => c.GetFeatureDependencies()))
|
||||
.Concat(DisplayCondition.GetFeatureDependencies());
|
||||
}
|
||||
|
||||
#region Rendering
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -355,9 +363,7 @@ public sealed class Folder : RenderProfileElement
|
||||
FolderEntity.Name = Name;
|
||||
FolderEntity.IsExpanded = IsExpanded;
|
||||
FolderEntity.Suspended = Suspended;
|
||||
|
||||
FolderEntity.ProfileId = Profile.EntityId;
|
||||
|
||||
|
||||
SaveRenderElement();
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ public sealed class Layer : RenderProfileElement
|
||||
{
|
||||
private const string BROKEN_STATE_BRUSH_NOT_FOUND = "Failed to load layer brush, ensure the plugin is enabled";
|
||||
private const string BROKEN_STATE_INIT_FAILED = "Failed to initialize layer brush";
|
||||
|
||||
|
||||
private readonly List<Layer> _renderCopies = new();
|
||||
private LayerGeneralProperties _general = new();
|
||||
private LayerTransformProperties _transform = new();
|
||||
@ -160,10 +160,12 @@ public sealed class Layer : RenderProfileElement
|
||||
public LayerAdapter Adapter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet;
|
||||
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet && HasBounds;
|
||||
|
||||
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
||||
|
||||
private bool HasBounds => Bounds.Width > 0 && Bounds.Height > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override List<ILayerProperty> GetAllLayerProperties()
|
||||
{
|
||||
@ -187,6 +189,16 @@ public sealed class Layer : RenderProfileElement
|
||||
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
|
||||
.Concat(LayerBrush?.GetFeatureDependencies() ?? [])
|
||||
.Concat(General.GetFeatureDependencies())
|
||||
.Concat(Transform.GetFeatureDependencies())
|
||||
.Concat(DisplayCondition.GetFeatureDependencies());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property affecting the rendering properties of this layer has been updated
|
||||
/// </summary>
|
||||
@ -248,8 +260,8 @@ public sealed class Layer : RenderProfileElement
|
||||
typeof(PropertyGroupDescriptionAttribute)
|
||||
)!;
|
||||
|
||||
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier};
|
||||
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier};
|
||||
LayerEntity.GeneralPropertyGroup ??= new PropertyGroupEntity {Identifier = generalAttribute.Identifier!};
|
||||
LayerEntity.TransformPropertyGroup ??= new PropertyGroupEntity {Identifier = transformAttribute.Identifier!};
|
||||
|
||||
General.Initialize(this, null, generalAttribute, LayerEntity.GeneralPropertyGroup);
|
||||
Transform.Initialize(this, null, transformAttribute, LayerEntity.TransformPropertyGroup);
|
||||
@ -309,7 +321,6 @@ public sealed class Layer : RenderProfileElement
|
||||
LayerEntity.Order = Order;
|
||||
LayerEntity.Suspended = Suspended;
|
||||
LayerEntity.Name = Name;
|
||||
LayerEntity.ProfileId = Profile.EntityId;
|
||||
|
||||
General.ApplyToEntity();
|
||||
Transform.ApplyToEntity();
|
||||
@ -383,7 +394,7 @@ public sealed class Layer : RenderProfileElement
|
||||
|
||||
if (ShouldBeEnabled)
|
||||
Enable();
|
||||
else if (Suspended || (Timeline.IsFinished && !_renderCopies.Any()))
|
||||
else if (Suspended || !HasBounds || (Timeline.IsFinished && !_renderCopies.Any()))
|
||||
Disable();
|
||||
|
||||
if (!Enabled || Timeline.Delta == TimeSpan.Zero)
|
||||
@ -735,6 +746,9 @@ public sealed class Layer : RenderProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Layer");
|
||||
|
||||
if (_leds.Contains(led))
|
||||
return;
|
||||
|
||||
_leds.Add(led);
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
@ -761,7 +775,9 @@ public sealed class Layer : RenderProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Layer");
|
||||
|
||||
_leds.Remove(led);
|
||||
if (!_leds.Remove(led))
|
||||
return;
|
||||
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
|
||||
@ -773,6 +789,9 @@ public sealed class Layer : RenderProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Layer");
|
||||
|
||||
if (!_leds.Any())
|
||||
return;
|
||||
|
||||
_leds.Clear();
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
@ -790,7 +809,7 @@ public sealed class Layer : RenderProfileElement
|
||||
{
|
||||
ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.Identifier == ledEntity.DeviceIdentifier &&
|
||||
a.RgbLed.Id.ToString() == ledEntity.LedName);
|
||||
if (match != null)
|
||||
if (match != null && !leds.Contains(match))
|
||||
leds.Add(match);
|
||||
else
|
||||
_missingLeds.Add(ledEntity);
|
||||
|
||||
@ -111,6 +111,15 @@ public class LayerAdapter : IStorageModel
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// A single LED assignment is turned into a hint for one matching LED ID on the same device type
|
||||
if (Layer.Leds.Count == 1)
|
||||
{
|
||||
ArtemisLed led = Layer.Leds.Single();
|
||||
SingleLedAdaptionHint hint = new() {LedId = led.RgbLed.Id, Amount = 1, LimitAmount = true};
|
||||
Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
return newHints;
|
||||
@ -184,6 +193,9 @@ public class LayerAdapter : IStorageModel
|
||||
case KeyboardSectionAdaptionHintEntity entity:
|
||||
Add(new KeyboardSectionAdaptionHint(entity));
|
||||
break;
|
||||
case SingleLedAdaptionHintEntity entity:
|
||||
Add(new SingleLedAdaptionHint(entity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@ -14,12 +15,13 @@ public readonly struct FloatRange
|
||||
/// </summary>
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
[JsonConstructor]
|
||||
public FloatRange(float start, float end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
_rand = new Random();
|
||||
_rand = Random.Shared;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -11,7 +11,7 @@ namespace Artemis.Core;
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface ILayerProperty : IStorageModel, IDisposable
|
||||
public interface ILayerProperty : IStorageModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description attribute applied to this property
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@ -14,12 +15,13 @@ public readonly struct IntRange
|
||||
/// </summary>
|
||||
/// <param name="start">The start value of the range</param>
|
||||
/// <param name="end">The end value of the range</param>
|
||||
[JsonConstructor]
|
||||
public IntRange(int start, int end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
_rand = new Random();
|
||||
_rand = Random.Shared;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -2,8 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@ -54,6 +54,12 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
||||
return $"{Path} - {CurrentValue} ({PropertyType})";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return DataBinding.GetFeatureDependencies();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
@ -324,8 +330,8 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
||||
// Reference types make a deep clone (ab)using JSON
|
||||
else
|
||||
{
|
||||
string json = CoreJson.SerializeObject(DefaultValue, true);
|
||||
SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
|
||||
string json = CoreJson.Serialize(DefaultValue);
|
||||
SetCurrentValue(CoreJson.Deserialize<T>(json)!);
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,7 +426,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
||||
|
||||
try
|
||||
{
|
||||
T? value = CoreJson.DeserializeObject<T>(keyframeEntity.Value);
|
||||
T? value = CoreJson.Deserialize<T>(keyframeEntity.Value);
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
@ -625,7 +631,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
||||
try
|
||||
{
|
||||
if (Entity.Value != null)
|
||||
BaseValue = CoreJson.DeserializeObject<T>(Entity.Value)!;
|
||||
BaseValue = CoreJson.Deserialize<T>(Entity.Value)!;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@ -664,7 +670,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
||||
if (!_isInitialized)
|
||||
throw new ArtemisCoreException("Layer property is not yet initialized");
|
||||
|
||||
Entity.Value = CoreJson.SerializeObject(BaseValue);
|
||||
Entity.Value = CoreJson.Serialize(BaseValue);
|
||||
Entity.KeyframesEnabled = KeyframesEnabled;
|
||||
Entity.KeyframeEntities.Clear();
|
||||
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
|
||||
|
||||
@ -69,7 +69,7 @@ public class LayerPropertyKeyframe<T> : CorePropertyChanged, ILayerPropertyKeyfr
|
||||
{
|
||||
return new KeyframeEntity
|
||||
{
|
||||
Value = CoreJson.SerializeObject(Value),
|
||||
Value = CoreJson.Serialize(Value),
|
||||
Position = Position,
|
||||
EasingFunction = (int) EasingFunction
|
||||
};
|
||||
|
||||
@ -15,7 +15,7 @@ namespace Artemis.Core;
|
||||
/// initialize these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class LayerPropertyGroup : IDisposable
|
||||
public abstract class LayerPropertyGroup : IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
private readonly List<ILayerProperty> _layerProperties;
|
||||
private readonly List<LayerPropertyGroup> _layerPropertyGroups;
|
||||
@ -240,7 +240,8 @@ public abstract class LayerPropertyGroup : IDisposable
|
||||
foreach (LayerPropertyGroup layerPropertyGroup in LayerPropertyGroups)
|
||||
{
|
||||
layerPropertyGroup.ApplyToEntity();
|
||||
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
|
||||
if (layerPropertyGroup.PropertyGroupEntity != null)
|
||||
PropertyGroupEntity.PropertyGroups.Add(layerPropertyGroup.PropertyGroupEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,4 +343,14 @@ public abstract class LayerPropertyGroup : IDisposable
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return LayerProperties.SelectMany(p => p.GetFeatureDependencies()).Concat(LayerPropertyGroups.SelectMany(g => g.GetFeatureDependencies()));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -14,16 +12,10 @@ namespace Artemis.Core;
|
||||
public sealed class Profile : ProfileElement
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
|
||||
private readonly ObservableCollection<ProfileScript> _scripts;
|
||||
private bool _isFreshImport;
|
||||
private ProfileElement? _lastSelectedProfileElement;
|
||||
|
||||
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||
{
|
||||
_scripts = new ObservableCollection<ProfileScript>();
|
||||
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
|
||||
|
||||
Opacity = 0d;
|
||||
ShouldDisplay = true;
|
||||
Configuration = configuration;
|
||||
@ -32,8 +24,6 @@ public sealed class Profile : ProfileElement
|
||||
EntityId = profileEntity.Id;
|
||||
|
||||
Exceptions = new List<Exception>();
|
||||
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
|
||||
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
|
||||
|
||||
Load();
|
||||
}
|
||||
@ -42,17 +32,7 @@ public sealed class Profile : ProfileElement
|
||||
/// Gets the profile configuration of this profile
|
||||
/// </summary>
|
||||
public ProfileConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all active scripts assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all script configurations assigned to this profile
|
||||
/// </summary>
|
||||
public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
|
||||
/// since import
|
||||
@ -67,15 +47,6 @@ public sealed class Profile : ProfileElement
|
||||
set => SetAndNotify(ref _isFreshImport, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last selected profile element of this profile
|
||||
/// </summary>
|
||||
public ProfileElement? LastSelectedProfileElement
|
||||
{
|
||||
get => _lastSelectedProfileElement;
|
||||
set => SetAndNotify(ref _lastSelectedProfileElement, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile entity this profile uses for persistent storage
|
||||
/// </summary>
|
||||
@ -95,17 +66,11 @@ public sealed class Profile : ProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdating(deltaTime);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Update(deltaTime);
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileUpdated(deltaTime);
|
||||
|
||||
const double OPACITY_PER_SECOND = 1;
|
||||
|
||||
|
||||
if (ShouldDisplay && Opacity < 1)
|
||||
Opacity = Math.Clamp(Opacity + OPACITY_PER_SECOND * deltaTime, 0d, 1d);
|
||||
if (!ShouldDisplay && Opacity > 0)
|
||||
@ -121,16 +86,13 @@ public sealed class Profile : ProfileElement
|
||||
if (Disposed)
|
||||
throw new ObjectDisposedException("Profile");
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
|
||||
|
||||
SKPaint? opacityPaint = null;
|
||||
bool applyOpacityLayer = Configuration.FadeInAndOut && Opacity < 1;
|
||||
|
||||
|
||||
if (applyOpacityLayer)
|
||||
{
|
||||
opacityPaint = new SKPaint();
|
||||
opacityPaint.Color = new SKColor(0, 0, 0, (byte)(255d * Easings.CubicEaseInOut(Opacity)));
|
||||
opacityPaint.Color = new SKColor(0, 0, 0, (byte) (255d * Easings.CubicEaseInOut(Opacity)));
|
||||
canvas.SaveLayer(opacityPaint);
|
||||
}
|
||||
|
||||
@ -143,9 +105,6 @@ public sealed class Profile : ProfileElement
|
||||
opacityPaint?.Dispose();
|
||||
}
|
||||
|
||||
foreach (ProfileScript profileScript in Scripts)
|
||||
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);
|
||||
|
||||
if (!Exceptions.Any())
|
||||
return;
|
||||
|
||||
@ -181,6 +140,12 @@ public sealed class Profile : ProfileElement
|
||||
return $"[Profile] {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
return GetRootFolder().GetFeatureDependencies();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates all the LEDs on the elements in this profile
|
||||
/// </summary>
|
||||
@ -209,10 +174,7 @@ public sealed class Profile : ProfileElement
|
||||
{
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
while (Scripts.Count > 0)
|
||||
RemoveScript(Scripts[0]);
|
||||
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
ChildrenList.Clear();
|
||||
@ -242,68 +204,11 @@ public sealed class Profile : ProfileElement
|
||||
AddChild(new Folder(this, this, rootFolder));
|
||||
}
|
||||
|
||||
List<RenderProfileElement> renderElements = GetAllRenderElements();
|
||||
|
||||
if (ProfileEntity.LastSelectedProfileElement != Guid.Empty)
|
||||
LastSelectedProfileElement = renderElements.FirstOrDefault(f => f.EntityId == ProfileEntity.LastSelectedProfileElement);
|
||||
else
|
||||
LastSelectedProfileElement = null;
|
||||
|
||||
while (_scriptConfigurations.Any())
|
||||
RemoveScriptConfiguration(_scriptConfigurations[0]);
|
||||
foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
|
||||
AddScriptConfiguration(scriptConfiguration);
|
||||
|
||||
// Load node scripts last since they may rely on the profile structure being in place
|
||||
foreach (RenderProfileElement renderProfileElement in renderElements)
|
||||
foreach (RenderProfileElement renderProfileElement in GetAllRenderElements())
|
||||
renderProfileElement.LoadNodeScript();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script configuration from the profile, if the configuration has an active script it is also removed.
|
||||
/// </summary>
|
||||
internal void RemoveScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
return;
|
||||
|
||||
Script? script = scriptConfiguration.Script;
|
||||
if (script != null)
|
||||
RemoveScript((ProfileScript) script);
|
||||
|
||||
_scriptConfigurations.Remove(scriptConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script configuration to the profile but does not instantiate it's script.
|
||||
/// </summary>
|
||||
internal void AddScriptConfiguration(ScriptConfiguration scriptConfiguration)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(scriptConfiguration))
|
||||
_scriptConfigurations.Add(scriptConfiguration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a script that has a script configuration belonging to this profile.
|
||||
/// </summary>
|
||||
internal void AddScript(ProfileScript script)
|
||||
{
|
||||
if (!_scriptConfigurations.Contains(script.ScriptConfiguration))
|
||||
throw new ArtemisCoreException("Cannot add a script to a profile whose script configuration doesn't belong to the same profile.");
|
||||
|
||||
if (!_scripts.Contains(script))
|
||||
_scripts.Add(script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a script from the profile and disposes it.
|
||||
/// </summary>
|
||||
internal void RemoveScript(ProfileScript script)
|
||||
{
|
||||
_scripts.Remove(script);
|
||||
script.Dispose();
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
if (Disposed)
|
||||
@ -312,7 +217,6 @@ public sealed class Profile : ProfileElement
|
||||
ProfileEntity.Id = EntityId;
|
||||
ProfileEntity.Name = Configuration.Name;
|
||||
ProfileEntity.IsFreshImport = IsFreshImport;
|
||||
ProfileEntity.LastSelectedProfileElement = LastSelectedProfileElement?.EntityId ?? Guid.Empty;
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Save();
|
||||
@ -322,12 +226,5 @@ public sealed class Profile : ProfileElement
|
||||
|
||||
ProfileEntity.Layers.Clear();
|
||||
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
|
||||
|
||||
ProfileEntity.ScriptConfigurations.Clear();
|
||||
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
|
||||
{
|
||||
scriptConfiguration.Save();
|
||||
ProfileEntity.ScriptConfigurations.Add(scriptConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core;
|
||||
@ -15,7 +16,6 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// </summary>
|
||||
public static readonly ProfileCategory Empty = new("Empty", -1);
|
||||
|
||||
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||
private bool _isCollapsed;
|
||||
private bool _isSuspended;
|
||||
private string _name;
|
||||
@ -31,14 +31,16 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
_name = name;
|
||||
_order = order;
|
||||
Entity = new ProfileCategoryEntity();
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>([]);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
internal ProfileCategory(ProfileCategoryEntity entity)
|
||||
{
|
||||
_name = null!;
|
||||
Entity = entity;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>([]);
|
||||
|
||||
Load();
|
||||
}
|
||||
@ -83,7 +85,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// <summary>
|
||||
/// Gets a read only collection of the profiles inside this category
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this category
|
||||
@ -96,28 +98,21 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// <summary>
|
||||
/// Adds a profile configuration to this category
|
||||
/// </summary>
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, ProfileConfiguration? target)
|
||||
{
|
||||
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
||||
if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration))
|
||||
targetIndex -= 1;
|
||||
|
||||
List<ProfileConfiguration> targetList = ProfileConfigurations.Where(c => c!= configuration).ToList();
|
||||
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||
|
||||
if (targetIndex != null)
|
||||
{
|
||||
targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count);
|
||||
_profileConfigurations.Insert(targetIndex.Value, configuration);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
targetList.Insert(targetList.IndexOf(target), configuration);
|
||||
else
|
||||
{
|
||||
_profileConfigurations.Add(configuration);
|
||||
}
|
||||
targetList.Add(configuration);
|
||||
|
||||
configuration.Category = this;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(targetList);
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
for (int index = 0; index < ProfileConfigurations.Count; index++)
|
||||
ProfileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
@ -155,11 +150,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
|
||||
internal void RemoveProfileConfiguration(ProfileConfiguration configuration)
|
||||
{
|
||||
if (!_profileConfigurations.Remove(configuration))
|
||||
return;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(ProfileConfigurations.Where(pc => pc != configuration).ToList());
|
||||
for (int index = 0; index < ProfileConfigurations.Count; index++)
|
||||
ProfileConfigurations[index].Order = index;
|
||||
|
||||
OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
@ -173,9 +167,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
IsSuspended = Entity.IsSuspended;
|
||||
Order = Entity.Order;
|
||||
|
||||
_profileConfigurations.Clear();
|
||||
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
|
||||
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(Entity.ProfileConfigurations.Select(pc => new ProfileConfiguration(this, pc)).OrderBy(pc => pc.Order).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -185,7 +177,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
Entity.IsCollapsed = IsCollapsed;
|
||||
Entity.IsSuspended = IsSuspended;
|
||||
Entity.Order = Order;
|
||||
|
||||
|
||||
Entity.ProfileConfigurations.Clear();
|
||||
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||
{
|
||||
|
||||
@ -9,7 +9,7 @@ namespace Artemis.Core;
|
||||
/// <summary>
|
||||
/// Represents an element of a <see cref="Profile" />
|
||||
/// </summary>
|
||||
public abstract class ProfileElement : BreakableModel, IDisposable
|
||||
public abstract class ProfileElement : BreakableModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
internal readonly List<ProfileElement> ChildrenList;
|
||||
private Guid _entityId;
|
||||
@ -122,6 +122,9 @@ public abstract class ProfileElement : BreakableModel, IDisposable
|
||||
return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<PluginFeature> GetFeatureDependencies();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child was added to the <see cref="Children" /> list
|
||||
/// </summary>
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal class Renderer : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private SKRect _lastBounds;
|
||||
private GRContext? _lastGraphicsContext;
|
||||
private SKRect _lastParentBounds;
|
||||
private bool _valid;
|
||||
public SKSurface? Surface { get; private set; }
|
||||
public SKPaint? Paint { get; private set; }
|
||||
public SKPath? Path { get; private set; }
|
||||
public SKPoint TargetLocation { get; private set; }
|
||||
|
||||
public bool IsOpen { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens the render context using the dimensions of the provided path
|
||||
/// </summary>
|
||||
public void Open(SKPath path, Folder? parent)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("Renderer");
|
||||
|
||||
if (IsOpen)
|
||||
throw new ArtemisCoreException("Cannot open render context because it is already open");
|
||||
|
||||
if (path.Bounds != _lastBounds || (parent != null && parent.Bounds != _lastParentBounds) || _lastGraphicsContext != Constants.ManagedGraphicsContext?.GraphicsContext)
|
||||
Invalidate();
|
||||
|
||||
if (!_valid || Surface == null)
|
||||
{
|
||||
SKRect pathBounds = path.Bounds;
|
||||
int width = (int) pathBounds.Width;
|
||||
int height = (int) pathBounds.Height;
|
||||
|
||||
SKImageInfo imageInfo = new(width, height);
|
||||
if (Constants.ManagedGraphicsContext?.GraphicsContext == null)
|
||||
Surface = SKSurface.Create(imageInfo);
|
||||
else
|
||||
Surface = SKSurface.Create(Constants.ManagedGraphicsContext.GraphicsContext, true, imageInfo);
|
||||
|
||||
Path = new SKPath(path);
|
||||
Path.Transform(SKMatrix.CreateTranslation(pathBounds.Left * -1, pathBounds.Top * -1));
|
||||
|
||||
TargetLocation = new SKPoint(pathBounds.Location.X, pathBounds.Location.Y);
|
||||
if (parent != null)
|
||||
TargetLocation -= parent.Bounds.Location;
|
||||
|
||||
Surface.Canvas.ClipPath(Path);
|
||||
|
||||
_lastParentBounds = parent?.Bounds ?? new SKRect();
|
||||
_lastBounds = path.Bounds;
|
||||
_lastGraphicsContext = Constants.ManagedGraphicsContext?.GraphicsContext;
|
||||
_valid = true;
|
||||
}
|
||||
|
||||
Paint = new SKPaint();
|
||||
|
||||
Surface.Canvas.Clear();
|
||||
Surface.Canvas.Save();
|
||||
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("Renderer");
|
||||
|
||||
Surface?.Canvas.Restore();
|
||||
|
||||
// Looks like every part of the paint needs to be disposed :(
|
||||
Paint?.ColorFilter?.Dispose();
|
||||
Paint?.ImageFilter?.Dispose();
|
||||
Paint?.MaskFilter?.Dispose();
|
||||
Paint?.PathEffect?.Dispose();
|
||||
Paint?.Dispose();
|
||||
|
||||
Paint = null;
|
||||
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("Renderer");
|
||||
|
||||
_valid = false;
|
||||
}
|
||||
|
||||
~Renderer()
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
|
||||
Surface?.Dispose();
|
||||
Paint?.Dispose();
|
||||
Path?.Dispose();
|
||||
|
||||
Surface = null;
|
||||
Paint = null;
|
||||
Path = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using Artemis.Core.Services;
|
||||
using System;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core;
|
||||
@ -16,6 +17,14 @@ public class Hotkey : CorePropertyChanged, IStorageModel
|
||||
Entity = new ProfileConfigurationHotkeyEntity();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Hotkey(KeyboardKey? key, KeyboardModifierKey? modifiers)
|
||||
{
|
||||
Key = key;
|
||||
Modifiers = modifiers;
|
||||
Entity = new ProfileConfigurationHotkeyEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Hotkey" /> based on the provided entity
|
||||
/// </summary>
|
||||
@ -46,7 +55,7 @@ public class Hotkey : CorePropertyChanged, IStorageModel
|
||||
/// <returns><see langword="true" /> if the event args match the hotkey; otherwise <see langword="false" /></returns>
|
||||
public bool MatchesEventArgs(ArtemisKeyboardKeyEventArgs eventArgs)
|
||||
{
|
||||
return eventArgs.Key == Key && eventArgs.Modifiers == Modifiers;
|
||||
return eventArgs.Key == Key && (eventArgs.Modifiers == Modifiers || (eventArgs.Modifiers == KeyboardModifierKey.None && Modifiers == null));
|
||||
}
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
@ -9,13 +9,13 @@ namespace Artemis.Core;
|
||||
/// <summary>
|
||||
/// Represents the configuration of a profile, contained in a <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an empty profile.
|
||||
/// </summary>
|
||||
public static readonly ProfileConfiguration Empty = new(ProfileCategory.Empty, "Empty", "Empty");
|
||||
|
||||
|
||||
private ActivationBehaviour _activationBehaviour;
|
||||
private bool _activationConditionMet;
|
||||
private ProfileCategory _category;
|
||||
@ -23,7 +23,6 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
private bool _disposed;
|
||||
private Hotkey? _enableHotkey;
|
||||
private ProfileConfigurationHotkeyMode _hotkeyMode;
|
||||
private bool _isBeingEdited;
|
||||
private bool _isMissingModule;
|
||||
private bool _isSuspended;
|
||||
private bool _fadeInAndOut;
|
||||
@ -38,13 +37,16 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
_name = name;
|
||||
_category = category;
|
||||
|
||||
Entity = new ProfileConfigurationEntity();
|
||||
Entity = new ProfileContainerEntity();
|
||||
Icon = new ProfileConfigurationIcon(Entity);
|
||||
Icon.SetIconByName(icon);
|
||||
ActivationCondition = new NodeScript<bool>("Activate profile", "Whether or not the profile should be active", this);
|
||||
|
||||
Entity.Profile.Id = Guid.NewGuid();
|
||||
Entity.ProfileConfiguration.ProfileId = Entity.Profile.Id;
|
||||
}
|
||||
|
||||
internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity)
|
||||
internal ProfileConfiguration(ProfileCategory category, ProfileContainerEntity entity)
|
||||
{
|
||||
// Will be loaded from the entity
|
||||
_name = null!;
|
||||
@ -148,15 +150,6 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
private set => SetAndNotify(ref _activationConditionMet, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this profile configuration is being edited
|
||||
/// </summary>
|
||||
public bool IsBeingEdited
|
||||
{
|
||||
get => _isBeingEdited;
|
||||
set => SetAndNotify(ref _isBeingEdited, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile of this profile configuration
|
||||
/// </summary>
|
||||
@ -169,8 +162,8 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether this profile should fade in and out when enabling or disabling
|
||||
/// </summary>
|
||||
public bool FadeInAndOut
|
||||
{
|
||||
public bool FadeInAndOut
|
||||
{
|
||||
get => _fadeInAndOut;
|
||||
set => SetAndNotify(ref _fadeInAndOut, value);
|
||||
}
|
||||
@ -202,12 +195,12 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
/// <summary>
|
||||
/// Gets the entity used by this profile config
|
||||
/// </summary>
|
||||
public ProfileConfigurationEntity Entity { get; }
|
||||
public ProfileContainerEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the profile of this profile configuration
|
||||
/// </summary>
|
||||
public Guid ProfileId => Entity.ProfileId;
|
||||
public Guid ProfileId => Entity.Profile.Id;
|
||||
|
||||
#region Overrides of BreakableModel
|
||||
|
||||
@ -243,8 +236,6 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
if (IsBeingEdited)
|
||||
return true;
|
||||
if (Category.IsSuspended || IsSuspended || IsMissingModule)
|
||||
return false;
|
||||
|
||||
@ -259,13 +250,26 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
return $"[ProfileConfiguration] {nameof(Name)}: {Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
if (Profile == null)
|
||||
throw new InvalidOperationException("Cannot determine feature dependencies when the profile is not loaded.");
|
||||
|
||||
return ActivationCondition.GetFeatureDependencies()
|
||||
.Concat(Profile.GetFeatureDependencies())
|
||||
.Concat(Module != null ? [Module] : []);
|
||||
}
|
||||
|
||||
internal void LoadModules(List<Module> enabledModules)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
|
||||
Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId);
|
||||
IsMissingModule = Module == null && Entity.ModuleId != null;
|
||||
Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ProfileConfiguration.ModuleId);
|
||||
IsMissingModule = Module == null && Entity.ProfileConfiguration.ModuleId != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -283,20 +287,20 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
|
||||
Name = Entity.Name;
|
||||
IsSuspended = Entity.IsSuspended;
|
||||
ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour;
|
||||
HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.HotkeyMode;
|
||||
FadeInAndOut = Entity.FadeInAndOut;
|
||||
Order = Entity.Order;
|
||||
Name = Entity.ProfileConfiguration.Name;
|
||||
IsSuspended = Entity.ProfileConfiguration.IsSuspended;
|
||||
ActivationBehaviour = (ActivationBehaviour) Entity.ProfileConfiguration.ActivationBehaviour;
|
||||
HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.ProfileConfiguration.HotkeyMode;
|
||||
FadeInAndOut = Entity.ProfileConfiguration.FadeInAndOut;
|
||||
Order = Entity.ProfileConfiguration.Order;
|
||||
|
||||
Icon.Load();
|
||||
|
||||
if (Entity.ActivationCondition != null)
|
||||
ActivationCondition.LoadFromEntity(Entity.ActivationCondition);
|
||||
if (Entity.ProfileConfiguration.ActivationCondition != null)
|
||||
ActivationCondition.LoadFromEntity(Entity.ProfileConfiguration.ActivationCondition);
|
||||
|
||||
EnableHotkey = Entity.EnableHotkey != null ? new Hotkey(Entity.EnableHotkey) : null;
|
||||
DisableHotkey = Entity.DisableHotkey != null ? new Hotkey(Entity.DisableHotkey) : null;
|
||||
EnableHotkey = Entity.ProfileConfiguration.EnableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.EnableHotkey) : null;
|
||||
DisableHotkey = Entity.ProfileConfiguration.DisableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.DisableHotkey) : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -305,26 +309,26 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ProfileConfiguration");
|
||||
|
||||
Entity.Name = Name;
|
||||
Entity.IsSuspended = IsSuspended;
|
||||
Entity.ActivationBehaviour = (int) ActivationBehaviour;
|
||||
Entity.HotkeyMode = (int) HotkeyMode;
|
||||
Entity.ProfileCategoryId = Category.Entity.Id;
|
||||
Entity.FadeInAndOut = FadeInAndOut;
|
||||
Entity.Order = Order;
|
||||
Entity.ProfileConfiguration.Name = Name;
|
||||
Entity.ProfileConfiguration.IsSuspended = IsSuspended;
|
||||
Entity.ProfileConfiguration.ActivationBehaviour = (int) ActivationBehaviour;
|
||||
Entity.ProfileConfiguration.HotkeyMode = (int) HotkeyMode;
|
||||
Entity.ProfileConfiguration.ProfileCategoryId = Category.Entity.Id;
|
||||
Entity.ProfileConfiguration.FadeInAndOut = FadeInAndOut;
|
||||
Entity.ProfileConfiguration.Order = Order;
|
||||
|
||||
Icon.Save();
|
||||
|
||||
ActivationCondition.Save();
|
||||
Entity.ActivationCondition = ActivationCondition.Entity;
|
||||
Entity.ProfileConfiguration.ActivationCondition = ActivationCondition.Entity;
|
||||
|
||||
EnableHotkey?.Save();
|
||||
Entity.EnableHotkey = EnableHotkey?.Entity;
|
||||
Entity.ProfileConfiguration.EnableHotkey = EnableHotkey?.Entity;
|
||||
DisableHotkey?.Save();
|
||||
Entity.DisableHotkey = DisableHotkey?.Entity;
|
||||
Entity.ProfileConfiguration.DisableHotkey = DisableHotkey?.Entity;
|
||||
|
||||
if (!IsMissingModule)
|
||||
Entity.ModuleId = Module?.Id;
|
||||
Entity.ProfileConfiguration.ModuleId = Module?.Id;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Artemis.Core.JsonConverters;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A model that can be used to serialize a profile configuration, it's profile and it's icon
|
||||
/// </summary>
|
||||
public class ProfileConfigurationExportModel : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the storage entity of the profile configuration
|
||||
/// </summary>
|
||||
public ProfileConfigurationEntity? ProfileConfigurationEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the storage entity of the profile
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public ProfileEntity ProfileEntity { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a stream containing the profile image
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StreamConverter))]
|
||||
public Stream? ProfileImage { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
ProfileImage?.Dispose();
|
||||
}
|
||||
}
|
||||
@ -10,14 +10,13 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
{
|
||||
private readonly ProfileConfigurationEntity _entity;
|
||||
private readonly ProfileContainerEntity _entity;
|
||||
private bool _fill;
|
||||
private string? _iconName;
|
||||
private Stream? _iconStream;
|
||||
private byte[]? _iconBytes;
|
||||
private ProfileConfigurationIconType _iconType;
|
||||
private string? _originalFileName;
|
||||
|
||||
internal ProfileConfigurationIcon(ProfileConfigurationEntity entity)
|
||||
internal ProfileConfigurationIcon(ProfileContainerEntity entity)
|
||||
{
|
||||
_entity = entity;
|
||||
}
|
||||
@ -40,15 +39,6 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
private set => SetAndNotify(ref _iconName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original file name of the icon (if applicable)
|
||||
/// </summary>
|
||||
public string? OriginalFileName
|
||||
{
|
||||
get => _originalFileName;
|
||||
private set => SetAndNotify(ref _originalFileName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not this icon should be filled.
|
||||
/// </summary>
|
||||
@ -58,6 +48,15 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
set => SetAndNotify(ref _fill, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon bytes if <see cref="IconType" /> is <see cref="ProfileConfigurationIconType.BitmapImage" />
|
||||
/// </summary>
|
||||
public byte[]? IconBytes
|
||||
{
|
||||
get => _iconBytes;
|
||||
private set => SetAndNotify(ref _iconBytes, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="IconName" /> to the provided value and changes the <see cref="IconType" /> is
|
||||
/// <see cref="ProfileConfigurationIconType.MaterialIcon" />
|
||||
@ -65,55 +64,37 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
/// <param name="iconName">The name of the icon</param>
|
||||
public void SetIconByName(string iconName)
|
||||
{
|
||||
if (iconName == null) throw new ArgumentNullException(nameof(iconName));
|
||||
ArgumentNullException.ThrowIfNull(iconName);
|
||||
|
||||
_iconStream?.Dispose();
|
||||
IconBytes = null;
|
||||
IconName = iconName;
|
||||
OriginalFileName = null;
|
||||
IconType = ProfileConfigurationIconType.MaterialIcon;
|
||||
|
||||
OnIconUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the stream returned by <see cref="GetIconStream" /> to the provided stream
|
||||
/// Updates the <see cref="IconBytes" /> to the provided value and changes the <see cref="IconType" /> is
|
||||
/// </summary>
|
||||
/// <param name="originalFileName">The original file name backing the stream, should include the extension</param>
|
||||
/// <param name="stream">The stream to copy</param>
|
||||
public void SetIconByStream(string originalFileName, Stream stream)
|
||||
public void SetIconByStream(Stream stream)
|
||||
{
|
||||
if (originalFileName == null) throw new ArgumentNullException(nameof(originalFileName));
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
_iconStream?.Dispose();
|
||||
_iconStream = new MemoryStream();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
stream.CopyTo(_iconStream);
|
||||
_iconStream.Seek(0, SeekOrigin.Begin);
|
||||
if (stream.CanSeek)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (MemoryStream ms = new())
|
||||
{
|
||||
stream.CopyTo(ms);
|
||||
IconBytes = ms.ToArray();
|
||||
}
|
||||
|
||||
IconName = null;
|
||||
OriginalFileName = originalFileName;
|
||||
IconType = ProfileConfigurationIconType.BitmapImage;
|
||||
OnIconUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of the stream containing the icon
|
||||
/// </summary>
|
||||
/// <returns>A stream containing the icon</returns>
|
||||
public Stream? GetIconStream()
|
||||
{
|
||||
if (_iconStream == null)
|
||||
return null;
|
||||
|
||||
MemoryStream stream = new();
|
||||
_iconStream.CopyTo(stream);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
_iconStream.Seek(0, SeekOrigin.Begin);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the icon was updated
|
||||
/// </summary>
|
||||
@ -132,21 +113,24 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
IconType = (ProfileConfigurationIconType) _entity.IconType;
|
||||
Fill = _entity.IconFill;
|
||||
if (IconType != ProfileConfigurationIconType.MaterialIcon)
|
||||
return;
|
||||
IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType;
|
||||
Fill = _entity.ProfileConfiguration.IconFill;
|
||||
|
||||
IconName = _entity.MaterialIcon;
|
||||
if (IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
IconName = _entity.ProfileConfiguration.MaterialIcon;
|
||||
else
|
||||
IconBytes = _entity.Icon;
|
||||
|
||||
OnIconUpdated();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
_entity.IconType = (int) IconType;
|
||||
_entity.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null;
|
||||
_entity.IconFill = Fill;
|
||||
_entity.ProfileConfiguration.IconType = (int) IconType;
|
||||
_entity.ProfileConfiguration.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null;
|
||||
_entity.ProfileConfiguration.IconFill = Fill;
|
||||
_entity.Icon = IconBytes ?? Array.Empty<byte>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Providers;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Surface;
|
||||
using RGB.NET.Core;
|
||||
@ -15,14 +17,20 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
public class ArtemisDevice : CorePropertyChanged
|
||||
{
|
||||
private readonly List<OriginalLed> _originalLeds;
|
||||
private readonly Size _originalSize;
|
||||
private SKPath? _path;
|
||||
private SKRect _rectangle;
|
||||
|
||||
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider)
|
||||
{
|
||||
rgbDevice.EnsureValidDimensions();
|
||||
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
|
||||
_originalSize = rgbDevice.Size;
|
||||
|
||||
RgbDevice = rgbDevice;
|
||||
Identifier = rgbDevice.GetDeviceIdentifier();
|
||||
DeviceEntity = new DeviceEntity();
|
||||
RgbDevice = rgbDevice;
|
||||
DeviceProvider = deviceProvider;
|
||||
|
||||
Rotation = 0;
|
||||
@ -38,19 +46,28 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
|
||||
|
||||
UpdateLeds();
|
||||
RgbDevice.ColorCorrections.Clear();
|
||||
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
|
||||
|
||||
CreateArtemisLeds(false);
|
||||
ApplyKeyboardLayout();
|
||||
ApplyToEntity();
|
||||
ApplyDefaultCategories();
|
||||
CalculateRenderProperties();
|
||||
Save();
|
||||
|
||||
RgbDevice.PropertyChanged += RgbDeviceOnPropertyChanged;
|
||||
}
|
||||
|
||||
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity)
|
||||
{
|
||||
rgbDevice.EnsureValidDimensions();
|
||||
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
|
||||
_originalSize = rgbDevice.Size;
|
||||
|
||||
RgbDevice = rgbDevice;
|
||||
Identifier = rgbDevice.GetDeviceIdentifier();
|
||||
DeviceEntity = deviceEntity;
|
||||
RgbDevice = rgbDevice;
|
||||
DeviceProvider = deviceProvider;
|
||||
|
||||
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(new Dictionary<LedId, ArtemisLed>());
|
||||
@ -58,12 +75,20 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LAYOUT_TYPE};
|
||||
|
||||
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
|
||||
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
|
||||
|
||||
UpdateLeds();
|
||||
RgbDevice.ColorCorrections.Clear();
|
||||
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
|
||||
|
||||
CreateArtemisLeds(false);
|
||||
Load();
|
||||
ApplyKeyboardLayout();
|
||||
CalculateRenderProperties();
|
||||
|
||||
RgbDevice.PropertyChanged += RgbDeviceOnPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -130,6 +155,11 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
/// </summary>
|
||||
public HashSet<DeviceCategory> Categories { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout selection applied to this device
|
||||
/// </summary>
|
||||
public LayoutSelection LayoutSelection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the X-position of the device
|
||||
/// </summary>
|
||||
@ -139,6 +169,8 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
set
|
||||
{
|
||||
DeviceEntity.X = value;
|
||||
if (RgbDevice.Surface != null)
|
||||
ApplyLocation(DeviceEntity.X, DeviceEntity.Y);
|
||||
OnPropertyChanged(nameof(X));
|
||||
}
|
||||
}
|
||||
@ -152,6 +184,8 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
set
|
||||
{
|
||||
DeviceEntity.Y = value;
|
||||
if (RgbDevice.Surface != null)
|
||||
ApplyLocation(DeviceEntity.X, DeviceEntity.Y);
|
||||
OnPropertyChanged(nameof(Y));
|
||||
}
|
||||
}
|
||||
@ -165,6 +199,8 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
set
|
||||
{
|
||||
DeviceEntity.Rotation = value;
|
||||
if (RgbDevice.Surface != null)
|
||||
RgbDevice.Rotation = DeviceEntity.Rotation;
|
||||
OnPropertyChanged(nameof(Rotation));
|
||||
}
|
||||
}
|
||||
@ -178,6 +214,8 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
set
|
||||
{
|
||||
DeviceEntity.Scale = value;
|
||||
if (RgbDevice.Surface != null)
|
||||
RgbDevice.Scale = DeviceEntity.Scale;
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
}
|
||||
}
|
||||
@ -236,7 +274,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this devices is enabled or not
|
||||
/// <para>Note: To enable/disable a device use the methods provided by <see cref="IRgbService" /></para>
|
||||
/// <para>Note: To enable/disable a device use the methods provided by <see cref="IDeviceService" /></para>
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
@ -262,19 +300,6 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether falling back to default layouts is enabled or not
|
||||
/// </summary>
|
||||
public bool DisableDefaultLayout
|
||||
{
|
||||
get => DeviceEntity.DisableDefaultLayout;
|
||||
set
|
||||
{
|
||||
DeviceEntity.DisableDefaultLayout = value;
|
||||
OnPropertyChanged(nameof(DisableDefaultLayout));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logical layout of the device e.g. DE, UK or US.
|
||||
/// <para>Only applicable to keyboards</para>
|
||||
@ -289,20 +314,6 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the custom layout to load when calling <see cref="IRgbService.ApplyBestDeviceLayout" />
|
||||
/// for this device
|
||||
/// </summary>
|
||||
public string? CustomLayoutPath
|
||||
{
|
||||
get => DeviceEntity.CustomLayoutPath;
|
||||
set
|
||||
{
|
||||
DeviceEntity.CustomLayoutPath = value;
|
||||
OnPropertyChanged(nameof(CustomLayoutPath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layout of the device expanded with Artemis-specific data
|
||||
/// </summary>
|
||||
@ -368,8 +379,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
case RGBDeviceType.Mousepad:
|
||||
case RGBDeviceType.HeadsetStand:
|
||||
case RGBDeviceType.Keypad:
|
||||
if (!Categories.Contains(DeviceCategory.Peripherals))
|
||||
Categories.Add(DeviceCategory.Peripherals);
|
||||
Categories.Add(DeviceCategory.Peripherals);
|
||||
break;
|
||||
case RGBDeviceType.Mainboard:
|
||||
case RGBDeviceType.GraphicsCard:
|
||||
@ -377,32 +387,20 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
case RGBDeviceType.Fan:
|
||||
case RGBDeviceType.LedStripe:
|
||||
case RGBDeviceType.Cooler:
|
||||
if (!Categories.Contains(DeviceCategory.Case))
|
||||
Categories.Add(DeviceCategory.Case);
|
||||
Categories.Add(DeviceCategory.Case);
|
||||
break;
|
||||
case RGBDeviceType.Speaker:
|
||||
if (!Categories.Contains(DeviceCategory.Desk))
|
||||
Categories.Add(DeviceCategory.Desk);
|
||||
Categories.Add(DeviceCategory.Desk);
|
||||
break;
|
||||
case RGBDeviceType.Monitor:
|
||||
if (!Categories.Contains(DeviceCategory.Monitor))
|
||||
Categories.Add(DeviceCategory.Monitor);
|
||||
Categories.Add(DeviceCategory.Monitor);
|
||||
break;
|
||||
case RGBDeviceType.LedMatrix:
|
||||
if (!Categories.Contains(DeviceCategory.Room))
|
||||
Categories.Add(DeviceCategory.Room);
|
||||
Categories.Add(DeviceCategory.Room);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DeviceUpdated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDeviceUpdated()
|
||||
{
|
||||
DeviceUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the provided layout to the device
|
||||
/// </summary>
|
||||
@ -415,28 +413,57 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
/// A boolean indicating whether to remove excess LEDs present in the device but missing
|
||||
/// in the layout
|
||||
/// </param>
|
||||
internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds)
|
||||
public void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds)
|
||||
{
|
||||
if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " +
|
||||
"set to true because the device provider does not support it");
|
||||
if (removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} " +
|
||||
"set to true because the device provider does not support it");
|
||||
if (layout != null && layout.IsValid && createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it");
|
||||
if (layout != null && layout.IsValid && removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
|
||||
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} set to true because the device provider does not support it");
|
||||
|
||||
if (layout.IsValid)
|
||||
layout.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
|
||||
// Always clear the current layout
|
||||
ClearLayout();
|
||||
|
||||
// If a valid layout was supplied, apply the layout to the device
|
||||
if (layout != null && layout.IsValid)
|
||||
{
|
||||
layout.ApplyToDevice(RgbDevice, createMissingLeds, removeExcessiveLeds);
|
||||
Layout = layout;
|
||||
}
|
||||
else
|
||||
{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
UpdateLeds();
|
||||
|
||||
Layout = layout;
|
||||
Layout.ApplyDevice(this);
|
||||
// Recreate Artemis LEDs
|
||||
CreateArtemisLeds(true);
|
||||
// Calculate render properties with the new layout
|
||||
CalculateRenderProperties();
|
||||
OnDeviceUpdated();
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DeviceUpdated" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDeviceUpdated()
|
||||
{
|
||||
DeviceUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ClearLayout()
|
||||
{
|
||||
if (Layout == null)
|
||||
return;
|
||||
|
||||
RgbDevice.DeviceInfo.LayoutMetadata = null;
|
||||
RgbDevice.Size = _originalSize;
|
||||
Layout = null;
|
||||
|
||||
while (RgbDevice.Any())
|
||||
RgbDevice.RemoveLed(RgbDevice.First().Id);
|
||||
foreach (OriginalLed originalLed in _originalLeds)
|
||||
RgbDevice.AddLed(originalLed.Id, originalLed.Location, originalLed.Size, originalLed.CustomData);
|
||||
}
|
||||
|
||||
internal void Save()
|
||||
{
|
||||
// Other properties are computed
|
||||
DeviceEntity.Id = Identifier;
|
||||
@ -444,13 +471,7 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
|
||||
DeviceEntity.InputIdentifiers.Clear();
|
||||
foreach (ArtemisDeviceInputIdentifier identifier in InputIdentifiers)
|
||||
{
|
||||
DeviceEntity.InputIdentifiers.Add(new DeviceInputIdentifierEntity
|
||||
{
|
||||
InputProvider = identifier.InputProvider,
|
||||
Identifier = identifier.Identifier
|
||||
});
|
||||
}
|
||||
DeviceEntity.InputIdentifiers.Add(new DeviceInputIdentifierEntity {InputProvider = identifier.InputProvider, Identifier = identifier.Identifier});
|
||||
|
||||
DeviceEntity.InputMappings.Clear();
|
||||
foreach ((ArtemisLed? original, ArtemisLed? mapped) in InputMappings)
|
||||
@ -459,33 +480,27 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
DeviceEntity.Categories.Clear();
|
||||
foreach (DeviceCategory deviceCategory in Categories)
|
||||
DeviceEntity.Categories.Add((int) deviceCategory);
|
||||
|
||||
DeviceEntity.LayoutType = LayoutSelection.Type;
|
||||
DeviceEntity.LayoutParameter = LayoutSelection.Parameter;
|
||||
}
|
||||
|
||||
internal void ApplyToRgbDevice()
|
||||
internal void Load()
|
||||
{
|
||||
RgbDevice.Rotation = DeviceEntity.Rotation;
|
||||
RgbDevice.Scale = DeviceEntity.Scale;
|
||||
|
||||
// Workaround for device rotation not applying
|
||||
if (DeviceEntity.X == 0 && DeviceEntity.Y == 0)
|
||||
RgbDevice.Location = new Point(1, 1);
|
||||
RgbDevice.Location = new Point(DeviceEntity.X, DeviceEntity.Y);
|
||||
|
||||
InputIdentifiers.Clear();
|
||||
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
|
||||
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
|
||||
|
||||
if (!RgbDevice.ColorCorrections.Any())
|
||||
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
|
||||
|
||||
Categories.Clear();
|
||||
foreach (int deviceEntityCategory in DeviceEntity.Categories)
|
||||
Categories.Add((DeviceCategory) deviceEntityCategory);
|
||||
if (!Categories.Any())
|
||||
ApplyDefaultCategories();
|
||||
|
||||
CalculateRenderProperties();
|
||||
OnDeviceUpdated();
|
||||
LayoutSelection.Type = DeviceEntity.LayoutType;
|
||||
LayoutSelection.Parameter = DeviceEntity.LayoutParameter;
|
||||
|
||||
LoadInputMappings();
|
||||
}
|
||||
|
||||
internal void CalculateRenderProperties()
|
||||
@ -502,13 +517,27 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
path.AddRect(artemisLed.AbsoluteRectangle);
|
||||
|
||||
Path = path;
|
||||
|
||||
OnDeviceUpdated();
|
||||
}
|
||||
|
||||
private void UpdateLeds()
|
||||
private void CreateArtemisLeds(bool loadInputMappings)
|
||||
{
|
||||
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
|
||||
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
|
||||
|
||||
if (loadInputMappings)
|
||||
LoadInputMappings();
|
||||
}
|
||||
|
||||
private void UpdateArtemisLeds()
|
||||
{
|
||||
foreach (ArtemisLed artemisLed in Leds)
|
||||
artemisLed.CalculateRectangles();
|
||||
}
|
||||
|
||||
private void LoadInputMappings()
|
||||
{
|
||||
InputMappings.Clear();
|
||||
foreach (InputMappingEntity deviceEntityInputMapping in DeviceEntity.InputMappings)
|
||||
{
|
||||
@ -535,6 +564,27 @@ public class ArtemisDevice : CorePropertyChanged
|
||||
else
|
||||
LogicalLayout = DeviceEntity.LogicalLayout;
|
||||
}
|
||||
|
||||
private void RgbDeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(IRGBDevice.Surface) || RgbDevice.Surface == null)
|
||||
return;
|
||||
|
||||
RgbDevice.Rotation = DeviceEntity.Rotation;
|
||||
RgbDevice.Scale = DeviceEntity.Scale;
|
||||
ApplyLocation(DeviceEntity.X, DeviceEntity.Y);
|
||||
}
|
||||
|
||||
private void ApplyLocation(float x, float y)
|
||||
{
|
||||
// Workaround for device rotation not applying
|
||||
if (x == 0 && y == 0)
|
||||
RgbDevice.Location = new Point(1, 1);
|
||||
RgbDevice.Location = new Point(x, y);
|
||||
|
||||
UpdateArtemisLeds();
|
||||
CalculateRenderProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -14,7 +14,7 @@ public class ArtemisDeviceInputIdentifier
|
||||
/// used by
|
||||
/// </param>
|
||||
/// <param name="identifier">A value used to identify the device</param>
|
||||
internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier)
|
||||
internal ArtemisDeviceInputIdentifier(string inputProvider, string identifier)
|
||||
{
|
||||
InputProvider = inputProvider;
|
||||
Identifier = identifier;
|
||||
@ -28,5 +28,5 @@ public class ArtemisDeviceInputIdentifier
|
||||
/// <summary>
|
||||
/// Gets or sets a value used to identify the device
|
||||
/// </summary>
|
||||
public object Identifier { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using RGB.NET.Core;
|
||||
using System.Linq;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core;
|
||||
@ -15,6 +16,9 @@ public class ArtemisLed : CorePropertyChanged
|
||||
{
|
||||
RgbLed = led;
|
||||
Device = device;
|
||||
Layout = device.Layout?.Leds.FirstOrDefault(l => l.RgbLayout.Id == led.Id.ToString());
|
||||
Layout?.ApplyCustomLedData(Device);
|
||||
|
||||
CalculateRectangles();
|
||||
}
|
||||
|
||||
@ -59,13 +63,13 @@ public class ArtemisLed : CorePropertyChanged
|
||||
|
||||
internal void CalculateRectangles()
|
||||
{
|
||||
Rectangle = Utilities.CreateScaleCompatibleRect(
|
||||
Rectangle = RenderScale.CreateScaleCompatibleRect(
|
||||
RgbLed.Boundary.Location.X,
|
||||
RgbLed.Boundary.Location.Y,
|
||||
RgbLed.Boundary.Size.Width,
|
||||
RgbLed.Boundary.Size.Height
|
||||
);
|
||||
AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
|
||||
AbsoluteRectangle = RenderScale.CreateScaleCompatibleRect(
|
||||
RgbLed.AbsoluteBoundary.Location.X,
|
||||
RgbLed.AbsoluteBoundary.Location.Y,
|
||||
RgbLed.AbsoluteBoundary.Size.Width,
|
||||
|
||||
@ -12,17 +12,18 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
public class ArtemisLayout
|
||||
{
|
||||
private static readonly string DefaultLayoutPath = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisLayout" /> class
|
||||
/// </summary>
|
||||
/// <param name="filePath">The path of the layout XML file</param>
|
||||
/// <param name="source">The source from where this layout is being loaded</param>
|
||||
public ArtemisLayout(string filePath, LayoutSource source)
|
||||
public ArtemisLayout(string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
Source = source;
|
||||
Leds = new List<ArtemisLedLayout>();
|
||||
|
||||
IsDefaultLayout = filePath.StartsWith(DefaultLayoutPath);
|
||||
|
||||
LoadLayout();
|
||||
}
|
||||
|
||||
@ -31,16 +32,6 @@ public class ArtemisLayout
|
||||
/// </summary>
|
||||
public string FilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source from where this layout was loaded
|
||||
/// </summary>
|
||||
public LayoutSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device this layout is applied to
|
||||
/// </summary>
|
||||
public ArtemisDevice? Device { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether a valid layout was loaded
|
||||
/// </summary>
|
||||
@ -66,10 +57,15 @@ public class ArtemisLayout
|
||||
/// </summary>
|
||||
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this layout is a default layout or not
|
||||
/// </summary>
|
||||
public bool IsDefaultLayout { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the layout to the provided device
|
||||
/// </summary>
|
||||
public void ApplyTo(IRGBDevice device, bool createMissingLeds = false, bool removeExcessiveLeds = false)
|
||||
public void ApplyToDevice(IRGBDevice device, bool createMissingLeds = false, bool removeExcessiveLeds = false)
|
||||
{
|
||||
device.Size = new Size(MathF.Round(RgbLayout.Width), MathF.Round(RgbLayout.Height));
|
||||
device.DeviceInfo.LayoutMetadata = RgbLayout.CustomData;
|
||||
@ -124,38 +120,30 @@ public class ArtemisLayout
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplyDevice(ArtemisDevice artemisDevice)
|
||||
{
|
||||
Device = artemisDevice;
|
||||
foreach (ArtemisLedLayout artemisLedLayout in Leds)
|
||||
artemisLedLayout.ApplyDevice(Device);
|
||||
}
|
||||
|
||||
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
|
||||
{
|
||||
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
|
||||
if (device.DeviceType == RGBDeviceType.Keyboard)
|
||||
{
|
||||
// XL layout is defined by its programmable macro keys
|
||||
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32))
|
||||
{
|
||||
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// L layout is defined by its numpad
|
||||
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete))
|
||||
{
|
||||
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// No numpad will result in TKL
|
||||
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ISO.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ANSI.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ISO.xml"));
|
||||
}
|
||||
|
||||
// if (device.DeviceType == RGBDeviceType.Mouse)
|
||||
@ -163,21 +151,21 @@ public class ArtemisLayout
|
||||
// if (device.Leds.Count == 1)
|
||||
// {
|
||||
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
|
||||
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
|
||||
// }
|
||||
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
|
||||
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
|
||||
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
|
||||
// }
|
||||
|
||||
if (device.DeviceType == RGBDeviceType.Headset)
|
||||
{
|
||||
if (device.Leds.Count == 1)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 1 LED headset.xml"));
|
||||
if (device.Leds.Count == 2)
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default);
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 2 LED headset.xml"));
|
||||
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 4 LED headset.xml"));
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -218,30 +206,10 @@ public class ArtemisLayout
|
||||
else
|
||||
Image = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a source from where a layout came
|
||||
/// </summary>
|
||||
public enum LayoutSource
|
||||
{
|
||||
/// <summary>
|
||||
/// A layout loaded from config
|
||||
/// </summary>
|
||||
Configured,
|
||||
|
||||
/// <summary>
|
||||
/// A layout loaded from the user layout folder
|
||||
/// </summary>
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// A layout loaded from the plugin folder
|
||||
/// </summary>
|
||||
Plugin,
|
||||
|
||||
/// <summary>
|
||||
/// A default layout loaded as a fallback option
|
||||
/// </summary>
|
||||
Default
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return FilePath;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using RGB.NET.Layout;
|
||||
@ -15,6 +16,11 @@ public class ArtemisLedLayout
|
||||
DeviceLayout = deviceLayout;
|
||||
RgbLayout = led;
|
||||
LayoutCustomLedData = (LayoutCustomLedData?) led.CustomData ?? new LayoutCustomLedData();
|
||||
|
||||
// Default to the first logical layout for images
|
||||
LayoutCustomLedDataLogicalLayout? defaultLogicalLayout = LayoutCustomLedData.LogicalLayouts?.FirstOrDefault();
|
||||
if (defaultLogicalLayout != null)
|
||||
ApplyLogicalLayout(defaultLogicalLayout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -27,11 +33,6 @@ public class ArtemisLedLayout
|
||||
/// </summary>
|
||||
public ILedLayout RgbLayout { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LED this layout is applied to
|
||||
/// </summary>
|
||||
public ArtemisLed? Led { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the logical layout this LED belongs to
|
||||
/// </summary>
|
||||
@ -47,16 +48,15 @@ public class ArtemisLedLayout
|
||||
/// </summary>
|
||||
public LayoutCustomLedData LayoutCustomLedData { get; }
|
||||
|
||||
internal void ApplyDevice(ArtemisDevice device)
|
||||
/// <summary>
|
||||
/// Gets the logical layout names available for this LED
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetLogicalLayoutNames()
|
||||
{
|
||||
Led = device.Leds.FirstOrDefault(d => d.RgbLed.Id.ToString() == RgbLayout.Id);
|
||||
if (Led != null)
|
||||
Led.Layout = this;
|
||||
|
||||
ApplyCustomLedData(device);
|
||||
return LayoutCustomLedData.LogicalLayouts?.Where(l => l.Name != null).Select(l => l.Name!) ?? [];
|
||||
}
|
||||
|
||||
private void ApplyCustomLedData(ArtemisDevice artemisDevice)
|
||||
internal void ApplyCustomLedData(ArtemisDevice artemisDevice)
|
||||
{
|
||||
if (LayoutCustomLedData.LogicalLayouts == null || !LayoutCustomLedData.LogicalLayouts.Any())
|
||||
return;
|
||||
@ -67,7 +67,17 @@ public class ArtemisLedLayout
|
||||
.ThenBy(l => l.Name == null)
|
||||
.First();
|
||||
|
||||
ApplyLogicalLayout(logicalLayout);
|
||||
}
|
||||
|
||||
private void ApplyLogicalLayout(LayoutCustomLedDataLogicalLayout logicalLayout)
|
||||
{
|
||||
string? layoutDirectory = Path.GetDirectoryName(DeviceLayout.FilePath);
|
||||
|
||||
LogicalName = logicalLayout.Name;
|
||||
Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute);
|
||||
if (layoutDirectory != null && logicalLayout.Image != null)
|
||||
Image = new Uri(Path.Combine(layoutDirectory, logicalLayout.Image!), UriKind.Absolute);
|
||||
else
|
||||
Image = null;
|
||||
}
|
||||
}
|
||||
38
src/Artemis.Core/Models/Surface/LayoutSelection.cs
Normal file
38
src/Artemis.Core/Models/Surface/LayoutSelection.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reference to a layout for a device.
|
||||
/// </summary>
|
||||
public class LayoutSelection : CorePropertyChanged
|
||||
{
|
||||
private string? _type;
|
||||
private string? _parameter;
|
||||
private string? _errorState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets what kind of layout reference this is.
|
||||
/// </summary>
|
||||
public string? Type
|
||||
{
|
||||
get => _type;
|
||||
set => SetAndNotify(ref _type, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter of the layout reference, such as a file path of workshop entry ID.
|
||||
/// </summary>
|
||||
public string? Parameter
|
||||
{
|
||||
get => _parameter;
|
||||
set => SetAndNotify(ref _parameter, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error state of the layout reference.
|
||||
/// </summary>
|
||||
public string? ErrorState
|
||||
{
|
||||
get => _errorState;
|
||||
set => SetAndNotify(ref _errorState, value);
|
||||
}
|
||||
}
|
||||
19
src/Artemis.Core/Models/Surface/OriginalLed.cs
Normal file
19
src/Artemis.Core/Models/Surface/OriginalLed.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
internal class OriginalLed
|
||||
{
|
||||
public OriginalLed(Led source)
|
||||
{
|
||||
Id = source.Id;
|
||||
Location = source.Location;
|
||||
Size = source.Size;
|
||||
CustomData = source.CustomData;
|
||||
}
|
||||
|
||||
public LedId Id { get; set; }
|
||||
public Point Location { get; set; }
|
||||
public Size Size { get; set; }
|
||||
public object? CustomData { get; set; }
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.Core.DeviceProviders;
|
||||
@ -11,20 +12,11 @@ namespace Artemis.Core.DeviceProviders;
|
||||
/// </summary>
|
||||
public abstract class DeviceProvider : PluginFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeviceProvider" /> class
|
||||
/// </summary>
|
||||
/// <param name="rgbDeviceProvider"></param>
|
||||
protected DeviceProvider(IRGBDeviceProvider rgbDeviceProvider)
|
||||
{
|
||||
RgbDeviceProvider = rgbDeviceProvider ?? throw new ArgumentNullException(nameof(rgbDeviceProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RGB.NET device provider backing this Artemis device provider
|
||||
/// </summary>
|
||||
public IRGBDeviceProvider RgbDeviceProvider { get; }
|
||||
|
||||
public abstract IRGBDeviceProvider RgbDeviceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
|
||||
/// <para>
|
||||
@ -57,6 +49,11 @@ public abstract class DeviceProvider : PluginFeature
|
||||
/// </summary>
|
||||
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether suspending the device provider is supported
|
||||
/// </summary>
|
||||
public bool SuspendSupported { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
|
||||
/// </summary>
|
||||
@ -71,7 +68,7 @@ public abstract class DeviceProvider : PluginFeature
|
||||
device.DeviceType.ToString(),
|
||||
GetDeviceLayoutName(device)
|
||||
);
|
||||
return new ArtemisLayout(filePath, LayoutSource.Plugin);
|
||||
return new ArtemisLayout(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -88,7 +85,7 @@ public abstract class DeviceProvider : PluginFeature
|
||||
device.DeviceType.ToString(),
|
||||
GetDeviceLayoutName(device)
|
||||
);
|
||||
return new ArtemisLayout(filePath, LayoutSource.User);
|
||||
return new ArtemisLayout(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -118,4 +115,12 @@ public abstract class DeviceProvider : PluginFeature
|
||||
|
||||
return fileName + ".xml";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the device provider is being suspended, like when the system is going to sleep.
|
||||
/// Note: This will be called while the plugin is disabled.
|
||||
/// </summary>
|
||||
public virtual void Suspend()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ namespace Artemis.Core.LayerBrushes;
|
||||
/// <summary>
|
||||
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="PerLedLayerBrush{T}" /> or instead
|
||||
/// </summary>
|
||||
public abstract class BaseLayerBrush : BreakableModel, IDisposable
|
||||
public abstract class BaseLayerBrush : BreakableModel, IDisposable, IPluginFeatureDependent
|
||||
{
|
||||
private LayerBrushType _brushType;
|
||||
private ILayerBrushConfigurationDialog? _configurationDialog;
|
||||
@ -199,6 +199,20 @@ public abstract class BaseLayerBrush : BreakableModel, IDisposable
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
IEnumerable<PluginFeature> result = [Descriptor.Provider];
|
||||
if (BaseProperties != null)
|
||||
result = result.Concat(BaseProperties.GetFeatureDependencies());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -65,7 +65,7 @@ public class LayerBrushDescriptor
|
||||
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Resolve(LayerBrushType);
|
||||
brush.Layer = layer;
|
||||
brush.Descriptor = this;
|
||||
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName};
|
||||
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName ?? throw new InvalidOperationException()};
|
||||
|
||||
brush.Initialize();
|
||||
return brush;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -7,7 +9,7 @@ namespace Artemis.Core.LayerEffects;
|
||||
/// <summary>
|
||||
/// For internal use only, please use <see cref="LayerEffect{T}" /> instead
|
||||
/// </summary>
|
||||
public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel
|
||||
public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageModel, IPluginFeatureDependent
|
||||
{
|
||||
private ILayerEffectConfigurationDialog? _configurationDialog;
|
||||
private LayerEffectDescriptor _descriptor;
|
||||
@ -164,7 +166,7 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
|
||||
// Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything
|
||||
// but LayerEffect<T> outside the core
|
||||
internal abstract void Initialize();
|
||||
|
||||
|
||||
internal void InternalUpdate(Timeline timeline)
|
||||
{
|
||||
BaseProperties?.Update(timeline);
|
||||
@ -231,8 +233,22 @@ public abstract class BaseLayerEffect : BreakableModel, IDisposable, IStorageMod
|
||||
return;
|
||||
|
||||
LayerEffectEntity.ProviderId = Descriptor.Provider.Id;
|
||||
LayerEffectEntity.EffectType = GetType().FullName;
|
||||
LayerEffectEntity.EffectType = GetType().FullName ?? throw new InvalidOperationException();
|
||||
BaseProperties?.ApplyToEntity();
|
||||
LayerEffectEntity.PropertyGroup = BaseProperties?.PropertyGroupEntity;
|
||||
}
|
||||
|
||||
#region Implementation of IPluginFeatureDependent
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginFeature> GetFeatureDependencies()
|
||||
{
|
||||
IEnumerable<PluginFeature> result = [Descriptor.Provider];
|
||||
if (BaseProperties != null)
|
||||
result = result.Concat(BaseProperties.GetFeatureDependencies());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Services;
|
||||
|
||||
namespace Artemis.Core.Modules;
|
||||
@ -36,21 +32,10 @@ public class ProcessActivationRequirement : IModuleActivationRequirement
|
||||
/// </summary>
|
||||
public string? Location { get; set; }
|
||||
|
||||
internal static IProcessMonitorService? ProcessMonitorService { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Evaluate()
|
||||
{
|
||||
if (ProcessMonitorService == null || (ProcessName == null && Location == null))
|
||||
return false;
|
||||
|
||||
IEnumerable<Process> processes = ProcessMonitorService.GetRunningProcesses();
|
||||
if (ProcessName != null)
|
||||
processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (Location != null)
|
||||
processes = processes.Where(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
return processes.Any();
|
||||
return ProcessMonitor.IsProcessRunning(ProcessName, Location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -4,8 +4,8 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Modules;
|
||||
|
||||
@ -14,8 +14,9 @@ namespace Artemis.Core.Modules;
|
||||
/// </summary>
|
||||
public abstract class DataModel
|
||||
{
|
||||
private const StringComparison PathsStringComparison = StringComparison.OrdinalIgnoreCase;
|
||||
private readonly List<DataModelPath> _activePaths = new();
|
||||
private readonly HashSet<string> _activePathsHashSet = new();
|
||||
private readonly HashSet<string> _activePathsHashSet = new(StringComparer.FromComparison(PathsStringComparison));
|
||||
private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
|
||||
|
||||
/// <summary>
|
||||
@ -332,11 +333,10 @@ public abstract class DataModel
|
||||
/// </param>
|
||||
internal bool IsPropertyInUse(string path, bool includeChildren)
|
||||
{
|
||||
path = path.ToUpperInvariant();
|
||||
lock (_activePaths)
|
||||
{
|
||||
return includeChildren
|
||||
? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal))
|
||||
? _activePathsHashSet.Any(p => p.StartsWith(path, PathsStringComparison))
|
||||
: _activePathsHashSet.Contains(path);
|
||||
}
|
||||
}
|
||||
@ -351,9 +351,7 @@ public abstract class DataModel
|
||||
_activePaths.Add(path);
|
||||
|
||||
// Add to the hashset if this is the first path pointing
|
||||
string hashPath = path.Path.ToUpperInvariant();
|
||||
if (!_activePathsHashSet.Contains(hashPath))
|
||||
_activePathsHashSet.Add(hashPath);
|
||||
_activePathsHashSet.Add(path.Path);
|
||||
}
|
||||
|
||||
OnActivePathAdded(new DataModelPathEventArgs(path));
|
||||
@ -368,7 +366,7 @@ public abstract class DataModel
|
||||
|
||||
// Remove from the hashset if this was the last path pointing there
|
||||
if (_activePaths.All(p => p.Path != path.Path))
|
||||
_activePathsHashSet.Remove(path.Path.ToUpperInvariant());
|
||||
_activePathsHashSet.Remove(path.Path);
|
||||
}
|
||||
|
||||
OnActivePathRemoved(new DataModelPathEventArgs(path));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user