1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2022-08-26 23:03:27 +02:00
commit db145d537a
81 changed files with 3433 additions and 939 deletions

View File

@ -7,14 +7,6 @@ trigger:
- master - master
pr: none pr: none
resources:
repositories:
- repository: RGBNET
type: github
endpoint: github.com_SpoinkyNL
name: DarthAffe/RGB.NET
ref: Development
pool: pool:
vmImage: 'windows-latest' vmImage: 'windows-latest'
@ -28,22 +20,13 @@ variables:
SourceVersion: $(Build.SourceVersion) SourceVersion: $(Build.SourceVersion)
steps: steps:
- checkout: RGBNET
path: s/RGB.NET
- checkout: self - checkout: self
path: s/Artemis path: s/Artemis
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'RGB.NET - Build' displayName: 'dotnet build Artemis'
inputs: inputs:
command: 'build' command: 'build'
projects: '$(rgbSolution)'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'dotnet restore Artemis'
inputs:
command: 'restore'
projects: '$(artemisSolution)' projects: '$(artemisSolution)'
feedsToUse: 'config' feedsToUse: 'config'
nugetConfigPath: '$(Pipeline.Workspace)/s/Artemis/src/NuGet.Config' nugetConfigPath: '$(Pipeline.Workspace)/s/Artemis/src/NuGet.Config'

View File

@ -1,2 +1,26 @@
# PLACEHOLDER
TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*! # 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

@ -1 +0,0 @@
Bummer, no guides 😌

View File

@ -1,3 +0,0 @@
Plugins 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).

View File

@ -1,4 +0,0 @@
- name: Introduction
href: intro.md
- name: Plugins - Getting started
href: plugins_getting_started.md

View File

@ -4,8 +4,8 @@
"src": [ "src": [
{ {
"files": [ "files": [
"Artemis.Core/Artemis.Core.csproj", "Artemis.Core/bin/net6.0/Artemis.Core.dll",
"Artemis.UI.Shared/Artemis.UI.Shared.csproj", "Artemis.UI.Shared/bin/net6.0/Artemis.UI.Shared.dll",
], ],
"src": "../../src" "src": "../../src"
} }
@ -20,14 +20,11 @@
"content": [ "content": [
{ {
"files": [ "files": [
"api/**.yml", "api/**.yml"
"api/index.md"
] ]
}, },
{ {
"files": [ "files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml", "toc.yml",
"*.md" "*.md"
] ]
@ -52,16 +49,24 @@
} }
], ],
"globalMetadata": { "globalMetadata": {
"_appTitle": "Artemis documentation", "_appTitle": "Artemis API Documentation",
"_enableSearch": true "_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>",
"_copyrightFooter": "Content is available under the PolyForm Noncommercial License, by Artemis RGB.",
"_enableSearch": true,
"_disableSideFilter": false,
"_enableNewTab": true,
"_disableContribution": false,
"_disableBreadcrumb": false
}, },
"dest": "_site", "dest": "_site",
"globalMetadataFiles": [], "globalMetadataFiles": [],
"fileMetadataFiles": [], "fileMetadataFiles": [],
"template": [ "template": [
"default", "default",
"templates/artemis", "templates/singulinkfx"
"templates/material"
], ],
"postProcessors": [], "postProcessors": [],
"markdownEngineName": "markdig", "markdownEngineName": "markdig",

View File

@ -11,6 +11,9 @@ apiRules:
- exclude: - exclude:
uidRegex: ^Stylet uidRegex: ^Stylet
type: Type type: Type
- exclude:
uidRegex: ^Artemis\.Core\.CorePropertyChanged
type: Type
- exclude: - exclude:
uidRegex: ^Artemis\.Core\.Properties uidRegex: ^Artemis\.Core\.Properties
type: Type type: Type

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

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="navbar-brand" href="{{_rel}}index.html">
<link rel="stylesheet" href="{{_rel}}styles/artemis.css">
<img id="logo" src="{{_rel}}images/logo-512.png" alt="Artemis logo" style="width: 38px; height: 38px; margin-top: 6px; margin-right: 6px;">
</a>

View File

@ -1,22 +0,0 @@
.sidefilter {
width: 320px;
}
.sidetoc {
width: 320px;
}
@media only screen and (max-width: 768px)
.article.grid-right {
margin-left: 0;
}
.article.grid-right {
margin-left: 340px;
}
@media (min-width: 1500px) {
.container {
width: 1470px;
}
}

View File

@ -1,38 +0,0 @@
{{!Copyright (c) Oscar Vasquez. 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 rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<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}}
<!-- Matomo -->
<script type="text/javascript">
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://stats.artemis-rgb.com/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="https://stats.artemis-rgb.com/matomo.php?idsite=2&amp;rec=1" style="border:0;" alt="" /></p></noscript>
<!-- End Matomo Code -->
</head>

View File

@ -1,314 +0,0 @@
/* COLOR VARIABLES*/
:root {
--header-bg-color: #009688;
--header-ft-color: #fff;
--highlight-light: #1de9b6;
--highlight-dark: #00bfa5;
--accent-dim: #e0e0e0;
--accent-super-dim: #f3f3f3;
--font-color: #34393e;
--card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16);
--search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46);
--transition: 350ms;
}
body {
color: var(--font-color);
font-family: "Roboto", sans-serif;
line-height: 1.5;
font-size: 16px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
word-wrap: break-word;
}
/* HIGHLIGHT COLOR */
button,
a {
color: var(--highlight-dark);
cursor: pointer;
}
button:hover,
button:focus,
a:hover,
a:focus {
color: var(--highlight-light);
text-decoration: none;
}
.toc .nav > li.active > a {
color: var(--highlight-dark);
}
.toc .nav > li.active > a:hover,
.toc .nav > li.active > a:focus {
color: var(--highlight-light);
}
.pagination > .active > a {
background-color: var(--header-bg-color);
border-color: var(--header-bg-color);
}
.pagination > .active > a,
.pagination > .active > a:focus,
.pagination > .active > a:hover,
.pagination > .active > span,
.pagination > .active > span:focus,
.pagination > .active > span:hover {
background-color: var(--highlight-light);
border-color: var(--highlight-light);
}
.affix ul > li.active > a, .affix ul > li.active > a:before {
color: var(--highlight-dark);
}
.affix > ul > li.active > a, .affix > ul > li.active > a:before {
color: var(--highlight-dark);
}
/* HEADINGS */
h1 {
font-weight: 600;
font-size: 32px;
}
h2 {
font-weight: 600;
font-size: 24px;
line-height: 1.8;
}
h3 {
font-weight: 600;
font-size: 20px;
line-height: 1.8;
}
h5 {
font-size: 14px;
padding: 10px 0px;
}
article h1,
article h2,
article h3,
article h4 {
margin-top: 35px;
margin-bottom: 15px;
}
article h4 {
padding-bottom: 8px;
border-bottom: 2px solid #ddd;
}
/* NAVBAR */
.navbar-brand > img {
color: var(--header-ft-color);
}
.navbar {
border: none;
/* Both navbars use box-shadow */
-webkit-box-shadow: var(--card-box-shadow);
-moz-box-shadow: var(--card-box-shadow);
box-shadow: var(--card-box-shadow);
}
.subnav {
border-top: 1px solid #ddd;
background-color: #fff;
}
.navbar-inverse {
background-color: var(--header-bg-color);
z-index: 100;
}
.navbar-inverse .navbar-nav > li > a,
.navbar-inverse .navbar-text {
color: var(--header-ft-color);
background-color: var(--header-bg-color);
border-bottom: 3px solid transparent;
padding-bottom: 12px;
transition: 350ms;
}
.navbar-inverse .navbar-nav > li > a:focus,
.navbar-inverse .navbar-nav > li > a:hover {
color: var(--header-ft-color);
background-color: var(--header-bg-color);
border-bottom: 3px solid white;
}
.navbar-inverse .navbar-nav > .active > a,
.navbar-inverse .navbar-nav > .active > a:focus,
.navbar-inverse .navbar-nav > .active > a:hover {
color: var(--header-ft-color);
background-color: var(--header-bg-color);
border-bottom: 3px solid white;
}
.navbar-form .form-control {
border: 0;
border-radius: 4px;
box-shadow: var(--search-box-shadow);
transition:var(--transition);
}
.navbar-form .form-control:hover {
background-color: var(--accent-dim);
}
/* NAVBAR TOGGLED (small screens) */
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
border: none;
}
.navbar-inverse .navbar-toggle {
box-shadow: var(--card-box-shadow);
border: none;
}
.navbar-inverse .navbar-toggle:focus,
.navbar-inverse .navbar-toggle:hover {
background-color: var(--highlight-dark);
}
/* SIDEBAR */
.toc .level1 > li {
font-weight: 400;
}
.toc .nav > li > a {
color: var(--font-color);
}
.sidefilter {
background-color: #fff;
border-left: none;
border-right: none;
}
.sidefilter {
background-color: #fff;
border-left: none;
border-right: none;
}
.toc-filter {
padding: 5px;
margin: 0;
box-shadow: var(--card-box-shadow);
transition:var(--transition);
}
.toc-filter:hover {
background-color: var(--accent-super-dim);
}
.toc-filter > input {
border: none;
background-color: inherit;
transition: inherit;
}
.toc-filter > .filter-icon {
display: none;
}
.sidetoc > .toc {
background-color: #fff;
overflow-x: hidden;
}
.sidetoc {
background-color: #fff;
border: none;
}
/* ALERTS */
.alert {
padding: 0px 0px 5px 0px;
color: inherit;
background-color: inherit;
border: none;
box-shadow: var(--card-box-shadow);
}
.alert > p {
margin-bottom: 0;
padding: 5px 10px;
}
.alert > ul {
margin-bottom: 0;
padding: 5px 40px;
}
.alert > h5 {
padding: 10px 15px;
margin-top: 0;
text-transform: uppercase;
font-weight: bold;
border-radius: 4px 4px 0 0;
}
.alert-info > h5 {
color: #1976d2;
border-bottom: 4px solid #1976d2;
background-color: #e3f2fd;
}
.alert-warning > h5 {
color: #f57f17;
border-bottom: 4px solid #f57f17;
background-color: #fff3e0;
}
.alert-danger > h5 {
color: #d32f2f;
border-bottom: 4px solid #d32f2f;
background-color: #ffebee;
}
/* CODE HIGHLIGHT */
pre {
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
word-break: break-all;
word-wrap: break-word;
background-color: #fffaef;
border-radius: 4px;
border: none;
box-shadow: var(--card-box-shadow);
}
/* STYLE FOR IMAGES */
.article .small-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 350px;
}
.article .medium-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 550px;
}
.article .large-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 700px;
}

