mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge pull request #713 from Artemis-RGB/feature/avalonia
Avalonia port
This commit is contained in:
commit
a282bf3fd7
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -61,3 +61,6 @@
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
|
||||
# Display axaml files with XML highlighting
|
||||
*.axaml linguist-language=xml
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -196,3 +196,5 @@ FakesAssemblies/
|
||||
src/Artemis\.Storage/Storage\.db
|
||||
!src/Artemis.UI/screens/Settings/Debug
|
||||
docfx/docfx_project/_site/
|
||||
|
||||
src/.idea/
|
||||
|
||||
@ -4,124 +4,118 @@
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
|
||||
|
||||
trigger:
|
||||
- master
|
||||
- master
|
||||
pr: none
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: Plugins
|
||||
type: github
|
||||
endpoint: github.com_SpoinkyNL
|
||||
name: Artemis-RGB/Artemis.Plugins
|
||||
ref: master
|
||||
- repository: Plugins
|
||||
type: github
|
||||
endpoint: github.com_SpoinkyNL
|
||||
name: Artemis-RGB/Artemis.Plugins
|
||||
ref: master
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-latest'
|
||||
vmImage: "windows-latest"
|
||||
|
||||
variables:
|
||||
artemisSolution: '**/Artemis.sln'
|
||||
pluginProjects: '**/Artemis.Plugins.*.csproj'
|
||||
windowsProject: "**/Artemis.UI.Windows/Artemis.UI.Windows.csproj"
|
||||
pluginProjects: "**/Artemis.Plugins.*.csproj"
|
||||
BuildId: $(Build.BuildId)
|
||||
BuildNumber: $(Build.BuildNumber)
|
||||
SourceBranch: $(Build.SourceBranch)
|
||||
SourceVersion: $(Build.SourceVersion)
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
path: s/Artemis
|
||||
- checkout: Plugins
|
||||
path: s/Artemis.Plugins
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Artemis - Publish'
|
||||
inputs:
|
||||
command: 'publish'
|
||||
publishWebProjects: false
|
||||
projects: '$(artemisSolution)'
|
||||
arguments: '--runtime win-x64 --self-contained false --configuration Release --output $(Build.ArtifactStagingDirectory)/build /nowarn:cs1591'
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
jobs:
|
||||
- job: Windows
|
||||
steps:
|
||||
- checkout: self
|
||||
path: s/Artemis
|
||||
- checkout: Plugins
|
||||
path: s/Artemis.Plugins
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Artemis - Create buildinfo.json'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$OFS = "`r`n"
|
||||
SET-Content -Path 'buildinfo.json' -Value ('{' + $OFS + ' "BuildId": 0,' + $OFS + ' "BuildNumber": 0.0,' + $OFS + ' "SourceBranch": "",' + $OFS + ' "SourceVersion": ""' + $OFS + '}')
|
||||
workingDirectory: '$(Build.ArtifactStagingDirectory)/build'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: "Artemis - Publish"
|
||||
inputs:
|
||||
command: "publish"
|
||||
publishWebProjects: false
|
||||
projects: "$(windowsProject)"
|
||||
arguments: '--configuration Release --runtime win10-x64 --output $(Build.ArtifactStagingDirectory)/windows-build /nowarn:cs1591'
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
|
||||
- task: FileTransform@1
|
||||
displayName: 'Artemis - Populate buildinfo.json'
|
||||
inputs:
|
||||
folderPath: '$(Build.ArtifactStagingDirectory)/build'
|
||||
fileType: 'json'
|
||||
targetFiles: '**/buildinfo.json'
|
||||
- task: PowerShell@2
|
||||
displayName: "Artemis - Create buildinfo.json"
|
||||
inputs:
|
||||
targetType: "inline"
|
||||
script: |
|
||||
$OFS = "`r`n"
|
||||
SET-Content -Path 'buildinfo.json' -Value ('{' + $OFS + ' "BuildId": 0,' + $OFS + ' "BuildNumber": 0.0,' + $OFS + ' "SourceBranch": "",' + $OFS + ' "SourceVersion": ""' + $OFS + '}')
|
||||
workingDirectory: "$(Build.ArtifactStagingDirectory)/windows-build"
|
||||
|
||||
# Copy Artemis binaries to where plugin projects expect them
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Plugins - Prepare Artemis binaries'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.ArtifactStagingDirectory)/build'
|
||||
Contents: '**'
|
||||
TargetFolder: 'Artemis/src/Artemis.UI/bin/net5.0-windows'
|
||||
- task: FileTransform@1
|
||||
displayName: "Artemis - Populate buildinfo.json"
|
||||
inputs:
|
||||
folderPath: "$(Build.ArtifactStagingDirectory)/windows-build"
|
||||
fileType: "json"
|
||||
targetFiles: "**/buildinfo.json"
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Plugins - Insert build number into plugin.json'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Get-ChildItem -Recurse -Filter plugin.json |
|
||||
Foreach-Object {
|
||||
$buidNumber = "1.0.1." + $Env:BUILD_BUILDID;
|
||||
$a = Get-Content $_.FullName | ConvertFrom-Json
|
||||
$a.Version = $buidNumber;
|
||||
$a | ConvertTo-Json | Set-Content $_.FullName
|
||||
}
|
||||
workingDirectory: 'Artemis.Plugins'
|
||||
- task: PowerShell@2
|
||||
displayName: "Plugins - Insert build number into plugin.json"
|
||||
inputs:
|
||||
targetType: "inline"
|
||||
script: |
|
||||
Get-ChildItem -Recurse -Filter plugin.json |
|
||||
Foreach-Object {
|
||||
$buidNumber = "1.0.1." + $Env:BUILD_BUILDID;
|
||||
$a = Get-Content $_.FullName | ConvertFrom-Json
|
||||
$a.Version = $buidNumber;
|
||||
$a | ConvertTo-Json | Set-Content $_.FullName
|
||||
}
|
||||
workingDirectory: "Artemis.Plugins"
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Plugins - Publish'
|
||||
inputs:
|
||||
command: 'publish'
|
||||
publishWebProjects: false
|
||||
arguments: '--runtime win-x64 --configuration Release --self-contained false --output $(Build.ArtifactStagingDirectory)/build/Plugins'
|
||||
projects: '$(pluginProjects)'
|
||||
zipAfterPublish: true
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: "Plugins - Publish"
|
||||
inputs:
|
||||
command: "publish"
|
||||
publishWebProjects: false
|
||||
arguments: "--configuration Release --runtime win10-x64 --output $(Build.ArtifactStagingDirectory)/windows-build/Plugins"
|
||||
projects: "$(pluginProjects)"
|
||||
zipAfterPublish: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload build to Azure Pipelines'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/build'
|
||||
artifact: 'Artemis build'
|
||||
publishLocation: 'pipeline'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: "Upload build to Azure Pipelines"
|
||||
inputs:
|
||||
targetPath: "$(Build.ArtifactStagingDirectory)/windows-build"
|
||||
artifact: "Artemis build"
|
||||
publishLocation: "pipeline"
|
||||
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'ZIP binaries'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/build'
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/archive/artemis-build.zip'
|
||||
replaceExistingArchive: true
|
||||
- task: ArchiveFiles@2
|
||||
displayName: "ZIP binaries"
|
||||
inputs:
|
||||
rootFolderOrFile: "$(Build.ArtifactStagingDirectory)/windows-build"
|
||||
includeRootFolder: false
|
||||
archiveType: "zip"
|
||||
archiveFile: "$(Build.ArtifactStagingDirectory)/archive/artemis-build-windows.zip"
|
||||
replaceExistingArchive: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Calculate ZIP hash'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: '(Get-FileHash .\artemis-build.zip).Hash | Out-File -FilePath .\hash.txt'
|
||||
workingDirectory: '$(Build.ArtifactStagingDirectory)/archive'
|
||||
- task: PowerShell@2
|
||||
displayName: "Calculate ZIP hash"
|
||||
inputs:
|
||||
targetType: "inline"
|
||||
script: '(Get-FileHash .\artemis-build-windows.zip).Hash | Out-File -FilePath .\hash-windows.txt'
|
||||
workingDirectory: "$(Build.ArtifactStagingDirectory)/archive"
|
||||
|
||||
- task: FtpUpload@2
|
||||
displayName: 'Upload binaries to FTP'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: 'ftp://artemis-rgb.com'
|
||||
username: 'devops'
|
||||
password: '$(ftp_password)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)/archive'
|
||||
filePatterns: '**'
|
||||
remoteDirectory: '/builds.artemis-rgb.com/binaries/$(Build.SourceBranchName)/$(Build.BuildNumber)'
|
||||
clean: false
|
||||
preservePaths: true
|
||||
trustSSL: false
|
||||
- task: FtpUpload@2
|
||||
displayName: "Upload binaries to FTP"
|
||||
inputs:
|
||||
credentialsOption: "inputs"
|
||||
serverUrl: "ftp://artemis-rgb.com"
|
||||
username: "devops"
|
||||
password: "$(ftp_password)"
|
||||
rootDirectory: "$(Build.ArtifactStagingDirectory)/archive"
|
||||
filePatterns: "**"
|
||||
remoteDirectory: "/builds.artemis-rgb.com/binaries/$(Build.SourceBranchName)/$(Build.BuildNumber)"
|
||||
clean: false
|
||||
preservePaths: true
|
||||
trustSSL: false
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<AssemblyName>Artemis.UI.Console</AssemblyName>
|
||||
<RootNamespace>Artemis.UI.Console</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Humanizer.Core" Version="2.11.10" />
|
||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Ninject;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage;
|
||||
using Ninject;
|
||||
|
||||
namespace Artemis.UI.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// This is just a little experiment to show that Artemis can run without the UI and even on other OSes
|
||||
/// Some notes
|
||||
/// - Any plugin relying on WPF and/or Artemis.UI.Shared won't load
|
||||
/// - There is no input provider so key-press events and brushes won't work
|
||||
/// - Device providers using Windows SDKs won't work, OpenRGB will though!
|
||||
/// - You may need to fiddle around to get SkiaSharp binaries going
|
||||
/// - There is no UI obviously
|
||||
/// </summary>
|
||||
internal class Program
|
||||
{
|
||||
private static readonly AutoResetEvent Closing = new(false);
|
||||
|
||||
protected static void OnExit(object sender, ConsoleCancelEventArgs args)
|
||||
{
|
||||
Closing.Set();
|
||||
}
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
StorageManager.CreateBackup(Constants.DataFolder);
|
||||
|
||||
Utilities.PrepareFirstLaunch();
|
||||
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
|
||||
StandardKernel kernel = new() {Settings = {InjectNonPublic = true}};
|
||||
kernel.Load<CoreModule>();
|
||||
|
||||
ICoreService core = kernel.Get<ICoreService>();
|
||||
core.StartupArguments = args.ToList();
|
||||
core.IsElevated = false;
|
||||
core.Initialize();
|
||||
|
||||
System.Console.CancelKeyPress += OnExit;
|
||||
Closing.WaitOne();
|
||||
}
|
||||
|
||||
private static void UtilitiesOnShutdownRequested(object sender, EventArgs e)
|
||||
{
|
||||
Closing.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
<PublishDir>bin\publish\linux</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -1,79 +1,78 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
||||
<AssemblyTitle>Artemis.Core</AssemblyTitle>
|
||||
<Product>Artemis Core</Product>
|
||||
<Copyright>Copyright © Robert Beekman - 2020</Copyright>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DocumentationFile>bin\Artemis.Core.xml</DocumentationFile>
|
||||
<NoWarn></NoWarn>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
||||
<AssemblyTitle>Artemis.Core</AssemblyTitle>
|
||||
<Product>Artemis Core</Product>
|
||||
<Copyright>Copyright © Robert Beekman - 2020</Copyright>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DocumentationFile>bin\Artemis.Core.xml</DocumentationFile>
|
||||
<NoWarn></NoWarn>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NrtRevisionFormat>1.0-{chash:6}</NrtRevisionFormat>
|
||||
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
|
||||
<NrtResolveInformationalAttribute>true</NrtResolveInformationalAttribute>
|
||||
<NrtResolveCopyright>true</NrtResolveCopyright>
|
||||
<NrtTagMatch>v[0-9]*</NrtTagMatch>
|
||||
<NrtRemoveTagV>true</NrtRemoveTagV>
|
||||
<NrtRequiredVcs>git</NrtRequiredVcs>
|
||||
<NrtShowRevision>true</NrtShowRevision>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<NrtRevisionFormat>1.0-{chash:6}</NrtRevisionFormat>
|
||||
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
|
||||
<NrtResolveInformationalAttribute>true</NrtResolveInformationalAttribute>
|
||||
<NrtResolveCopyright>true</NrtResolveCopyright>
|
||||
<NrtTagMatch>v[0-9]*</NrtTagMatch>
|
||||
<NrtRemoveTagV>true</NrtRemoveTagV>
|
||||
<NrtRequiredVcs>git</NrtRequiredVcs>
|
||||
<NrtShowRevision>true</NrtShowRevision>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DocumentationFile>bin\Artemis.Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.11.10" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.10" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
|
||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.32" />
|
||||
<PackageReference Include="RGB.NET.Layout" Version="1.0.0-prerelease.32" />
|
||||
<PackageReference Include="RGB.NET.Presets" Version="1.0.0-prerelease.32" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Resources\intro-profile.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DocumentationFile>bin\Artemis.Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.12" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
|
||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease.46" />
|
||||
<PackageReference Include="RGB.NET.Layout" Version="1.0.0-prerelease.46" />
|
||||
<PackageReference Include="RGB.NET.Presets" Version="1.0.0-prerelease.46" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Resources\intro-profile.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="DefaultLayouts\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="DefaultLayouts\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -1,5 +1,6 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cnodes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofileconfiguration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaption/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cadaptionhints/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -43,12 +44,15 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers_005Cabstract/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel_005Cvaluechangedevent/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdisplay/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Cattributes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayershapes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csurface_005Clayout/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cvisualscripting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cvisualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mvvm/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=placeholders/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -63,6 +67,7 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cscriptingproviders_005Cscripts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=providers_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -71,6 +76,9 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cevents/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -83,4 +91,6 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwebserver_005Cjson/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=stores_005Cregistrations/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=visualscripting_005Cinterfaces/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.JsonConverters;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Core;
|
||||
@ -14,6 +15,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// The Artemis.Core assembly
|
||||
/// </summary>
|
||||
public static readonly Assembly CoreAssembly = typeof(Constants).Assembly;
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis application folder
|
||||
/// </summary>
|
||||
@ -24,10 +30,30 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public static readonly string ExecutablePath = Utilities.GetCurrentLocation();
|
||||
|
||||
/// <summary>
|
||||
/// The base path for Artemis application data folder
|
||||
/// </summary>
|
||||
public static readonly string BaseFolder = Environment.GetFolderPath(OperatingSystem.IsWindows() ? Environment.SpecialFolder.CommonApplicationData : Environment.SpecialFolder.LocalApplicationData);
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis data folder
|
||||
/// </summary>
|
||||
public static readonly string DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Artemis");
|
||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
/// </summary>
|
||||
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis plugins folder
|
||||
/// </summary>
|
||||
public static readonly string PluginsFolder = Path.Combine(DataFolder, "Plugins");
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis user layouts folder
|
||||
/// </summary>
|
||||
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
|
||||
|
||||
/// <summary>
|
||||
/// The plugin info used by core components of Artemis
|
||||
@ -62,13 +88,13 @@ namespace Artemis.Core
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertSettings = new()
|
||||
{
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
|
||||
internal static JsonSerializerSettings JsonConvertTypedSettings = new()
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.All,
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new ForgivingIntConverter()}
|
||||
Converters = new List<JsonConverter> {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EnumContainsConditionOperator : ConditionOperator<Enum, Enum>
|
||||
{
|
||||
public override string Description => "Contains";
|
||||
public override string Icon => "Contain";
|
||||
|
||||
public override bool Evaluate(Enum a, Enum b)
|
||||
{
|
||||
return a != null && b != null && a.HasFlag(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EnumNotContainsConditionOperator : ConditionOperator<Enum, Enum>
|
||||
{
|
||||
public override string Description => "Does not contain";
|
||||
public override string Icon => "FormatStrikethrough";
|
||||
|
||||
public override bool Evaluate(Enum a, Enum b)
|
||||
{
|
||||
return a != null && (b == null || !a.HasFlag(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class EqualsConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
{
|
||||
return Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class GreaterThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is greater than";
|
||||
public override string Icon => "GreaterThan";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class GreaterThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is greater than or equal to";
|
||||
public override string Icon => "GreaterThanOrEqual";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a >= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LessThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is less than";
|
||||
public override string Icon => "LessThan";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class LessThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Is less than or equal to";
|
||||
public override string Icon => "LessThanOrEqual";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NotEqualConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
{
|
||||
return !Equals(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NotNullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
public override string Description => "Is not null";
|
||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
||||
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
public override string Description => "Is null";
|
||||
public override string Icon => "Null";
|
||||
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NumberEqualsConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) < 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NumberNotEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) > 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Contains";
|
||||
public override string Icon => "Contain";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringEndsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Ends with";
|
||||
public override string Icon => "ContainEnd";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.EndsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringEqualsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Artemis.Core {
|
||||
internal class StringMatchesRegexConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Matches Regex";
|
||||
public override string Icon => "Regex";
|
||||
|
||||
public override bool Evaluate(string text, string regex)
|
||||
{
|
||||
// Ensures full match
|
||||
if (!regex.StartsWith("^"))
|
||||
regex = "^" + regex;
|
||||
if (!regex.EndsWith("$"))
|
||||
regex += "$";
|
||||
|
||||
return Regex.IsMatch(text, regex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Does not contain";
|
||||
public override string Icon => "FormatStrikethrough";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && (b == null || !a.Contains(b, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotEqualConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return !string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNotNullConditionOperator : ConditionOperator<string>
|
||||
{
|
||||
public override string Description => "Is not null";
|
||||
public override string Icon => "CheckboxMarkedCircleOutline";
|
||||
|
||||
public override bool Evaluate(string a)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringNullConditionOperator : ConditionOperator<string>
|
||||
{
|
||||
public override string Description => "Is null";
|
||||
public override string Icon => "Null";
|
||||
|
||||
public override bool Evaluate(string a)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class StringStartsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override string Description => "Starts with";
|
||||
public override string Icon => "ContainStart";
|
||||
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
return a != null && b != null && a.StartsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class FloatDataBindingConverter : FloatDataBindingConverter<float>
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <typeparam name="T">The type of layer property this converter is applied to</typeparam>
|
||||
public class FloatDataBindingConverter<T> : DataBindingConverter<T, float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="FloatDataBindingConverter{T}" /> class
|
||||
/// </summary>
|
||||
public FloatDataBindingConverter()
|
||||
{
|
||||
SupportsSum = true;
|
||||
SupportsInterpolate = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float Sum(float a, float b)
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float Interpolate(float a, float b, double progress)
|
||||
{
|
||||
float diff = b - a;
|
||||
return (float) (a + diff * progress);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ApplyValue(float value)
|
||||
{
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MaxInputValue is float max)
|
||||
value = Math.Min(value, max);
|
||||
if (DataBinding!.LayerProperty.PropertyDescription.MinInputValue is float min)
|
||||
value = Math.Max(value, min);
|
||||
|
||||
base.ApplyValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic data binding converter that acts as the bridge between a
|
||||
/// <see cref="DataBinding{TLayerProperty, TProperty}" /> and a <see cref="LayerProperty{T}" /> and does not support
|
||||
/// sum or interpolation
|
||||
/// </summary>
|
||||
public class GeneralDataBindingConverter<T> : DataBindingConverter<T, T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="GeneralDataBindingConverter{T}" /> class
|
||||
/// </summary>
|
||||
public GeneralDataBindingConverter()
|
||||
{
|
||||
SupportsSum = false;
|
||||
SupportsInterpolate = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T Sum(T a, T b)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T Interpolate(T a, T b, double progress)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class IntDataBindingConverter : IntDataBindingConverter<int>
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public class IntDataBindingConverter<T> : DataBindingConverter<T, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="IntDataBindingConverter{T}" /> class
|
||||
/// </summary>
|
||||
public IntDataBindingConverter()
|
||||
{
|
||||
SupportsSum = true;
|
||||
SupportsInterpolate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="MidpointRounding" /> mode used for rounding during interpolation. Defaults to
|
||||
/// <see cref="MidpointRounding.AwayFromZero" />
|
||||
/// </summary>
|
||||
public MidpointRounding InterpolationRoundingMode { get; set; } = MidpointRounding.AwayFromZero;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Sum(int a, int b)
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Interpolate(int a, int b, double progress)
|
||||
{
|
||||
int diff = b - a;
|
||||
return (int) Math.Round(a + diff * progress, InterpolationRoundingMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class SKColorDataBindingConverter : DataBindingConverter<SKColor, SKColor>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SKColorDataBindingConverter" /> class
|
||||
/// </summary>
|
||||
public SKColorDataBindingConverter()
|
||||
{
|
||||
SupportsInterpolate = true;
|
||||
SupportsSum = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Sum(SKColor a, SKColor b)
|
||||
{
|
||||
return a.Sum(b);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
|
||||
{
|
||||
return a.Interpolate(b, (float) progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorBrightenModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Brighten by";
|
||||
public override string Icon => "CarLightHigh";
|
||||
public override string Description => "Brightens the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
l *= (parameterValue + 100f) / 100f;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorDarkenModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Darken by";
|
||||
public override string Icon => "CarLightDimmed";
|
||||
public override string Description => "Darkens the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
l *= (parameterValue * -1 + 100f) / 100f;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorDesaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Desaturate";
|
||||
public override string Icon => "ImageMinus";
|
||||
public override string Description => "Desaturates the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
s -= parameterValue;
|
||||
s = Math.Clamp(s, 0, 100);
|
||||
|
||||
return SKColor.FromHsl(h, s, l, currentValue.Alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorInvertModifierType : DataBindingModifierType<SKColor>
|
||||
{
|
||||
public override string Name => "Invert color";
|
||||
public override string Icon => "InvertColors";
|
||||
public override string Description => "Inverts the color by rotating its hue by a 180°";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
h += 180;
|
||||
return SKColor.FromHsl(h % 360, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorRotateHueModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Rotate Hue by";
|
||||
public override string Icon => "RotateRight";
|
||||
public override string Description => "Rotates the hue of the color by the amount in degrees";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsl(out float h, out float s, out float l);
|
||||
h += parameterValue;
|
||||
return SKColor.FromHsl(h % 360, s, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorSaturateModifierType : DataBindingModifierType<SKColor, float>
|
||||
{
|
||||
public override string Name => "Saturate";
|
||||
public override string Icon => "ImagePlus";
|
||||
public override string Description => "Saturates the color by the amount in percent";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, float parameterValue)
|
||||
{
|
||||
currentValue.ToHsv(out float h, out float s, out float v);
|
||||
s += parameterValue;
|
||||
s = Math.Clamp(s, 0, 100);
|
||||
|
||||
return SKColor.FromHsv(h, s, v, currentValue.Alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SKColorSumModifierType : DataBindingModifierType<SKColor, SKColor>
|
||||
{
|
||||
public override string Name => "Combine with";
|
||||
public override string Icon => "FormatColorFill";
|
||||
public override string Description => "Adds the two colors together";
|
||||
|
||||
public override SKColor Apply(SKColor currentValue, SKColor parameterValue)
|
||||
{
|
||||
return currentValue.Sum(parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class AbsoluteModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Absolute";
|
||||
public override string Icon => "NumericPositive1";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Converts the input value to an absolute value";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Abs(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DivideModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Divide by";
|
||||
public override string Icon => "Divide";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
// Ye ye none of that
|
||||
if (parameterValue == 0)
|
||||
return 0;
|
||||
|
||||
return currentValue / parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MaxModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Max";
|
||||
public override string Icon => "ChevronUpBoxOutline";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Keeps only the largest of input value and parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Max(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MinModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Min";
|
||||
public override string Icon => "ChevronDownBoxOutline";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Keeps only the smallest of input value and parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Min(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ModuloModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Modulo";
|
||||
public override string Icon => "Stairs";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Calculates the remained of the division between the input value and the parameter";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue % parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class MultiplicationModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Multiply by";
|
||||
public override string Icon => "Close";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue * parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class PercentageOfModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Percentage of";
|
||||
public override string Icon => "Percent";
|
||||
public override string Description => "Calculates how much percent the parameter value is of the current value";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
// Ye ye none of that
|
||||
if (parameterValue == 0d)
|
||||
return 100d;
|
||||
|
||||
return 100d / parameterValue * currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class PowerModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Power";
|
||||
public override string Icon => "Exponent";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Raises the input value to the power of the parameter value";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return Math.Pow(currentValue, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CeilingModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round up";
|
||||
public override string Icon => "ArrowUp";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Ceils the input, rounding it up to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Ceiling(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class FloorModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round down";
|
||||
public override string Icon => "ArrowDown";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Floors the input, rounding it down to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Floor(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class RoundModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Round";
|
||||
public override string Icon => "ArrowCollapse";
|
||||
public override string Category => "Rounding";
|
||||
public override string Description => "Rounds the input to the nearest whole number";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Round(currentValue, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SquareRootModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Square root";
|
||||
public override string Icon => "SquareRoot";
|
||||
public override string Category => "Advanced";
|
||||
public override string Description => "Calculates square root of the input value";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Sqrt(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SubtractModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Subtract";
|
||||
public override string Icon => "Minus";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue - parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SumModifierType : DataBindingModifierType<double, double>
|
||||
{
|
||||
public override string Name => "Sum";
|
||||
public override string Icon => "Plus";
|
||||
|
||||
public override double Apply(double currentValue, double parameterValue)
|
||||
{
|
||||
return currentValue + parameterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CosecantModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cosecant";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cosecant";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Sin(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CosineModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cosine";
|
||||
public override string Icon => "MathCos";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cosine";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Cos(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class CotangentModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Cotangent";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the cotangent";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Tan(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SecantModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Secant";
|
||||
public override string? Icon => null;
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the secant";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return 1d / Math.Cos(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class SineModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Sine";
|
||||
public override string Icon => "MathSin";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the sine";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Sin(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class TangentModifierType : DataBindingModifierType<double>
|
||||
{
|
||||
public override string Name => "Tangent";
|
||||
public override string Icon => "MathTan";
|
||||
public override string Category => "Trigonometry";
|
||||
public override string Description => "Treats the input as an angle and calculates the tangent";
|
||||
|
||||
public override double Apply(double currentValue)
|
||||
{
|
||||
return Math.Tan(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,9 +4,14 @@
|
||||
public class BoolLayerProperty : LayerProperty<bool>
|
||||
{
|
||||
internal BoolLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new GeneralDataBindingConverter<bool>(), "Value");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,113 +1,92 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ColorGradientLayerProperty : LayerProperty<ColorGradient>
|
||||
private ColorGradient? _subscribedGradient;
|
||||
|
||||
internal ColorGradientLayerProperty()
|
||||
{
|
||||
private ColorGradient? _subscribedGradient;
|
||||
|
||||
internal ColorGradientLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = true;
|
||||
DefaultValue = new ColorGradient();
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
}
|
||||
|
||||
private void CreateDataBindingRegistrations()
|
||||
{
|
||||
ClearDataBindingProperties();
|
||||
if (CurrentValue == null)
|
||||
return;
|
||||
|
||||
for (int index = 0; index < CurrentValue.Count; index++)
|
||||
{
|
||||
int stopIndex = index;
|
||||
|
||||
void Setter(SKColor value)
|
||||
{
|
||||
CurrentValue[stopIndex].Color = value;
|
||||
}
|
||||
|
||||
RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
public static implicit operator ColorGradient(ColorGradientLayerProperty p)
|
||||
{
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Color Gradients do not support keyframes.");
|
||||
}
|
||||
|
||||
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
|
||||
{
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
if (_subscribedGradient != BaseValue)
|
||||
{
|
||||
if (_subscribedGradient != null)
|
||||
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient = BaseValue;
|
||||
_subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged;
|
||||
}
|
||||
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
|
||||
#region Overrides of LayerProperty<ColorGradient>
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null)
|
||||
BaseValue = DefaultValue ?? new ColorGradient();
|
||||
|
||||
base.OnInitialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
KeyframesSupported = false;
|
||||
DefaultValue = new ColorGradient();
|
||||
}
|
||||
|
||||
internal class ColorStopDataBindingConverter : DataBindingConverter<ColorGradient, SKColor>
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ColorGradientLayerProperty" /> to a <see cref="ColorGradient" />
|
||||
/// </summary>
|
||||
public static implicit operator ColorGradient(ColorGradientLayerProperty p)
|
||||
{
|
||||
public ColorStopDataBindingConverter()
|
||||
return p.CurrentValue;
|
||||
}
|
||||
|
||||
#region Overrides of LayerProperty<ColorGradient>
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnCurrentValueSet()
|
||||
{
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null!)
|
||||
BaseValue = new ColorGradient(DefaultValue);
|
||||
|
||||
if (!ReferenceEquals(_subscribedGradient, BaseValue))
|
||||
{
|
||||
SupportsInterpolate = true;
|
||||
SupportsSum = true;
|
||||
if (_subscribedGradient != null)
|
||||
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
|
||||
_subscribedGradient = BaseValue;
|
||||
_subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Sum(SKColor a, SKColor b)
|
||||
{
|
||||
return a.Sum(b);
|
||||
}
|
||||
CreateDataBindingRegistrations();
|
||||
base.OnCurrentValueSet();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SKColor Interpolate(SKColor a, SKColor b, double progress)
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Color Gradients do not support keyframes.");
|
||||
}
|
||||
|
||||
#region Overrides of LayerProperty<ColorGradient>
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
// Don't allow color gradients to be null
|
||||
if (BaseValue == null!)
|
||||
BaseValue = new ColorGradient(DefaultValue);
|
||||
|
||||
base.OnInitialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void CreateDataBindingRegistrations()
|
||||
{
|
||||
DataBinding.ClearDataBindingProperties();
|
||||
if (CurrentValue == null!)
|
||||
return;
|
||||
|
||||
for (int index = 0; index < CurrentValue.Count; index++)
|
||||
{
|
||||
return a.Interpolate(b, (float) progress);
|
||||
int stopIndex = index;
|
||||
|
||||
void Setter(SKColor value)
|
||||
{
|
||||
CurrentValue[stopIndex].Color = value;
|
||||
}
|
||||
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, $"Color #{stopIndex + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
if (CurrentValue.Count != DataBinding.Properties.Count)
|
||||
CreateDataBindingRegistrations();
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,6 @@ namespace Artemis.Core
|
||||
internal EnumLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -5,7 +5,12 @@
|
||||
{
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new FloatDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -3,13 +3,11 @@
|
||||
/// <inheritdoc />
|
||||
public class FloatRangeLayerProperty : LayerProperty<FloatRange>
|
||||
{
|
||||
internal FloatRangeLayerProperty()
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new FloatDataBindingConverter<FloatRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new FloatDataBindingConverter<FloatRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
DefaultValue = new FloatRange();
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new FloatRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new FloatRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -18,15 +16,9 @@
|
||||
float startDiff = NextKeyframe!.Value.Start - CurrentKeyframe!.Value.Start;
|
||||
float endDiff = NextKeyframe!.Value.End - CurrentKeyframe!.Value.End;
|
||||
CurrentValue = new FloatRange(
|
||||
(int) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
|
||||
(int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
(float) (CurrentKeyframe!.Value.Start + startDiff * keyframeProgressEased),
|
||||
(float) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
);
|
||||
}
|
||||
|
||||
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
|
||||
{
|
||||
// Don't allow the int range to be null
|
||||
BaseValue ??= DefaultValue ?? new FloatRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,12 @@ namespace Artemis.Core
|
||||
{
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new IntDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -3,13 +3,11 @@
|
||||
/// <inheritdoc />
|
||||
public class IntRangeLayerProperty : LayerProperty<IntRange>
|
||||
{
|
||||
internal IntRangeLayerProperty()
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue.Start = value, new IntDataBindingConverter<IntRange>(), "Start");
|
||||
RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue.End = value, new IntDataBindingConverter<IntRange>(), "End");
|
||||
|
||||
CurrentValueSet += OnCurrentValueSet;
|
||||
DefaultValue = new IntRange();
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Start, value => CurrentValue = new IntRange(value, CurrentValue.End), "Start");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.End, value => CurrentValue = new IntRange(CurrentValue.Start, value), "End");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -22,11 +20,5 @@
|
||||
(int) (CurrentKeyframe!.Value.End + endDiff * keyframeProgressEased)
|
||||
);
|
||||
}
|
||||
|
||||
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
|
||||
{
|
||||
// Don't allow the int range to be null
|
||||
BaseValue ??= DefaultValue ?? new IntRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,6 @@
|
||||
internal LayerBrushReferenceLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
DataBindingsSupported = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,7 +7,12 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, new SKColorDataBindingConverter(), "Value");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue, value => CurrentValue = value, "Value");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,8 +7,13 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), new FloatDataBindingConverter<SKPoint>(), "X");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), new FloatDataBindingConverter<SKPoint>(), "Y");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.X, value => CurrentValue = new SKPoint(value, CurrentValue.Y), "X");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Y, value => CurrentValue = new SKPoint(CurrentValue.X, value), "Y");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,8 +7,13 @@ namespace Artemis.Core
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), new FloatDataBindingConverter<SKSize>(), "Width");
|
||||
RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), new FloatDataBindingConverter<SKSize>(), "Height");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Width, (value) => CurrentValue = new SKSize(value, CurrentValue.Height), "Width");
|
||||
DataBinding.RegisterDataBindingProperty(() => CurrentValue.Height, (value) => CurrentValue = new SKSize(CurrentValue.Width, value), "Height");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
20
src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs
Normal file
20
src/Artemis.Core/Events/Profiles/DataBindingEventArgs.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for data binding events.
|
||||
/// </summary>
|
||||
public class DataBindingEventArgs : EventArgs
|
||||
{
|
||||
internal DataBindingEventArgs(IDataBinding dataBinding)
|
||||
{
|
||||
DataBinding = dataBinding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data binding this event is related to
|
||||
/// </summary>
|
||||
public IDataBinding DataBinding { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for layer adapter hint events.
|
||||
/// </summary>
|
||||
public class LayerAdapterHintEventArgs : EventArgs
|
||||
{
|
||||
internal LayerAdapterHintEventArgs(IAdaptionHint adaptionHint)
|
||||
{
|
||||
AdaptionHint = adaptionHint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer adaption hint this event is related to
|
||||
/// </summary>
|
||||
public IAdaptionHint AdaptionHint { get; }
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for layer property events.
|
||||
/// </summary>
|
||||
public class LayerPropertyKeyframeEventArgs : EventArgs
|
||||
{
|
||||
internal LayerPropertyKeyframeEventArgs(ILayerPropertyKeyframe keyframe)
|
||||
{
|
||||
Keyframe = keyframe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe this event is related to
|
||||
/// </summary>
|
||||
public ILayerPropertyKeyframe Keyframe { get; }
|
||||
}
|
||||
}
|
||||
20
src/Artemis.Core/Events/Profiles/ProfileElementEventArgs.cs
Normal file
20
src/Artemis.Core/Events/Profiles/ProfileElementEventArgs.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for profile element events.
|
||||
/// </summary>
|
||||
public class ProfileElementEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileElementEventArgs(ProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element this event is related to
|
||||
/// </summary>
|
||||
public ProfileElement ProfileElement { get; }
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class ConditionOperatorStoreEvent
|
||||
{
|
||||
public ConditionOperatorStoreEvent(ConditionOperatorRegistration registration)
|
||||
{
|
||||
Registration = registration;
|
||||
}
|
||||
|
||||
public ConditionOperatorRegistration Registration { get; }
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class DataBindingModifierTypeStoreEvent
|
||||
{
|
||||
public DataBindingModifierTypeStoreEvent(DataBindingModifierTypeRegistration typeRegistration)
|
||||
{
|
||||
TypeRegistration = typeRegistration;
|
||||
}
|
||||
|
||||
public DataBindingModifierTypeRegistration TypeRegistration { get; }
|
||||
}
|
||||
}
|
||||
12
src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs
Normal file
12
src/Artemis.Core/Events/Stores/NodeTypeStoreEvent.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal class NodeTypeStoreEvent
|
||||
{
|
||||
public NodeTypeStoreEvent(NodeTypeRegistration typeRegistration)
|
||||
{
|
||||
TypeRegistration = typeRegistration;
|
||||
}
|
||||
|
||||
public NodeTypeRegistration TypeRegistration { get; }
|
||||
}
|
||||
}
|
||||
@ -31,66 +31,23 @@ namespace Artemis.Core
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all distinct elements of the given source, where "distinctness"
|
||||
/// is determined via a projection and the default equality comparer for the projected type.
|
||||
/// Returns the index of the provided element inside the read only collection
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operator uses deferred execution and streams the results, although
|
||||
/// a set of already-seen keys is retained. If a key is seen multiple times,
|
||||
/// only the first element with that key is returned.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TSource">Type of the source sequence</typeparam>
|
||||
/// <typeparam name="TKey">Type of the projected element</typeparam>
|
||||
/// <param name="source">Source sequence</param>
|
||||
/// <param name="keySelector">Projection for determining "distinctness"</param>
|
||||
/// <returns>
|
||||
/// A sequence consisting of distinct elements from the source sequence,
|
||||
/// comparing them by the specified key projection.
|
||||
/// </returns>
|
||||
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector)
|
||||
/// <typeparam name="T">The type of element to find</typeparam>
|
||||
/// <param name="self">The collection to search in</param>
|
||||
/// <param name="elementToFind">The element to find</param>
|
||||
/// <returns>If found, the index of the element to find; otherwise -1</returns>
|
||||
public static int IndexOf<T>(this IReadOnlyCollection<T> self, T elementToFind)
|
||||
{
|
||||
return source.DistinctBy(keySelector, null!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all distinct elements of the given source, where "distinctness"
|
||||
/// is determined via a projection and the specified comparer for the projected type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operator uses deferred execution and streams the results, although
|
||||
/// a set of already-seen keys is retained. If a key is seen multiple times,
|
||||
/// only the first element with that key is returned.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TSource">Type of the source sequence</typeparam>
|
||||
/// <typeparam name="TKey">Type of the projected element</typeparam>
|
||||
/// <param name="source">Source sequence</param>
|
||||
/// <param name="keySelector">Projection for determining "distinctness"</param>
|
||||
/// <param name="comparer">
|
||||
/// The equality comparer to use to determine whether or not keys are equal.
|
||||
/// If null, the default equality comparer for <c>TSource</c> is used.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A sequence consisting of distinct elements from the source sequence,
|
||||
/// comparing them by the specified key projection.
|
||||
/// </returns>
|
||||
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
|
||||
Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
|
||||
|
||||
return _();
|
||||
|
||||
IEnumerable<TSource> _()
|
||||
int i = 0;
|
||||
foreach (T element in self)
|
||||
{
|
||||
HashSet<TKey> knownKeys = new(comparer);
|
||||
foreach (TSource element in source)
|
||||
{
|
||||
if (knownKeys.Add(keySelector(element)))
|
||||
yield return element;
|
||||
}
|
||||
if (Equals(element, elementToFind))
|
||||
return i;
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ namespace Artemis.Core
|
||||
/// <returns>The RGB.NET color</returns>
|
||||
public static Color ToRgbColor(this SKColor color)
|
||||
{
|
||||
return new(color.Alpha, color.Red, color.Green, color.Blue);
|
||||
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -49,7 +49,7 @@ namespace Artemis.Core
|
||||
/// <returns>The sum of the two colors</returns>
|
||||
public static SKColor Sum(this SKColor a, SKColor b)
|
||||
{
|
||||
return new(
|
||||
return new SKColor(
|
||||
ClampToByte(a.Red + b.Red),
|
||||
ClampToByte(a.Green + b.Green),
|
||||
ClampToByte(a.Blue + b.Blue),
|
||||
@ -57,6 +57,19 @@ namespace Artemis.Core
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the color by the specified amount
|
||||
/// </summary>
|
||||
/// <param name="c">The color to darken</param>
|
||||
/// <param name="amount">The brightness of the new color</param>
|
||||
/// <returns>The darkened color</returns>
|
||||
public static SKColor Darken(this SKColor c, float amount)
|
||||
{
|
||||
c.ToHsl(out float h, out float s, out float l);
|
||||
l *= 1f - amount;
|
||||
return SKColor.FromHsl(h, s, l);
|
||||
}
|
||||
|
||||
private static byte ClampToByte(float value)
|
||||
{
|
||||
return (byte) Math.Clamp(value, 0, 255);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Humanizer;
|
||||
@ -49,7 +50,7 @@ namespace Artemis.Core
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <param name="genericType">The generic type to match</param>
|
||||
/// <returns>True if the <paramref name="type" /> is generic and of generic type <paramref name="genericType" /></returns>
|
||||
public static bool IsGenericType(this Type type, Type genericType)
|
||||
public static bool IsGenericType(this Type? type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
return false;
|
||||
@ -92,19 +93,9 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
/// <param name="value">The value to check</param>
|
||||
/// <returns><see langword="true" /> if the value is of a numeric type, otherwise <see langword="false" /></returns>
|
||||
public static bool IsNumber(this object value)
|
||||
public static bool IsNumber([NotNullWhenAttribute(true)] this object? value)
|
||||
{
|
||||
return value is sbyte
|
||||
|| value is byte
|
||||
|| value is short
|
||||
|| value is ushort
|
||||
|| value is int
|
||||
|| value is uint
|
||||
|| value is long
|
||||
|| value is ulong
|
||||
|| value is float
|
||||
|| value is double
|
||||
|| value is decimal;
|
||||
return value is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal;
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/2224421/5015269 but inverted and renamed to match similar framework methods
|
||||
@ -130,8 +121,11 @@ namespace Artemis.Core
|
||||
/// at all
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int ScoreCastability(this Type to, Type from)
|
||||
public static int ScoreCastability(this Type to, Type? from)
|
||||
{
|
||||
if (from == null)
|
||||
return 0;
|
||||
|
||||
if (to == from)
|
||||
return 5;
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
@ -188,6 +182,52 @@ namespace Artemis.Core
|
||||
return enumerableType?.GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the <paramref name="typeToCheck"></paramref> is of a certain <paramref name="genericType"/>.
|
||||
/// </summary>
|
||||
/// <param name="typeToCheck">The type to check.</param>
|
||||
/// <param name="genericType">The generic type it should be or implement</param>
|
||||
public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
|
||||
{
|
||||
return typeToCheck.IsOfGenericType(genericType, out Type? _);
|
||||
}
|
||||
|
||||
private static bool IsOfGenericType(this Type? typeToCheck, Type genericType, out Type? concreteGenericType)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
concreteGenericType = null;
|
||||
|
||||
if (genericType == null)
|
||||
throw new ArgumentNullException(nameof(genericType));
|
||||
|
||||
if (!genericType.IsGenericTypeDefinition)
|
||||
throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
|
||||
|
||||
if (typeToCheck == null || typeToCheck == typeof(object))
|
||||
return false;
|
||||
|
||||
if (typeToCheck == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
|
||||
{
|
||||
concreteGenericType = typeToCheck;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericType.IsInterface)
|
||||
foreach (Type i in typeToCheck.GetInterfaces())
|
||||
if (i.IsOfGenericType(genericType, out concreteGenericType))
|
||||
return true;
|
||||
|
||||
typeToCheck = typeToCheck.BaseType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a display name for the given type
|
||||
/// </summary>
|
||||
|
||||
25
src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
Normal file
25
src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.JsonConverters
|
||||
{
|
||||
internal class NumericJsonConverter : JsonConverter<Numeric>
|
||||
{
|
||||
#region Overrides of JsonConverter<Numeric>
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
|
||||
{
|
||||
float floatValue = value;
|
||||
writer.WriteValue(floatValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
return new Numeric(reader.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -21,14 +21,14 @@ namespace Artemis.Core
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the property already matches the desirec value or needs to be updated.
|
||||
/// Checks if the property already matches the desired value or needs to be updated.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to the backing-filed.</param>
|
||||
/// <param name="value">Value to apply.</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected virtual bool RequiresUpdate<T>(ref T storage, T value)
|
||||
protected bool RequiresUpdate<T>(ref T storage, T value)
|
||||
{
|
||||
return !Equals(storage, value);
|
||||
}
|
||||
@ -46,7 +46,7 @@ namespace Artemis.Core
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected virtual bool SetAndNotify<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
|
||||
protected bool SetAndNotify<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (!RequiresUpdate(ref storage, value)) return false;
|
||||
|
||||
@ -64,7 +64,7 @@ namespace Artemis.Core
|
||||
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute" />
|
||||
/// .
|
||||
/// </param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
@ -7,8 +7,13 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain category of devices
|
||||
/// </summary>
|
||||
public class CategoryAdaptionHint : IAdaptionHint
|
||||
public class CategoryAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private DeviceCategory _category;
|
||||
private int _skip;
|
||||
private bool _limitAmount;
|
||||
private int _amount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CategoryAdaptionHint" /> class
|
||||
/// </summary>
|
||||
@ -27,22 +32,38 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets or sets the category of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public DeviceCategory Category { get; set; }
|
||||
public DeviceCategory Category
|
||||
{
|
||||
get => _category;
|
||||
set => SetAndNotify(ref _category, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip { get; set; }
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount { get; set; }
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount { get; set; }
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
|
||||
@ -8,8 +8,13 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain type of devices
|
||||
/// </summary>
|
||||
public class DeviceAdaptionHint : IAdaptionHint
|
||||
public class DeviceAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private RGBDeviceType _deviceType;
|
||||
private int _skip;
|
||||
private bool _limitAmount;
|
||||
private int _amount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeviceAdaptionHint" /> class
|
||||
/// </summary>
|
||||
@ -28,22 +33,38 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets or sets the type of devices LEDs will be applied to
|
||||
/// </summary>
|
||||
public RGBDeviceType DeviceType { get; set; }
|
||||
public RGBDeviceType DeviceType
|
||||
{
|
||||
get => _deviceType;
|
||||
set => SetAndNotify(ref _deviceType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to skip
|
||||
/// </summary>
|
||||
public int Skip { get; set; }
|
||||
public int Skip
|
||||
{
|
||||
get => _skip;
|
||||
set => SetAndNotify(ref _skip, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether a limited amount of devices should be used
|
||||
/// </summary>
|
||||
public bool LimitAmount { get; set; }
|
||||
public bool LimitAmount
|
||||
{
|
||||
get => _limitAmount;
|
||||
set => SetAndNotify(ref _limitAmount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of devices to limit to if <see cref="LimitAmount" /> is <see langword="true" />
|
||||
/// </summary>
|
||||
public int Amount { get; set; }
|
||||
public int Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAndNotify(ref _amount, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Represents a hint that adapts layers to a certain region of keyboards
|
||||
/// </summary>
|
||||
public class KeyboardSectionAdaptionHint : IAdaptionHint
|
||||
public class KeyboardSectionAdaptionHint : CorePropertyChanged, IAdaptionHint
|
||||
{
|
||||
private static readonly Dictionary<KeyboardSection, List<LedId>> RegionLedIds = new()
|
||||
{
|
||||
@ -18,6 +18,8 @@ namespace Artemis.Core
|
||||
{KeyboardSection.Extra, Enum.GetValues<LedId>().Where(l => l >= LedId.Keyboard_Custom1 && l <= LedId.Keyboard_Custom64).ToList()}
|
||||
};
|
||||
|
||||
private KeyboardSection _section;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="KeyboardSectionAdaptionHint" /> class
|
||||
/// </summary>
|
||||
@ -33,7 +35,11 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets or sets the section this hint will apply LEDs to
|
||||
/// </summary>
|
||||
public KeyboardSection Section { get; set; }
|
||||
public KeyboardSection Section
|
||||
{
|
||||
get => _section;
|
||||
set => SetAndNotify(ref _section, value);
|
||||
}
|
||||
|
||||
#region Implementation of IAdaptionHint
|
||||
|
||||
|
||||
@ -6,319 +6,621 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A gradient containing a list of <see cref="ColorGradientStop" />s
|
||||
/// </summary>
|
||||
public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// A gradient containing a list of <see cref="ColorGradientStop" />s
|
||||
/// </summary>
|
||||
public class ColorGradient : IList<ColorGradientStop>, INotifyCollectionChanged
|
||||
private static readonly SKColor[] FastLedRainbow =
|
||||
{
|
||||
private static readonly SKColor[] FastLedRainbow =
|
||||
{
|
||||
new(0xFFFF0000), // Red
|
||||
new(0xFFFF9900), // Orange
|
||||
new(0xFFFFFF00), // Yellow
|
||||
new(0xFF00FF00), // Green
|
||||
new(0xFF00FF7E), // Aqua
|
||||
new(0xFF0078FF), // Blue
|
||||
new(0xFF9E22FF), // Purple
|
||||
new(0xFFFF34AE), // Pink
|
||||
new(0xFFFF0000) // and back to Red
|
||||
};
|
||||
new(0xFFFF0000), // Red
|
||||
new(0xFFFF9900), // Orange
|
||||
new(0xFFFFFF00), // Yellow
|
||||
new(0xFF00FF00), // Green
|
||||
new(0xFF00FF7E), // Aqua
|
||||
new(0xFF0078FF), // Blue
|
||||
new(0xFF9E22FF), // Purple
|
||||
new(0xFFFF34AE), // Pink
|
||||
new(0xFFFF0000) // and back to Red
|
||||
};
|
||||
|
||||
private readonly List<ColorGradientStop> _stops;
|
||||
private readonly List<ColorGradientStop> _stops;
|
||||
private bool _updating;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||
/// </summary>
|
||||
public ColorGradient()
|
||||
{
|
||||
_stops = new List<ColorGradientStop>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the colors in the color gradient
|
||||
/// </summary>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the colors</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
/// <returns>An array containing each color in the gradient</returns>
|
||||
public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
List<SKColor> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
{
|
||||
result = this.Select(c => c.Color).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= timesToRepeat; i++)
|
||||
result.AddRange(this.Select(c => c.Color));
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
result.Add(result[0]);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the positions in the color gradient
|
||||
/// </summary>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
/// <returns>An array containing a position for each color between 0.0 and 1.0</returns>
|
||||
public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
List<float> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
{
|
||||
result = this.Select(c => c.Position).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create stops and a list of divided stops
|
||||
List<float> stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
|
||||
|
||||
// For each repeat cycle, add the base stops to the end result
|
||||
for (int i = 0; i <= timesToRepeat; i++)
|
||||
{
|
||||
float lastStop = result.LastOrDefault();
|
||||
result.AddRange(stops.Select(s => s + lastStop));
|
||||
}
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
{
|
||||
// Compress current points evenly
|
||||
float compression = 1f - 1f / result.Count;
|
||||
for (int index = 0; index < result.Count; index++)
|
||||
result[index] = result[index] * compression;
|
||||
// Add one extra point at the end
|
||||
result.Add(1f);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color at any position between 0.0 and 1.0 using interpolation
|
||||
/// </summary>
|
||||
/// <param name="position">A position between 0.0 and 1.0</param>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
public SKColor GetColor(float position, int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
if (!this.Any())
|
||||
return new SKColor(255, 255, 255);
|
||||
|
||||
SKColor[] colors = GetColorsArray(timesToRepeat, seamless);
|
||||
float[] stops = GetPositionsArray(timesToRepeat, seamless);
|
||||
|
||||
// If at or over the edges, return the corresponding edge
|
||||
if (position <= 0) return colors[0];
|
||||
if (position >= 1) return colors[^1];
|
||||
|
||||
// Walk through the stops until we find the one at or after the requested position, that becomes the right stop
|
||||
// The left stop is the previous stop before the right one was found.
|
||||
float left = stops[0];
|
||||
float? right = null;
|
||||
foreach (float stop in stops)
|
||||
{
|
||||
if (stop >= position)
|
||||
{
|
||||
right = stop;
|
||||
break;
|
||||
}
|
||||
|
||||
left = stop;
|
||||
}
|
||||
|
||||
// Get the left stop's color
|
||||
SKColor leftColor = colors[Array.IndexOf(stops, left)];
|
||||
|
||||
// If no right stop was found or the left and right stops are on the same spot, return the left stop's color
|
||||
if (right == null || left == right)
|
||||
return leftColor;
|
||||
|
||||
// Get the right stop's color
|
||||
SKColor rightColor = colors[Array.IndexOf(stops, right)];
|
||||
|
||||
// Interpolate the position between the left and right color
|
||||
position = MathF.Round((position - left) / (right.Value - left), 2);
|
||||
byte a = (byte) ((rightColor.Alpha - leftColor.Alpha) * position + leftColor.Alpha);
|
||||
byte r = (byte) ((rightColor.Red - leftColor.Red) * position + leftColor.Red);
|
||||
byte g = (byte) ((rightColor.Green - leftColor.Green) * position + leftColor.Green);
|
||||
byte b = (byte) ((rightColor.Blue - leftColor.Blue) * position + leftColor.Blue);
|
||||
return new SKColor(r, g, b, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new ColorGradient with colors looping through the HSV-spectrum
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ColorGradient GetUnicornBarf()
|
||||
{
|
||||
ColorGradient gradient = new();
|
||||
for (int index = 0; index < FastLedRainbow.Length; index++)
|
||||
{
|
||||
SKColor skColor = FastLedRainbow[index];
|
||||
float position = 1f / (FastLedRainbow.Length - 1f) * index;
|
||||
gradient.Add(new ColorGradientStop(skColor, position));
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the gradient is seamless
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
|
||||
public bool IsSeamless()
|
||||
{
|
||||
return Count == 0 || this.First().Color.Equals(this.Last().Color);
|
||||
}
|
||||
|
||||
internal void Sort()
|
||||
{
|
||||
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
|
||||
}
|
||||
|
||||
private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
#region Implementation of IEnumerable
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<ColorGradientStop> GetEnumerator()
|
||||
{
|
||||
return _stops.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICollection<ColorGradientStop>
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(ColorGradientStop item)
|
||||
{
|
||||
_stops.Add(item);
|
||||
item.PropertyChanged += ItemOnPropertyChanged;
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _stops.IndexOf(item)));
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
foreach (ColorGradientStop item in _stops)
|
||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops.Clear();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(ColorGradientStop item)
|
||||
{
|
||||
return _stops.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(ColorGradientStop[] array, int arrayIndex)
|
||||
{
|
||||
_stops.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(ColorGradientStop item)
|
||||
{
|
||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||
int index = _stops.IndexOf(item);
|
||||
bool removed = _stops.Remove(item);
|
||||
if (removed)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _stops.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IList<ColorGradientStop>
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(ColorGradientStop item)
|
||||
{
|
||||
return _stops.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, ColorGradientStop item)
|
||||
{
|
||||
_stops.Insert(index, item);
|
||||
item.PropertyChanged += ItemOnPropertyChanged;
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_stops[index].PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops.RemoveAt(index);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ColorGradientStop this[int index]
|
||||
{
|
||||
get => _stops[index];
|
||||
set
|
||||
{
|
||||
ColorGradientStop? oldValue = _stops[index];
|
||||
oldValue.PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops[index] = value;
|
||||
_stops[index].PropertyChanged += ItemOnPropertyChanged;
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of INotifyCollectionChanged
|
||||
|
||||
/// <inheritdoc />
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||
/// </summary>
|
||||
public ColorGradient()
|
||||
{
|
||||
_stops = new List<ColorGradientStop>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||
/// </summary>
|
||||
/// <param name="colorGradient">The color gradient to copy</param>
|
||||
public ColorGradient(ColorGradient? colorGradient)
|
||||
{
|
||||
_stops = new List<ColorGradientStop>();
|
||||
if (colorGradient == null)
|
||||
return;
|
||||
|
||||
foreach (ColorGradientStop colorGradientStop in colorGradient)
|
||||
{
|
||||
ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position);
|
||||
stop.PropertyChanged += ItemOnPropertyChanged;
|
||||
_stops.Add(stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||
/// </summary>
|
||||
/// <param name="stops">The stops to copy</param>
|
||||
public ColorGradient(List<ColorGradientStop> stops)
|
||||
{
|
||||
_stops = new List<ColorGradientStop>();
|
||||
foreach (ColorGradientStop colorGradientStop in stops)
|
||||
{
|
||||
ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position);
|
||||
stop.PropertyChanged += ItemOnPropertyChanged;
|
||||
_stops.Add(stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the colors in the color gradient
|
||||
/// </summary>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the colors</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
/// <returns>An array containing each color in the gradient</returns>
|
||||
public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
List<SKColor> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
{
|
||||
result = this.Select(c => c.Color).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= timesToRepeat; i++)
|
||||
result.AddRange(this.Select(c => c.Color));
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
result.Add(result[0]);
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the positions in the color gradient
|
||||
/// </summary>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
/// <returns>An array containing a position for each color between 0.0 and 1.0</returns>
|
||||
public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
List<float> result = new();
|
||||
if (timesToRepeat == 0)
|
||||
{
|
||||
result = this.Select(c => c.Position).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create stops and a list of divided stops
|
||||
List<float> stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
|
||||
|
||||
// For each repeat cycle, add the base stops to the end result
|
||||
for (int i = 0; i <= timesToRepeat; i++)
|
||||
{
|
||||
float lastStop = result.LastOrDefault();
|
||||
result.AddRange(stops.Select(s => s + lastStop));
|
||||
}
|
||||
}
|
||||
|
||||
if (seamless && !IsSeamless())
|
||||
{
|
||||
// Compress current points evenly
|
||||
float compression = 1f - 1f / result.Count;
|
||||
for (int index = 0; index < result.Count; index++)
|
||||
result[index] *= compression;
|
||||
// Add one extra point at the end
|
||||
result.Add(1f);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color at any position between 0.0 and 1.0 using interpolation
|
||||
/// </summary>
|
||||
/// <param name="position">A position between 0.0 and 1.0</param>
|
||||
/// <param name="timesToRepeat">The amount of times to repeat the positions</param>
|
||||
/// <param name="seamless">
|
||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||
/// last color
|
||||
/// </param>
|
||||
public SKColor GetColor(float position, int timesToRepeat = 0, bool seamless = false)
|
||||
{
|
||||
if (!this.Any())
|
||||
return new SKColor(255, 255, 255);
|
||||
|
||||
SKColor[] colors = GetColorsArray(timesToRepeat, seamless);
|
||||
float[] stops = GetPositionsArray(timesToRepeat, seamless);
|
||||
|
||||
// If at or over the edges, return the corresponding edge
|
||||
if (position <= 0) return colors[0];
|
||||
if (position >= 1) return colors[^1];
|
||||
|
||||
// Walk through the stops until we find the one at or after the requested position, that becomes the right stop
|
||||
// The left stop is the previous stop before the right one was found.
|
||||
float left = stops[0];
|
||||
float? right = null;
|
||||
foreach (float stop in stops)
|
||||
{
|
||||
if (stop >= position)
|
||||
{
|
||||
right = stop;
|
||||
break;
|
||||
}
|
||||
|
||||
left = stop;
|
||||
}
|
||||
|
||||
// Get the left stop's color
|
||||
SKColor leftColor = colors[Array.IndexOf(stops, left)];
|
||||
|
||||
// If no right stop was found or the left and right stops are on the same spot, return the left stop's color
|
||||
if (right == null || left == right)
|
||||
return leftColor;
|
||||
|
||||
// Get the right stop's color
|
||||
SKColor rightColor = colors[Array.IndexOf(stops, right)];
|
||||
|
||||
// Interpolate the position between the left and right color
|
||||
position = MathF.Round((position - left) / (right.Value - left), 2);
|
||||
byte a = (byte) ((rightColor.Alpha - leftColor.Alpha) * position + leftColor.Alpha);
|
||||
byte r = (byte) ((rightColor.Red - leftColor.Red) * position + leftColor.Red);
|
||||
byte g = (byte) ((rightColor.Green - leftColor.Green) * position + leftColor.Green);
|
||||
byte b = (byte) ((rightColor.Blue - leftColor.Blue) * position + leftColor.Blue);
|
||||
return new SKColor(r, g, b, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new ColorGradient with colors looping through the HSV-spectrum
|
||||
/// </summary>
|
||||
public static ColorGradient GetUnicornBarf()
|
||||
{
|
||||
ColorGradient gradient = new();
|
||||
for (int index = 0; index < FastLedRainbow.Length; index++)
|
||||
{
|
||||
SKColor skColor = FastLedRainbow[index];
|
||||
float position = 1f / (FastLedRainbow.Length - 1f) * index;
|
||||
gradient.Add(new ColorGradientStop(skColor, position));
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new ColorGradient with random colors from the HSV-spectrum
|
||||
/// </summary>
|
||||
/// <param name="stops">The amount of stops to add</param>
|
||||
public ColorGradient GetRandom(int stops)
|
||||
{
|
||||
ColorGradient gradient = new();
|
||||
gradient.Randomize(stops);
|
||||
return gradient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the gradient is seamless
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
|
||||
public bool IsSeamless()
|
||||
{
|
||||
return Count == 0 || this.First().Color.Equals(this.Last().Color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spreads the color stops equally across the gradient.
|
||||
/// </summary>
|
||||
public void SpreadStops()
|
||||
{
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
for (int i = 0; i < Count; i++)
|
||||
this[i].Position = MathF.Round(i / ((float) Count - 1), 3, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If not already the case, makes the gradient seamless by adding the first color to the end of the gradient and
|
||||
/// compressing the other stops.
|
||||
/// <para>
|
||||
/// If the gradient is already seamless, removes the last color and spreads the remaining stops to fill the freed
|
||||
/// space.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void ToggleSeamless()
|
||||
{
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
|
||||
if (IsSeamless())
|
||||
{
|
||||
ColorGradientStop stopToRemove = this.Last();
|
||||
Remove(stopToRemove);
|
||||
|
||||
// Uncompress the stops if there is still more than one
|
||||
if (Count >= 2)
|
||||
{
|
||||
float multiplier = Count / (Count - 1f);
|
||||
foreach (ColorGradientStop stop in this)
|
||||
stop.Position = MathF.Round(Math.Min(stop.Position * multiplier, 100f), 3, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compress existing stops to the left
|
||||
float multiplier = (Count - 1f) / Count;
|
||||
foreach (ColorGradientStop stop in this)
|
||||
stop.Position = MathF.Round(stop.Position * multiplier, 3, MidpointRounding.AwayFromZero);
|
||||
|
||||
// Add a stop to the end that is the same color as the first stop
|
||||
ColorGradientStop newStop = new(this.First().Color, 1f);
|
||||
Add(newStop);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the stops of the gradient.
|
||||
/// </summary>
|
||||
public void FlipStops()
|
||||
{
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
foreach (ColorGradientStop stop in this)
|
||||
stop.Position = 1 - stop.Position;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the stops of the gradient shifting every stop over to the position of it's neighbor and wrapping around at
|
||||
/// the end of the gradient.
|
||||
/// </summary>
|
||||
/// <param name="inverse">A boolean indicating whether or not the invert the rotation.</param>
|
||||
public void RotateStops(bool inverse)
|
||||
{
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
List<ColorGradientStop> stops = inverse
|
||||
? this.OrderBy(s => s.Position).ToList()
|
||||
: this.OrderByDescending(s => s.Position).ToList();
|
||||
|
||||
float lastStopPosition = stops.Last().Position;
|
||||
foreach (ColorGradientStop stop in stops)
|
||||
(stop.Position, lastStopPosition) = (lastStopPosition, stop.Position);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes the color gradient with the given amount of <paramref name="stops" />.
|
||||
/// </summary>
|
||||
/// <param name="stops">The amount of stops to put into the gradient.</param>
|
||||
public void Randomize(int stops)
|
||||
{
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
|
||||
Clear();
|
||||
Random random = new();
|
||||
for (int index = 0; index < stops; index++)
|
||||
{
|
||||
SKColor skColor = SKColor.FromHsv(random.NextSingle() * 360, 100, 100);
|
||||
float position = 1f / (stops - 1f) * index;
|
||||
Add(new ColorGradientStop(skColor, position));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when any of the stops has changed in some way
|
||||
/// </summary>
|
||||
public event EventHandler? StopChanged;
|
||||
|
||||
internal void Sort()
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
int requiredIndex = 0;
|
||||
foreach (ColorGradientStop colorGradientStop in _stops.OrderBy(s => s.Position).ToList())
|
||||
{
|
||||
int actualIndex = _stops.IndexOf(colorGradientStop);
|
||||
if (requiredIndex != actualIndex)
|
||||
{
|
||||
_stops.RemoveAt(actualIndex);
|
||||
_stops.Insert(requiredIndex, colorGradientStop);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, colorGradientStop, requiredIndex, actualIndex));
|
||||
}
|
||||
|
||||
requiredIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Sort();
|
||||
OnStopChanged();
|
||||
}
|
||||
|
||||
private void OnStopChanged()
|
||||
{
|
||||
StopChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#region Equality members
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all the stops in this gradient are equal to the stops in the given <paramref name="other" />
|
||||
/// gradient.
|
||||
/// </summary>
|
||||
/// <param name="other">The other gradient to compare to</param>
|
||||
protected bool Equals(ColorGradient other)
|
||||
{
|
||||
if (Count != other.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
if (!Equals(this[i], other[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
return Equals((ColorGradient) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 19;
|
||||
foreach (ColorGradientStop stops in this)
|
||||
hash = hash * 31 + stops.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IEnumerable
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<ColorGradientStop> GetEnumerator()
|
||||
{
|
||||
return _stops.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICollection<ColorGradientStop>
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(ColorGradientStop item)
|
||||
{
|
||||
_stops.Add(item);
|
||||
item.PropertyChanged += ItemOnPropertyChanged;
|
||||
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||
Sort();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Add(object? value)
|
||||
{
|
||||
if (value is ColorGradientStop stop)
|
||||
Add(stop);
|
||||
|
||||
return IndexOf(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IList.Clear" />
|
||||
public void Clear()
|
||||
{
|
||||
foreach (ColorGradientStop item in _stops)
|
||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops.Clear();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(object? value)
|
||||
{
|
||||
return _stops.Contains(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(object? value)
|
||||
{
|
||||
return _stops.IndexOf(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, object? value)
|
||||
{
|
||||
if (value is ColorGradientStop stop)
|
||||
Insert(index, stop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(object? value)
|
||||
{
|
||||
if (value is ColorGradientStop stop)
|
||||
Remove(stop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(ColorGradientStop item)
|
||||
{
|
||||
return _stops.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(ColorGradientStop[] array, int arrayIndex)
|
||||
{
|
||||
_stops.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(ColorGradientStop item)
|
||||
{
|
||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||
int index = _stops.IndexOf(item);
|
||||
bool removed = _stops.Remove(item);
|
||||
if (removed)
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
_stops.CopyTo((ColorGradientStop[]) array, index);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ICollection{T}.Count" />
|
||||
public int Count => _stops.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object SyncRoot => this;
|
||||
|
||||
/// <inheritdoc cref="ICollection{T}.IsReadOnly" />
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
object? IList.this[int index]
|
||||
{
|
||||
get => this[index];
|
||||
set => this[index] = (ColorGradientStop) value!;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IList<ColorGradientStop>
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(ColorGradientStop item)
|
||||
{
|
||||
return _stops.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, ColorGradientStop item)
|
||||
{
|
||||
_stops.Insert(index, item);
|
||||
item.PropertyChanged += ItemOnPropertyChanged;
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IList{T}.RemoveAt" />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_stops[index].PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops.RemoveAt(index);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsFixedSize => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ColorGradientStop this[int index]
|
||||
{
|
||||
get => _stops[index];
|
||||
set
|
||||
{
|
||||
ColorGradientStop? oldValue = _stops[index];
|
||||
oldValue.PropertyChanged -= ItemOnPropertyChanged;
|
||||
_stops[index] = value;
|
||||
_stops[index].PropertyChanged += ItemOnPropertyChanged;
|
||||
Sort();
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of INotifyCollectionChanged
|
||||
|
||||
/// <inheritdoc />
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
|
||||
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
@ -7,6 +8,34 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public class ColorGradientStop : CorePropertyChanged
|
||||
{
|
||||
#region Equality members
|
||||
|
||||
/// <inheritdoc cref="object.Equals(object)" />
|
||||
protected bool Equals(ColorGradientStop other)
|
||||
{
|
||||
return _color.Equals(other._color) && _position.Equals(other._position);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
return Equals((ColorGradientStop) obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_color, _position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private SKColor _color;
|
||||
private float _position;
|
||||
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation
|
||||
/// <para>
|
||||
/// To implement your own condition operator, inherit <see cref="ConditionOperator{TLeftSide, TRightSide}" /> or
|
||||
/// <see cref="ConditionOperator{TLeftSide}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class BaseConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this condition operator belongs to
|
||||
/// <para>Note: Not set until after registering</para>
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left side type of this condition operator
|
||||
/// </summary>
|
||||
public abstract Type LeftSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right side type of this condition operator. May be null if the operator does not support a right side
|
||||
/// </summary>
|
||||
public abstract Type? RightSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type is supported by the operator
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
|
||||
/// <param name="side">Which side of the operator to check, left or right</param>
|
||||
public bool SupportsType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
if (type == null)
|
||||
return true;
|
||||
if (side == ConditionParameterSide.Left)
|
||||
return LeftSideType.IsCastableFrom(type);
|
||||
return RightSideType != null && RightSideType.IsCastableFrom(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition with the input types being provided as objects
|
||||
/// <para>
|
||||
/// This leaves the caller responsible for the types matching <see cref="LeftSideType" /> and
|
||||
/// <see cref="RightSideType" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="leftSideValue">The left side value, type should match <see cref="LeftSideType" /></param>
|
||||
/// <param name="rightSideValue">The right side value, type should match <see cref="RightSideType" /></param>
|
||||
/// <returns>The result of the boolean condition's evaluation</returns>
|
||||
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a side of a condition parameter
|
||||
/// </summary>
|
||||
public enum ConditionParameterSide
|
||||
{
|
||||
/// <summary>
|
||||
/// The left side of a condition parameter
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// The right side of a condition parameter
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using a left- and right-side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type RightSideType => typeof(TRightSide);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
/// <param name="b">The parameter on the right side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
{
|
||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = (TLeftSide) leftSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftSide = default;
|
||||
}
|
||||
|
||||
TRightSide rightSide;
|
||||
if (rightSideValue != null)
|
||||
{
|
||||
if (rightSideValue.GetType() != typeof(TRightSide) && leftSideValue is IConvertible)
|
||||
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
|
||||
else
|
||||
rightSide = (TRightSide) rightSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightSide = default;
|
||||
}
|
||||
|
||||
return Evaluate(leftSide!, rightSide!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using only a left side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <summary>
|
||||
/// Always <c>null</c>, not applicable to this type of condition operator
|
||||
/// </summary>
|
||||
public override Type? RightSideType => null;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a);
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
{
|
||||
if (leftSideValue.GetType() != typeof(TLeftSide) && leftSideValue is IConvertible)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = (TLeftSide) leftSideValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftSide = default;
|
||||
}
|
||||
|
||||
return Evaluate(leftSide!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract class for display condition parts
|
||||
/// </summary>
|
||||
public abstract class DataModelConditionPart : IDisposable
|
||||
{
|
||||
private readonly List<DataModelConditionPart> _children = new();
|
||||
|
||||
protected DataModelConditionPart()
|
||||
{
|
||||
Children = new(_children);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this part
|
||||
/// </summary>
|
||||
public DataModelConditionPart? Parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of this part
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<DataModelConditionPart> Children { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a child to the display condition part's <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="dataModelConditionPart"></param>
|
||||
/// <param name="index">An optional index at which to insert the condition</param>
|
||||
public void AddChild(DataModelConditionPart dataModelConditionPart, int? index = null)
|
||||
{
|
||||
if (!_children.Contains(dataModelConditionPart))
|
||||
{
|
||||
dataModelConditionPart.Parent = this;
|
||||
if (index != null)
|
||||
_children.Insert(index.Value, dataModelConditionPart);
|
||||
else
|
||||
_children.Add(dataModelConditionPart);
|
||||
|
||||
OnChildAdded();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a child from the display condition part's <see cref="Children" /> collection
|
||||
/// </summary>
|
||||
/// <param name="dataModelConditionPart">The child to remove</param>
|
||||
public void RemoveChild(DataModelConditionPart dataModelConditionPart)
|
||||
{
|
||||
if (_children.Contains(dataModelConditionPart))
|
||||
{
|
||||
dataModelConditionPart.Parent = null;
|
||||
_children.Remove(dataModelConditionPart);
|
||||
OnChildRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all children. You monster.
|
||||
/// </summary>
|
||||
public void ClearChildren()
|
||||
{
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the data model
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract bool Evaluate();
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the given target (currently only for lists)
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
internal abstract bool EvaluateObject(object? target);
|
||||
|
||||
internal abstract void Save();
|
||||
internal abstract DataModelConditionPartEntity GetEntity();
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Disposed the condition part
|
||||
/// </summary>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child-condition was added
|
||||
/// </summary>
|
||||
public event EventHandler? ChildAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a child-condition was removed
|
||||
/// </summary>
|
||||
public event EventHandler? ChildRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invokers the <see cref="ChildAdded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildAdded()
|
||||
{
|
||||
ChildAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokers the <see cref="ChildRemoved" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnChildRemoved()
|
||||
{
|
||||
ChildRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,411 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||
/// static value
|
||||
/// </summary>
|
||||
public abstract class DataModelConditionPredicate : DataModelConditionPart
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
/// <param name="entity">A new empty entity</param>
|
||||
protected DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType, DataModelConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
PredicateType = predicateType;
|
||||
}
|
||||
|
||||
internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
PredicateType = (ProfileRightSideType) entity.PredicateType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the predicate type
|
||||
/// </summary>
|
||||
public ProfileRightSideType PredicateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the operator
|
||||
/// </summary>
|
||||
public BaseConditionOperator? Operator { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the left property
|
||||
/// </summary>
|
||||
public DataModelPath? LeftPath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the right property
|
||||
/// </summary>
|
||||
public DataModelPath? RightPath { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right static value, only used it <see cref="PredicateType" /> is
|
||||
/// <see cref="ProfileRightSideType.Static" />
|
||||
/// </summary>
|
||||
public object? RightStaticValue { get; protected set; }
|
||||
|
||||
internal DataModelConditionPredicateEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
return $"[Dynamic] {LeftPath} {Operator?.Description} {RightPath}";
|
||||
return $"[Static] {LeftPath} {Operator?.Description} {RightStaticValue}";
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
|
||||
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||
|
||||
LeftPath?.Dispose();
|
||||
RightPath?.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
|
||||
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
|
||||
|
||||
InitializeLeftPath();
|
||||
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
InitializeRightPath();
|
||||
// Right side static
|
||||
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If the left path is not valid we cannot reliably set up the right side because the type is unknown
|
||||
// Because of that wait for it to validate first
|
||||
if (LeftPath != null && !LeftPath.IsValid)
|
||||
{
|
||||
LeftPath.PathValidated += InitializeRightSideStatic;
|
||||
return;
|
||||
}
|
||||
if (LeftPath == null)
|
||||
return;
|
||||
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
object? rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
catch (JsonReaderException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRightSideStatic(object? sender, EventArgs args)
|
||||
{
|
||||
if (LeftPath == null)
|
||||
return;
|
||||
|
||||
LeftPath.PathValidated -= InitializeRightSideStatic;
|
||||
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
object? rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = CoreJson.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
DeserializationLogger.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the left path of this condition predicate
|
||||
/// </summary>
|
||||
protected abstract void InitializeLeftPath();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the right path of this condition predicate
|
||||
/// </summary>
|
||||
protected abstract void InitializeRightPath();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <summary>
|
||||
/// Updates the left side of the predicate
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the left side value inside the data model</param>
|
||||
public virtual void UpdateLeftSide(DataModelPath? path)
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path");
|
||||
|
||||
LeftPath?.Dispose();
|
||||
LeftPath = path != null ? new DataModelPath(path) : null;
|
||||
|
||||
ValidateOperator();
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the right side value inside the data model</param>
|
||||
public void UpdateRightSideDynamic(DataModelPath? path)
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
|
||||
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
|
||||
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
|
||||
|
||||
RightPath?.Dispose();
|
||||
RightPath = path != null ? new DataModelPath(path) : null;
|
||||
|
||||
PredicateType = ProfileRightSideType.Dynamic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="staticValue">The right side value to use</param>
|
||||
public void UpdateRightSideStatic(object? staticValue)
|
||||
{
|
||||
PredicateType = ProfileRightSideType.Static;
|
||||
RightPath?.Dispose();
|
||||
RightPath = null;
|
||||
|
||||
// If the operator is null simply apply the value, any validation will wait
|
||||
if (Operator == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the operator does not support a right side, always set it to null
|
||||
if (Operator.RightSideType == null)
|
||||
{
|
||||
RightStaticValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
Type? preferredType = GetPreferredRightSideType();
|
||||
if (staticValue != null && staticValue.GetType() == preferredType || preferredType == null)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, preferredType);
|
||||
// If null create a default instance for value types or simply make it null for reference types
|
||||
else if (preferredType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(preferredType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the operator of the predicate and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="conditionOperator"></param>
|
||||
public void UpdateOperator(BaseConditionOperator? conditionOperator)
|
||||
{
|
||||
if (conditionOperator == null)
|
||||
{
|
||||
Operator = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to check for compatibility without a left side, when left site does get set it will be checked again
|
||||
if (LeftPath == null || !LeftPath.IsValid)
|
||||
{
|
||||
Operator = conditionOperator;
|
||||
return;
|
||||
}
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
// Left side can't go empty so enforce a match
|
||||
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
|
||||
$"it does not support left side type {leftType.Name}");
|
||||
|
||||
Operator = conditionOperator;
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the best type to use for the right side op this predicate
|
||||
/// </summary>
|
||||
public abstract Type? GetPreferredRightSideType();
|
||||
|
||||
private void ValidateOperator()
|
||||
{
|
||||
if (LeftPath == null || !LeftPath.IsValid || Operator == null)
|
||||
return;
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
if (Operator == null)
|
||||
return;
|
||||
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Type rightSideType = RightPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue == null)
|
||||
return;
|
||||
|
||||
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
|
||||
UpdateRightSideStatic(null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// If the operator does not support a right side, immediately evaluate with null
|
||||
if (Operator.RightSideType == null)
|
||||
return Operator.InternalEvaluate(LeftPath.GetValue(), null);
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = LeftPath.GetValue();
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (LeftPath != null && !LeftPath.IsValid || RightPath != null && !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
|
||||
LeftPath?.Save();
|
||||
Entity.LeftPath = LeftPath?.Entity;
|
||||
RightPath?.Save();
|
||||
Entity.RightPath = RightPath?.Entity;
|
||||
|
||||
Entity.RightStaticValue = CoreJson.SerializeObject(RightStaticValue);
|
||||
|
||||
if (Operator?.Plugin != null)
|
||||
{
|
||||
Entity.OperatorPluginGuid = Operator.Plugin.Guid;
|
||||
Entity.OperatorType = Operator.GetType().Name;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
if (Entity.OperatorPluginGuid == conditionOperator.Plugin!.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
if (e.Registration.ConditionOperator != Operator)
|
||||
return;
|
||||
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition that is always true.
|
||||
/// </summary>
|
||||
public class AlwaysOnCondition : ICondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = new AlwaysOnConditionEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AlwaysOnCondition" /> class.
|
||||
/// </summary>
|
||||
/// <param name="alwaysOnConditionEntity">The entity used to store this condition.</param>
|
||||
/// <param name="profileElement">The profile element this condition applies to.</param>
|
||||
public AlwaysOnCondition(AlwaysOnConditionEntity alwaysOnConditionEntity, RenderProfileElement profileElement)
|
||||
{
|
||||
ProfileElement = profileElement;
|
||||
Entity = alwaysOnConditionEntity;
|
||||
}
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IStorageModel
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ICondition
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConditionEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public RenderProfileElement ProfileElement { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMet { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update()
|
||||
{
|
||||
if (ProfileElement.Parent is RenderProfileElement parent)
|
||||
IsMet = parent.DisplayConditionMet;
|
||||
else
|
||||
IsMet = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateTimeline(double deltaTime)
|
||||
{
|
||||
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OverrideTimeline(TimeSpan position)
|
||||
{
|
||||
ProfileElement.Timeline.Override(position, position > ProfileElement.Timeline.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,257 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A condition that evaluates to true when an event is triggered
|
||||
/// </summary>
|
||||
public class DataModelConditionEvent : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
private bool _reinitializing;
|
||||
private IDataModelEvent? _valueChangedEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEvent" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionEvent(DataModelConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionEventEntity();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionEvent(DataModelConditionPart parent, DataModelConditionEventEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the event property
|
||||
/// </summary>
|
||||
public DataModelPath? EventPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time the event this condition is applied to was triggered
|
||||
/// </summary>
|
||||
public DateTime LastTrigger { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of argument the event provides
|
||||
/// </summary>
|
||||
public Type? EventArgumentType { get; private set; }
|
||||
|
||||
internal DataModelConditionEventEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||
|
||||
IDataModelEvent? dataModelEvent = GetDataModelEvent();
|
||||
if (dataModelEvent == null)
|
||||
return false;
|
||||
dataModelEvent.Update();
|
||||
|
||||
// Only evaluate to true once every time the event has been triggered since the last evaluation
|
||||
if (dataModelEvent.LastTrigger <= LastTrigger)
|
||||
return false;
|
||||
|
||||
LastTrigger = DateTime.Now;
|
||||
|
||||
// If there is a child (root group), it must evaluate to true whenever the event triggered
|
||||
if (Children.Any())
|
||||
return Children[0].EvaluateObject(dataModelEvent.LastEventArgumentsUntyped);
|
||||
|
||||
// If there are no children, we always evaluate to true whenever the event triggered
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the event the condition is triggered by
|
||||
/// </summary>
|
||||
public void UpdateEvent(DataModelPath? path)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionEvent");
|
||||
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update event to an invalid path");
|
||||
|
||||
EventPath?.Dispose();
|
||||
EventPath = path != null ? new DataModelPath(path) : null;
|
||||
SubscribeToEventPath();
|
||||
CreateValueChangedEventIfNeeded();
|
||||
|
||||
// Remove the old root group that was tied to the old data model
|
||||
ClearChildren();
|
||||
|
||||
if (EventPath != null)
|
||||
{
|
||||
EventArgumentType = GetEventArgumentType();
|
||||
// Create a new root group
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
EventArgumentType = null;
|
||||
}
|
||||
|
||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IDataModelEvent" /> this <see cref="DataModelConditionEvent" /> is triggered by</returns>
|
||||
public IDataModelEvent? GetDataModelEvent()
|
||||
{
|
||||
if (_valueChangedEvent != null)
|
||||
return _valueChangedEvent;
|
||||
return EventPath?.GetValue() as IDataModelEvent;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
EventPath?.Dispose();
|
||||
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (EventPath != null && !EventPath.IsValid)
|
||||
return;
|
||||
|
||||
// Target list
|
||||
EventPath?.Save();
|
||||
Entity.EventPath = EventPath?.Entity;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
ClearChildren();
|
||||
|
||||
if (Entity.EventPath == null)
|
||||
return;
|
||||
|
||||
DataModelPath eventPath = new(null, Entity.EventPath);
|
||||
EventPath = eventPath;
|
||||
SubscribeToEventPath();
|
||||
CreateValueChangedEventIfNeeded();
|
||||
|
||||
EventArgumentType = GetEventArgumentType();
|
||||
// There should only be one child and it should be a group
|
||||
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
|
||||
{
|
||||
AddChild(new DataModelConditionGroup(this, rootGroup));
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
|
||||
LastTrigger = GetDataModelEvent()?.LastTrigger ?? DateTime.Now;
|
||||
}
|
||||
|
||||
private Type? GetEventArgumentType()
|
||||
{
|
||||
if (EventPath == null || !EventPath.IsValid)
|
||||
return null;
|
||||
|
||||
if (_valueChangedEvent != null)
|
||||
return _valueChangedEvent.ArgumentsType;
|
||||
|
||||
// Cannot rely on EventPath.GetValue() because part of the path might be null
|
||||
Type eventType = EventPath.GetPropertyType()!;
|
||||
return eventType.IsGenericType ? eventType.GetGenericArguments()[0] : typeof(DataModelEventArgs);
|
||||
}
|
||||
|
||||
private void SubscribeToEventPath()
|
||||
{
|
||||
if (EventPath == null) return;
|
||||
EventPath.PathValidated += EventPathOnPathValidated;
|
||||
EventPath.PathInvalidated += EventPathOnPathInvalidated;
|
||||
}
|
||||
|
||||
private void CreateValueChangedEventIfNeeded()
|
||||
{
|
||||
Type? propertyType = EventPath?.GetPropertyType();
|
||||
if (propertyType == null)
|
||||
return;
|
||||
|
||||
if (!typeof(IDataModelEvent).IsAssignableFrom(propertyType))
|
||||
{
|
||||
IDataModelEvent? instance = (IDataModelEvent?) Activator.CreateInstance(typeof(DataModelValueChangedEvent<>).MakeGenericType(propertyType), EventPath);
|
||||
_valueChangedEvent = instance ?? throw new ArtemisCoreException("Failed to create a DataModelValueChangedEvent for a property changed data model event");
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueChangedEvent = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void EventPathOnPathValidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
EventPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
private void EventPathOnPathInvalidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
EventPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate like evaluated inside a <see cref="DataModelConditionEvent" />
|
||||
/// </summary>
|
||||
public class DataModelConditionEventPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionEventPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionEventPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionEventPredicateEntity())
|
||||
{
|
||||
DataModelConditionEvent = null!;
|
||||
ApplyParentEvent();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionEventPredicate(DataModelConditionPart parent, DataModelConditionEventPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
DataModelConditionEvent = null!;
|
||||
ApplyParentEvent();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model condition event this predicate belongs to
|
||||
/// </summary>
|
||||
public DataModelConditionEvent DataModelConditionEvent { get; private set; }
|
||||
|
||||
private void ApplyParentEvent()
|
||||
{
|
||||
DataModelConditionPart? current = Parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is DataModelConditionEvent parentEvent)
|
||||
{
|
||||
DataModelConditionEvent = parentEvent;
|
||||
return;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
if (DataModelConditionEvent == null)
|
||||
throw new ArtemisCoreException("This data model condition event predicate does not belong to a data model condition event");
|
||||
}
|
||||
|
||||
private object? GetEventPathValue(DataModelPath path, object? target)
|
||||
{
|
||||
lock (path)
|
||||
{
|
||||
if (!(path.Target is EventPredicateWrapperDataModel wrapper))
|
||||
throw new ArtemisCoreException("Data model condition event predicate has a path with an invalid target");
|
||||
|
||||
wrapper.UntypedArguments = target;
|
||||
return path.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = DataModelConditionEvent.EventArgumentType != null
|
||||
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.LeftPath)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
{
|
||||
// Right side dynamic using event arguments
|
||||
if (Entity.RightPath.WrapperType == PathWrapperType.Event)
|
||||
{
|
||||
RightPath = DataModelConditionEvent.EventArgumentType != null
|
||||
? new DataModelPath(EventPredicateWrapperDataModel.Create(DataModelConditionEvent.EventArgumentType), Entity.RightPath)
|
||||
: null;
|
||||
}
|
||||
// Right side dynamic
|
||||
else
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <summary>
|
||||
/// Not supported for event predicates, always returns <c>false</c>
|
||||
/// </summary>
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// If the operator does not support a right side, immediately evaluate with null
|
||||
if (Operator.RightSideType == null)
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), null);
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = GetEventPathValue(LeftPath, target);
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
// If the path targets a property inside the event, evaluate on the event path value instead of the right path value
|
||||
if (RightPath.Target is EventPredicateWrapperDataModel)
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), GetEventPathValue(RightPath, target));
|
||||
return Operator.InternalEvaluate(GetEventPathValue(LeftPath, target), RightPath.GetValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate in a data model condition using either two data model values or one data model value and a
|
||||
/// static value
|
||||
/// </summary>
|
||||
public class DataModelConditionGeneralPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGeneralPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionGeneralPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionGeneralPredicateEntity())
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionGeneralPredicate(DataModelConditionPart parent, DataModelConditionGeneralPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = new DataModelPath(null, Entity.LeftPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,190 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A group containing zero to many <see cref="DataModelConditionPart" />s which it evaluates using a boolean specific
|
||||
/// operator
|
||||
/// </summary>
|
||||
public class DataModelConditionGroup : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionGroup(DataModelConditionPart? parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionGroupEntity();
|
||||
ChildAdded += OnChildrenChanged;
|
||||
ChildRemoved += OnChildrenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionGroup" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="entity"></param>
|
||||
public DataModelConditionGroup(DataModelConditionPart? parent, DataModelConditionGroupEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
|
||||
|
||||
foreach (DataModelConditionPartEntity childEntity in Entity.Children)
|
||||
{
|
||||
if (childEntity is DataModelConditionGroupEntity groupEntity)
|
||||
AddChild(new DataModelConditionGroup(this, groupEntity));
|
||||
else if (childEntity is DataModelConditionListEntity listEntity)
|
||||
AddChild(new DataModelConditionList(this, listEntity));
|
||||
else if (childEntity is DataModelConditionEventEntity eventEntity)
|
||||
AddChild(new DataModelConditionEvent(this, eventEntity));
|
||||
else if (childEntity is DataModelConditionGeneralPredicateEntity predicateEntity)
|
||||
AddChild(new DataModelConditionGeneralPredicate(this, predicateEntity));
|
||||
else if (childEntity is DataModelConditionListPredicateEntity listPredicateEntity)
|
||||
AddChild(new DataModelConditionListPredicate(this, listPredicateEntity));
|
||||
else if (childEntity is DataModelConditionEventPredicateEntity eventPredicateEntity)
|
||||
AddChild(new DataModelConditionEventPredicate(this, eventPredicateEntity));
|
||||
}
|
||||
|
||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||
ChildAdded += OnChildrenChanged;
|
||||
ChildRemoved += OnChildrenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the boolean operator of this group
|
||||
/// </summary>
|
||||
public BooleanOperator BooleanOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this group contains any events
|
||||
/// </summary>
|
||||
public bool ContainsEvents { get; private set; }
|
||||
|
||||
internal DataModelConditionGroupEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
||||
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].Evaluate();
|
||||
|
||||
if (ContainsEvents)
|
||||
{
|
||||
bool eventTriggered = Children.Where(c => c is DataModelConditionEvent).Any(c => c.Evaluate());
|
||||
return eventTriggered && EvaluateWithOperator(Children.Where(c => !(c is DataModelConditionEvent)));
|
||||
}
|
||||
return EvaluateWithOperator(Children);
|
||||
}
|
||||
|
||||
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
|
||||
{
|
||||
return BooleanOperator switch
|
||||
{
|
||||
BooleanOperator.And => targets.All(c => c.Evaluate()),
|
||||
BooleanOperator.Or => targets.Any(c => c.Evaluate()),
|
||||
BooleanOperator.AndNot => targets.All(c => !c.Evaluate()),
|
||||
BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionGroup");
|
||||
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].EvaluateObject(target);
|
||||
|
||||
return BooleanOperator switch
|
||||
{
|
||||
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
|
||||
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
Entity.BooleanOperator = (int) BooleanOperator;
|
||||
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
private void OnChildrenChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ContainsEvents = Children.Any(c => c is DataModelConditionEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a boolean operator
|
||||
/// </summary>
|
||||
public enum BooleanOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// All the conditions in the group should evaluate to true
|
||||
/// </summary>
|
||||
And,
|
||||
|
||||
/// <summary>
|
||||
/// Any of the conditions in the group should evaluate to true
|
||||
/// </summary>
|
||||
Or,
|
||||
|
||||
/// <summary>
|
||||
/// All the conditions in the group should evaluate to false
|
||||
/// </summary>
|
||||
AndNot,
|
||||
|
||||
/// <summary>
|
||||
/// Any of the conditions in the group should evaluate to false
|
||||
/// </summary>
|
||||
OrNot
|
||||
}
|
||||
}
|
||||
@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A condition that evaluates one or more predicates inside a list
|
||||
/// </summary>
|
||||
public class DataModelConditionList : DataModelConditionPart
|
||||
{
|
||||
private bool _disposed;
|
||||
private bool _reinitializing;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionList" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
public DataModelConditionList(DataModelConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DataModelConditionListEntity();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionList(DataModelConditionPart parent, DataModelConditionListEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list operator
|
||||
/// </summary>
|
||||
public ListOperator ListOperator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the list property
|
||||
/// </summary>
|
||||
public DataModelPath? ListPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content of the list this predicate is evaluated on
|
||||
/// </summary>
|
||||
public Type? ListType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the list contains primitives
|
||||
/// </summary>
|
||||
public bool IsPrimitiveList { get; set; }
|
||||
|
||||
internal DataModelConditionListEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (ListPath == null || !ListPath.IsValid)
|
||||
return false;
|
||||
|
||||
return EvaluateObject(ListPath.GetValue());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the list the predicate is evaluated on
|
||||
/// </summary>
|
||||
/// <param name="path">The path pointing to the list inside the list</param>
|
||||
public void UpdateList(DataModelPath? path)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update list to an invalid path");
|
||||
|
||||
ListPath?.Dispose();
|
||||
ListPath = path != null ? new DataModelPath(path) : null;
|
||||
SubscribeToListPath();
|
||||
|
||||
// Remove the old root group that was tied to the old data model
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
|
||||
if (ListPath != null)
|
||||
{
|
||||
Type listType = ListPath.GetPropertyType()!;
|
||||
ListType = listType.GetGenericEnumerableType();
|
||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
||||
|
||||
// Create a new root group
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
ListPath?.Dispose();
|
||||
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("DataModelConditionList");
|
||||
|
||||
if (!Children.Any())
|
||||
return false;
|
||||
if (!(target is IEnumerable enumerable))
|
||||
return false;
|
||||
|
||||
IEnumerable<object> objectList = enumerable.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
{
|
||||
// Don't save an invalid state
|
||||
if (ListPath != null && !ListPath.IsValid)
|
||||
return;
|
||||
|
||||
// Target list
|
||||
ListPath?.Save();
|
||||
Entity.ListPath = ListPath?.Entity;
|
||||
|
||||
// Operator
|
||||
Entity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (DataModelConditionPart child in Children)
|
||||
child.Save();
|
||||
}
|
||||
|
||||
internal override DataModelConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
while (Children.Any())
|
||||
RemoveChild(Children[0]);
|
||||
|
||||
if (Entity.ListPath == null)
|
||||
return;
|
||||
|
||||
// Ensure the list path is valid and points to a list
|
||||
DataModelPath listPath = new(null, Entity.ListPath);
|
||||
Type listType = listPath.GetPropertyType()!;
|
||||
// Can't check this on an invalid list, if it becomes valid later lets hope for the best
|
||||
if (listPath.IsValid && !PointsToList(listPath))
|
||||
return;
|
||||
|
||||
ListPath = listPath;
|
||||
SubscribeToListPath();
|
||||
if (ListPath.IsValid)
|
||||
{
|
||||
ListType = listType.GetGenericEnumerableType();
|
||||
IsPrimitiveList = ListType == null || ListType.IsPrimitive || ListType.IsEnum || ListType == typeof(string);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
IsPrimitiveList = false;
|
||||
}
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
if (Entity.Children.FirstOrDefault() is DataModelConditionGroupEntity rootGroup)
|
||||
{
|
||||
AddChild(new DataModelConditionGroup(this, rootGroup));
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DataModelConditionGroup(this));
|
||||
}
|
||||
}
|
||||
|
||||
private bool PointsToList(DataModelPath dataModelPath)
|
||||
{
|
||||
Type? type = dataModelPath.GetPropertyType();
|
||||
return type?.IsGenericEnumerable() ?? false;
|
||||
}
|
||||
|
||||
private void SubscribeToListPath()
|
||||
{
|
||||
if (ListPath == null) return;
|
||||
ListPath.PathValidated += ListPathOnPathValidated;
|
||||
ListPath.PathInvalidated += ListPathOnPathInvalidated;
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void ListPathOnPathValidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
ListPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
private void ListPathOnPathInvalidated(object? sender, EventArgs e)
|
||||
{
|
||||
if (_reinitializing)
|
||||
return;
|
||||
|
||||
_reinitializing = true;
|
||||
ListPath?.Dispose();
|
||||
Initialize();
|
||||
_reinitializing = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a list operator
|
||||
/// </summary>
|
||||
public enum ListOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Any of the list items should evaluate to true
|
||||
/// </summary>
|
||||
Any,
|
||||
|
||||
/// <summary>
|
||||
/// All of the list items should evaluate to true
|
||||
/// </summary>
|
||||
All,
|
||||
|
||||
/// <summary>
|
||||
/// None of the list items should evaluate to true
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// A specific amount of the list items should evaluate to true
|
||||
/// </summary>
|
||||
Count
|
||||
}
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A predicate like evaluated inside a <see cref="DataModelConditionList" />
|
||||
/// </summary>
|
||||
public class DataModelConditionListPredicate : DataModelConditionPredicate
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelConditionListPredicate" /> class
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="predicateType"></param>
|
||||
public DataModelConditionListPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType)
|
||||
: base(parent, predicateType, new DataModelConditionListPredicateEntity())
|
||||
{
|
||||
DataModelConditionList = null!;
|
||||
ApplyParentList();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal DataModelConditionListPredicate(DataModelConditionPart parent, DataModelConditionListPredicateEntity entity)
|
||||
: base(parent, entity)
|
||||
{
|
||||
DataModelConditionList = null!;
|
||||
ApplyParentList();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model condition list this predicate belongs to
|
||||
/// </summary>
|
||||
public DataModelConditionList DataModelConditionList { get; private set; }
|
||||
|
||||
private void ApplyParentList()
|
||||
{
|
||||
DataModelConditionPart? current = Parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is DataModelConditionList parentList)
|
||||
{
|
||||
DataModelConditionList = parentList;
|
||||
return;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
if (DataModelConditionList == null)
|
||||
throw new ArtemisCoreException("This data model condition list predicate does not belong to a data model condition list");
|
||||
}
|
||||
|
||||
private object? GetListPathValue(DataModelPath path, object? target)
|
||||
{
|
||||
if (!(path.Target is ListPredicateWrapperDataModel wrapper))
|
||||
throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target");
|
||||
|
||||
wrapper.UntypedValue = target;
|
||||
return path.GetValue();
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeLeftPath()
|
||||
{
|
||||
if (Entity.LeftPath != null)
|
||||
LeftPath = DataModelConditionList.ListType != null
|
||||
? new DataModelPath(ListPredicateWrapperDataModel.Create(
|
||||
DataModelConditionList.ListType,
|
||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
||||
), Entity.LeftPath)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InitializeRightPath()
|
||||
{
|
||||
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
|
||||
{
|
||||
// Right side dynamic inside the list
|
||||
if (Entity.RightPath.WrapperType == PathWrapperType.List)
|
||||
{
|
||||
RightPath = DataModelConditionList.ListType != null
|
||||
? new DataModelPath(ListPredicateWrapperDataModel.Create(
|
||||
DataModelConditionList.ListType,
|
||||
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
|
||||
), Entity.RightPath)
|
||||
: null;
|
||||
}
|
||||
// Right side dynamic
|
||||
else
|
||||
RightPath = new DataModelPath(null, Entity.RightPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modification
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? GetPreferredRightSideType()
|
||||
{
|
||||
Type? preferredType = Operator?.RightSideType;
|
||||
Type? leftSideType = DataModelConditionList.IsPrimitiveList
|
||||
? DataModelConditionList.ListType
|
||||
: LeftPath?.GetPropertyType();
|
||||
if (preferredType == null)
|
||||
return null;
|
||||
|
||||
if (leftSideType != null && preferredType.IsAssignableFrom(leftSideType))
|
||||
preferredType = leftSideType;
|
||||
|
||||
return preferredType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evaluation
|
||||
|
||||
/// <summary>
|
||||
/// Not supported for list predicates, always returns <c>false</c>
|
||||
/// </summary>
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override bool EvaluateObject(object? target)
|
||||
{
|
||||
if (Operator == null || LeftPath == null || !LeftPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with a static value
|
||||
if (PredicateType == ProfileRightSideType.Static)
|
||||
{
|
||||
object? leftSideValue = GetListPathValue(LeftPath, target);
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
// If the path targets a property inside the list, evaluate on the list path value instead of the right path value
|
||||
if (RightPath.Target is ListPredicateWrapperDataModel)
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user