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
pr: none
resources:
repositories:
- repository: RGBNET
type: github
endpoint: github.com_SpoinkyNL
name: DarthAffe/RGB.NET
ref: Development
pool:
vmImage: 'windows-latest'
@ -28,22 +20,13 @@ variables:
SourceVersion: $(Build.SourceVersion)
steps:
- checkout: RGBNET
path: s/RGB.NET
- checkout: self
path: s/Artemis
- task: DotNetCoreCLI@2
displayName: 'RGB.NET - Build'
displayName: 'dotnet build Artemis'
inputs:
command: 'build'
projects: '$(rgbSolution)'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'dotnet restore Artemis'
inputs:
command: 'restore'
projects: '$(artemisSolution)'
feedsToUse: '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": [
{
"files": [
"Artemis.Core/Artemis.Core.csproj",
"Artemis.UI.Shared/Artemis.UI.Shared.csproj",
"Artemis.Core/bin/net6.0/Artemis.Core.dll",
"Artemis.UI.Shared/bin/net6.0/Artemis.UI.Shared.dll",
],
"src": "../../src"
}
@ -20,14 +20,11 @@
"content": [
{
"files": [
"api/**.yml",
"api/index.md"
"api/**.yml"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
@ -52,16 +49,24 @@
}
],
"globalMetadata": {
"_appTitle": "Artemis documentation",
"_enableSearch": true
"_appTitle": "Artemis API Documentation",
"_appName": "Artemis",
"_appFaviconPath": "images/application.ico",
"_appLogoPath": "images/application.ico",
"_appFooter": "<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi-globe\" role=\"img\" aria-label=\"GitHub\" style=\"margin: 0 10\"></i>\r</a>\r<a href=\"https://github.com/Artemis-RGB/Artemis\" style=\"margin: 0 5px;\">\r <i class=\"bi-github\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://artemis-rgb.com\" style=\"margin: 0 5px;\">\r <i class=\"bi bi-layout-text-sidebar-reverse\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/\" style=\"margin: 0 5px;\">\r <i class=\"bi-chat-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>\r<a href=\"https://wiki.artemis-rgb.com/en/donating\" style=\"margin: 0 5px;\">\r <i class=\"bi-gift-fill\" role=\"img\" aria-label=\"GitHub\"></i>\r</a>",
"_copyrightFooter": "Content is available under the PolyForm Noncommercial License, by Artemis RGB.",
"_enableSearch": true,
"_disableSideFilter": false,
"_enableNewTab": true,
"_disableContribution": false,
"_disableBreadcrumb": false
},
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"default",
"templates/artemis",
"templates/material"
"templates/singulinkfx"
],
"postProcessors": [],
"markdownEngineName": "markdig",

View File

@ -11,6 +11,9 @@ apiRules:
- exclude:
uidRegex: ^Stylet
type: Type
- exclude:
uidRegex: ^Artemis\.Core\.CorePropertyChanged
type: Type
- exclude:
uidRegex: ^Artemis\.Core\.Properties
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
href: api/
homepage: api/index.md

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using Artemis.Core.JsonConverters;
@ -90,6 +91,11 @@ public static class Constants
/// </summary>
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 EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};

View File

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

View File

