1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-11 04:48:46 +00:00

Compare commits

...

404 Commits

Author SHA1 Message Date
Robert
acd35176e1 Merge branch 'development' 2025-02-16 17:06:39 +01:00
Robert
10a10b9149 Plugin Settings - When searching, search in plugin and features
Plugin Settings - Never scroll inside feature list, just show everything if a plugin has many features
2025-02-16 17:06:22 +01:00
Robert
37b8c2c3e9 Webserver - Add option to enable remote access 2025-02-16 12:05:26 +01:00
Robert
3367280576 Startup wizard - Correctly remember wizard was compelted
Settings - Fix double navigation occuring when opening settings
Settings - Fix double navigation occuring when opening releases
2025-02-16 11:15:52 +01:00
Diogo Trindade
ff74376ca2
Merge pull request #863 from Artemis-RGB/feature/linux-fixes
Handle unplugging devices properly on Linux
2025-02-15 17:56:29 +00:00
Diogo Trindade
c82f86aed3 handle unplugging devices properly 2025-02-15 15:55:57 +00:00
RobertBeekman
17e6c655ff
Core - Replace EmbedIO with GenHTTP
Replace EmbedIO with GenHTTP
2025-02-15 16:53:05 +01:00
Robert
fcf00af130 Add missing status handler, add TODO 2025-02-15 16:52:26 +01:00
Robert
7176aba0d6 Nodes - Inform nodes they are being loaded using the new IsLoading property 2025-02-14 22:15:03 +01:00
Robert
14c7940a21 UI - Fix visual regressions that occured after updating Avalonia at some point 2025-02-14 19:02:56 +01:00
Robert
0a26319914 Remove unneccesary error handler 2025-02-14 18:20:54 +01:00
Robert
eb4a6ceafb Finish implementing GenHTTP 2025-02-13 21:35:35 +01:00
Robert
e09389c6db Replace EmbedIO with GenHTTP 2025-02-08 22:48:51 +01:00
Robert
1ff509aac9 Bump .NET version in CI 2025-02-04 21:21:14 +01:00
Robert
2acac792f2 Merge branch 'development' 2025-02-04 21:01:16 +01:00
Robert
cfdcbe44c9 Meta - Update dependencies 2025-02-04 20:52:09 +01:00
Robert
df95e64567 Wizard - Refactored code into separate view models per step
Wizard - Skip layout and surface steps when no devices are found
Workshop - Fixed crash when opening library
2024-12-29 13:31:06 +01:00
Robert
4c6ca9b511 Merge branch 'development' 2024-11-14 20:35:46 +01:00
Robert
25786c6951 Workshop - Limit home page submissions 2024-11-14 15:03:14 +01:00
Robert
d725234d56 Storage - Downgrade EF for now to latest 8.x
Core - Include backwards compatible color quantizer methods
2024-11-14 14:58:54 +01:00
Robert
37f973b093 Core - Removed scripting providers
Meta - Updated packages
2024-11-14 14:09:51 +01:00
Robert
7691af95b9 Merge remote-tracking branch 'origin/RGB.NET_v3' into development 2024-11-14 13:30:08 +01:00
Robert
907c758b83 Meta - Update packages 2024-09-23 22:17:46 +02:00
Robert
d2afc77bb8 Merge branch 'development' of github.com:Artemis-RGB/Artemis into development 2024-09-23 21:39:25 +02:00
RobertBeekman
a6e75d7a40
Merge pull request #854 from Artemis-RGB/JsonUpdate
Bumped System-Text.Json to 8.0.4
2024-08-17 14:10:42 +02:00
bedf1f5f38 Bumped System-Text.Json to 8.0.4 2024-08-10 22:49:45 +02:00
Robert
ec5fbba87c Workshop - Revert voting changes, was fun to try but not useful enough 2024-07-24 22:07:58 +02:00
Robert
1e8c68bbeb Workshop - Avoid crashes when auto-updating without internet
Workshop - Added vote based entry score system
2024-07-24 11:39:56 +02:00
5d82c159e1 Replaced the quantization code and with the HPPH implementation 2024-07-22 23:52:50 +02:00
db84f1dc75 Major upgrade of RGB.NET to v3 2024-07-22 23:26:53 +02:00
Robert
972f1c638c Merge branch 'development' 2024-07-22 14:52:36 +02:00
Robert
7f5bb589af Try to fix CI build 2024-07-22 14:52:30 +02:00
Robert
4debfee6c1 Merge branch 'development' 2024-07-22 14:42:24 +02:00
Robert
9001a12625 Workshop - Refactor InstalledEntry to not implement IEntryDetails
This turned out to be a hassle
2024-07-22 14:42:17 +02:00
Robert
a793a08213 Merge branch 'development' 2024-07-22 11:02:05 +02:00
Robert
a45d98c171 Workshop - Added indicator for official workshop entries 2024-07-22 11:01:50 +02:00
Robert
b00f5ca73a Workshop - Added indicator for official submissions
Router - Reload previous screen if navigation is cancelled
Profile editor - Disable auto-update when editing workshop profiles
2024-07-20 22:34:41 +02:00
Robert
3b2d799bfc Workshop - Auto-updating WIP 2024-07-14 22:14:17 +02:00
Robert
99d11e1921 Workshop - Redesigned library
Workshop - Limit screen width to keep main content at 1000px
Workshop - Auto-updating WIP
2024-07-13 20:25:40 +02:00
Robert
0e911d68ba Merge branch 'development' 2024-07-12 23:42:49 +02:00
Robert
4552b3ba17 UI - Fix Markdown link issues 2024-07-12 23:41:50 +02:00
Robert
bd904178c8 Merge branch 'development' 2024-07-04 20:16:21 +02:00
Robert
00948de9d6 Workshop - Fix installing new versions of profiles 2024-07-02 22:34:25 +02:00
Robert
648b7765ef Core - Added API for retrieving current suspended device providers
Core - Added events for plugin removal, entry installlation/uninstallation
Workshop - Remove the related workshop entry when manually removing a plugin or profile
Workshop - Prevent installing profiles with missing plugins and show a dialog with which plugins are missing
2024-06-30 09:42:41 +02:00
Robert
86f78940b1 Workshop - Manage entries after installing them
Workshop - Auto-enable plugins after installing them
Workshop - Show the latest release above the details page
2024-06-25 21:29:52 +02:00
Robert
966ca47335 Meta - Updated packages 2024-06-22 22:47:02 +02:00
Robert
35d5ef1743 Startup wizard - Show device providers from all plugins 2024-05-25 16:45:59 +02:00
Robert
7052ee38b6 Merge branch 'development' 2024-05-18 13:32:53 +02:00
RobertBeekman
3022c7df65 Core - Add device provider suspension system 2024-05-16 20:40:45 +02:00
Robert
db6fb33c96 Data model picker - Implemented search 2024-05-05 16:27:08 +02:00
Robert
d9a3a238af Merge branch 'development' 2024-05-03 21:08:48 +02:00
Robert
10ad1d6a58 Core - Remove DataModelPath equality override 2024-05-03 21:08:39 +02:00
RobertBeekman
957bfde0af Workshop - Added layout finder 2024-05-02 20:02:12 +02:00
Robert
c18f542a61 Profile editor - Fix update timer frequency which was causing freezes 2024-05-02 19:36:34 +02:00
Robert
058513fd2a Merge branch 'development' 2024-04-14 16:00:08 +02:00
Robert
a9d4bd2385 Workshop - Plugin/layout management visual tweaks
Workshop - Avoid acidentally pre-selecting a release
2024-04-14 15:59:21 +02:00
RobertBeekman
c37c031dc2
Merge pull request #850 from Artemis-RGB/feature/releases
Workshop - Added release details and reworked installation management
2024-04-14 15:49:28 +02:00
Robert
74c24c84ae Workshop - Visual pass 2024-04-14 15:47:29 +02:00
RobertBeekman
7b71ee05da Workshop - Refactor markdown editor
Workshop - Add changelog during upload
2024-04-13 16:54:57 +02:00
Robert
62057d657a Workshop - Improve child navigation performance 2024-04-12 22:53:40 +02:00
RobertBeekman
cac44d748d Workshop - Added release management 2024-04-10 20:59:53 +02:00
Robert
7030c7af2a Merge branch 'development' into feature/releases 2024-03-30 20:46:09 +01:00
Robert
ff2e57aeaa Device properties - We can do a bit better with UX than that :P 2024-03-30 20:40:24 +01:00
Robert
f152812064 Device properties - Show layout error state 2024-03-30 20:28:01 +01:00
Robert
6b4ed48d05 Workshop - Reworked installation management 2024-03-30 17:55:51 +01:00
Robert
d6f1ba9aad Release page - Added release installation 2024-03-29 10:27:42 +01:00
RobertBeekman
9e994840f6 Workshop - Added dedicated release page 2024-03-27 21:43:45 +01:00
Robert
107b604c86 Linux - Fix crash in keyframes 2024-03-25 20:41:15 +01:00
Robert
257fa8ae0d Markdown - Tweak inline code and HR styles 2024-03-24 21:58:49 +01:00
Robert
9c04932afa Show multiple releases and improve workshop routing 2024-03-24 21:29:48 +01:00
RobertBeekman
e5cb058152 Merge branch 'development' 2024-03-21 20:15:58 +01:00
RobertBeekman
da3d47d7b8 Core - Prevent double-shutdowns which can cause crashes
Storage - Make the DbContext factory thread safe
Workshop - Replaced pagination with infinite scroll
Workshop - Added supported platforms and admin requirements to plugin details page
2024-03-21 20:12:40 +01:00
Robert
4c2eca29cc Storage - Fix profile order not loading/saving 2024-03-15 23:41:00 +01:00
Robert
8d7af6bb85 Merge branch 'development' 2024-03-15 17:13:27 +01:00
Robert
7e981a61d3 Storage - Fix new profile creation
UI - Fix a crash when dragging and dropping in the sidebar
2024-03-15 17:13:21 +01:00
Robert
91d1d16f24 Merge branch 'development' 2024-03-14 20:44:51 +01:00
Robert
18d75318d9 Workshop - Replace non-workshop plugins when installing them from the workshop 2024-03-14 20:44:45 +01:00
Robert
e588a06516 Merge branch 'development' 2024-03-14 20:08:17 +01:00
Robert
fa8b03104f Storage - Fix features of newly discovered plugins disabling after restart 2024-03-14 20:08:12 +01:00
Robert
8c1fef2883 Merge branch 'development' 2024-03-14 07:25:41 +01:00
Robert
90ddc3006c UI - Avoid crashing because of outdated plugins when picking data model paths 2024-03-14 07:25:18 +01:00
Robert
1201820799 Merge branch 'development' 2024-03-14 07:12:21 +01:00
Robert
ef8359dd2e Storage - Don't attempt to migrate if already done so but failed to remove the old DB 2024-03-14 07:12:11 +01:00
Robert
59c9538e65 Merge branch 'development' 2024-03-13 20:19:53 +01:00
RobertBeekman
551921db9f
Storage - Replace LiteDB with SQLite + EF Core (#843)
Storage - Added LiteDB to SQLite migration
UI - Try to die a bit more gracefully
Core - Delay start watching plugins for hot reload after initializing
UI - Simplify category management logic
UI - Avoid crash during profile icon load
Storage - Fix entry metadata retrieval
2024-03-13 20:19:21 +01:00
RobertBeekman
9fcd20d762 Merge branch 'development' 2024-03-03 22:09:53 +01:00
RobertBeekman
9132301dbf Workshop - Show required plugins on profile pages
Workshop - Show related profiles on plugin pages
2024-03-03 22:09:10 +01:00
RobertBeekman
36bff3c5f3 Authentication - Sign out in the browser when logging out 2024-03-03 20:45:05 +01:00
RobertBeekman
e5a5f10286
Profiles - Added IPluginFeatureDependent interface and implement throughout profiles (#842)
* Profiles - Added IPluginFeatureDependent interface and implement througout profiles
* Workshop - Include dependencies in profile upload request
2024-03-03 20:19:36 +01:00
RobertBeekman
28640f9502 Profiles - Avoid unnecessary OnBrokenStateChanged calls
Profiles - Fixed int range/float range deserialization
Profiles - Use Random.Shared where applicable
2024-03-02 17:38:20 +01:00
RobertBeekman
5c7497a859 Update FTP action again 2024-03-02 12:28:46 +01:00
RobertBeekman
554f3e1ac7 Merge branch 'development' 2024-03-02 12:20:00 +01:00
Robert
a113497941 Guess forked actions aren't a thing, downgrade FTP for now 2024-03-01 22:37:47 +01:00
Robert
13c4820738 Merge branch 'development' 2024-03-01 22:35:34 +01:00
Robert
765f413326 Update actions 2024-03-01 22:35:29 +01:00
Robert
ba3838489f Core - Simplify PluginInfo and PluginFeatureInfo 2024-03-01 22:23:49 +01:00
Robert
baae4af153 Storage - Added migrators for node storage 2024-02-29 22:45:33 +01:00
Robert
1d23c69d39 Merge branch 'development' 2024-02-28 23:27:41 +01:00
Robert
fb59443f9a Core - Fix plugins without a version in their plugin.json always being considered incompatible 2024-02-28 23:27:31 +01:00
RobertBeekman
a48e0d2d90 Workshop - Fix submissions not scrolling 2024-02-28 22:11:28 +01:00
RobertBeekman
e6565a2896 Merge branch 'development' 2024-02-28 20:03:29 +01:00
RobertBeekman
a340f8822e
Replace JSON.NET with System.Text.json (#840) 2024-02-28 19:31:38 +01:00
Diogo Trindade
39c0b6c51b
Merge branch 'development' 2024-02-25 19:12:54 +00:00
Diogo Trindade
e112ca9da8
Storage - downgrade litedb 2024-02-25 19:12:40 +00:00
RobertBeekman
be92701c67 Merge branch 'development' 2024-02-25 15:32:33 +01:00
RobertBeekman
24f470a480 Storage - Avoid broken LiteDB API 2024-02-25 15:32:25 +01:00
RobertBeekman
6956f09bb6 Merge branch 'development' 2024-02-25 11:45:47 +01:00
RobertBeekman
5203e95141 Core - Add migration to also migrate profile activation conditions 2024-02-25 11:45:41 +01:00
RobertBeekman
2e600e76b0 Merge branch 'master' into development 2024-02-23 23:26:55 +01:00
RobertBeekman
110b6b90a0 Work around idiotic .NET 8 breaking change 2024-02-23 23:25:22 +01:00
RobertBeekman
aaaae5be52 Revert "Trial and error paths on CIs yaaaay"
This reverts commit e5edc97077077773ecf92e5c256020ea982e2331.
2024-02-23 22:42:31 +01:00
RobertBeekman
e5edc97077 Trial and error paths on CIs yaaaay 2024-02-23 22:39:49 +01:00
RobertBeekman
108b881fd6 CI - Fix Windows RID 2024-02-23 22:18:58 +01:00
RobertBeekman
d48d0acb5b Merge branch 'development' 2024-02-23 22:13:52 +01:00
RobertBeekman
585a2fbe18 Workshop - Fix info about new images not applying 2024-02-23 22:13:46 +01:00
RobertBeekman
27ead861a7 CI - Bump .NET to 8 there as well... 2024-02-23 22:07:33 +01:00
RobertBeekman
ef08000f56 Merge branch 'development' 2024-02-23 21:45:35 +01:00
RobertBeekman
9d955bd983
Merge pull request #837 from Artemis-RGB/feature/cpm
Centralized package management
2024-02-23 21:21:39 +01:00
Robert
d0e354952d Merge branch 'development' into feature/cpm 2024-02-23 21:21:27 +01:00
RobertBeekman
93e74ef8d3
Core - Implemented node providers and removed nodes from the main project 2024-02-23 21:16:08 +01:00
Robert
bcf0b74fcc Clean up 2024-02-23 21:15:34 +01:00
Robert
49ed0205b7 Move migrations to storage, add Storage migration for nodes change 2024-02-23 20:58:32 +01:00
Robert
313b4a0dea Core - Fix node related warnings 2024-02-23 17:41:11 +01:00
Robert
6adcae94b4 Merge branch 'development' into feature/node-providers 2024-02-23 17:36:47 +01:00
Robert
248d4c8e18 Storage - Use nullable reference types
Core - Fix build warnings
2024-02-23 17:35:06 +01:00
RobertBeekman
bfd4a436de Profile service - Migrate profiles before importing them 2024-02-22 21:23:47 +01:00
RobertBeekman
39d7d8132f Core - Added node provider plugin feature type
Visual Scripting - Removed all nodes
2024-02-22 19:42:25 +01:00
Diogo Trindade
f88459fddd
Centralized package management 2024-02-20 21:51:39 +00:00
RobertBeekman
f0e9581fe1
Merge pull request #836 from Artemis-RGB/feature/docs
Fix docs
2024-02-20 17:10:55 +01:00
RobertBeekman
2d00a64a7d Finish config 2024-02-20 17:08:25 +01:00
RobertBeekman
c11d12ded4 Merge branch 'development' into feature/docs 2024-02-20 16:35:37 +01:00
RobertBeekman
f51aa3ca06 Bump RGB.NET 2024-02-20 12:58:24 +01:00
RobertBeekman
1ed13d8b68 Remove Qodana for now 2024-02-20 12:19:29 +01:00
RobertBeekman
78d3b58f99 Upgrade .NET 8 and packages
Include Qodana :D
2024-02-20 12:06:21 +01:00
RobertBeekman
c901bce626 Merge branch 'development' 2024-02-19 22:46:52 +01:00
RobertBeekman
9126601ae7 UI - Fixed UI showing during auto-run even with show on startup disabled
UI - Fixen an issue during startup where Artemis would navigate to home after a few seconds
Workshop - Added Submit new entry button to submissions tab
2024-02-19 21:54:53 +01:00
Robert
00c510cad3 Merge branch 'development' 2024-02-17 15:01:38 +01:00
Robert
0b6bf5685e Core - Fixed crash on startup if workshop folder is missing
Updating - Properly delay auto-update check until the UI shows,
2024-02-17 15:01:18 +01:00
RobertBeekman
c61e3d289a Merge branch 'development' 2024-02-17 11:54:29 +01:00
RobertBeekman
cf10193bd2 Workshop - Finished initial plugin implementation 2024-02-17 11:54:22 +01:00
RobertBeekman
f2c8de1746 Plugins - Fixed cancelling prerequisite installation
Plugins - Tweaked prerequisite installation popup size
Account settings - Added personal access token management
Workshop - Added plugin loading, installing and removal
2024-02-16 23:04:24 +01:00
Robert
21b8112de5 Workshop - Implemented plugin browsing, installation and removal 2024-02-15 22:57:33 +01:00
RobertBeekman
d9df443970 Workshop - Added plugin upload support 2024-02-14 23:04:19 +01:00
RobertBeekman
5184d53d97 Merge branch 'development' 2024-02-13 21:55:23 +01:00
RobertBeekman
38a1318170 Account management - Final tweaks 2024-02-13 21:55:09 +01:00
RobertBeekman
a2fe5d5d08 Settings - Added account management 2024-02-08 20:58:16 +01:00
RobertBeekman
3952a268d1 Debugger - Fixed current renderer always showing as Software
Render service - Log which renderer is used
Updating - Fixed release not loading when viewing it from the desktop notification
Core - Removed old unused code
Data model event cycle node - Fixed crash on shutdown
2024-02-02 09:45:18 +01:00
RobertBeekman
654ea94e16 Swap to a basic DocFX config that hopefully doesn't break every 10 minutes 2024-01-30 22:44:45 +01:00
RobertBeekman
158c380747 Merge branch 'development' 2024-01-30 22:13:08 +01:00
RobertBeekman
6364ce3fc4 Workshop - Fixed uploading layouts trying to write a ZIP file to desktop
UI - Always show user icon regardless of window
2024-01-30 22:12:57 +01:00
RobertBeekman
960991e77b Merge branch 'development' 2024-01-30 13:15:42 +01:00
RobertBeekman
17bd62e673 Workshop - Fixed current user avatar not displaying
Workshop - Improve error handling while uploading new entries
2024-01-30 13:14:24 +01:00
RobertBeekman
9438bba386 Merge branch 'development' 2024-01-29 22:55:44 +01:00
RobertBeekman
c29d30d222 Workshop - Update image delete URL 2024-01-29 22:55:38 +01:00
RobertBeekman
ab64d1332b Merge branch 'development' 2024-01-29 22:12:43 +01:00
RobertBeekman
cea8c1b796 Update UpdateEntryImage signature 2024-01-29 22:03:41 +01:00
RobertBeekman
78b96deeab Router - Added reload
Workshop - Added image management to existing entries
Workshop - Fixed pagination in layouts page
2024-01-28 22:43:35 +01:00
RobertBeekman
65f62486cc Merge branch 'development' 2024-01-16 11:39:56 +01:00
RobertBeekman
19082481c3 Workshop - Fix profile installation 404ing 2024-01-16 11:39:49 +01:00
RobertBeekman
f0ab5fa250 Merge branch 'development' 2024-01-16 09:33:40 +01:00
RobertBeekman
e73ae961ba Layouts - Remove feature flag 2024-01-16 09:33:29 +01:00
Robert
ff6efe9456 Merge branch 'development' 2024-01-16 08:39:51 +01:00
Diogo Trindade
7edaf709e6
Merge pull request #832 from Artemis-RGB/feature/cleanup-deps
Meta - Clean up nuget package references
2024-01-15 21:43:19 +00:00
RobertBeekman
833ab7ac78 Workshop - Offer to apply layout to devices 2024-01-15 22:21:15 +01:00
Diogo Trindade
3ca3b6a18a
Update packages 2024-01-15 19:33:40 +00:00
Diogo Trindade
50b5f13051
remove another redundant 2024-01-15 19:18:15 +00:00
Diogo Trindade
b3dc12790c
Remove Artemis.props file 2024-01-15 19:08:16 +00:00
Diogo Trindade
0c107410d5
Meta - consolidate dependencies 2024-01-14 22:41:18 +00:00
Robert
b01c5b16fc Storage - Added layout migration 2024-01-14 22:48:33 +01:00
Robert
8b4b5d8810 Workshop - Fix library virtualization
Workshop - Tweak screenshots scaling
2024-01-14 20:38:17 +01:00
RobertBeekman
28edabae89 Workshop - Implemented workshop installation, layout selection and removal 2024-01-14 12:46:51 +01:00
RobertBeekman
dad6a56238 Devices - Implemented switching between layout providers 2024-01-13 11:59:12 +01:00
RobertBeekman
e8590abd61 Devices - Reworking layout loading 2024-01-09 21:27:51 +01:00
RobertBeekman
9393bf2b68 Workshop - Implemented image gallery dialog 2023-12-13 21:55:27 +01:00
RobertBeekman
e304d67035 Workshop - Simplify upload to a busy indicator, show images in profiles 2023-12-10 22:44:03 +01:00
Robert
e33ae8a066 Workshop - Added image uploading 2023-11-09 23:34:35 +01:00
RobertBeekman
0667d58ed8 Images WIP 2023-11-05 21:27:41 +01:00
RobertBeekman
6b4e84c95a Added layout archive generation 2023-11-02 22:29:42 +01:00
Robert
59fe1df40f Move IEntrySource to workshop 2023-11-01 20:19:00 +01:00
Robert
c1e0dadce8 Workshop - Layout info and images WIP 2023-10-29 20:43:30 +01:00
Robert
f4b9b67f1a Workshop - Layout info WIP 2023-10-23 19:33:38 +02:00
Robert
c77d51fb58 Workshop - Layout submission WIP 2023-10-21 20:19:15 +02:00
Robert
190d797f1a UI - Use PropertyChanged.SourceGenerator everywhere else 2023-10-20 20:33:44 +02:00
Robert
d656c6960d UI - Use PropertyChanged.SourceGenerator in workshop 2023-10-20 20:00:07 +02:00
Robert
8f1509fc28 UI - Upgrade to (preview :c) version of HotChocolate to fix emoji issue 2023-10-20 19:43:00 +02:00
Robert
1bd4efbbc5 Meta - Fixed a bunch of build warnings 2023-10-18 23:13:57 +02:00
Robert
6921561317 UI - Add PropertyChanged.SourceGenerator and use it in the profile editor 2023-10-18 22:50:28 +02:00
Robert
fb6845be5d Core - Call OnFrameRendering after core has rendered instead of before 2023-10-18 21:11:06 +02:00
Robert
4e8d532c63 Meta - Upgrade Nuget packages 2023-10-18 20:53:08 +02:00
Robert
3894d9f9fe Merge branch 'master' into development 2023-10-17 20:03:51 +02:00
Robert
1f3f213405 Merge branch 'development' 2023-10-17 19:54:24 +02:00
Robert
4737173c2a Revert "Revert "Merge branch 'development'""
This reverts commit ebf40992bcdd75d6024d4bce25410511cbf88287.
2023-10-17 19:54:19 +02:00
Diogo Trindade
c55cb5266b WebApiRemote - allow passing parameters to restart 2023-10-17 00:56:58 +01:00
Robert
0f8c8a6e11 Remove leftover debug write lines 2023-10-16 21:30:38 +02:00
Robert
d73312c4f4 Web server - Ensure controllers and web modules provided by plugins are removed on disable 2023-10-16 21:29:37 +02:00
Robert
43e396bf6d Core - Refactored layout loading, fixing LED images
Core - Make a better effort at removing orphaned devices from device providers that failed to load
2023-10-16 19:44:13 +02:00
Robert
38f18613c1 Core - Ensure devices owned by ArtemisDevice always have valid dimensions 2023-10-14 19:36:46 +02:00
Robert
76d424012a Core - Brought device load/save logic in line with the rest of the code 2023-10-14 17:20:22 +02:00
Diogo Trindade
46c035851a Core - Use StringComparison instead of ToUpper 2023-10-11 21:44:41 +01:00
Robert
38eb0ff460 Device visualizer - Never return NaN measurements
Device logical layout picker - Fixed NRE
Surface manager - Improve thread safety
2023-10-11 20:53:21 +02:00
Robert
7e72e22295 Revert "Undo device changes"
This reverts commit 41c1458b5af460769fdd06b9e86fe4b03ef54ed2.
2023-10-11 19:30:49 +02:00
Robert
41c1458b5a Undo device changes 2023-10-09 20:54:59 +02:00
Robert
ccd79de67c Always recreate LED group when recreating surface 2023-10-09 20:47:09 +02:00
Robert
3e88aa37ff Change surface manager SKTexture handling 2023-10-09 20:04:23 +02:00
Robert
ebf40992bc Revert "Merge branch 'development'"
This reverts commit be217ffc02e793d44a031aace51a92ee15d87b40, reversing
changes made to 524296e4e9891a1c4ccedd5f0dcfcbe54b0c8006.
2023-10-09 07:26:17 +02:00
Robert
be217ffc02 Merge branch 'development' 2023-10-08 14:56:46 +02:00
Robert
543b62a715 Profile editor - Added back hotkeys for the tools
Profile editor - Fixed hotkeys not always working
2023-10-07 20:12:35 +02:00
Robert
f985682e78 Profile editor - Fixed easing options not applying
Device properties - Fixed layout changes not saving
2023-10-06 21:28:02 +02:00
Robert
e866afbfb0 Merge branch 'feature/device-refactor' into development 2023-10-06 20:40:19 +02:00
Robert
97d18d38cc Surface Editor - Fixed multi-select 2023-10-05 20:55:16 +02:00
Robert
2a0a0f3cfd Core - Split up RGBService into RenderService and DeviceService
Core - Removed rendering from CoreService
Core - Store and restore original device LEDs when applying/clearing layouts
2023-10-05 20:38:07 +02:00
Robert
524296e4e9 Merge branch 'development' 2023-09-25 22:04:41 +02:00
Robert
919199a8d0 Nodes - Avoid overriding paths pointing to unloaded plugins with empty values
Nodes  - Store the type of paths so that node connections can be restored even if the path their types are based on are unavailable
2023-09-25 20:21:00 +02:00
Robert
279761abd3 Workshop - Added button to copy share link to profile detail page 2023-09-25 19:52:56 +02:00
Robert
1b0796c68b Nodes - Fixed boolean branch node not picking a type if both pins are connected 2023-09-25 19:34:31 +02:00
Robert
f990641724 UI - Set window state to normal if minimized when opening main window 2023-09-25 19:28:07 +02:00
Robert
0a5cc6955e Added artemis:// protocol support 2023-09-24 20:10:21 +02:00
Robert
d055f4818c Merge branch 'development' 2023-09-23 23:10:46 +02:00
Robert
9bf38f54a8 CI - Use SFTP in docs upload 2023-09-23 23:10:15 +02:00
Robert
4994b3fb44 Workshop - Added filtering, sorting and changable entries per page 2023-09-23 23:08:28 +02:00
Robert
aa8519b33c CI - Update DocFX FTP config 2023-09-21 23:43:03 +02:00
Robert
a8dadfc0c8 Meta - Bump SkiaSharp to 2.88.6 2023-09-21 22:33:09 +02:00
Robert
94a1939c69 Merge branch 'development' 2023-09-20 22:58:15 +02:00
Robert
68eae085e4 Workshop - Disable continue button in email validation step 2023-09-20 22:58:06 +02:00
Robert
c466d03988 Merge branch 'development' 2023-09-20 22:55:52 +02:00
Robert
fbf908c032 Workshop - Fixed email not being detected as verified
Settings - Fixed new releases not loading when there are more than 20
2023-09-20 22:55:37 +02:00
Robert
3394786d31 Merge branch 'master' into development 2023-09-20 22:54:27 +02:00
Robert
ce927d320f Merge branch 'development' 2023-09-17 12:14:02 +02:00
Robert
19984b87f7 Merge remote-tracking branch 'origin/development' into development 2023-09-17 11:58:41 +02:00
Robert
8a4390fb80 Meta - Upgraded Nuget packages 2023-09-17 11:58:38 +02:00
Robert
8d5640aba3 UI - Fixed various rename dialogs cancelling instead of submitting when pressing enter
Submission wizard - Allow window to scroll when setting up layer adaption hints
Local web API - Added status endpoint to determine whether webserver is running
2023-09-17 11:35:19 +02:00
RobertBeekman
bd723dfc1c
Merge pull request #818 from Artemis-RGB/feature/disallow-pr-master
CI - disallow PRs to master from any branch but dev
2023-09-10 17:14:57 +02:00
Diogo Trindade
d57e90956f CI - disallow PRs to master from any branch but dev 2023-09-10 15:19:12 +01:00
RobertBeekman
888ed3743e
Merge pull request #815 from Artemis-RGB/development
Adaption hints - Added more hints and sections
2023-09-10 12:00:15 +02:00
Robert
d96581f11c Adaption hints - Added single LED adaption hint
Adaption hints - Added more sections to keyboard section adaption hint
2023-09-10 11:58:37 +02:00
Robert
bbadef7a9a Merge branch 'development' 2023-09-09 20:47:51 +02:00
RobertBeekman
ca9ce948c5
Merge pull request #814 from Artemis-RGB/feature/workshop
Workshop - Initial implementation
2023-09-09 20:47:29 +02:00
Diogo Trindade
4b15d3d976 Workshop - Fixed crash when converting string back to DateTimeOffset 2023-09-09 16:08:57 +01:00
Robert
898ed19f29 Profile Editor - Fixed transforms breaking when hitting play 2023-09-09 13:42:46 +02:00
Robert
0fdb40bd35 Workshop - Update workshop IDs to be long 2023-09-09 13:30:26 +02:00
Robert
a798980eec Workshop - Feature flagged out layouts
Settings - Use Fluent tabs
2023-09-09 00:32:58 +02:00
Robert
0ac973d4bc Workshop - Use production APIs 2023-09-09 00:11:45 +02:00
Robert
3d1e53e395 Workshop Library - Finished basic implementation of installed tab 2023-09-08 17:06:48 +02:00
Robert
876465cfdb Workshop Library - Installed entries WIP 2023-09-06 21:24:15 +02:00
Robert
c69be2836e Workshop - Added installed entries and update profiles when reinstalling them 2023-09-06 20:39:43 +02:00
RobertBeekman
b7c0435648
Merge pull request #812 from Artemis-RGB/feature/process-monitor-opt
Core - Further reduced allocations in process monitor checking
2023-09-05 22:40:35 +02:00
Robert
fcde1d4ecc UI - Tweaked monospace font sizing 2023-09-05 21:39:43 +02:00
Robert
2ee170b803 Workshop - Fixed deep linking to an entry
Workshop - Added the ability to upload new releases to existing submissions
2023-09-04 20:30:57 +02:00
Diogo Trindade
625fff4b73 Fix namespaces 2023-09-04 15:56:59 +01:00
Diogo Trindade
8a1b302e48 Core - Further reduced allocations in process monitor checking 2023-09-04 15:55:27 +01:00
Robert
bf3d5fc75d UI - Replaced Consolas with Roboto Mono
Workshop library - Added most of submission management
2023-09-03 16:51:22 +02:00
Robert
a0536b4302 Workshop - Did all the things (Markdown stuff) 2023-09-02 22:58:35 +02:00
Robert
c132edeb51 Router - Require routable VMs to implement new RoutableScreen 2023-09-02 15:52:17 +02:00
Robert
318ec99ad4 Workshop library - Match browser design after all 2023-09-02 10:57:18 +02:00
Robert
7b7ef2045a Merge branch 'development' 2023-09-02 10:12:19 +02:00
Robert
730881b6d0 Merge branch 'development' into feature/workshop 2023-09-02 09:46:48 +02:00
RobertBeekman
d26f62223b
Merge pull request #811 from Artemis-RGB/ProcessMonitoring
Added more low level process-monitoring to reduce allocations and cpu…
2023-09-02 09:45:27 +02:00
RobertBeekman
7f75a89d97
Merge pull request #810 from Artemis-RGB/feature/profiler-call-count
Profiler - Added number of calls
2023-09-02 09:45:04 +02:00
RobertBeekman
d3b0da0b1f
Merge pull request #808 from Artemis-RGB/feature/windows-input-allocs
Reduce memory allocation in Windows Input Provider
2023-09-02 09:44:54 +02:00
Robert
742496b13d Workshop - UI tweaks 2023-09-02 09:43:42 +02:00
Robert
9c6d7329a6 Workshop Library - Added library pages
UI - Tweaked design to more closely match WinUI 3 gallery examples
2023-09-01 20:33:50 +02:00
Diogo Trindade
51e1ecea68 Remove weird import
not sure how that got there haha
2023-09-01 08:55:05 +01:00
ef3e349da4 Splitted the ProcessMonitor to have a cleaner separation of windows-specific and corss-platform code 2023-09-01 01:46:44 +02:00
abeb6885ce Added more low level process-monitoring to reduce allocations and cpu-time 2023-09-01 01:20:54 +02:00
Robert
e545d2f3da Workshop - Added library view models 2023-08-31 22:04:57 +02:00
Diogo Trindade
402242b79c Profiler - Added number of calls 2023-08-31 14:18:24 +01:00
Robert
d605afe6cc Merge branch 'development' 2023-08-27 12:18:16 +02:00
Robert
c75e839756 Profile upload - Use new format 2023-08-27 11:50:08 +02:00
Robert
23f80895b6 Merge branch 'development' into feature/workshop 2023-08-27 11:48:07 +02:00
Robert
e5c42ee228 Profiles - Redesigned export file format 2023-08-27 11:46:14 +02:00
Robert
3158e4247a Merge branch 'development' into feature/workshop 2023-08-26 22:39:08 +02:00
Robert
811917c1c9 UI - Unload current page when closing screen
UI - Restore last page when opening via double click on tray icon
Windows - Fixed settings tray menu item opening release tab
2023-08-26 22:39:01 +02:00
Robert
606a34c7be Merge branch 'development' into feature/workshop 2023-08-26 21:52:07 +02:00
Robert
f3a4ccea8b Sidebar - Navigate on click instead of select, allowing to go back to the main screen of a menu item 2023-08-26 21:51:58 +02:00
Robert
eed6ff7a2b UI - Log Avalonia logs in Serilog instead of their own slow solution 2023-08-26 21:46:01 +02:00
Robert
671c587df6 Profiles - Added workshop installing 2023-08-26 20:47:48 +02:00
Robert
4143cc2de8 Added profile details 2023-08-25 22:54:56 +02:00
Robert
e21edd0ed6 About tab - Fixed my icon being smoll, lmao
About tab - Use async image loader instead of manual bitmaps
Workshop - Use async image loader for entry icons
2023-08-24 19:38:47 +02:00
Robert
2a34381926 Submission wizard - Style tweaks to
Profile details - Started work on side pane
2023-08-22 22:20:56 +02:00
Robert
f6f6f0fe6a Core - Made SKPaintExtensions public 2023-08-22 16:01:32 +02:00
Diogo Trindade
11392f6043 Reduce memory allocation in Windows Input Provider 2023-08-22 12:41:06 +01:00
Robert
a51ab70298 Merge branch 'development' into feature/workshop 2023-08-21 20:15:46 +02:00
Robert
5ccd1a9761 Merge branch 'master' into development 2023-08-21 20:14:46 +02:00
Robert
e4c487e2de Device providers - Fixed an exception when re-enabling device providers 2023-08-21 20:08:25 +02:00
Robert
9cedeea2f0 Nuget - Use separate version for Avalonia behaviours 2023-08-20 20:51:53 +02:00
Robert
138d168cf2 Merge branch 'development' 2023-08-20 14:52:12 +02:00
Robert
af5bae52ec Nuget - Upgrade Avalonia and RGB.NET 2023-08-20 14:39:56 +02:00
Robert
2dff152e90 About tab - Made version selectable 2023-08-20 14:37:47 +02:00
RobertBeekman
bb3bc576db
Merge pull request #807 from Artemis-RGB/feature/fix-layouts
UI - Fix layout loading
2023-08-19 20:47:59 +02:00
Diogo Trindade
6a02d5031b Early return when copying isn't possible 2023-08-19 19:10:03 +01:00
Robert
77bed1bf94 Entry list - Show icons
Workshop search - Show icons, update design
2023-08-19 11:32:22 +02:00
Robert
176a28761f Submission wizard - Upload entry icon
Entry lists - Show entry icon
2023-08-16 20:01:28 +02:00
Diogo Trindade
4223879e72 UI - Fix layout loading 2023-08-16 17:27:51 +01:00
Robert
6014bb9e3c Merge branch 'development' into feature/workshop 2023-08-14 21:00:07 +02:00
Robert
65c071e0bd UI - Fixed navigating to deep links 2023-08-14 20:55:45 +02:00
Robert
89a910d9e2 Merge branch 'development' into feature/workshop 2023-08-14 20:43:00 +02:00
Robert
4a152f4e95 UI - Fixed possible crash when loading profiles with broken icons
Debugger - Added routing debugger
2023-08-14 20:42:51 +02:00
Robert
a1fd8d5099 Shared UI - Added HttpClientUtilities offering progressable stream content
Submission wizard - Added upload progress
Submission wizard - Close on finish
2023-08-14 20:10:13 +02:00
Robert
ad4da3032d Workshop - Added profile uploading
Submission wizard - Final upload steps
2023-08-13 21:11:05 +02:00
Robert
e52faf1c9f Submission wizard - Added entry specifications step 2023-08-13 10:42:51 +02:00
Robert
56abc48ab3 Profiles - Resize profile images to 128x128 2023-08-13 10:42:28 +02:00
Robert
e9f2b77fd6 Shared UI - Added tags input control 2023-08-13 10:41:29 +02:00
Robert
3a6171726c Core - Removed OriginalFileName from icon since it's irrelevant 2023-08-13 10:41:12 +02:00
Robert
e1f0ccbcc1 Submission wizard - Added adaption hint and entry details steps 2023-08-11 14:43:35 +02:00
Robert
d2b8123a30 Core - Reworked profile render override for the editor and new previewer 2023-08-10 11:54:37 +02:00
Robert
c6a318b6e3 Merge branch 'development' into feature/workshop 2023-08-09 11:58:37 +02:00
Robert
e0869e4f28 UI - Update Avalonia to 11.0.2
UI - Update FluentAvalonia to 2.0.1
2023-08-09 11:58:28 +02:00
Robert
78cb95aabc Submission wizard - Added Lottie animations and the first few steps 2023-08-09 11:54:35 +02:00
Robert
661242ebf9 Submission wizard - Added logging in and email verification check 2023-08-07 20:25:00 +02:00
Robert
eeaf3999cf Workshop login - Use code auth flow instead of device code 2023-08-06 22:05:34 +02:00
Robert
2621542479 Editor - Fixed crash when moving folders due to a race condition 2023-07-31 22:30:52 +02:00
Robert
990f55b7c5 Workshop - Added login UI 2023-07-31 22:20:45 +02:00
Robert
7eccdf079a UI - Move notifications to the overlay layer 2023-07-31 22:20:25 +02:00
Robert
704d649fba Core - Limit log file size to 5 MB and only retain max 14 log files 2023-07-30 10:42:31 +02:00
Robert
8639cde83a Merge branch 'development' 2023-07-27 20:47:17 +02:00
Robert
86a22a9d39 Upgrade RGB.NET and hide Mica option outside W11 2023-07-27 20:47:10 +02:00
Robert
3d0916b17c Merge branch 'development' 2023-07-27 20:40:44 +02:00
Robert
1c438be158 Feature flag out the workshop 2023-07-27 20:40:24 +02:00
Robert
44691dd5a8 Show question mark in race condition 2023-07-27 20:18:05 +02:00
Robert
3030312df9 Merge branch 'feature/workshop' into development 2023-07-27 20:17:06 +02:00
Robert
0225457b1b Merge remote-tracking branch 'origin/feature/fix-crash' into development 2023-07-27 20:16:59 +02:00
Robert
02798b6d6e Routing - Added missing XAML comments to public types 2023-07-27 20:16:02 +02:00
Robert
66f263ff2a Gradient picker - Fixed selecting stops in the top part of the picker 2023-07-27 20:15:52 +02:00
Robert
ee6d295fd8 Upgrade Avalonia 2023-07-26 22:13:40 +02:00
Diogo Trindade
3830e8566e Fix race condition when opening window that causes a crash 2023-07-24 16:43:00 +01:00
Robert
e03d6b20e9 Added acrylic blur test 2023-07-23 17:19:56 +02:00
Robert
0e077e31b3 Merge branch 'master' into feature/workshop 2023-07-22 09:45:57 +02:00
Robert
552879ca77 Merge branch 'development' 2023-07-21 23:14:11 +02:00
RobertBeekman
fc598d88ad
Merge pull request #805 from Artemis-RGB/feature/debug-logs-select
Fixed a few annoying UI bugs
2023-07-21 23:11:48 +02:00
Robert
7c19937bce Implemented pagination in profile list 2023-07-21 23:10:50 +02:00
Robert
cfb39b986d Added pagination 2023-07-21 22:11:35 +02:00
Diogo Trindade
4936213249 Fix plugin module being case sensitive comparing guids 2023-07-21 17:00:35 +01:00
Diogo Trindade
27d69bc1bc Fixed enum swich node not respecting DisplayAttribute 2023-07-16 17:30:27 +01:00
Diogo Trindade
de7c09f047 Core - Override ColorSwatch.ToString
This cleans up the datamodel viewer a lot, currently since ColorSwatch is a struct, it has a very long name. Since the properties can be seen by looking at the children in the datamodel, this information is useless.
2023-07-14 23:19:28 +01:00
Robert
07d4539add Workshop list progress 2023-07-14 23:05:44 +02:00
Diogo Trindade
a991917ea9 Fixed text selection in the debug logs view 2023-07-14 17:02:41 +01:00
Robert
2a3fd30313 Merge branch 'development' into feature/workshop 2023-07-14 10:34:13 +02:00
Robert
329eeb8a5c Merge branch 'feature/avalonia.11' into development 2023-07-14 10:32:00 +02:00
Robert
b298900ed2 Upgrade Avalonia and FluentAvalonia, use a props file to sync versions 2023-07-14 10:31:44 +02:00
Robert
1c07bef3cf Use real backend 2023-07-14 09:21:09 +02:00
Robert
428bbd73e3 Added basic profile list 2023-07-09 12:54:07 +02:00
Robert
af1920141c Device visualizer - Clear device cache on UI thread on property change
Surface editor - Don't quietly fail device updates
2023-07-09 08:29:54 +02:00
Robert
65f81ab768 Added workshop home, changed VM routing structure 2023-07-08 21:44:39 +02:00
Robert
99a365be0b Sidebar - Added subitems support 2023-07-07 22:55:02 +02:00
Robert
229d93901b Merge branch 'development' into feature/workshop 2023-07-06 18:41:59 +02:00
Robert
d18ef4f5f4 Update Avalonia to 11 rc2.2 2023-07-04 20:24:38 +02:00
Robert
850038ffd1 Merge branch 'master' into development 2023-07-02 22:16:53 +02:00
RobertBeekman
962b13c6ed
UI - Added routing (#802)
ColorGradient - Fixed GetColor returning the first stop if position was after the last stop
2023-07-02 22:15:38 +02:00
Robert
4726190355 ColorGradient - Fixed GetColor returning the first stop if position was after the last stop 2023-06-24 15:44:06 +02:00
Robert
b5de3b65b2 Merge branch 'development' into feature/workshop 2023-06-23 16:10:06 +02:00
Robert
79309dcee7 Merge branch 'development' 2023-06-22 23:16:56 +02:00
Robert
f43ebd2153 UI - Fix URLs 2023-06-22 23:16:49 +02:00
Robert
3860ef0f3d Merge branch 'development' into feature/workshop 2023-06-22 23:09:26 +02:00
RobertBeekman
6ba2aa176c
Merge pull request #796 from Artemis-RGB/feature/export-layout
Reworked the Device Properties window
2023-06-22 23:04:09 +02:00
Diogo Trindade
91364158c0 Merge remote-tracking branch 'origin/development' into feature/export-layout 2023-06-22 22:03:14 +01:00
RobertBeekman
6156da0161
Merge pull request #795 from Artemis-RGB/dependabot/nuget/src/Artemis.UI.Windows/Microsoft.Windows.Compatibility-7.0.3
Bump Microsoft.Windows.Compatibility from 7.0.0 to 7.0.3 in /src/Artemis.UI.Windows
2023-06-22 23:02:32 +02:00
RobertBeekman
8181127977
Merge pull request #798 from Artemis-RGB/feature/url-campaign
UI - Added Matomo campaign to URLs so we know whether they're used
2023-06-22 23:00:23 +02:00
RobertBeekman
d9c63d63e4
Merge pull request #797 from Artemis-RGB/feature/device-visualizer
Cleaned up the device visualizer
2023-06-22 23:00:09 +02:00
Robert
4224875fb0 Added basic login 2023-06-22 22:56:24 +02:00
Diogo Trindade
5ef6c4b8a7 Made the UI save automatically, removed apply and reset buttons 2023-06-21 23:07:56 +01:00
Robert
52a769be96 Added Matomo campaign to URLs so we know whether they're used 2023-06-18 12:35:46 +02:00
Diogo Trindade
480e0b9b12 Clean up layout view 2023-06-16 22:52:42 +01:00
Robert
49d229cd2b Tweak background 2023-06-16 22:58:48 +02:00
Diogo Trindade
af03f291d3 Removed Device info and properties tabs 2023-06-16 19:26:19 +01:00
Diogo Trindade
db069ea8bf Reworked device properties screen 2023-06-16 19:20:13 +01:00
Diogo Trindade
cdfa14441e Clean up csproj 2023-06-16 18:48:04 +01:00
Diogo Trindade
0122ab00d9 Added more Border styles 2023-06-16 18:48:04 +01:00
dependabot[bot]
ff55bf0d26
Bump Microsoft.Windows.Compatibility in /src/Artemis.UI.Windows
Bumps [Microsoft.Windows.Compatibility](https://github.com/dotnet/runtime) from 7.0.0 to 7.0.3.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v7.0.3)

---
updated-dependencies:
- dependency-name: Microsoft.Windows.Compatibility
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-14 17:10:22 +00:00
Robert
35f8ee5f77 Merge branch 'development' 2023-06-12 20:05:43 +02:00
Robert
a903771be3 Bump RGB.NET to 2.0.0-prerelease.83 2023-06-12 20:03:37 +02:00
Robert
24a241f91f Merge branch 'master' into development 2023-06-12 20:01:54 +02:00
Robert
f5688db630 Merge branch 'development' 2023-06-11 22:51:15 +02:00
Robert
63591ec3a6 Merge branch 'feature/avalonia-11rc1' into development 2023-06-11 22:50:40 +02:00
Robert
61e4886869 Bump RGB.NET to 2.0.0-prerelease.78 2023-06-11 22:49:49 +02:00
Robert
bde4e861c2 Initial workshop commit 2023-06-11 18:52:17 +02:00
Robert
b6762d074a Start with enabled keybinds 2023-06-11 18:03:18 +02:00
Robert
deaee707f1 Removed old Console.WriteLine 2023-06-11 09:43:05 +02:00
Robert
2e9b3d59d6 Added workaround for invalidation issue in pan/zoom elements 2023-06-10 13:23:38 +02:00
Robert
34824dde42 HotkeyBox - Focus fixes 2023-06-06 20:17:30 +02:00
Robert
aa4a740b78 DraggableNumberBox - Focus fixes 2023-06-06 19:19:43 +02:00
Diogo Trindade
88322baafd DeviceVisualizer - Tied update to core update instead of hardcoded 25fps 2023-06-06 12:17:31 +01:00
Diogo Trindade
567ca193a4 DeviceVisualizer - don't check dirty unless necessary 2023-06-06 12:00:48 +01:00
Diogo Trindade
06e6075c4d DeviceVisualizer - reduce allocations 2023-06-06 12:00:05 +01:00
Robert
f249f80d19 Upgrade Avalonia to 11 rc1 2023-06-05 23:39:57 +02:00
Diogo Trindade
70b6035b0d Fixed save file picker path 2023-06-05 14:25:39 +01:00
Diogo Trindade
c0323f7248 Input - Added F13-F24 2023-06-05 12:18:39 +01:00
Robert
c24e57e556 NumberBox - Fixed values getting coerced between 0 and 100
Data model event cycle node - Listen to the event directly, avoiding race conditions
2023-05-26 00:16:26 +02:00
Robert
9cfa45972d Merge branch 'development' 2023-05-22 20:54:29 +02:00
Robert
dc8147d5a7 UI - Fix draggable number box clamping the value to 0-100 when unloading 2023-05-22 20:19:42 +02:00
Robert
ec594f02b1 Upgrade RGB.NET to 2.0.0-prerelease.69 2023-05-22 19:58:52 +02:00
Robert
6136861215 UI - Use lower quality rendering on the device visualizer 2023-05-22 15:03:10 +02:00
Diogo Trindade
a846bd592e Core - Do not load abstract plugin features
UI - Replace Equals check with EqualityComparer
2023-05-21 15:33:37 +01:00
Robert
26ad9a3385 Merge remote-tracking branch 'origin/development' into development 2023-05-21 10:33:22 +02:00
Robert
e190059623 Device visualizer - Only rerender dirty devices 2023-05-21 10:33:15 +02:00
Diogo Trindade
51f08379eb Fix possible NRE in node script import 2023-05-20 22:15:08 +01:00
Robert
fa710777d7 UI - Use Timer instead of DispatcherTimer where feasible 2023-05-20 23:11:51 +02:00
Robert
0b4588ea58 Merge branch 'development' 2023-05-18 20:03:53 +02:00
Robert
4ffe8d8848 Nuget - Upgraded Markdown, removed unused Skia package 2023-05-18 20:03:02 +02:00
Robert
8ae066935e Update readme 2023-05-18 19:47:38 +02:00
Robert
cb98f25780 Merge branch 'feature/icon-picker' into development 2023-05-18 19:44:21 +02:00
Robert
ed3a770881 UI - Added new icon picker to profile configuration dialog 2023-05-18 19:39:15 +02:00
Robert
07f8209b7b UI - Initial icon picker commit 2023-05-18 13:27:04 +02:00
Robert
c86dba30fc Settings - Brought the with and margins of different tabs in line with eachother
Settings - Tweaked card title font size and spacing
Plugins - Show extra information on plugins in their config dialog 
Plugins - Added the ability to provide a license in plugin.json
2023-05-18 11:18:55 +02:00
Robert
a76ecec97d Profile editor - Fix render quality of devices 2023-05-13 11:18:59 +02:00
1170 changed files with 32194 additions and 19081 deletions

View File

@ -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/

View File

@ -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 }}

View File

@ -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
View 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
View File

@ -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/

View File

@ -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.**

View File

@ -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

View File

@ -1,5 +0,0 @@
###############
# temp file #
###############
*.yml
.manifest

View File

@ -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)

View File

@ -0,0 +1,4 @@
#logo {
height: 40px;
margin-right: 10px;
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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).

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
}
});
});
});