View File

@ -0,0 +1,62 @@
{{!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

@ -0,0 +1,4 @@
<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

@ -0,0 +1,24 @@
{{!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

@ -0,0 +1,31 @@
{{!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

@ -0,0 +1,6 @@
{{!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

@ -0,0 +1,13 @@
{{!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

@ -0,0 +1,19 @@
{{!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

@ -0,0 +1,12 @@
{{!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

@ -0,0 +1,9 @@
{{!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

@ -0,0 +1,5 @@
{{!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

@ -0,0 +1,114 @@
/* 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

@ -0,0 +1,681 @@
/* 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

@ -0,0 +1,44 @@
<?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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,317 @@
/*!
* 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

@ -0,0 +1,471 @@
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

@ -0,0 +1,38 @@
// 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

@ -0,0 +1 @@
/*! 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

@ -0,0 +1,22 @@
{{!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,6 +1,2 @@
- name: Articles
href: articles/
homepage: articles/intro.md
- name: API Documentation - name: API Documentation
href: api/ href: api/
homepage: api/index.md

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using Artemis.Core.JsonConverters; using Artemis.Core.JsonConverters;
@ -90,6 +91,11 @@ public static class Constants
/// </summary> /// </summary>
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
/// <summary>
/// Gets the startup arguments provided to the application
/// </summary>
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; 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 EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};

View File

@ -58,7 +58,6 @@ internal class CoreService : ICoreService
_scriptingService = scriptingService; _scriptingService = scriptingService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_frameStopWatch = new Stopwatch(); _frameStopWatch = new Stopwatch();
StartupArguments = new List<string>();
_rgbService.Surface.Updating += SurfaceOnUpdating; _rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
@ -78,7 +77,7 @@ internal class CoreService : ICoreService
private void ApplyLoggingLevel() private void ApplyLoggingLevel()
{ {
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); string? argument = Constants.StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null) if (argument != null)
{ {
// Parse the provided log level // Parse the provided log level
@ -194,7 +193,6 @@ internal class CoreService : ICoreService
public int FrameRate { get; private set; } public int FrameRate { get; private set; }
public TimeSpan FrameTime { get; private set; } public TimeSpan FrameTime { get; private set; }
public bool ProfileRenderingDisabled { get; set; } public bool ProfileRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; }
public bool IsElevated { get; set; } public bool IsElevated { get; set; }
public void Dispose() public void Dispose()
@ -217,7 +215,7 @@ internal class CoreService : ICoreService
Constants.BuildInfo.BuildNumber, Constants.BuildInfo.BuildNumber,
Constants.BuildInfo.SourceBranch Constants.BuildInfo.SourceBranch
); );
_logger.Information("Startup arguments: {args}", StartupArguments); _logger.Information("Startup arguments: {args}", Constants.StartupArguments);
_logger.Information("Elevated permissions: {perms}", IsElevated); _logger.Information("Elevated permissions: {perms}", IsElevated);
_logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution); _logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution);
@ -230,9 +228,9 @@ internal class CoreService : ICoreService
// Initialize the services // Initialize the services
_pluginManagementService.CopyBuiltInPlugins(); _pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated); _pluginManagementService.LoadPlugins(IsElevated);
_rgbService.ApplyPreferredGraphicsContext(StartupArguments.Contains("--force-software-render")); _rgbService.ApplyPreferredGraphicsContext(Constants.StartupArguments.Contains("--force-software-render"));
_rgbService.SetRenderPaused(false); _rgbService.SetRenderPaused(false);
OnInitialized(); OnInitialized();
} }

View File

@ -27,12 +27,7 @@ public interface ICoreService : IArtemisService, IDisposable
/// Gets or sets whether profiles are rendered each frame by calling their Render method /// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary> /// </summary>
bool ProfileRenderingDisabled { get; set; } bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets or sets a list of startup arguments
/// </summary>
List<string> StartupArguments { get; set; }
/// <summary> /// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions) /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary> /// </summary>

View File

@ -26,7 +26,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
/// <summary> /// <summary>
/// Loads all installed plugins. If plugins already loaded this will reload them all /// Loads all installed plugins. If plugins already loaded this will reload them all
/// </summary> /// </summary>
void LoadPlugins(List<string> startupArguments, bool isElevated); void LoadPlugins(bool isElevated);
/// <summary> /// <summary>
/// Unloads all installed plugins. /// Unloads all installed plugins.

View File

@ -203,17 +203,17 @@ internal class PluginManagementService : IPluginManagementService
#region Plugins #region Plugins
public void LoadPlugins(List<string> startupArguments, bool isElevated) public void LoadPlugins(bool isElevated)
{ {
if (startupArguments.Contains("--no-plugins")) if (Constants.StartupArguments.Contains("--no-plugins"))
{ {
_logger.Warning("Artemis launched with --no-plugins, skipping the loading of plugins"); _logger.Warning("Artemis launched with --no-plugins, skipping the loading of plugins");
return; return;
} }
bool ignorePluginLock = startupArguments.Contains("--ignore-plugin-lock"); bool ignorePluginLock = Constants.StartupArguments.Contains("--ignore-plugin-lock");
bool stayElevated = startupArguments.Contains("--force-elevation"); bool stayElevated = Constants.StartupArguments.Contains("--force-elevation");
bool droppedAdmin = startupArguments.Contains("--dropped-admin"); bool droppedAdmin = Constants.StartupArguments.Contains("--dropped-admin");
if (LoadingPlugins) if (LoadingPlugins)
throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet.");

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using EmbedIO; using EmbedIO;
@ -17,15 +18,19 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly List<WebApiControllerRegistration> _controllers; private readonly List<WebApiControllerRegistration> _controllers;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly List<WebModuleRegistration> _modules; private readonly List<WebModuleRegistration> _modules;
private readonly PluginSetting<bool> _webServerEnabledSetting;
private readonly PluginSetting<int> _webServerPortSetting; private readonly PluginSetting<int> _webServerPortSetting;
private CancellationTokenSource? _cts;
public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService) public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{ {
_logger = logger; _logger = logger;
_controllers = new List<WebApiControllerRegistration>(); _controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>(); _modules = new List<WebModuleRegistration>();
_webServerEnabledSetting = settingsService.GetSetting("WebServer.Enabled", true);
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696); _webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerEnabledSetting.SettingChanged += WebServerEnabledSettingOnSettingChanged;
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged; _webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled; pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
@ -33,9 +38,9 @@ internal class WebServerService : IWebServerService, IDisposable
StartWebServer(); StartWebServer();
} }
protected virtual void OnWebServerStarting() private void WebServerEnabledSettingOnSettingChanged(object? sender, EventArgs e)
{ {
WebServerStarting?.Invoke(this, EventArgs.Empty); StartWebServer();
} }
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e) private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
@ -72,14 +77,23 @@ internal class WebServerService : IWebServerService, IDisposable
public WebServer? Server { get; private set; } public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; } public PluginsModule PluginsModule { get; }
public event EventHandler? WebServerStarting;
#region Web server managament #region Web server managament
private WebServer CreateWebServer() private WebServer CreateWebServer()
{ {
Server?.Dispose(); if (Server != null)
Server = null; {
if (_cts != null)
{
_cts.Cancel();
_cts = null;
}
Server.Dispose();
OnWebServerStopped();
Server = null;
}
WebApiModule apiModule = new("/", JsonNetSerializer); WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/"; PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
@ -112,8 +126,20 @@ internal class WebServerService : IWebServerService, IDisposable
private void StartWebServer() private void StartWebServer()
{ {
Server = CreateWebServer(); Server = CreateWebServer();
if (!_webServerEnabledSetting.Value)
return;
if (Constants.StartupArguments.Contains("--disable-webserver"))
{
_logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver");
return;
}
OnWebServerStarting(); OnWebServerStarting();
Server.Start(); _cts = new CancellationTokenSource();
Server.Start(_cts.Token);
OnWebServerStarted();
} }
#endregion #endregion
@ -276,4 +302,27 @@ internal class WebServerService : IWebServerService, IDisposable
} }
#endregion #endregion
#region Events
protected virtual void OnWebServerStopped()
{
WebServerStopped?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnWebServerStarting()
{
WebServerStarting?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnWebServerStarted()
{
WebServerStarted?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? WebServerStopped;
public event EventHandler? WebServerStarting;
public event EventHandler? WebServerStarted;
#endregion
} }

View File

@ -159,7 +159,12 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
{ {
return (byte) Math.Clamp(p._value, 0, 255); return (byte) Math.Clamp(p._value, 0, 255);
} }
public static implicit operator Numeric(double d) => new(d);
public static implicit operator Numeric(float f) => new(f);
public static implicit operator Numeric(int i) => new(i);
public static implicit operator Numeric(byte b) => new(b);
public static implicit operator long(Numeric p) public static implicit operator long(Numeric p)
{ {
return (long) p._value; return (long) p._value;

View File

@ -94,18 +94,11 @@ public sealed class InputPin : Pin
/// <param name="type">The new type of the pin.</param> /// <param name="type">The new type of the pin.</param>
public void ChangeType(Type type) public void ChangeType(Type type)
{ {
if (_type == type) if (type == _type)
return; return;
// Disconnect pins incompatible with the new type base.ChangeType(type, ref _type);
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList();
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type
SetAndNotify(ref _type, type, nameof(Type));
Value = type.GetDefault(); Value = type.GetDefault();
IsNumeric = type == typeof(Numeric);
} }
private void Evaluate() private void Evaluate()
@ -117,10 +110,10 @@ public sealed class InputPin : Pin
else else
Value = Type.GetDefault()!; Value = Type.GetDefault()!;
} }
else else if (ConnectedTo.Count > 0)
{
Value = ConnectedTo[0].PinValue; Value = ConnectedTo[0].PinValue;
} else
Value = null;
} }
#endregion #endregion

View File

@ -85,6 +85,7 @@ public interface IPin
/// Determines whether this pin is compatible with the given type /// Determines whether this pin is compatible with the given type
/// </summary> /// </summary>
/// <param name="type">The type to check for compatibility</param> /// <param name="type">The type to check for compatibility</param>
/// <param name="forgivingEnumMatching">A boolean indicating whether or not enums should be exactly equal or just both be enums</param>
/// <returns><see langword="true" /> if the type is compatible, otherwise <see langword="false" />.</returns> /// <returns><see langword="true" /> if the type is compatible, otherwise <see langword="false" />.</returns>
public bool IsTypeCompatible(Type type); public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true);
} }

View File

@ -341,8 +341,14 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageMod
private void SavePins(INode node, int collectionId, IEnumerable<IPin> pins) private void SavePins(INode node, int collectionId, IEnumerable<IPin> pins)
{ {
int sourcePinId = 0; int sourcePinId = 0;
foreach (IPin sourcePin in pins.Where(p => p.Direction == PinDirection.Input)) foreach (IPin sourcePin in pins)
{ {
if (sourcePin.Direction == PinDirection.Output)
{
sourcePinId++;
continue;
}
foreach (IPin targetPin in sourcePin.ConnectedTo) foreach (IPin targetPin in sourcePin.ConnectedTo)
{ {
int targetPinCollectionId = -1; int targetPinCollectionId = -1;

View File

@ -84,15 +84,11 @@ public sealed class OutputPin : Pin
/// <param name="type">The new type of the pin.</param> /// <param name="type">The new type of the pin.</param>
public void ChangeType(Type type) public void ChangeType(Type type)
{ {
// Disconnect pins incompatible with the new type if (type == _type)
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList(); return;
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type base.ChangeType(type, ref _type);
SetAndNotify(ref _type, type, nameof(Type));
Value = type.GetDefault(); Value = type.GetDefault();
IsNumeric = type == typeof(Numeric);
} }
#endregion #endregion

View File

@ -127,13 +127,46 @@ public abstract class Pin : CorePropertyChanged, IPin
} }
/// <inheritdoc /> /// <inheritdoc />
public bool IsTypeCompatible(Type type) public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true)
{ {
return Type == type return Type == type
|| (Type == typeof(Enum) && type.IsEnum) || (Direction == PinDirection.Input && type.IsAssignableTo(Type))
|| (Type.IsEnum && type == typeof(Enum)) || (Direction == PinDirection.Output && type.IsAssignableFrom(Type))
|| (Direction == PinDirection.Input && Type == typeof(object)) || (Direction == PinDirection.Input && Type == typeof(Enum) && type.IsEnum && forgivingEnumMatching)
|| (Direction == PinDirection.Output && type == typeof(object)); || (Direction == PinDirection.Output && type == typeof(Enum) && Type.IsEnum && forgivingEnumMatching);
}
/// <summary>
/// Changes the type of this pin, disconnecting any pins that are incompatible with the new type.
/// </summary>
/// <param name="type">The new type of the pin.</param>
/// <param name="currentType">The backing field of the current type of the pin.</param>
protected void ChangeType(Type type, ref Type currentType)
{
// Enums are a special case that disconnect and, if still compatible, reconnect
if (type.IsEnum && currentType.IsEnum)
{
List<IPin> connections = new(ConnectedTo);
DisconnectAll();
// Change the type
SetAndNotify(ref currentType, type, nameof(Type));
IsNumeric = type == typeof(Numeric);
foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type)))
ConnectTo(pin);
}
// Disconnect pins incompatible with the new type
else
{
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type, false)).ToList();
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type
SetAndNotify(ref currentType, type, nameof(Type));
IsNumeric = type == typeof(Numeric);
}
} }
#endregion #endregion

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Artemis.Core; using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor; namespace Artemis.UI.Shared.Services.NodeEditor;
@ -30,20 +31,18 @@ public class NodeConnectionStore
public void Store() public void Store()
{ {
_pinConnections.Clear(); _pinConnections.Clear();
foreach (IPin nodePin in Node.Pins)
{ // Iterate to save
foreach (IPin nodePin in Node.Pins.ToList())
_pinConnections.Add(nodePin, new List<IPin>(nodePin.ConnectedTo));
foreach (IPin nodePin in Node.PinCollections.ToList().SelectMany(nodePinCollection => nodePinCollection))
_pinConnections.Add(nodePin, new List<IPin>(nodePin.ConnectedTo)); _pinConnections.Add(nodePin, new List<IPin>(nodePin.ConnectedTo));
nodePin.DisconnectAll();
}
foreach (IPinCollection nodePinCollection in Node.PinCollections) // Iterate to disconnect
{ foreach (IPin nodePin in Node.Pins.ToList())
foreach (IPin nodePin in nodePinCollection) nodePin.DisconnectAll();
{ foreach (IPin nodePin in Node.PinCollections.ToList().SelectMany(nodePinCollection => nodePinCollection))
_pinConnections.Add(nodePin, new List<IPin>(nodePin.ConnectedTo)); nodePin.DisconnectAll();
nodePin.DisconnectAll();
}
}
} }
/// <summary> /// <summary>
@ -51,23 +50,10 @@ public class NodeConnectionStore
/// </summary> /// </summary>
public void Restore() public void Restore()
{ {
foreach (IPin nodePin in Node.Pins) foreach ((IPin? pin, List<IPin>? connections) in _pinConnections)
{ {
if (!_pinConnections.TryGetValue(nodePin, out List<IPin>? connections))
continue;
foreach (IPin connection in connections) foreach (IPin connection in connections)
nodePin.ConnectTo(connection); pin.ConnectTo(connection);
}
foreach (IPinCollection nodePinCollection in Node.PinCollections)
{
foreach (IPin nodePin in nodePinCollection)
{
if (!_pinConnections.TryGetValue(nodePin, out List<IPin>? connections))
continue;
foreach (IPin connection in connections)
nodePin.ConnectTo(connection);
}
} }
_pinConnections.Clear(); _pinConnections.Clear();

View File

@ -1,3 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Windows.Ninject; using Artemis.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers.Input; using Artemis.UI.Windows.Providers.Input;
@ -13,14 +21,23 @@ namespace Artemis.UI.Windows;
public class App : Application public class App : Application
{ {
private StandardKernel? _kernel;
private bool _shutDown;
// ReSharper disable NotAccessedField.Local // ReSharper disable NotAccessedField.Local
private ApplicationStateManager? _applicationStateManager; private ApplicationStateManager? _applicationStateManager;
private Mutex? _artemisMutex;
// ReSharper restore NotAccessedField.Local // ReSharper restore NotAccessedField.Local
private StandardKernel? _kernel;
public override void Initialize() public override void Initialize()
{ {
// If Artemis is already running, bring it to foreground and stop this process
if (FocusExistingInstance())
{
_shutDown = true;
Environment.Exit(1);
}
_kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule()); _kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule());
Program.CreateLogger(_kernel); Program.CreateLogger(_kernel);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
@ -29,7 +46,7 @@ public class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode) if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return; return;
ArtemisBootstrapper.Initialize(); ArtemisBootstrapper.Initialize();
@ -42,4 +59,55 @@ public class App : Application
IInputService inputService = standardKernel.Get<IInputService>(); IInputService inputService = standardKernel.Get<IInputService>();
inputService.AddInputProvider(standardKernel.Get<WindowsInputProvider>()); inputService.AddInputProvider(standardKernel.Get<WindowsInputProvider>());
} }
private bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew);
return !createdNew && RemoteFocus();
}
private bool RemoteFocus()
{
// At this point we cannot read the database yet to retrieve the web server port.
// Instead use the method external applications should use as well.
if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
{
KillOtherInstances();
return false;
}
string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
}
} }

View File

@ -3,12 +3,9 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Security.Principal; using System.Security.Principal;
using System.Threading;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Artemis.UI.Windows.Utilities; using Artemis.UI.Windows.Utilities;
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
@ -19,14 +16,8 @@ namespace Artemis.UI.Windows;
public class ApplicationStateManager public class ApplicationStateManager
{ {
private readonly IWindowService _windowService;
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
private Mutex? _artemisMutex;
public ApplicationStateManager(IKernel kernel, string[] startupArguments) public ApplicationStateManager(IKernel kernel, string[] startupArguments)
{ {
_windowService = kernel.Get<IWindowService>();
StartupArguments = startupArguments; StartupArguments = startupArguments;
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
@ -51,72 +42,6 @@ public class ApplicationStateManager
public string[] StartupArguments { get; } public string[] StartupArguments { get; }
public bool IsElevated { get; } public bool IsElevated { get; }
public bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew);
if (createdNew)
return false;
return RemoteFocus();
}
public void DisplayException(Exception e)
{
try
{
_windowService.ShowExceptionDialog("An unhandled exception occured", e);
}
catch
{
// ignored, we tried
}
}
private bool RemoteFocus()
{
// At this point we cannot read the database yet to retrieve the web server port.
// Instead use the method external applications should use as well.
if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
{
KillOtherInstances();
return false;
}
string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
}
private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e) private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e)
{ {
List<string> argsList = new(); List<string> argsList = new();

View File

@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive; using System.Reactive;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
@ -52,6 +55,8 @@ public static class ArtemisBootstrapper
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return; return;
Constants.StartupArguments = new ReadOnlyCollection<string>(new List<string>(desktop.Args));
// Don't shut down when the last window closes, we might still be active in the tray // Don't shut down when the last window closes, we might still be active in the tray
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// Create the root view model that drives the UI // Create the root view model that drives the UI

View File

@ -47,12 +47,12 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
private void OnActivated(object? sender, EventArgs e) private void OnActivated(object? sender, EventArgs e)
{ {
ViewModel.Focused(); ViewModel?.Focused();
} }
private void OnDeactivated(object? sender, EventArgs e) private void OnDeactivated(object? sender, EventArgs e)
{ {
ViewModel.Unfocused(); ViewModel?.Unfocused();
} }
private void InitializeComponent() private void InitializeComponent()

View File

@ -54,8 +54,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
_defaultTitleBarViewModel = defaultTitleBarViewModel; _defaultTitleBarViewModel = defaultTitleBarViewModel;
_sidebarVmFactory = sidebarVmFactory; _sidebarVmFactory = sidebarVmFactory;
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
coreService.StartupArguments = _lifeTime.Args.ToList();
mainWindowService.ConfigureMainWindowProvider(this); mainWindowService.ConfigureMainWindowProvider(this);
DisplayAccordingToSettings(); DisplayAccordingToSettings();
@ -99,8 +98,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
private void DisplayAccordingToSettings() private void DisplayAccordingToSettings()
{ {
bool autoRunning = _coreService.StartupArguments.Contains("--autorun"); bool autoRunning = Constants.StartupArguments.Contains("--autorun");
bool minimized = _coreService.StartupArguments.Contains("--minimized"); bool minimized = Constants.StartupArguments.Contains("--minimized");
bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value; bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if ((autoRunning && !showOnAutoRun) || minimized) if ((autoRunning && !showOnAutoRun) || minimized)

View File

@ -13,364 +13,379 @@
x:DataType="settings:GeneralTabViewModel"> x:DataType="settings:GeneralTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="15" MaxWidth="1000"> <StackPanel Margin="15" MaxWidth="1000">
<!-- General settings --> <!-- General settings -->
<TextBlock Classes="h4" Margin="0 15"> <TextBlock Classes="h4" Margin="0 15">
General General
</TextBlock> </TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel> <StackPanel>
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}"> <StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock> <TextBlock>Auto-run on startup</TextBlock>
</StackPanel> </StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/> <ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" OnContent="Yes"
</Grid> OffContent="No" />
<Separator Classes="card-separator" /> </Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock> <TextBlock>Hide window on auto-run</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/> <ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" OnContent="Yes"
</StackPanel> OffContent="No" />
</Grid> </StackPanel>
<Separator Classes="card-separator" /> </Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock> <TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis. Set the amount of seconds to wait before auto-running Artemis.
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120"> <controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
<Interaction.Behaviors> <Interaction.Behaviors>
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}"/> <behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}" />
</Interaction.Behaviors> </Interaction.Behaviors>
</controls:NumberBox> </controls:NumberBox>
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock> <TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Separator Classes="card-separator" /> <Separator Classes="card-separator" />
</StackPanel> </StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Log level
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the logging level, a higher logging level will result in more log files.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<shared:EnumComboBox Width="150" Value="{CompiledBinding CoreLoggingLevel.Value}" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Logs Log level
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Opens the directory where logs are stored. Sets the logging level, a higher logging level will result in more log files.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowLogs}" Width="150" Content="Show logs" /> <shared:EnumComboBox Width="150" Value="{CompiledBinding CoreLoggingLevel.Value}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> <Separator Classes="card-separator" />
</Border>
<!-- Web server settings --> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<TextBlock Classes="h4" Margin="0 15"> <StackPanel Grid.Column="0" VerticalAlignment="Center">
Web server <TextBlock>
</TextBlock> Logs
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> </TextBlock>
<StackPanel> <TextBlock Classes="subtitle">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> Opens the directory where logs are stored.
<StackPanel Grid.Column="0"> </TextBlock>
<TextBlock> </StackPanel>
Web server port <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</TextBlock> <Button Command="{CompiledBinding ShowLogs}" Width="150" Content="Show logs" />
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </StackPanel>
Artemis runs a local web server that can be used to externally interact with the application. </Grid>
</TextBlock> </StackPanel>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </Border>
This web server can only be accessed by applications running on your own computer, e.g. supported games.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:NumberBox Width="150">
<Interaction.Behaviors>
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding WebServerPort.Value}"/>
</Interaction.Behaviors>
</controls:NumberBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Update settings --> <!-- Web server settings -->
<StackPanel IsVisible="{CompiledBinding IsUpdatingSupported}"> <TextBlock Classes="h4" Margin="0 15">
<TextBlock Classes="h4" Margin="0 15"> Web server
Updating </TextBlock>
</TextBlock> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> <StackPanel>
<StackPanel> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <StackPanel Grid.Column="0">
<StackPanel Grid.Column="0"> <TextBlock>Enable web server</TextBlock>
<TextBlock> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Check for updates Artemis runs a local web server that can be used to externally interact with the application.
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running. This web server can only be accessed by applications running on your own computer, e.g. supported games.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" /> <ToggleSwitch IsChecked="{CompiledBinding WebServerEnabled.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Separator Classes="card-separator" /> <Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Auto-install updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, new updates will automatically be installed.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Update Web server port
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Use the button on the right to check for updates now. If the webserver does not work you can try changing the port to one that is available.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding CheckForUpdate}" Width="150" Content="Check now" /> <controls:NumberBox Width="150">
</StackPanel> <Interaction.Behaviors>
</Grid> <behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding WebServerPort.Value}" />
</StackPanel> </Interaction.Behaviors>
</Border> </controls:NumberBox>
</StackPanel> </StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Profile editor settings --> <!-- Update settings -->
<TextBlock Classes="h4" Margin="0 15"> <StackPanel IsVisible="{CompiledBinding IsUpdatingSupported}">
Profile editor <TextBlock Classes="h4" Margin="0 15">
</TextBlock> Updating
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> </TextBlock>
<StackPanel> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <StackPanel>
<StackPanel Grid.Column="0"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<TextBlock> <StackPanel Grid.Column="0">
Show condition data model values <TextBlock>
</TextBlock> Check for updates
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </TextBlock>
While selecting a condition target, show the current values of the data model. <TextBlock Classes="subtitle" TextWrapping="Wrap">
</TextBlock> If enabled, we'll check for updates on startup and periodically while running.
</StackPanel> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> </StackPanel>
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" /> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</StackPanel> <ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</Grid> </StackPanel>
<Separator Classes="card-separator" /> </Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Default brush Auto-install updates
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the default brush that is applied to new layers If enabled, new updates will automatically be installed.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<StackPanel.Styles> <ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
<Style Selector="ComboBox.brush /template/ ContentControl#ContentPresenter"> </StackPanel>
<Setter Property="ContentTemplate"> </Grid>
<Setter.Value> <Separator Classes="card-separator" />
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Height="20" Width="20" VerticalAlignment="Center" Margin="0 0 5 0"/>
<TextBlock Text="{CompiledBinding DisplayName}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Styles>
<ComboBox Classes="brush"
Width="200"
HorizontalAlignment="Left"
Items="{CompiledBinding LayerBrushDescriptors}"
SelectedItem="{CompiledBinding SelectedLayerBrushDescriptor}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
<avalonia:MaterialIcon Grid.Row="0"
Grid.RowSpan="2"
Kind="{CompiledBinding Icon}"
Height="20"
Width="20"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{CompiledBinding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Grid.Row="1" Grid.Column="1" Text="{CompiledBinding Description}" TextWrapping="Wrap" MaxWidth="350" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Rendering settings --> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<TextBlock Classes="h4" Margin="0 15"> <StackPanel Grid.Column="0" VerticalAlignment="Center">
Rendering <TextBlock>
</TextBlock> Update
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> </TextBlock>
<StackPanel> <TextBlock Classes="subtitle">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> Use the button on the right to check for updates now.
<StackPanel Grid.Column="0"> </TextBlock>
<TextBlock> </StackPanel>
Preferred render method <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</TextBlock> <Button Command="{CompiledBinding CheckForUpdate}" Width="150" Content="Check now" />
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </StackPanel>
Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration. </Grid>
</TextBlock> </StackPanel>
</StackPanel> </Border>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> </StackPanel>
<ComboBox Width="150"
SelectedItem="{CompiledBinding CorePreferredGraphicsContext.Value}"
Items="{CompiledBinding GraphicsContexts}"/>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <!-- Profile editor settings -->
<StackPanel Grid.Column="0"> <TextBlock Classes="h4" Margin="0 15">
<TextBlock> Profile editor
Render scale </TextBlock>
</TextBlock> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <StackPanel>
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces. <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
</TextBlock> <StackPanel Grid.Column="0">
</StackPanel> <TextBlock>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> Show condition data model values
<ComboBox Width="150" </TextBlock>
SelectedItem="{CompiledBinding SelectedRenderScale}" <TextBlock Classes="subtitle" TextWrapping="Wrap">
Items="{CompiledBinding RenderScales}"> While selecting a condition target, show the current values of the data model.
<ComboBox.ItemTemplate> </TextBlock>
<DataTemplate> </StackPanel>
<TextBlock Text="{CompiledBinding Display}" /> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</DataTemplate> <ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</ComboBox.ItemTemplate> </StackPanel>
</ComboBox> </Grid>
</StackPanel> <Separator Classes="card-separator" />
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Target frame rate Default brush
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations. Sets the default brush that is applied to new layers
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </StackPanel>
The options past 45 FPS are mostly useless unless you are using a custom device. <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</TextBlock> <StackPanel.Styles>
</StackPanel> <Style Selector="ComboBox.brush /template/ ContentControl#ContentPresenter">
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <Setter Property="ContentTemplate">
<ComboBox Width="150" <Setter.Value>
SelectedItem="{CompiledBinding SelectedTargetFrameRate}" <DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
Items="{CompiledBinding TargetFrameRates}"> <StackPanel Orientation="Horizontal">
<ComboBox.ItemTemplate> <avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Height="20" Width="20" VerticalAlignment="Center" Margin="0 0 5 0" />
<DataTemplate> <TextBlock Text="{CompiledBinding DisplayName}" VerticalAlignment="Center" />
<TextBlock Text="{CompiledBinding Display}" /> </StackPanel>
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </Setter.Value>
</ComboBox> </Setter>
</StackPanel> </Style>
</Grid> </StackPanel.Styles>
</StackPanel> <ComboBox Classes="brush"
</Border> Width="200"
HorizontalAlignment="Left"
Items="{CompiledBinding LayerBrushDescriptors}"
SelectedItem="{CompiledBinding SelectedLayerBrushDescriptor}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
<avalonia:MaterialIcon Grid.Row="0"
Grid.RowSpan="2"
Kind="{CompiledBinding Icon}"
Height="20"
Width="20"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{CompiledBinding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Grid.Row="1" Grid.Column="1" Text="{CompiledBinding Description}" TextWrapping="Wrap" MaxWidth="350" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Tools --> <!-- Rendering settings -->
<TextBlock Classes="h4" Margin="0 15"> <TextBlock Classes="h4" Margin="0 15">
Tools Rendering
</TextBlock> </TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel> <StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Setup wizard Preferred render method
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Opens the startup wizard usually shown when Artemis first starts. Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowSetupWizard}" Width="150" Content="Show wizard" /> <ComboBox Width="150"
</StackPanel> SelectedItem="{CompiledBinding CorePreferredGraphicsContext.Value}"
</Grid> Items="{CompiledBinding GraphicsContexts}" />
<Separator Classes="card-separator" /> </StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Debugger Render scale
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Use the debugger to see the raw image Artemis is rendering on the surface. Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowDebugger}" Width="150" Content="Show debugger" /> <ComboBox Width="150"
</StackPanel> SelectedItem="{CompiledBinding SelectedRenderScale}"
</Grid> Items="{CompiledBinding RenderScales}">
<Separator Classes="card-separator" /> <ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{CompiledBinding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0">
<TextBlock> <TextBlock>
Application files Target frame rate
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Opens the directory where application files like plugins and settings are stored. Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations.
</TextBlock> </TextBlock>
</StackPanel> <TextBlock Classes="subtitle" TextWrapping="Wrap">
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> The options past 45 FPS are mostly useless unless you are using a custom device.
<Button Command="{CompiledBinding ShowDataFolder}" Width="150" Content="Show app files" /> </TextBlock>
</StackPanel> </StackPanel>
</Grid> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</StackPanel> <ComboBox Width="150"
</Border> SelectedItem="{CompiledBinding SelectedTargetFrameRate}"
</StackPanel> Items="{CompiledBinding TargetFrameRates}">
</ScrollViewer> <ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{CompiledBinding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Tools -->
<TextBlock Classes="h4" Margin="0 15">
Tools
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Setup wizard
</TextBlock>
<TextBlock Classes="subtitle">
Opens the startup wizard usually shown when Artemis first starts.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowSetupWizard}" Width="150" Content="Show wizard" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Debugger
</TextBlock>
<TextBlock Classes="subtitle">
Use the debugger to see the raw image Artemis is rendering on the surface.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowDebugger}" Width="150" Content="Show debugger" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Application files
</TextBlock>
<TextBlock Classes="subtitle">
Opens the directory where application files like plugins and settings are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowDataFolder}" Width="150" Content="Show app files" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl> </UserControl>

View File

@ -149,6 +149,7 @@ public class GeneralTabViewModel : ActivatableViewModelBase
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25); public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30); public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<bool> WebServerEnabled => _settingsService.GetSetting("WebServer.Enabled", true);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696); public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
private void ExecuteShowLogs() private void ExecuteShowLogs()

View File

@ -7,6 +7,7 @@
xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.CableView" x:Class="Artemis.UI.Screens.VisualScripting.CableView"
x:DataType="visualScripting:CableViewModel" x:DataType="visualScripting:CableViewModel"
@ -79,6 +80,9 @@
<DataTemplate DataType="core:Numeric"> <DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/> <TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="collections:IList">
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)'}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/> <TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate> </DataTemplate>

View File

@ -38,7 +38,8 @@
Grid.Row="1" Grid.Row="1"
Items="{CompiledBinding Categories}" Items="{CompiledBinding Categories}"
IsVisible="{CompiledBinding Categories.Count}" IsVisible="{CompiledBinding Categories.Count}"
SelectedItem="{CompiledBinding SelectedNode}"> SelectedItem="{CompiledBinding SelectedNode}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem"> <Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="True" /> <Setter Property="IsExpanded" Value="True" />
@ -47,8 +48,8 @@
<TreeView.DataTemplates> <TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type core:NodeData}"> <TreeDataTemplate DataType="{x:Type core:NodeData}">
<StackPanel Margin="-15 1 0 1" Background="Transparent" PointerReleased="InputElement_OnPointerReleased"> <StackPanel Margin="-15 1 0 1" Background="Transparent" PointerReleased="InputElement_OnPointerReleased">
<TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}"></TextBlock> <TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}" TextWrapping="Wrap"></TextBlock>
<TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}"></TextBlock> <TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
</StackPanel> </StackPanel>
</TreeDataTemplate> </TreeDataTemplate>
<TreeDataTemplate ItemsSource="{Binding Items}"> <TreeDataTemplate ItemsSource="{Binding Items}">

View File

@ -10,6 +10,7 @@ using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands; using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia; using Avalonia;
using DynamicData; using DynamicData;
using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting; namespace Artemis.UI.Screens.VisualScripting;
@ -35,6 +36,11 @@ public class NodePickerViewModel : ActivatableViewModelBase
nodeSourceList.Connect() nodeSourceList.Connect()
.Filter(nodeFilter) .Filter(nodeFilter)
.Sort(SortExpressionComparer<NodeData>
.Descending(d => d.Category == "Data Model")
.ThenByDescending(d => d.Category == "Static")
.ThenByAscending(d => d.Category)
.ThenByAscending(d => d.Name))
.GroupWithImmutableState(n => n.Category) .GroupWithImmutableState(n => n.Category)
.Bind(out ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> categories) .Bind(out ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> categories)
.Subscribe(); .Subscribe();

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Linq; using System.Linq;
using System.Reflection;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Controllers; using Artemis.UI.Controllers;
@ -106,6 +107,9 @@ public class RegistrationService : IRegistrationService
_nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF)); _nodeService.RegisterTypeColor(Constants.CorePlugin, typeof(Enum), new SKColor(0xFF1E90FF));
foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface)) foreach (Type nodeType in typeof(SumNumericsNode).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface))
_nodeService.RegisterNodeType(Constants.CorePlugin, nodeType); {
if (nodeType.GetCustomAttribute(typeof(NodeAttribute)) != null)
_nodeService.RegisterNodeType(Constants.CorePlugin, nodeType);
}
} }
} }

View File

@ -0,0 +1,57 @@
using Artemis.Core;
using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.VisualScripting.Nodes.Color;
[Node("Lerp (Color)", "Interpolates linear between the two colors A and B", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class LerpSKColorNode : Node
{
#region Properties & Fields
public InputPin<SKColor> A { get; }
public InputPin<SKColor> B { get; }
public InputPin<Numeric> T { get; }
public OutputPin<SKColor> Result { get; }
#endregion
#region Constructors
public LerpSKColorNode()
: base("Lerp", "Interpolates linear between the two values A and B")
{
A = CreateInputPin<SKColor>("A");
B = CreateInputPin<SKColor>("B");
T = CreateInputPin<Numeric>("T");
Result = CreateOutputPin<SKColor>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate()
{
SKColor a = A.Value;
SKColor b = B.Value;
float t = ((float)T.Value).Clamp(0f, 1f);
float aAlpha = a.Alpha.GetPercentageFromByteValue();
float aRed = a.Red.GetPercentageFromByteValue();
float aGreen = a.Green.GetPercentageFromByteValue();
float aBlue = a.Blue.GetPercentageFromByteValue();
float alpha = ((b.Alpha.GetPercentageFromByteValue() - aAlpha) * t) + aAlpha;
float red = ((b.Red.GetPercentageFromByteValue() - aRed) * t) + aRed;
float green = ((b.Green.GetPercentageFromByteValue() - aGreen) * t) + aGreen;
float blue = ((b.Blue.GetPercentageFromByteValue() - aBlue) * t) + aBlue;
Result.Value = new SKColor(red.GetByteValueFromPercentage(), green.GetByteValueFromPercentage(), blue.GetByteValueFromPercentage(), alpha.GetByteValueFromPercentage());
}
#endregion
}

View File

@ -0,0 +1,38 @@
using Artemis.Core;
using SkiaSharp;
namespace Artemis.VisualScripting.Nodes.Color;
[Node("RGB Color", "Creates a color from red, green and blue values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))]
public class RgbSKColorNode : Node
{
#region Properties & Fields
public InputPin<Numeric> R { get; set; }
public InputPin<Numeric> G { get; set; }
public InputPin<Numeric> B { get; set; }
public OutputPin<SKColor> Output { get; }
#endregion
#region Constructors
public RgbSKColorNode()
: base("RGB Color", "Creates a color from red, green and blue values")
{
R = CreateInputPin<Numeric>("R");
G = CreateInputPin<Numeric>("G");
B = CreateInputPin<Numeric>("B");
Output = CreateOutputPin<SKColor>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate() => Output.Value = new SKColor(R.Value, G.Value, B.Value);
#endregion
}

View File

@ -3,7 +3,7 @@ using Artemis.VisualScripting.Nodes.External.Screens;
namespace Artemis.VisualScripting.Nodes.External; namespace Artemis.VisualScripting.Nodes.External;
[Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")] // [Node("Layer/Folder Property", "Outputs the property of a selected layer or folder", "External")]
public class LayerPropertyNode : Node<LayerPropertyNodeEntity, LayerPropertyNodeCustomViewModel> public class LayerPropertyNode : Node<LayerPropertyNodeEntity, LayerPropertyNodeCustomViewModel>
{ {
private readonly object _layerPropertyLock = new(); private readonly object _layerPropertyLock = new();

View File

@ -0,0 +1,46 @@
using System.Collections;
using Artemis.Core;
using Artemis.VisualScripting.Nodes.List.Screens;
namespace Artemis.VisualScripting.Nodes.List;
[Node("List Operator (Simple)", "Checks if any/all/no value in the input list matches the input value", "List", InputType = typeof(IEnumerable), OutputType = typeof(bool))]
public class ListOperatorNode : Node<ListOperator, ListOperatorNodeCustomViewModel>
{
public ListOperatorNode() : base("List Operator", "Checks if any/all/no value in the input list matches the input value")
{
InputList = CreateInputPin<IList>();
InputValue = CreateInputPin<object>();
Ouput = CreateOutputPin<bool>();
}
public InputPin<IList> InputList { get; }
public InputPin<object> InputValue { get; }
public OutputPin<bool> Ouput { get; }
/// <inheritdoc />
public override void Evaluate()
{
if (InputList.Value == null)
{
Ouput.Value = Storage == ListOperator.None;
return;
}
object? input = InputValue.Value;
if (Storage == ListOperator.Any)
Ouput.Value = InputList.Value.Cast<object>().Any(v => v.Equals(input));
else if (Storage == ListOperator.All)
Ouput.Value = InputList.Value.Cast<object>().All(v => v.Equals(input));
else if (Storage == ListOperator.All)
Ouput.Value = InputList.Value.Cast<object>().All(v => !v.Equals(input));
}
}
public enum ListOperator
{
Any,
All,
None
}

View File

@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.List.Screens"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.List.Screens.ListOperatorNodeCustomView"
x:DataType="screens:ListOperatorNodeCustomViewModel">
<shared:EnumComboBox Value="{CompiledBinding CurrentValue}" Classes="condensed"/>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.List.Screens;
public partial class ListOperatorNodeCustomView : ReactiveUserControl<ListOperatorNodeCustomViewModel>
{
public ListOperatorNodeCustomView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,27 @@
using Artemis.Core;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.VisualScripting;
using ReactiveUI;
namespace Artemis.VisualScripting.Nodes.List.Screens;
public class ListOperatorNodeCustomViewModel : CustomNodeViewModel
{
private readonly ListOperatorNode _node;
private readonly INodeEditorService _nodeEditorService;
public ListOperatorNodeCustomViewModel(ListOperatorNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
{
_node = node;
_nodeEditorService = nodeEditorService;
NodeModified += (_, _) => this.RaisePropertyChanged(nameof(CurrentValue));
}
public ListOperator CurrentValue
{
get => _node.Storage;
set => _nodeEditorService.ExecuteCommand(Script, new UpdateStorage<ListOperator>(_node, value));
}
}

View File

@ -0,0 +1,39 @@
using Artemis.Core;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Clamp", "Clamps the value to be in between min and max", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class ClampNode : Node
{
#region Properties & Fields
public InputPin<Numeric> Value { get; }
public InputPin<Numeric> Min { get; }
public InputPin<Numeric> Max { get; }
public OutputPin<Numeric> Result { get; }
#endregion
#region Constructors
public ClampNode()
: base("Clamp", "Clamps the value to be in between min and max")
{
Value = CreateInputPin<Numeric>("Value");
Min = CreateInputPin<Numeric>("Min");
Max = CreateInputPin<Numeric>("Max");
Result = CreateOutputPin<Numeric>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(Min.Value, Max.Value);
#endregion
}

View File

@ -0,0 +1,45 @@
using Artemis.Core;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Lerp", "Interpolates linear between the two values A and B", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class LerpNode : Node
{
#region Properties & Fields
public InputPin<Numeric> A { get; }
public InputPin<Numeric> B { get; }
public InputPin<Numeric> T { get; }
public OutputPin<Numeric> Result { get; }
#endregion
#region Constructors
public LerpNode()
: base("Lerp", "Interpolates linear between the two values A and B")
{
A = CreateInputPin<Numeric>("A");
B = CreateInputPin<Numeric>("B");
T = CreateInputPin<Numeric>("T");
Result = CreateOutputPin<Numeric>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate()
{
float a = A.Value;
float b = B.Value;
float t = ((float)T.Value).Clamp(0f, 1f);
Result.Value = ((b - a) * t) + a;
}
#endregion
}

View File

@ -0,0 +1,48 @@
using Artemis.Core;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Range", "Selects the best integer value in the given range by the given percentage", "Static", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class RangeNode : Node
{
#region Properties & Fields
public InputPin<Numeric> Min { get; }
public InputPin<Numeric> Max { get; }
public InputPin<Numeric> Percentage { get; }
public OutputPin<Numeric> Result { get; }
#endregion
#region Constructors
public RangeNode()
: base("Range", "Selects the best integer value in the given range by the given percentage")
{
Min = CreateInputPin<Numeric>("Min");
Max = CreateInputPin<Numeric>("Max");
Percentage = CreateInputPin<Numeric>("Percentage");
Result = CreateOutputPin<Numeric>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate()
{
int min = Min.Value;
int max = Max.Value;
float percentage = ((float)Percentage.Value).Clamp(0f, 1f);
int range = max - min;
Result.Value = percentage >= 1.0f ? max : ((int)(percentage * (range + 1)) + min);
}
#endregion
}

View File

@ -0,0 +1,35 @@
using Artemis.Core;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Saturate", "Clamps the value to be in between 0 and 1", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class SaturateNode : Node
{
#region Properties & Fields
public InputPin<Numeric> Value { get; }
public OutputPin<Numeric> Result { get; }
#endregion
#region Constructors
public SaturateNode()
: base("Saturate", "Clamps the value to be in between 0 and 1")
{
Value = CreateInputPin<Numeric>();
Result = CreateOutputPin<Numeric>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate() => Result.Value = ((float)Value.Value).Clamp(0f, 1f);
#endregion
}

View File

@ -0,0 +1,32 @@
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.Static;
[Node("Random", "Generates a random value between 0 and 1", "Static", OutputType = typeof(Numeric))]
public class RandomNumericValueNode : Node
{
#region Properties & Fields
private static readonly Random RANDOM = new();
public OutputPin<Numeric> Output { get; }
#endregion
#region Constructors
public RandomNumericValueNode()
: base("Random", "Generates a random value between 0 and 1")
{
Output = CreateOutputPin<Numeric>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate() => Output.Value = RANDOM.NextSingle();
#endregion
}

View File

@ -6,6 +6,7 @@
xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp" xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:converters1="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:converters1="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.Static.Screens.DisplayValueNodeCustomView" x:Class="Artemis.VisualScripting.Nodes.Static.Screens.DisplayValueNodeCustomView"
x:DataType="screens:DisplayValueNodeCustomViewModel"> x:DataType="screens:DisplayValueNodeCustomViewModel">
@ -43,6 +44,9 @@
<DataTemplate DataType="core:Numeric"> <DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/> <TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="collections:IList">
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)'}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/> <TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate> </DataTemplate>

View File

@ -3,13 +3,13 @@ using Artemis.VisualScripting.Nodes.Static.Screens;
namespace Artemis.VisualScripting.Nodes.Static; namespace Artemis.VisualScripting.Nodes.Static;
[Node("String-Value", "Outputs a configurable static string value.", "Static", OutputType = typeof(string))] [Node("Text-Value", "Outputs a configurable static text value.", "Static", OutputType = typeof(string))]
public class StaticStringValueNode : Node<string, StaticStringValueNodeCustomViewModel> public class StaticStringValueNode : Node<string, StaticStringValueNodeCustomViewModel>
{ {
#region Constructors #region Constructors
public StaticStringValueNode() public StaticStringValueNode()
: base("String", "Outputs a configurable string value.") : base("Text", "Outputs a configurable text value.")
{ {
Output = CreateOutputPin<string>(); Output = CreateOutputPin<string>();
} }

View File

@ -2,7 +2,7 @@
namespace Artemis.VisualScripting.Nodes.Text; namespace Artemis.VisualScripting.Nodes.Text;
[Node("Format", "Formats the input string.", "Text", InputType = typeof(object), OutputType = typeof(string))] [Node("Format", "Formats the input text.", "Text", InputType = typeof(object), OutputType = typeof(string))]
public class StringFormatNode : Node public class StringFormatNode : Node
{ {
#region Constructors #region Constructors

View File

@ -0,0 +1,24 @@
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.Text;
[Node("Text Length", "Outputs the length of the input text.",
"Text", InputType = typeof(string), OutputType = typeof(Numeric))]
public class StringLengthNode : Node
{
public StringLengthNode()
: base("Text Length", "Outputs text length.")
{
Input1 = CreateInputPin<string>();
Result = CreateOutputPin<Numeric>();
}
public InputPin<string> Input1 { get; }
public OutputPin<Numeric> Result { get; }
public override void Evaluate()
{
Result.Value = Input1.Value == null ? new Numeric(0) : new Numeric(Input1.Value.Length);
}
}

View File

@ -0,0 +1,25 @@
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.Text;
[Node("Text is empty", "Outputs true if the input text is empty, false if it contains any text.",
"Text", InputType = typeof(string), OutputType = typeof(bool))]
public class StringNullOrEmptyNode : Node
{
public StringNullOrEmptyNode()
: base("Text is empty", "Outputs true if empty")
{
Input1 = CreateInputPin<string>();
Output1 = CreateOutputPin<bool>();
}
public InputPin<string> Input1 { get; }
public OutputPin<bool> Output1 { get; }
public override void Evaluate()
{
bool isNullOrWhiteSpace = string.IsNullOrWhiteSpace(Input1.Value);
Output1.Value = isNullOrWhiteSpace;
}
}

View File

@ -0,0 +1,117 @@
using System.Diagnostics;
using Artemis.Core;
using Artemis.Core.Events;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Timing;
[Node("Delay", "Delays the resolution of the input pin(s) for the given time after each update", "Timing", InputType = typeof(object), OutputType = typeof(object))]
public class DelayNode : Node
{
#region Properties & Fields
private long _lastUpdateTimestamp = 0;
public InputPin<Numeric> Delay { get; }
public InputPinCollection Input { get; }
public OutputPin<bool> IsUpdated { get; }
public OutputPin<Numeric> NextUpdateTime { get; }
private Dictionary<IPin, OutputPin> _pinPairs = new();
#endregion
#region Constructors
public DelayNode()
: base("Delay", "Delays the resolution of the input pin(s) for the given time after each update")
{
Delay = CreateInputPin<Numeric>("Delay");
Input = CreateInputPinCollection(typeof(object), initialCount: 0);
IsUpdated = CreateOutputPin<bool>("Updated");
NextUpdateTime = CreateOutputPin<Numeric>("Next Update");
Input.PinAdded += OnInputPinAdded;
Input.PinRemoved += OnInputPinRemoved;
Input.Add(Input.CreatePin());
}
#endregion
#region Methods
private void OnInputPinAdded(object? sender, SingleValueEventArgs<IPin> args)
{
IPin inputPin = args.Value;
_pinPairs.Add(inputPin, CreateOutputPin(typeof(object)));
inputPin.PinConnected += OnInputPinConnected;
inputPin.PinDisconnected += OnInputPinDisconnected;
UpdatePinNames();
}
private void OnInputPinRemoved(object? sender, SingleValueEventArgs<IPin> args)
{
IPin inputPin = args.Value;
RemovePin(_pinPairs[inputPin]);
_pinPairs.Remove(inputPin);
inputPin.PinConnected -= OnInputPinConnected;
inputPin.PinDisconnected -= OnInputPinDisconnected;
UpdatePinNames();
}
private void OnInputPinConnected(object? sender, SingleValueEventArgs<IPin> args)
{
if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return;
OutputPin outputPin = _pinPairs[inputPin];
outputPin.ChangeType(args.Value.Type);
}
private void OnInputPinDisconnected(object? sender, SingleValueEventArgs<IPin> args)
{
if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return;
OutputPin outputPin = _pinPairs[inputPin];
outputPin.ChangeType(typeof(object));
}
private void UpdatePinNames()
{
int counter = 1;
foreach (IPin inputPin in Input.Pins)
{
string name = counter.ToString();
inputPin.Name = name;
_pinPairs[inputPin].Name = name;
counter++;
}
}
/// <inheritdoc />
public override void Evaluate()
{
double nextUpdateIn = Delay.Value - TimerHelper.GetElapsedTime(_lastUpdateTimestamp);
NextUpdateTime.Value = nextUpdateIn;
if (nextUpdateIn <= 0)
{
IsUpdated.Value = true;
foreach ((IPin input, OutputPin output) in _pinPairs)
output.Value = input.PinValue;
_lastUpdateTimestamp = Stopwatch.GetTimestamp();
}
else
IsUpdated.Value = false;
}
#endregion
}

View File

@ -0,0 +1,41 @@
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.Timing;
[Node("Edge", "Outputs true on each edge when the input changes", "Timing", InputType = typeof(bool), OutputType = typeof(bool))]
public class EdgeNode : Node
{
#region Properties & Fields
private bool _lastInput;
public InputPin<bool> Input { get; }
public OutputPin<bool> Output { get; }
#endregion
#region Constructors
public EdgeNode()
: base("Edge", "Outputs true on each edge when the input changes")
{
Input = CreateInputPin<bool>();
Output = CreateOutputPin<bool>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate()
{
bool input = Input.Value;
Output.Value = input != _lastInput;
_lastInput = input;
}
#endregion
}

View File

@ -0,0 +1,45 @@
using Artemis.Core;
namespace Artemis.VisualScripting.Nodes.Timing;
[Node("FlipFlop", "Inverts the output when the input changes from false to true", "Timing", InputType = typeof(bool), OutputType = typeof(bool))]
public class FlipFlopNode : Node
{
#region Properties & Fields
private bool _lastInput;
private bool _currentValue;
public InputPin<bool> Input { get; }
public OutputPin<bool> Output { get; }
#endregion
#region Constructors
public FlipFlopNode()
: base("FlipFlop", "Inverts the output when the input changes from false to true")
{
Input = CreateInputPin<bool>();
Output = CreateOutputPin<bool>();
}
#endregion
#region Methods
/// <inheritdoc />
public override void Evaluate()
{
bool input = Input.Value;
if (input && !_lastInput)
{
_currentValue = !_currentValue;
Output.Value = _currentValue;
}
_lastInput = input;
}
#endregion
}

View File

@ -0,0 +1,113 @@
using System.Diagnostics;
using Artemis.Core;
using Artemis.Core.Events;
using RGB.NET.Core;
namespace Artemis.VisualScripting.Nodes.Timing;
[Node("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.", "Timing", InputType = typeof(object), OutputType = typeof(object))]
public class LatchNode : Node
{
#region Properties & Fields
private long _lastUpdateTimestamp = 0;
public InputPin<bool> Control { get; }
public InputPinCollection Input { get; }
//TODO DarthAffe 21.08.2022: Find something to output to aling in- and outputs
public OutputPin<Numeric> LastUpdateTime { get; }
private Dictionary<IPin, OutputPin> _pinPairs = new();
#endregion
#region Constructors
public LatchNode()
: base("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.")
{
Control = CreateInputPin<bool>("Control");
Input = CreateInputPinCollection(typeof(object), initialCount: 0);
LastUpdateTime = CreateOutputPin<Numeric>("Last Update");
Input.PinAdded += OnInputPinAdded;
Input.PinRemoved += OnInputPinRemoved;
Input.Add(Input.CreatePin());
}
#endregion
#region Methods
private void OnInputPinAdded(object? sender, SingleValueEventArgs<IPin> args)
{
IPin inputPin = args.Value;
_pinPairs.Add(inputPin, CreateOutputPin(typeof(object)));
inputPin.PinConnected += OnInputPinConnected;
inputPin.PinDisconnected += OnInputPinDisconnected;
UpdatePinNames();
}
private void OnInputPinRemoved(object? sender, SingleValueEventArgs<IPin> args)
{
IPin inputPin = args.Value;
RemovePin(_pinPairs[inputPin]);
_pinPairs.Remove(inputPin);
inputPin.PinConnected -= OnInputPinConnected;
inputPin.PinDisconnected -= OnInputPinDisconnected;
UpdatePinNames();
}
private void OnInputPinConnected(object? sender, SingleValueEventArgs<IPin> args)
{
if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return;
OutputPin outputPin = _pinPairs[inputPin];
outputPin.ChangeType(args.Value.Type);
}
private void OnInputPinDisconnected(object? sender, SingleValueEventArgs<IPin> args)
{
if (sender is not IPin inputPin || !_pinPairs.ContainsKey(inputPin)) return;
OutputPin outputPin = _pinPairs[inputPin];
outputPin.ChangeType(typeof(object));
}
private void UpdatePinNames()
{
int counter = 1;
foreach (IPin inputPin in Input.Pins)
{
string name = counter.ToString();
inputPin.Name = name;
_pinPairs[inputPin].Name = name;
counter++;
}
}
/// <inheritdoc />
public override void Evaluate()
{
if (Control.Value)
{
foreach ((IPin input, OutputPin output) in _pinPairs)
output.Value = input.PinValue;
LastUpdateTime.Value = 0;
_lastUpdateTimestamp = Stopwatch.GetTimestamp();
}
else
LastUpdateTime.Value = TimerHelper.GetElapsedTime(_lastUpdateTimestamp);
}
#endregion
}

View File

@ -0,0 +1,136 @@
using Artemis.Core;
using Artemis.Core.Events;
namespace Artemis.VisualScripting.Nodes.Timing;
[Node("Sequencer", "Advances on input every time the control has a rising edge (change to true)", "Timing", OutputType = typeof(object))]
public class SequencerNode : Node
{
#region Properties & Fields
private int _currentIndex;
private Type _currentType;
private bool _updating;
private IPin? _currentCyclePin;
private bool _lastInput;
public InputPin<bool> Input { get; }
public InputPinCollection CycleValues { get; }
public OutputPin Output { get; }
#endregion
#region Constructors
public SequencerNode()
: base("Sequencer", "Advances on input every time the control has a rising edge (change to true)")
{
_currentType = typeof(object);
Input = CreateInputPin<bool>("Control");
CycleValues = CreateInputPinCollection(typeof(object), "", 0);
Output = CreateOutputPin(typeof(object));
CycleValues.PinAdded += CycleValuesOnPinAdded;
CycleValues.PinRemoved += CycleValuesOnPinRemoved;
CycleValues.Add(CycleValues.CreatePin());
}
#endregion
#region Methods
public override void Evaluate()
{
bool input = Input.Value;
if (input != _lastInput)
{
_currentIndex++;
if (_currentIndex >= CycleValues.Count())
_currentIndex = 0;
_currentCyclePin = null;
}
_currentCyclePin ??= CycleValues.ElementAt(_currentIndex);
object? outputValue = _currentCyclePin.PinValue;
if (Output.Type.IsInstanceOfType(outputValue))
Output.Value = outputValue;
else if (Output.Type.IsValueType)
Output.Value = Output.Type.GetDefault()!;
_lastInput = input;
}
private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
{
e.Value.PinConnected += OnPinConnected;
e.Value.PinDisconnected += OnPinDisconnected;
_currentCyclePin = null;
}
private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs<IPin> e)
{
e.Value.PinConnected -= OnPinConnected;
e.Value.PinDisconnected -= OnPinDisconnected;
_currentCyclePin = null;
}
private void OnPinDisconnected(object? sender, SingleValueEventArgs<IPin> e) => ProcessPinDisconnected();
private void OnPinConnected(object? sender, SingleValueEventArgs<IPin> e) => ProcessPinConnected(e.Value);
private void ProcessPinConnected(IPin source)
{
if (_updating)
return;
try
{
_updating = true;
// No need to change anything if the types haven't changed
if (_currentType != source.Type)
ChangeCurrentType(source.Type);
}
finally
{
_updating = false;
}
}
private void ChangeCurrentType(Type type)
{
CycleValues.ChangeType(type);
Output.ChangeType(type);
_currentType = type;
}
private void ProcessPinDisconnected()
{
if (_updating)
return;
try
{
// If there's still a connected pin, stick to the current type
if (CycleValues.Any(v => v.ConnectedTo.Any()))
return;
ChangeCurrentType(typeof(object));
}
finally
{
_updating = false;
}
}
#endregion
}