@ -28,11 +28,6 @@ public interface ICoreService : IArtemisService, IDisposable
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets or sets a list of startup arguments
/// </summary>
List<string> StartupArguments { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>

View File

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

View File

@ -203,17 +203,17 @@ internal class PluginManagementService : IPluginManagementService
#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");
return;
}
bool ignorePluginLock = startupArguments.Contains("--ignore-plugin-lock");
bool stayElevated = startupArguments.Contains("--force-elevation");
bool droppedAdmin = startupArguments.Contains("--dropped-admin");
bool ignorePluginLock = Constants.StartupArguments.Contains("--ignore-plugin-lock");
bool stayElevated = Constants.StartupArguments.Contains("--force-elevation");
bool droppedAdmin = Constants.StartupArguments.Contains("--dropped-admin");
if (LoadingPlugins)
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.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using EmbedIO;
@ -17,15 +18,19 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly List<WebApiControllerRegistration> _controllers;
private readonly ILogger _logger;
private readonly List<WebModuleRegistration> _modules;
private readonly PluginSetting<bool> _webServerEnabledSetting;
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;
_controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
_webServerEnabledSetting = settingsService.GetSetting("WebServer.Enabled", true);
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerEnabledSetting.SettingChanged += WebServerEnabledSettingOnSettingChanged;
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
@ -33,9 +38,9 @@ internal class WebServerService : IWebServerService, IDisposable
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)
@ -72,14 +77,23 @@ internal class WebServerService : IWebServerService, IDisposable
public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; }
public event EventHandler? WebServerStarting;
#region Web server managament
private WebServer CreateWebServer()
{
Server?.Dispose();
if (Server != null)
{
if (_cts != null)
{
_cts.Cancel();
_cts = null;
}
Server.Dispose();
OnWebServerStopped();
Server = null;
}
WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
@ -112,8 +126,20 @@ internal class WebServerService : IWebServerService, IDisposable
private void StartWebServer()
{
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();
Server.Start();
_cts = new CancellationTokenSource();
Server.Start(_cts.Token);
OnWebServerStarted();
}
#endregion
@ -276,4 +302,27 @@ internal class WebServerService : IWebServerService, IDisposable
}
#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

@ -160,6 +160,11 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
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)
{
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>
public void ChangeType(Type type)
{
if (_type == type)
if (type == _type)
return;
// Disconnect pins incompatible with the new 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));
base.ChangeType(type, ref _type);
Value = type.GetDefault();
IsNumeric = type == typeof(Numeric);
}
private void Evaluate()
@ -117,10 +110,10 @@ public sealed class InputPin : Pin
else
Value = Type.GetDefault()!;
}
else
{
else if (ConnectedTo.Count > 0)
Value = ConnectedTo[0].PinValue;
}
else
Value = null;
}
#endregion

View File