View File

@ -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)}});

View File

@ -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>

View File

@ -1,2 +1,2 @@
- name: API Documentation
href: api/
- name: Plugin API
href: api/

View File

@ -0,0 +1 @@
# Getting Started

View File

@ -0,0 +1 @@
# Introduction

4
docfx/docs/toc.yml Normal file
View File

@ -0,0 +1,4 @@
- name: Introduction
href: introduction.md
- name: Getting Started
href: getting-started.md

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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)
);
}
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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
}

View File

@ -1,6 +0,0 @@
namespace Artemis.Core.ColorScience;
internal enum SortTarget
{
None, Red, Green, Blue
}

View File

@ -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()}};
}

View File

@ -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);

View File

@ -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

View 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; }
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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();

View File

@ -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");
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
}

View File

@ -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();

View 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();
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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++)
{

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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>

View File

@ -243,4 +243,14 @@ public class DataBinding<TLayerProperty> : IDataBinding
}
#endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Script.GetFeatureDependencies();
}
#endregion
}

View File

@ -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

View File

@ -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
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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()));

View File

@ -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
};

View File

@ -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
}

View File

@ -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);
}
}
}

View File

@ -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)
{

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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

View File

@ -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>

View File

@ -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; }
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}
}

View 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; }
}

View File

@ -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()
{
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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
}

View File

@ -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 />

View File

@ -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