@ -85,6 +85,7 @@ public interface IPin
/// Determines whether this pin is compatible with the given type
/// </summary>
/// <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>
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)
{
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)
{
int targetPinCollectionId = -1;

View File

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

View File

@ -127,13 +127,46 @@ public abstract class Pin : CorePropertyChanged, IPin
}
/// <inheritdoc />
public bool IsTypeCompatible(Type type)
public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true)
{
return Type == type
|| (Type == typeof(Enum) && type.IsEnum)
|| (Type.IsEnum && type == typeof(Enum))
|| (Direction == PinDirection.Input && Type == typeof(object))
|| (Direction == PinDirection.Output && type == typeof(object));
|| (Direction == PinDirection.Input && type.IsAssignableTo(Type))
|| (Direction == PinDirection.Output && type.IsAssignableFrom(Type))
|| (Direction == PinDirection.Input && Type == typeof(Enum) && type.IsEnum && forgivingEnumMatching)
|| (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

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor;
@ -30,20 +31,18 @@ public class NodeConnectionStore
public void Store()
{
_pinConnections.Clear();
foreach (IPin nodePin in Node.Pins)
{
_pinConnections.Add(nodePin, new List<IPin>(nodePin.ConnectedTo));
nodePin.DisconnectAll();
}
foreach (IPinCollection nodePinCollection in Node.PinCollections)
{
foreach (IPin nodePin in nodePinCollection)
{
// 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));
// Iterate to disconnect
foreach (IPin nodePin in Node.Pins.ToList())
nodePin.DisconnectAll();
foreach (IPin nodePin in Node.PinCollections.ToList().SelectMany(nodePinCollection => nodePinCollection))
nodePin.DisconnectAll();
}
}
}
/// <summary>
@ -51,23 +50,10 @@ public class NodeConnectionStore
/// </summary>
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)
nodePin.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);
}
pin.ConnectTo(connection);
}
_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.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers.Input;
@ -13,14 +21,23 @@ namespace Artemis.UI.Windows;
public class App : Application
{
private StandardKernel? _kernel;
private bool _shutDown;
// ReSharper disable NotAccessedField.Local
private ApplicationStateManager? _applicationStateManager;
private Mutex? _artemisMutex;
// ReSharper restore NotAccessedField.Local
private StandardKernel? _kernel;
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());
Program.CreateLogger(_kernel);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
@ -29,7 +46,7 @@ public class App : Application
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode)
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return;
ArtemisBootstrapper.Initialize();
@ -42,4 +59,55 @@ public class App : Application
IInputService inputService = standardKernel.Get<IInputService>();
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.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Artemis.UI.Windows.Utilities;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
@ -19,14 +16,8 @@ namespace Artemis.UI.Windows;
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)
{
_windowService = kernel.Get<IWindowService>();
StartupArguments = startupArguments;
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
@ -51,72 +42,6 @@ public class ApplicationStateManager
public string[] StartupArguments { 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)
{
List<string> argsList = new();

View File

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.Ninject;
@ -52,6 +55,8 @@ public static class ArtemisBootstrapper
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
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
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// 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)
{
ViewModel.Focused();
ViewModel?.Focused();
}
private void OnDeactivated(object? sender, EventArgs e)
{
ViewModel.Unfocused();
ViewModel?.Unfocused();
}
private void InitializeComponent()

View File

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

View File

@ -25,7 +25,8 @@
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</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"
OffContent="No" />
</Grid>
<Separator Classes="card-separator" />
@ -34,7 +35,8 @@
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<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"
OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -100,9 +102,7 @@
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Web server port
</TextBlock>
<TextBlock>Enable web server</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Artemis runs a local web server that can be used to externally interact with the application.
</TextBlock>
@ -110,6 +110,21 @@
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">
<ToggleSwitch IsChecked="{CompiledBinding WebServerEnabled.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Web server port
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If the webserver does not work you can try changing the port to one that is available.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:NumberBox Width="150">
<Interaction.Behaviors>
@ -138,7 +153,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -153,7 +168,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -191,7 +206,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" />
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />

View File

@ -149,6 +149,7 @@ public class GeneralTabViewModel : ActivatableViewModelBase
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
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);
private void ExecuteShowLogs()

View File

@ -7,6 +7,7 @@
xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
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"
x:Class="Artemis.UI.Screens.VisualScripting.CableView"
x:DataType="visualScripting:CableViewModel"
@ -79,6 +80,9 @@
<DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate DataType="collections:IList">
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)'}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>

View File

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

View File

@ -10,6 +10,7 @@ using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting;
@ -35,6 +36,11 @@ public class NodePickerViewModel : ActivatableViewModelBase
nodeSourceList.Connect()
.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)
.Bind(out ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> categories)
.Subscribe();

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Controllers;
@ -106,6 +107,9 @@ public class RegistrationService : IRegistrationService
_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))
{
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;
[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>
{
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:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
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"
x:Class="Artemis.VisualScripting.Nodes.Static.Screens.DisplayValueNodeCustomView"
x:DataType="screens:DisplayValueNodeCustomViewModel">
@ -43,6 +44,9 @@
<DataTemplate DataType="core:Numeric">
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate DataType="collections:IList">
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)'}" FontFamily="Consolas"/>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="Consolas"/>
</DataTemplate>

View File

@ -3,13 +3,13 @@ using Artemis.VisualScripting.Nodes.Static.Screens;
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>
{
#region Constructors
public StaticStringValueNode()
: base("String", "Outputs a configurable string value.")
: base("Text", "Outputs a configurable text value.")
{
Output = CreateOutputPin<string>();
}

View File

@ -2,7 +2,7 @@
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
{
#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
}