Meta - Replace WPF projects with Avalonia projects

This commit is contained in:
Robert committed 2022-03-27 11:55:26 +02:00
1 parent fcf0376a9a
commit d7f3ee1190
1110 files changed
+10872 -52240

No files matched your search

-454
View File
@@ -1,454 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
-12
View File
@@ -1,12 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:Artemis.UI;assembly=Artemis.UI"
x:Class="Artemis.UI.Linux.App">
<Application.DataTemplates>
<ui:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<StyleInclude Source="avares://Artemis.UI/Styles/Artemis.axaml" />
</Application.Styles>
</Application>
@@ -1,39 +0,0 @@
using Artemis.Core.Services;
using Artemis.UI.Linux.Providers.Input;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
using Ninject;
using Avalonia.Controls.ApplicationLifetimes;
namespace Artemis.UI.Linux
{
public class App : Application
{
private StandardKernel? _kernel;
private ApplicationStateManager? _applicationStateManager;
public override void Initialize()
{
_kernel = ArtemisBootstrapper.Bootstrap(this);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
RegisterProviders();
}
public override void OnFrameworkInitializationCompleted()
{
ArtemisBootstrapper.Initialize();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
}
private void RegisterProviders()
{
IInputService inputService = _kernel.Get<IInputService>();
inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>());
}
}
}
@@ -1,144 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using Ninject;
namespace Artemis.UI.Linux
{
public class ApplicationStateManager
{
private readonly IWindowService _windowService;
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
private Mutex? _artemisMutex;
public ApplicationStateManager(IKernel kernel, string[] startupArguments)
{
_windowService = kernel.Get<IWindowService>();
StartupArguments = startupArguments;
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
// On OS shutdown dispose the kernel just so device providers get a chance to clean up
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
{
controlledApplicationLifetime.Exit += (_, _) =>
{
RunForcedShutdownIfEnabled();
// Dispose plugins before disposing the kernel because plugins might access services during dispose
kernel.Get<IPluginManagementService>().Dispose();
kernel.Dispose();
};
}
}
public string[] StartupArguments { get; }
public bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535d", out bool createdNew);
if (createdNew)
return false;
return RemoteFocus();
}
public void DisplayException(Exception e)
{
try
{
_windowService.ShowExceptionDialog("An unhandled exception occured", e);
}
catch
{
// ignored, we tried
}
}
private bool RemoteFocus()
{
// At this point we cannot read the database yet to retrieve the web server port.
// Instead use the method external applications should use as well.
if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
{
KillOtherInstances();
return false;
}
string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
}
private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e)
{
List<string> argsList = new();
argsList.AddRange(StartupArguments);
if (e.ExtraArgs != null)
argsList.AddRange(e.ExtraArgs.Except(argsList));
//TODO: start new instance with correct arguments
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
}
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
{
RunForcedShutdownIfEnabled();
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
}
private void RunForcedShutdownIfEnabled()
{
if (StartupArguments.Contains("--disable-forced-shutdown"))
return;
//todo
}
}
}
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup>
</Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

-24
View File
@@ -1,24 +0,0 @@
using System;
using Avalonia;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Linux
{
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
}
}
}
@@ -1,34 +0,0 @@
namespace Artemis.UI.Linux.Providers.Input
{
public enum LinuxAbsoluteAxis
{
ABS_X = 0x00,
ABS_Y = 0x01,
ABS_Z = 0x02,
ABS_RX = 0x03,
ABS_RY = 0x04,
ABS_RZ = 0x05,
ABS_THROTTLE = 0x06,
ABS_RUDDER = 0x07,
ABS_WHEEL = 0x08,
ABS_GAS = 0x09,
ABS_BRAKE = 0x0a,
ABS_HAT0X = 0x10,
ABS_HAT0Y = 0x11,
ABS_HAT1X = 0x12,
ABS_HAT1Y = 0x13,
ABS_HAT2X = 0x14,
ABS_HAT2Y = 0x15,
ABS_HAT3X = 0x16,
ABS_HAT3Y = 0x17,
ABS_PRESSURE = 0x18,
ABS_DISTANCE = 0x19,
ABS_TILT_X = 0x1a,
ABS_TILT_Y = 0x1b,
ABS_TOOL_WIDTH = 0x1c,
ABS_VOLUME = 0x20,
ABS_MISC = 0x28,
}
}
@@ -1,93 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using Humanizer;
namespace Artemis.UI.Linux.Providers.Input
{
/// <summary>
/// Data transfer object representing a device read from /proc/bus/input/devices
/// </summary>
public class LinuxInputDevice
{
public string InputId { get; }
public string? Bus { get; }
public string? Vendor { get; }
public string? Product { get; }
public string? Version { get; }
public string? Name { get; }
public string? Phys { get; }
public string? Sysfs { get; }
public string? Uniq { get; }
public string[]? Handlers { get; }
public bool IsMouse => Handlers.Any(h => h.Contains("mouse"));
public bool IsKeyboard => Handlers.Any(h => h.Contains("kbd"));
public bool IsGamePad => Handlers.Any(h => h.Contains("js"));
public string EventPath => $"/dev/input/{Handlers.First(h => h.Contains("event"))}";
public LinuxInputDevice(IEnumerable<string> lines)
{
foreach (string line in lines)
{
char dataType = line.First();
string data = line.Substring(3);
//get the first character in each line and set the according property with relevant data
switch (dataType)
{
case 'I':
InputId = data;
foreach (string component in data.Split(" "))
{
string?[] parts = component.Split('=');
switch (parts[0])
{
case "Bus":
Bus = parts[1];
break;
case "Vendor":
Vendor = parts[1];
break;
case "Product":
Product = parts[1];
break;
case "Version":
Version = parts[1];
break;
default:
break;
}
}
break;
case 'N':
Name = data.Replace("\"", "")
.Replace("Name=", "");
break;
case 'P':
Phys = data.Replace("Phys=", "");
break;
case 'S':
Sysfs = data.Replace("Sysfs=", "");
break;
case 'H':
Handlers = data.Replace("Handlers=", "").Split(" ");
break;
case 'U':
Uniq = data.Replace("Uniq=", "");
break;
default:
//do we need any more of this data?
break;
}
}
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => $"{Name} - {EventPath}";
#endregion
}
}
@@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Artemis.UI.Linux.Providers.Input
{
public static class LinuxInputDeviceFinder
{
private const string DEVICES_FILE = "/proc/bus/input/devices";
public static IEnumerable<LinuxInputDevice> Find()
{
return File.ReadAllLines(DEVICES_FILE)
.PartitionBy(s => s == "") //split on empty lines
.Select(lineGroup => new LinuxInputDevice(lineGroup));
}
//https://stackoverflow.com/questions/56623354
private static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> a, Func<T, bool> predicate)
{
int groupNumber = 0;
Func<bool, int?> getGroupNumber = skip =>
{
if (skip)
{
// prepare next group, we don't care if we increment more than once
// we only want to split groups
groupNumber++;
// null, to be able to filter out group separators
return null;
}
return groupNumber;
};
return a
.Select(x => new { Value = x, GroupNumber = getGroupNumber(predicate(x))} )
.Where(x => x.GroupNumber != null)
.GroupBy(x => x.GroupNumber)
.Select(g => g.Select(x => x.Value));
}
}
}
@@ -1,60 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Linux.Utilities;
namespace Artemis.UI.Linux.Providers.Input
{
internal class LinuxInputDeviceReader
{
private readonly FileStream _stream;
private readonly Task _task;
private readonly CancellationTokenSource _cts;
private readonly byte[] _buffer;
internal event EventHandler<LinuxInputEventArgs>? InputEvent;
public LinuxInputDevice InputDevice { get; }
public LinuxInputDeviceReader(LinuxInputDevice inputDevice)
{
InputDevice = inputDevice;
_buffer = new byte[Marshal.SizeOf<LinuxInputEventArgs>()];
_stream = new FileStream(InputDevice.EventPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 2048, FileOptions.Asynchronous);
_cts = new CancellationTokenSource();
_task = Task.Run(Read, _cts.Token);
}
private async Task Read()
{
while (!_cts.IsCancellationRequested)
{
try
{
int readBytes = await _stream.ReadAsync(_buffer, _cts.Token);
if (readBytes == 0)
continue;
InputEvent?.Invoke(this, MemoryMarshal.Read<LinuxInputEventArgs>(_buffer));
}
catch
{
// ignored
}
}
}
public void Dispose()
{
_cts.Cancel();
_stream.Flush();
_stream.Dispose();
//_task.Wait(); //TODO: fix this, it hangs
_cts.Dispose();
}
}
}
@@ -1,15 +0,0 @@
// ReSharper disable UnusedMember.Global
#pragma warning disable CS0649
namespace Artemis.UI.Linux.Providers.Input
{
// https://www.kernel.org/doc/Documentation/input/input.txt
internal readonly struct LinuxInputEventArgs
{
internal readonly long TimeSeconds;
internal readonly long TimeMicroseconds;
internal readonly LinuxInputEventType Type;
internal readonly short Code;
internal readonly int Value;
}
}
@@ -1,68 +0,0 @@
namespace Artemis.UI.Linux.Providers.Input
{
/// <summary>
/// https://www.kernel.org/doc/Documentation/input/event-codes.txt
/// </summary>
internal enum LinuxInputEventType : ushort
{
/// <summary>
/// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
/// </summary>
SYN = 0x00,
/// <summary>
/// Used to describe state changes of keyboards, buttons, or other key-like devices.
/// </summary>
KEY = 0x01,
/// <summary>
/// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
/// </summary>
REL = 0x02,
/// <summary>
/// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
/// </summary>
ABS = 0x03,
/// <summary>
/// Used to describe miscellaneous input data that do not fit into other types.
/// </summary>
MSC = 0x04,
/// <summary>
/// Used to describe binary state input switches.
/// </summary>
SW = 0x05,
/// <summary>
/// Used to turn LEDs on devices on and off.
/// </summary>
LED = 0x11,
/// <summary>
/// Used to output sound to devices.
/// </summary>
SND = 0x12,
/// <summary>
/// Used for autorepeating devices.
/// </summary>
REP = 0x14,
/// <summary>
/// Used to send force feedback commands to an input device.
/// </summary>
FF = 0x15,
/// <summary>
/// A special type for power button and switch input.
/// </summary>
PWR = 0x16,
/// <summary>
/// Used to receive force feedback device status.
/// </summary>
FF_STATUS = 0x17,
}
}
@@ -1,119 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Services;
using Artemis.UI.Linux.Utilities;
using Serilog;
namespace Artemis.UI.Linux.Providers.Input
{
public class LinuxInputProvider : InputProvider
{
private readonly IInputService _inputService;
private readonly ILogger _logger;
private readonly List<LinuxInputDeviceReader> _readers;
public LinuxInputProvider(ILogger logger, IInputService inputService)
{
_logger = logger;
_inputService = inputService;
_readers = new List<LinuxInputDeviceReader>();
foreach (LinuxInputDevice deviceDefinition in LinuxInputDeviceFinder.Find())
{
LinuxInputDeviceReader? reader = new LinuxInputDeviceReader(deviceDefinition);
reader.InputEvent += OnInputEvent;
_readers.Add(reader);
}
}
private void OnInputEvent(object? sender, LinuxInputEventArgs e)
{
if (sender is not LinuxInputDeviceReader reader)
return;
if (reader.InputDevice.IsKeyboard)
{
HandleKeyboardData(reader.InputDevice, e);
}
else if (reader.InputDevice.IsMouse)
{
HandleMouseData(reader.InputDevice, e);
}
else if (reader.InputDevice.IsGamePad)
{
//TODO: handle game pad input?
}
}
private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs e)
{
switch (e.Type)
{
case LinuxInputEventType.KEY:
KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) e.Code);
bool isDown = e.Value != 0;
_logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes) e.Code} | Down: {isDown}");
//TODO: identify
OnKeyboardDataReceived(null, key, isDown);
break;
default:
_logger.Verbose($"Unknown keyboard event type: {e.Type}");
break;
}
}
private void HandleMouseData(LinuxInputDevice mouse, LinuxInputEventArgs e)
{
switch (e.Type)
{
case LinuxInputEventType.KEY:
bool isDown = e.Value != 0;
MouseButton button = InputUtilities.MouseButtonFromButtonCode((LinuxKeyboardKeyCodes)e.Code);
_logger.Verbose($"Mouse Button: {(LinuxKeyboardKeyCodes) e.Code} | Down: {isDown}");
//TODO: identify
OnMouseButtonDataReceived(null, button, isDown);
break;
case LinuxInputEventType.ABS:
LinuxAbsoluteAxis absoluteAxis = (LinuxAbsoluteAxis) e.Code;
_logger.Verbose($"Absolute mouse: axis={absoluteAxis} | value={e.Value}");
break;
case LinuxInputEventType.REL:
LinuxRelativeAxis relativeAxis = (LinuxRelativeAxis) e.Code;
_logger.Verbose($"Relative mouse: axis={relativeAxis} | value={e.Value}");
//TODO: handle mouse movement
break;
default:
_logger.Verbose($"Unknown mouse event type: {e.Type}");
break;
}
}
#region Overrides of InputProvider
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
for (int i = _readers.Count - 1; i >= 0; i--)
{
_readers[i].InputEvent -= OnInputEvent;
_readers[i].Dispose();
_readers.RemoveAt(i);
}
}
base.Dispose(disposing);
}
#endregion
}
}
@@ -1,620 +0,0 @@
namespace Artemis.UI.Linux.Providers.Input
{
//https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
public enum LinuxKeyboardKeyCodes
{
KEY_RESERVED = 0,
KEY_ESC = 1,
KEY_1 = 2,
KEY_2 = 3,
KEY_3 = 4,
KEY_4 = 5,
KEY_5 = 6,
KEY_6 = 7,
KEY_7 = 8,
KEY_8 = 9,
KEY_9 = 10,
KEY_0 = 11,
KEY_MINUS = 12,
KEY_EQUAL = 13,
KEY_BACKSPACE = 14,
KEY_TAB = 15,
KEY_Q = 16,
KEY_W = 17,
KEY_E = 18,
KEY_R = 19,
KEY_T = 20,
KEY_Y = 21,
KEY_U = 22,
KEY_I = 23,
KEY_O = 24,
KEY_P = 25,
KEY_LEFTBRACE = 26,
KEY_RIGHTBRACE = 27,
KEY_ENTER = 28,
KEY_LEFTCTRL = 29,
KEY_A = 30,
KEY_S = 31,
KEY_D = 32,
KEY_F = 33,
KEY_G = 34,
KEY_H = 35,
KEY_J = 36,
KEY_K = 37,
KEY_L = 38,
KEY_SEMICOLON = 39,
KEY_APOSTROPHE = 40,
KEY_GRAVE = 41,
KEY_LEFTSHIFT = 42,
KEY_BACKSLASH = 43,
KEY_Z = 44,
KEY_X = 45,
KEY_C = 46,
KEY_V = 47,
KEY_B = 48,
KEY_N = 49,
KEY_M = 50,
KEY_COMMA = 51,
KEY_DOT = 52,
KEY_SLASH = 53,
KEY_RIGHTSHIFT = 54,
KEY_KPASTERISK = 55,
KEY_LEFTALT = 56,
KEY_SPACE = 57,
KEY_CAPSLOCK = 58,
KEY_F1 = 59,
KEY_F2 = 60,
KEY_F3 = 61,
KEY_F4 = 62,
KEY_F5 = 63,
KEY_F6 = 64,
KEY_F7 = 65,
KEY_F8 = 66,
KEY_F9 = 67,
KEY_F10 = 68,
KEY_NUMLOCK = 69,
KEY_SCROLLLOCK = 70,
KEY_KP7 = 71,
KEY_KP8 = 72,
KEY_KP9 = 73,
KEY_KPMINUS = 74,
KEY_KP4 = 75,
KEY_KP5 = 76,
KEY_KP6 = 77,
KEY_KPPLUS = 78,
KEY_KP1 = 79,
KEY_KP2 = 80,
KEY_KP3 = 81,
KEY_KP0 = 82,
KEY_KPDOT = 83,
KEY_ZENKAKUHANKAKU = 85,
KEY_102ND = 86,
KEY_F11 = 87,
KEY_F12 = 88,
KEY_RO = 89,
KEY_KATAKANA = 90,
KEY_HIRAGANA = 91,
KEY_HENKAN = 92,
KEY_KATAKANAHIRAGANA = 93,
KEY_MUHENKAN = 94,
KEY_KPJPCOMMA = 95,
KEY_KPENTER = 96,
KEY_RIGHTCTRL = 97,
KEY_KPSLASH = 98,
KEY_SYSRQ = 99,
KEY_RIGHTALT = 100,
KEY_LINEFEED = 101,
KEY_HOME = 102,
KEY_UP = 103,
KEY_PAGEUP = 104,
KEY_LEFT = 105,
KEY_RIGHT = 106,
KEY_END = 107,
KEY_DOWN = 108,
KEY_PAGEDOWN = 109,
KEY_INSERT = 110,
KEY_DELETE = 111,
KEY_MACRO = 112,
KEY_MUTE = 113,
KEY_VOLUMEDOWN = 114,
KEY_VOLUMEUP = 115,
KEY_POWER = 116, /* SC System Power Down */
KEY_KPEQUAL = 117,
KEY_KPPLUSMINUS = 118,
KEY_PAUSE = 119,
KEY_SCALE = 120, /* AL Compiz Scale (Expose) */
KEY_KPCOMMA = 121,
KEY_HANGEUL = 122,
KEY_HANGUEL = KEY_HANGEUL,
KEY_HANJA = 123,
KEY_YEN = 124,
KEY_LEFTMETA = 125,
KEY_RIGHTMETA = 126,
KEY_COMPOSE = 127,
KEY_STOP = 128, /* AC Stop */
KEY_AGAIN = 129,
KEY_PROPS = 130, /* AC Properties */
KEY_UNDO = 131, /* AC Undo */
KEY_FRONT = 132,
KEY_COPY = 133, /* AC Copy */
KEY_OPEN = 134, /* AC Open */
KEY_PASTE = 135, /* AC Paste */
KEY_FIND = 136, /* AC Search */
KEY_CUT = 137, /* AC Cut */
KEY_HELP = 138, /* AL Integrated Help Center */
KEY_MENU = 139, /* Menu (show menu) */
KEY_CALC = 140, /* AL Calculator */
KEY_SETUP = 141,
KEY_SLEEP = 142, /* SC System Sleep */
KEY_WAKEUP = 143, /* System Wake Up */
KEY_FILE = 144, /* AL Local Machine Browser */
KEY_SENDFILE = 145,
KEY_DELETEFILE = 146,
KEY_XFER = 147,
KEY_PROG1 = 148,
KEY_PROG2 = 149,
KEY_WWW = 150, /* AL Internet Browser */
KEY_MSDOS = 151,
KEY_COFFEE = 152, /* AL Terminal Lock/Screensaver */
KEY_SCREENLOCK = KEY_COFFEE,
KEY_ROTATE_DISPLAY = 153, /* Display orientation for e.g. tablets */
KEY_DIRECTION = KEY_ROTATE_DISPLAY,
KEY_CYCLEWINDOWS = 154,
KEY_MAIL = 155,
KEY_BOOKMARKS = 156, /* AC Bookmarks */
KEY_COMPUTER = 157,
KEY_BACK = 158, /* AC Back */
KEY_FORWARD = 159, /* AC Forward */
KEY_CLOSECD = 160,
KEY_EJECTCD = 161,
KEY_EJECTCLOSECD = 162,
KEY_NEXTSONG = 163,
KEY_PLAYPAUSE = 164,
KEY_PREVIOUSSONG = 165,
KEY_STOPCD = 166,
KEY_RECORD = 167,
KEY_REWIND = 168,
KEY_PHONE = 169, /* Media Select Telephone */
KEY_ISO = 170,
KEY_CONFIG = 171, /* AL Consumer Control Configuration */
KEY_HOMEPAGE = 172, /* AC Home */
KEY_REFRESH = 173, /* AC Refresh */
KEY_EXIT = 174, /* AC Exit */
KEY_MOVE = 175,
KEY_EDIT = 176,
KEY_SCROLLUP = 177,
KEY_SCROLLDOWN = 178,
KEY_KPLEFTPAREN = 179,
KEY_KPRIGHTPAREN = 180,
KEY_NEW = 181, /* AC New */
KEY_REDO = 182, /* AC Redo/Repeat */
KEY_F13 = 183,
KEY_F14 = 184,
KEY_F15 = 185,
KEY_F16 = 186,
KEY_F17 = 187,
KEY_F18 = 188,
KEY_F19 = 189,
KEY_F20 = 190,
KEY_F21 = 191,
KEY_F22 = 192,
KEY_F23 = 193,
KEY_F24 = 194,
KEY_PLAYCD = 200,
KEY_PAUSECD = 201,
KEY_PROG3 = 202,
KEY_PROG4 = 203,
KEY_DASHBOARD = 204, /* AL Dashboard */
KEY_SUSPEND = 205,
KEY_CLOSE = 206, /* AC Close */
KEY_PLAY = 207,
KEY_FASTFORWARD = 208,
KEY_BASSBOOST = 209,
KEY_PRINT = 210, /* AC Print */
KEY_HP = 211,
KEY_CAMERA = 212,
KEY_SOUND = 213,
KEY_QUESTION = 214,
KEY_EMAIL = 215,
KEY_CHAT = 216,
KEY_SEARCH = 217,
KEY_CONNECT = 218,
KEY_FINANCE = 219, /* AL Checkbook/Finance */
KEY_SPORT = 220,
KEY_SHOP = 221,
KEY_ALTERASE = 222,
KEY_CANCEL = 223, /* AC Cancel */
KEY_BRIGHTNESSDOWN = 224,
KEY_BRIGHTNESSUP = 225,
KEY_MEDIA = 226,
KEY_SWITCHVIDEOMODE = 227,
KEY_KBDILLUMTOGGLE = 228,
KEY_KBDILLUMDOWN = 229,
KEY_KBDILLUMUP = 230,
KEY_SEND = 231, /* AC Send */
KEY_REPLY = 232, /* AC Reply */
KEY_FORWARDMAIL = 233, /* AC Forward Msg */
KEY_SAVE = 234, /* AC Save */
KEY_DOCUMENTS = 235,
KEY_BATTERY = 236,
KEY_BLUETOOTH = 237,
KEY_WLAN = 238,
KEY_UWB = 239,
KEY_UNKNOWN = 240,
KEY_VIDEO_NEXT = 241, /* drive next video source */
KEY_VIDEO_PREV = 242, /* drive previous video source */
KEY_BRIGHTNESS_CYCLE = 243, /* brightness up, after max is min */
KEY_BRIGHTNESS_AUTO = 244, /* Set Auto Brightness: manual
KEY_BRIGHTNESS_ZERO = KEY_BRIGHTNESS_AUTO,
KEY_DISPLAY_OFF = 245, /* display device to off state */
KEY_WWAN = 246, /* Wireless WAN (LTE, UMTS, GSM, etc.) */
KEY_WIMAX = KEY_WWAN,
KEY_RFKILL = 247, /* Key that controls all radios */
KEY_MICMUTE = 248, /* Mute / unmute the microphone */
BTN_MISC = 0x100,
BTN_0 = 0x100,
BTN_1 = 0x101,
BTN_2 = 0x102,
BTN_3 = 0x103,
BTN_4 = 0x104,
BTN_5 = 0x105,
BTN_6 = 0x106,
BTN_7 = 0x107,
BTN_8 = 0x108,
BTN_9 = 0x109,
BTN_MOUSE = 0x110,
BTN_LEFT = 0x110,
BTN_RIGHT = 0x111,
BTN_MIDDLE = 0x112,
BTN_SIDE = 0x113,
BTN_EXTRA = 0x114,
BTN_FORWARD = 0x115,
BTN_BACK = 0x116,
BTN_TASK = 0x117,
BTN_JOYSTICK = 0x120,
BTN_TRIGGER = 0x120,
BTN_THUMB = 0x121,
BTN_THUMB2 = 0x122,
BTN_TOP = 0x123,
BTN_TOP2 = 0x124,
BTN_PINKIE = 0x125,
BTN_BASE = 0x126,
BTN_BASE2 = 0x127,
BTN_BASE3 = 0x128,
BTN_BASE4 = 0x129,
BTN_BASE5 = 0x12a,
BTN_BASE6 = 0x12b,
BTN_DEAD = 0x12f,
BTN_GAMEPAD = 0x130,
BTN_SOUTH = 0x130,
BTN_A = BTN_SOUTH,
BTN_EAST = 0x131,
BTN_B = BTN_EAST,
BTN_C = 0x132,
BTN_NORTH = 0x133,
BTN_X = BTN_NORTH,
BTN_WEST = 0x134,
BTN_Y = BTN_WEST,
BTN_Z = 0x135,
BTN_TL = 0x136,
BTN_TR = 0x137,
BTN_TL2 = 0x138,
BTN_TR2 = 0x139,
BTN_SELECT = 0x13a,
BTN_START = 0x13b,
BTN_MODE = 0x13c,
BTN_THUMBL = 0x13d,
BTN_THUMBR = 0x13e,
BTN_DIGI = 0x140,
BTN_TOOL_PEN = 0x140,
BTN_TOOL_RUBBER = 0x141,
BTN_TOOL_BRUSH = 0x142,
BTN_TOOL_PENCIL = 0x143,
BTN_TOOL_AIRBRUSH = 0x144,
BTN_TOOL_FINGER = 0x145,
BTN_TOOL_MOUSE = 0x146,
BTN_TOOL_LENS = 0x147,
BTN_TOOL_QUINTTAP = 0x148, /* Five fingers on trackpad */
BTN_STYLUS3 = 0x149,
BTN_TOUCH = 0x14a,
BTN_STYLUS = 0x14b,
BTN_STYLUS2 = 0x14c,
BTN_TOOL_DOUBLETAP = 0x14d,
BTN_TOOL_TRIPLETAP = 0x14e,
BTN_TOOL_QUADTAP = 0x14f, /* Four fingers on trackpad */
BTN_WHEEL = 0x150,
BTN_GEAR_DOWN = 0x150,
BTN_GEAR_UP = 0x151,
KEY_OK = 0x160,
KEY_SELECT = 0x161,
KEY_GOTO = 0x162,
KEY_CLEAR = 0x163,
KEY_POWER2 = 0x164,
KEY_OPTION = 0x165,
KEY_INFO = 0x166, /* AL OEM Features/Tips/Tutorial */
KEY_TIME = 0x167,
KEY_VENDOR = 0x168,
KEY_ARCHIVE = 0x169,
KEY_PROGRAM = 0x16a, /* Media Select Program Guide */
KEY_CHANNEL = 0x16b,
KEY_FAVORITES = 0x16c,
KEY_EPG = 0x16d,
KEY_PVR = 0x16e, /* Media Select Home */
KEY_MHP = 0x16f,
KEY_LANGUAGE = 0x170,
KEY_TITLE = 0x171,
KEY_SUBTITLE = 0x172,
KEY_ANGLE = 0x173,
KEY_FULL_SCREEN = 0x174, /* AC View Toggle */
KEY_ZOOM = KEY_FULL_SCREEN,
KEY_MODE = 0x175,
KEY_KEYBOARD = 0x176,
KEY_ASPECT_RATIO = 0x177, /* HUTRR37: Aspect */
KEY_SCREEN = KEY_ASPECT_RATIO,
KEY_PC = 0x178, /* Media Select Computer */
KEY_TV = 0x179, /* Media Select TV */
KEY_TV2 = 0x17a, /* Media Select Cable */
KEY_VCR = 0x17b, /* Media Select VCR */
KEY_VCR2 = 0x17c, /* VCR Plus */
KEY_SAT = 0x17d, /* Media Select Satellite */
KEY_SAT2 = 0x17e,
KEY_CD = 0x17f, /* Media Select CD */
KEY_TAPE = 0x180, /* Media Select Tape */
KEY_RADIO = 0x181,
KEY_TUNER = 0x182, /* Media Select Tuner */
KEY_PLAYER = 0x183,
KEY_TEXT = 0x184,
KEY_DVD = 0x185, /* Media Select DVD */
KEY_AUX = 0x186,
KEY_MP3 = 0x187,
KEY_AUDIO = 0x188, /* AL Audio Browser */
KEY_VIDEO = 0x189, /* AL Movie Browser */
KEY_DIRECTORY = 0x18a,
KEY_LIST = 0x18b,
KEY_MEMO = 0x18c, /* Media Select Messages */
KEY_CALENDAR = 0x18d,
KEY_RED = 0x18e,
KEY_GREEN = 0x18f,
KEY_YELLOW = 0x190,
KEY_BLUE = 0x191,
KEY_CHANNELUP = 0x192, /* Channel Increment */
KEY_CHANNELDOWN = 0x193, /* Channel Decrement */
KEY_FIRST = 0x194,
KEY_LAST = 0x195, /* Recall Last */
KEY_AB = 0x196,
KEY_NEXT = 0x197,
KEY_RESTART = 0x198,
KEY_SLOW = 0x199,
KEY_SHUFFLE = 0x19a,
KEY_BREAK = 0x19b,
KEY_PREVIOUS = 0x19c,
KEY_DIGITS = 0x19d,
KEY_TEEN = 0x19e,
KEY_TWEN = 0x19f,
KEY_VIDEOPHONE = 0x1a0, /* Media Select Video Phone */
KEY_GAMES = 0x1a1, /* Media Select Games */
KEY_ZOOMIN = 0x1a2, /* AC Zoom In */
KEY_ZOOMOUT = 0x1a3, /* AC Zoom Out */
KEY_ZOOMRESET = 0x1a4, /* AC Zoom */
KEY_WORDPROCESSOR = 0x1a5, /* AL Word Processor */
KEY_EDITOR = 0x1a6, /* AL Text Editor */
KEY_SPREADSHEET = 0x1a7, /* AL Spreadsheet */
KEY_GRAPHICSEDITOR = 0x1a8, /* AL Graphics Editor */
KEY_PRESENTATION = 0x1a9, /* AL Presentation App */
KEY_DATABASE = 0x1aa, /* AL Database App */
KEY_NEWS = 0x1ab, /* AL Newsreader */
KEY_VOICEMAIL = 0x1ac, /* AL Voicemail */
KEY_ADDRESSBOOK = 0x1ad, /* AL Contacts/Address Book */
KEY_MESSENGER = 0x1ae, /* AL Instant Messaging */
KEY_DISPLAYTOGGLE = 0x1af, /* Turn display (LCD) on and off */
KEY_BRIGHTNESS_TOGGLE = KEY_DISPLAYTOGGLE,
KEY_SPELLCHECK = 0x1b0, /* AL Spell Check */
KEY_LOGOFF = 0x1b1, /* AL Logoff */
KEY_DOLLAR = 0x1b2,
KEY_EURO = 0x1b3,
KEY_FRAMEBACK = 0x1b4, /* Consumer - transport controls */
KEY_FRAMEFORWARD = 0x1b5,
KEY_CONTEXT_MENU = 0x1b6, /* GenDesc - system context menu */
KEY_MEDIA_REPEAT = 0x1b7, /* Consumer - transport control */
KEY_10CHANNELSUP = 0x1b8, /* 10 channels up (10+) */
KEY_10CHANNELSDOWN = 0x1b9, /* 10 channels down (10-) */
KEY_IMAGES = 0x1ba, /* AL Image Browser */
KEY_NOTIFICATION_CENTER = 0x1bc, /* Show/hide the notification center */
KEY_PICKUP_PHONE = 0x1bd, /* Answer incoming call */
KEY_HANGUP_PHONE = 0x1be, /* Decline incoming call */
KEY_DEL_EOL = 0x1c0,
KEY_DEL_EOS = 0x1c1,
KEY_INS_LINE = 0x1c2,
KEY_DEL_LINE = 0x1c3,
KEY_FN = 0x1d0,
KEY_FN_ESC = 0x1d1,
KEY_FN_F1 = 0x1d2,
KEY_FN_F2 = 0x1d3,
KEY_FN_F3 = 0x1d4,
KEY_FN_F4 = 0x1d5,
KEY_FN_F5 = 0x1d6,
KEY_FN_F6 = 0x1d7,
KEY_FN_F7 = 0x1d8,
KEY_FN_F8 = 0x1d9,
KEY_FN_F9 = 0x1da,
KEY_FN_F10 = 0x1db,
KEY_FN_F11 = 0x1dc,
KEY_FN_F12 = 0x1dd,
KEY_FN_1 = 0x1de,
KEY_FN_2 = 0x1df,
KEY_FN_D = 0x1e0,
KEY_FN_E = 0x1e1,
KEY_FN_F = 0x1e2,
KEY_FN_S = 0x1e3,
KEY_FN_B = 0x1e4,
KEY_FN_RIGHT_SHIFT = 0x1e5,
KEY_BRL_DOT1 = 0x1f1,
KEY_BRL_DOT2 = 0x1f2,
KEY_BRL_DOT3 = 0x1f3,
KEY_BRL_DOT4 = 0x1f4,
KEY_BRL_DOT5 = 0x1f5,
KEY_BRL_DOT6 = 0x1f6,
KEY_BRL_DOT7 = 0x1f7,
KEY_BRL_DOT8 = 0x1f8,
KEY_BRL_DOT9 = 0x1f9,
KEY_BRL_DOT10 = 0x1fa,
KEY_NUMERIC_0 = 0x200, /* used by phones, remote controls, */
KEY_NUMERIC_1 = 0x201, /* and other keypads */
KEY_NUMERIC_2 = 0x202,
KEY_NUMERIC_3 = 0x203,
KEY_NUMERIC_4 = 0x204,
KEY_NUMERIC_5 = 0x205,
KEY_NUMERIC_6 = 0x206,
KEY_NUMERIC_7 = 0x207,
KEY_NUMERIC_8 = 0x208,
KEY_NUMERIC_9 = 0x209,
KEY_NUMERIC_STAR = 0x20a,
KEY_NUMERIC_POUND = 0x20b,
KEY_NUMERIC_A = 0x20c, /* Phone key A - HUT Telephony 0xb9 */
KEY_NUMERIC_B = 0x20d,
KEY_NUMERIC_C = 0x20e,
KEY_NUMERIC_D = 0x20f,
KEY_CAMERA_FOCUS = 0x210,
KEY_WPS_BUTTON = 0x211, /* WiFi Protected Setup key */
KEY_TOUCHPAD_TOGGLE = 0x212, /* Request switch touchpad on or off */
KEY_TOUCHPAD_ON = 0x213,
KEY_TOUCHPAD_OFF = 0x214,
KEY_CAMERA_ZOOMIN = 0x215,
KEY_CAMERA_ZOOMOUT = 0x216,
KEY_CAMERA_UP = 0x217,
KEY_CAMERA_DOWN = 0x218,
KEY_CAMERA_LEFT = 0x219,
KEY_CAMERA_RIGHT = 0x21a,
KEY_ATTENDANT_ON = 0x21b,
KEY_ATTENDANT_OFF = 0x21c,
KEY_ATTENDANT_TOGGLE = 0x21d, /* Attendant call on or off */
KEY_LIGHTS_TOGGLE = 0x21e, /* Reading light on or off */
BTN_DPAD_UP = 0x220,
BTN_DPAD_DOWN = 0x221,
BTN_DPAD_LEFT = 0x222,
BTN_DPAD_RIGHT = 0x223,
KEY_ALS_TOGGLE = 0x230, /* Ambient light sensor */
KEY_ROTATE_LOCK_TOGGLE = 0x231, /* Display rotation lock */
KEY_BUTTONCONFIG = 0x240, /* AL Button Configuration */
KEY_TASKMANAGER = 0x241, /* AL Task/Project Manager */
KEY_JOURNAL = 0x242, /* AL Log/Journal/Timecard */
KEY_CONTROLPANEL = 0x243, /* AL Control Panel */
KEY_APPSELECT = 0x244, /* AL Select Task/Application */
KEY_SCREENSAVER = 0x245, /* AL Screen Saver */
KEY_VOICECOMMAND = 0x246, /* Listening Voice Command */
KEY_ASSISTANT = 0x247, /* AL Context-aware desktop assistant */
KEY_KBD_LAYOUT_NEXT = 0x248, /* AC Next Keyboard Layout Select */
KEY_EMOJI_PICKER = 0x249, /* Show/hide emoji picker (HUTRR101) */
KEY_BRIGHTNESS_MIN = 0x250, /* Set Brightness to Minimum */
KEY_BRIGHTNESS_MAX = 0x251, /* Set Brightness to Maximum */
KEY_KBDINPUTASSIST_PREV = 0x260,
KEY_KBDINPUTASSIST_NEXT = 0x261,
KEY_KBDINPUTASSIST_PREVGROUP = 0x262,
KEY_KBDINPUTASSIST_NEXTGROUP = 0x263,
KEY_KBDINPUTASSIST_ACCEPT = 0x264,
KEY_KBDINPUTASSIST_CANCEL = 0x265,
KEY_RIGHT_UP = 0x266,
KEY_RIGHT_DOWN = 0x267,
KEY_LEFT_UP = 0x268,
KEY_LEFT_DOWN = 0x269,
KEY_ROOT_MENU = 0x26a, /* Show Device's Root Menu */
KEY_MEDIA_TOP_MENU = 0x26b,
KEY_NUMERIC_11 = 0x26c,
KEY_NUMERIC_12 = 0x26d,
KEY_AUDIO_DESC = 0x26e,
KEY_3D_MODE = 0x26f,
KEY_NEXT_FAVORITE = 0x270,
KEY_STOP_RECORD = 0x271,
KEY_PAUSE_RECORD = 0x272,
KEY_VOD = 0x273, /* Video on Demand */
KEY_UNMUTE = 0x274,
KEY_FASTREVERSE = 0x275,
KEY_SLOWREVERSE = 0x276,
KEY_DATA = 0x277,
KEY_ONSCREEN_KEYBOARD = 0x278,
KEY_PRIVACY_SCREEN_TOGGLE = 0x279,
KEY_SELECTIVE_SCREENSHOT = 0x27a,
KEY_MACRO1 = 0x290,
KEY_MACRO2 = 0x291,
KEY_MACRO3 = 0x292,
KEY_MACRO4 = 0x293,
KEY_MACRO5 = 0x294,
KEY_MACRO6 = 0x295,
KEY_MACRO7 = 0x296,
KEY_MACRO8 = 0x297,
KEY_MACRO9 = 0x298,
KEY_MACRO10 = 0x299,
KEY_MACRO11 = 0x29a,
KEY_MACRO12 = 0x29b,
KEY_MACRO13 = 0x29c,
KEY_MACRO14 = 0x29d,
KEY_MACRO15 = 0x29e,
KEY_MACRO16 = 0x29f,
KEY_MACRO17 = 0x2a0,
KEY_MACRO18 = 0x2a1,
KEY_MACRO19 = 0x2a2,
KEY_MACRO20 = 0x2a3,
KEY_MACRO21 = 0x2a4,
KEY_MACRO22 = 0x2a5,
KEY_MACRO23 = 0x2a6,
KEY_MACRO24 = 0x2a7,
KEY_MACRO25 = 0x2a8,
KEY_MACRO26 = 0x2a9,
KEY_MACRO27 = 0x2aa,
KEY_MACRO28 = 0x2ab,
KEY_MACRO29 = 0x2ac,
KEY_MACRO30 = 0x2ad,
KEY_MACRO_RECORD_START = 0x2b0,
KEY_MACRO_RECORD_STOP = 0x2b1,
KEY_MACRO_PRESET_CYCLE = 0x2b2,
KEY_MACRO_PRESET1 = 0x2b3,
KEY_MACRO_PRESET2 = 0x2b4,
KEY_MACRO_PRESET3 = 0x2b5,
KEY_KBD_LCD_MENU1 = 0x2b8,
KEY_KBD_LCD_MENU2 = 0x2b9,
KEY_KBD_LCD_MENU3 = 0x2ba,
KEY_KBD_LCD_MENU4 = 0x2bb,
KEY_KBD_LCD_MENU5 = 0x2bc,
BTN_TRIGGER_HAPPY = 0x2c0,
BTN_TRIGGER_HAPPY1 = 0x2c0,
BTN_TRIGGER_HAPPY2 = 0x2c1,
BTN_TRIGGER_HAPPY3 = 0x2c2,
BTN_TRIGGER_HAPPY4 = 0x2c3,
BTN_TRIGGER_HAPPY5 = 0x2c4,
BTN_TRIGGER_HAPPY6 = 0x2c5,
BTN_TRIGGER_HAPPY7 = 0x2c6,
BTN_TRIGGER_HAPPY8 = 0x2c7,
BTN_TRIGGER_HAPPY9 = 0x2c8,
BTN_TRIGGER_HAPPY10 = 0x2c9,
BTN_TRIGGER_HAPPY11 = 0x2ca,
BTN_TRIGGER_HAPPY12 = 0x2cb,
BTN_TRIGGER_HAPPY13 = 0x2cc,
BTN_TRIGGER_HAPPY14 = 0x2cd,
BTN_TRIGGER_HAPPY15 = 0x2ce,
BTN_TRIGGER_HAPPY16 = 0x2cf,
BTN_TRIGGER_HAPPY17 = 0x2d0,
BTN_TRIGGER_HAPPY18 = 0x2d1,
BTN_TRIGGER_HAPPY19 = 0x2d2,
BTN_TRIGGER_HAPPY20 = 0x2d3,
BTN_TRIGGER_HAPPY21 = 0x2d4,
BTN_TRIGGER_HAPPY22 = 0x2d5,
BTN_TRIGGER_HAPPY23 = 0x2d6,
BTN_TRIGGER_HAPPY24 = 0x2d7,
BTN_TRIGGER_HAPPY25 = 0x2d8,
BTN_TRIGGER_HAPPY26 = 0x2d9,
BTN_TRIGGER_HAPPY27 = 0x2da,
BTN_TRIGGER_HAPPY28 = 0x2db,
BTN_TRIGGER_HAPPY29 = 0x2dc,
BTN_TRIGGER_HAPPY30 = 0x2dd,
BTN_TRIGGER_HAPPY31 = 0x2de,
BTN_TRIGGER_HAPPY32 = 0x2df,
BTN_TRIGGER_HAPPY33 = 0x2e0,
BTN_TRIGGER_HAPPY34 = 0x2e1,
BTN_TRIGGER_HAPPY35 = 0x2e2,
BTN_TRIGGER_HAPPY36 = 0x2e3,
BTN_TRIGGER_HAPPY37 = 0x2e4,
BTN_TRIGGER_HAPPY38 = 0x2e5,
BTN_TRIGGER_HAPPY39 = 0x2e6,
BTN_TRIGGER_HAPPY40 = 0x2e7,
}
}
@@ -1,16 +0,0 @@
namespace Artemis.UI.Linux.Providers.Input
{
public enum LinuxRelativeAxis
{
REL_X = 0x00,
REL_Y = 0x01,
REL_Z = 0x02,
REL_RX = 0x03,
REL_RY = 0x04,
REL_RZ = 0x05,
REL_HWHEEL = 0x06,
REL_DIAL = 0x07,
REL_WHEEL = 0x08,
REL_MISC = 0x09,
}
}
@@ -1,621 +0,0 @@
using System;
using Artemis.Core.Services;
using Artemis.UI.Linux.Providers.Input;
namespace Artemis.UI.Linux.Utilities
{
public static class InputUtilities
{
public static KeyboardKey KeyFromKeyCode(LinuxKeyboardKeyCodes code) => code switch
{
LinuxKeyboardKeyCodes.KEY_ESC => KeyboardKey.Escape,
LinuxKeyboardKeyCodes.KEY_1 => KeyboardKey.D1,
LinuxKeyboardKeyCodes.KEY_2 => KeyboardKey.D2,
LinuxKeyboardKeyCodes.KEY_3 => KeyboardKey.D3,
LinuxKeyboardKeyCodes.KEY_4 => KeyboardKey.D4,
LinuxKeyboardKeyCodes.KEY_5 => KeyboardKey.D5,
LinuxKeyboardKeyCodes.KEY_6 => KeyboardKey.D6,
LinuxKeyboardKeyCodes.KEY_7 => KeyboardKey.D7,
LinuxKeyboardKeyCodes.KEY_8 => KeyboardKey.D8,
LinuxKeyboardKeyCodes.KEY_9 => KeyboardKey.D9,
LinuxKeyboardKeyCodes.KEY_0 => KeyboardKey.D0,
LinuxKeyboardKeyCodes.KEY_MINUS => KeyboardKey.OemMinus,
LinuxKeyboardKeyCodes.KEY_EQUAL => KeyboardKey.OemPlus,
LinuxKeyboardKeyCodes.KEY_BACKSPACE => KeyboardKey.Back,
LinuxKeyboardKeyCodes.KEY_TAB => KeyboardKey.Tab,
LinuxKeyboardKeyCodes.KEY_Q => KeyboardKey.Q,
LinuxKeyboardKeyCodes.KEY_W => KeyboardKey.W,
LinuxKeyboardKeyCodes.KEY_E => KeyboardKey.E,
LinuxKeyboardKeyCodes.KEY_R => KeyboardKey.R,
LinuxKeyboardKeyCodes.KEY_T => KeyboardKey.T,
LinuxKeyboardKeyCodes.KEY_Y => KeyboardKey.Y,
LinuxKeyboardKeyCodes.KEY_U => KeyboardKey.U,
LinuxKeyboardKeyCodes.KEY_I => KeyboardKey.I,
LinuxKeyboardKeyCodes.KEY_O => KeyboardKey.O,
LinuxKeyboardKeyCodes.KEY_P => KeyboardKey.P,
LinuxKeyboardKeyCodes.KEY_LEFTBRACE => KeyboardKey.OemOpenBrackets,
LinuxKeyboardKeyCodes.KEY_RIGHTBRACE => KeyboardKey.OemCloseBrackets,
LinuxKeyboardKeyCodes.KEY_ENTER => KeyboardKey.Enter,
LinuxKeyboardKeyCodes.KEY_LEFTCTRL => KeyboardKey.LeftCtrl,
LinuxKeyboardKeyCodes.KEY_A => KeyboardKey.A,
LinuxKeyboardKeyCodes.KEY_S => KeyboardKey.S,
LinuxKeyboardKeyCodes.KEY_D => KeyboardKey.D,
LinuxKeyboardKeyCodes.KEY_F => KeyboardKey.F,
LinuxKeyboardKeyCodes.KEY_G => KeyboardKey.G,
LinuxKeyboardKeyCodes.KEY_H => KeyboardKey.H,
LinuxKeyboardKeyCodes.KEY_J => KeyboardKey.J,
LinuxKeyboardKeyCodes.KEY_K => KeyboardKey.K,
LinuxKeyboardKeyCodes.KEY_L => KeyboardKey.L,
LinuxKeyboardKeyCodes.KEY_SEMICOLON => KeyboardKey.OemSemicolon,
LinuxKeyboardKeyCodes.KEY_APOSTROPHE => KeyboardKey.OemQuotes,
LinuxKeyboardKeyCodes.KEY_GRAVE => KeyboardKey.OemTilde,
LinuxKeyboardKeyCodes.KEY_LEFTSHIFT => KeyboardKey.LeftShift,
LinuxKeyboardKeyCodes.KEY_BACKSLASH => KeyboardKey.OemBackslash,
LinuxKeyboardKeyCodes.KEY_Z => KeyboardKey.Z,
LinuxKeyboardKeyCodes.KEY_X => KeyboardKey.X,
LinuxKeyboardKeyCodes.KEY_C => KeyboardKey.C,
LinuxKeyboardKeyCodes.KEY_V => KeyboardKey.V,
LinuxKeyboardKeyCodes.KEY_B => KeyboardKey.B,
LinuxKeyboardKeyCodes.KEY_N => KeyboardKey.N,
LinuxKeyboardKeyCodes.KEY_M => KeyboardKey.M,
LinuxKeyboardKeyCodes.KEY_COMMA => KeyboardKey.OemComma,
LinuxKeyboardKeyCodes.KEY_DOT => KeyboardKey.OemPeriod,
LinuxKeyboardKeyCodes.KEY_SLASH => KeyboardKey.OemQuestion,
LinuxKeyboardKeyCodes.KEY_RIGHTSHIFT => KeyboardKey.RightShift,
//LinuxKeyboardKeyCodes.KEY_KPASTERISK => ,
LinuxKeyboardKeyCodes.KEY_LEFTALT => KeyboardKey.LeftAlt,
LinuxKeyboardKeyCodes.KEY_SPACE => KeyboardKey.Space,
LinuxKeyboardKeyCodes.KEY_CAPSLOCK => KeyboardKey.CapsLock,
LinuxKeyboardKeyCodes.KEY_F1 => KeyboardKey.F1,
LinuxKeyboardKeyCodes.KEY_F2 => KeyboardKey.F2,
LinuxKeyboardKeyCodes.KEY_F3 => KeyboardKey.F3,
LinuxKeyboardKeyCodes.KEY_F4 => KeyboardKey.F4,
LinuxKeyboardKeyCodes.KEY_F5 => KeyboardKey.F5,
LinuxKeyboardKeyCodes.KEY_F6 => KeyboardKey.F6,
LinuxKeyboardKeyCodes.KEY_F7 => KeyboardKey.F7,
LinuxKeyboardKeyCodes.KEY_F8 => KeyboardKey.F8,
LinuxKeyboardKeyCodes.KEY_F9 => KeyboardKey.F9,
LinuxKeyboardKeyCodes.KEY_F10 => KeyboardKey.F10,
LinuxKeyboardKeyCodes.KEY_NUMLOCK => KeyboardKey.NumLock,
LinuxKeyboardKeyCodes.KEY_SCROLLLOCK => KeyboardKey.Scroll,
LinuxKeyboardKeyCodes.KEY_KP7 => KeyboardKey.NumPad7,
LinuxKeyboardKeyCodes.KEY_KP8 => KeyboardKey.NumPad8,
LinuxKeyboardKeyCodes.KEY_KP9 => KeyboardKey.NumPad9,
LinuxKeyboardKeyCodes.KEY_KPMINUS => KeyboardKey.Subtract,
LinuxKeyboardKeyCodes.KEY_KP4 => KeyboardKey.NumPad4,
LinuxKeyboardKeyCodes.KEY_KP5 => KeyboardKey.NumPad5,
LinuxKeyboardKeyCodes.KEY_KP6 => KeyboardKey.NumPad6,
LinuxKeyboardKeyCodes.KEY_KPPLUS => KeyboardKey.Add,
LinuxKeyboardKeyCodes.KEY_KP1 => KeyboardKey.NumPad1,
LinuxKeyboardKeyCodes.KEY_KP2 => KeyboardKey.NumPad2,
LinuxKeyboardKeyCodes.KEY_KP3 => KeyboardKey.NumPad3,
LinuxKeyboardKeyCodes.KEY_KP0 => KeyboardKey.NumPad0,
LinuxKeyboardKeyCodes.KEY_KPDOT => KeyboardKey.Decimal,
// LinuxKeyboardKeyCodes.KEY_ZENKAKUHANKAKU => expr,
// LinuxKeyboardKeyCodes.KEY_102ND => expr,
LinuxKeyboardKeyCodes.KEY_F11 => KeyboardKey.F11,
LinuxKeyboardKeyCodes.KEY_F12 => KeyboardKey.F12,
//LinuxKeyboardKeyCodes.KEY_RO => expr,
// LinuxKeyboardKeyCodes.KEY_KATAKANA => expr,
// LinuxKeyboardKeyCodes.KEY_HIRAGANA => expr,
// LinuxKeyboardKeyCodes.KEY_HENKAN => expr,
// LinuxKeyboardKeyCodes.KEY_KATAKANAHIRAGANA => expr,
// LinuxKeyboardKeyCodes.KEY_MUHENKAN => expr,
// LinuxKeyboardKeyCodes.KEY_KPJPCOMMA => expr,
LinuxKeyboardKeyCodes.KEY_KPENTER => KeyboardKey.NumPadEnter,
LinuxKeyboardKeyCodes.KEY_RIGHTCTRL => KeyboardKey.RightCtrl,
LinuxKeyboardKeyCodes.KEY_KPSLASH => KeyboardKey.Divide,
LinuxKeyboardKeyCodes.KEY_SYSRQ => KeyboardKey.System,//TODO:?
LinuxKeyboardKeyCodes.KEY_RIGHTALT => KeyboardKey.RightAlt,
LinuxKeyboardKeyCodes.KEY_LINEFEED => KeyboardKey.LineFeed,
LinuxKeyboardKeyCodes.KEY_HOME => KeyboardKey.Home,
LinuxKeyboardKeyCodes.KEY_UP => KeyboardKey.Up,
LinuxKeyboardKeyCodes.KEY_PAGEUP => KeyboardKey.PageUp,
LinuxKeyboardKeyCodes.KEY_LEFT => KeyboardKey.Left,
LinuxKeyboardKeyCodes.KEY_RIGHT => KeyboardKey.Right,
LinuxKeyboardKeyCodes.KEY_END => KeyboardKey.End,
LinuxKeyboardKeyCodes.KEY_DOWN => KeyboardKey.Down,
LinuxKeyboardKeyCodes.KEY_PAGEDOWN => KeyboardKey.PageDown,
LinuxKeyboardKeyCodes.KEY_INSERT => KeyboardKey.Insert,
LinuxKeyboardKeyCodes.KEY_DELETE => KeyboardKey.Delete,
// LinuxKeyboardKeyCodes.KEY_MACRO => KeyboardKey.macro,
LinuxKeyboardKeyCodes.KEY_MUTE => KeyboardKey.VolumeMute,
LinuxKeyboardKeyCodes.KEY_VOLUMEDOWN => KeyboardKey.VolumeDown,
LinuxKeyboardKeyCodes.KEY_VOLUMEUP => KeyboardKey.VolumeUp,
// LinuxKeyboardKeyCodes.KEY_POWER => KeyboardKey.power,
LinuxKeyboardKeyCodes.KEY_KPEQUAL => KeyboardKey.NumPadEnter,//todo: ?
// LinuxKeyboardKeyCodes.KEY_KPPLUSMINUS => KeyboardKey.numpad,
LinuxKeyboardKeyCodes.KEY_PAUSE => KeyboardKey.Pause,
// LinuxKeyboardKeyCodes.KEY_SCALE => KeyboardKey.scal,
// LinuxKeyboardKeyCodes.KEY_KPCOMMA => KeyboardKey.ke,
// LinuxKeyboardKeyCodes.KEY_HANGEUL => expr,
// LinuxKeyboardKeyCodes.KEY_HANJA => expr,
// LinuxKeyboardKeyCodes.KEY_YEN => expr,
LinuxKeyboardKeyCodes.KEY_LEFTMETA => KeyboardKey.LWin,
LinuxKeyboardKeyCodes.KEY_RIGHTMETA => KeyboardKey.RWin,
// LinuxKeyboardKeyCodes.KEY_COMPOSE => ,
LinuxKeyboardKeyCodes.KEY_STOP => KeyboardKey.MediaStop,
// LinuxKeyboardKeyCodes.KEY_AGAIN => ,
// LinuxKeyboardKeyCodes.KEY_PROPS => expr,
// LinuxKeyboardKeyCodes.KEY_UNDO => expr,
// LinuxKeyboardKeyCodes.KEY_FRONT => expr,
// LinuxKeyboardKeyCodes.KEY_COPY => expr,
// LinuxKeyboardKeyCodes.KEY_OPEN => expr,
// LinuxKeyboardKeyCodes.KEY_PASTE => expr,
// LinuxKeyboardKeyCodes.KEY_FIND => expr,
// LinuxKeyboardKeyCodes.KEY_CUT => expr,
// LinuxKeyboardKeyCodes.KEY_HELP => expr,
// LinuxKeyboardKeyCodes.KEY_MENU => expr,
// LinuxKeyboardKeyCodes.KEY_CALC => expr,
// LinuxKeyboardKeyCodes.KEY_SETUP => expr,
// LinuxKeyboardKeyCodes.KEY_SLEEP => expr,
// LinuxKeyboardKeyCodes.KEY_WAKEUP => expr,
// LinuxKeyboardKeyCodes.KEY_FILE => expr,
// LinuxKeyboardKeyCodes.KEY_SENDFILE => expr,
// LinuxKeyboardKeyCodes.KEY_DELETEFILE => expr,
// LinuxKeyboardKeyCodes.KEY_XFER => expr,
// LinuxKeyboardKeyCodes.KEY_PROG1 => expr,
// LinuxKeyboardKeyCodes.KEY_PROG2 => expr,
// LinuxKeyboardKeyCodes.KEY_WWW => expr,
// LinuxKeyboardKeyCodes.KEY_MSDOS => expr,
// LinuxKeyboardKeyCodes.KEY_COFFEE => expr,
// LinuxKeyboardKeyCodes.KEY_ROTATE_DISPLAY => expr,
// LinuxKeyboardKeyCodes.KEY_CYCLEWINDOWS => expr,
// LinuxKeyboardKeyCodes.KEY_MAIL => expr,
// LinuxKeyboardKeyCodes.KEY_BOOKMARKS => expr,
// LinuxKeyboardKeyCodes.KEY_COMPUTER => expr,
// LinuxKeyboardKeyCodes.KEY_BACK => expr,
// LinuxKeyboardKeyCodes.KEY_FORWARD => expr,
// LinuxKeyboardKeyCodes.KEY_CLOSECD => expr,
// LinuxKeyboardKeyCodes.KEY_EJECTCD => expr,
// LinuxKeyboardKeyCodes.KEY_EJECTCLOSECD => expr,
// LinuxKeyboardKeyCodes.KEY_NEXTSONG => expr,
// LinuxKeyboardKeyCodes.KEY_PLAYPAUSE => expr,
// LinuxKeyboardKeyCodes.KEY_PREVIOUSSONG => expr,
// LinuxKeyboardKeyCodes.KEY_STOPCD => expr,
// LinuxKeyboardKeyCodes.KEY_RECORD => expr,
// LinuxKeyboardKeyCodes.KEY_REWIND => expr,
// LinuxKeyboardKeyCodes.KEY_PHONE => expr,
// LinuxKeyboardKeyCodes.KEY_ISO => expr,
// LinuxKeyboardKeyCodes.KEY_CONFIG => expr,
// LinuxKeyboardKeyCodes.KEY_HOMEPAGE => expr,
// LinuxKeyboardKeyCodes.KEY_REFRESH => expr,
// LinuxKeyboardKeyCodes.KEY_EXIT => expr,
// LinuxKeyboardKeyCodes.KEY_MOVE => expr,
// LinuxKeyboardKeyCodes.KEY_EDIT => expr,
// LinuxKeyboardKeyCodes.KEY_SCROLLUP => expr,
// LinuxKeyboardKeyCodes.KEY_SCROLLDOWN => expr,
// LinuxKeyboardKeyCodes.KEY_KPLEFTPAREN => expr,
// LinuxKeyboardKeyCodes.KEY_KPRIGHTPAREN => expr,
// LinuxKeyboardKeyCodes.KEY_NEW => expr,
// LinuxKeyboardKeyCodes.KEY_REDO => expr,
// LinuxKeyboardKeyCodes.KEY_F13 => expr,
// LinuxKeyboardKeyCodes.KEY_F14 => expr,
// LinuxKeyboardKeyCodes.KEY_F15 => expr,
// LinuxKeyboardKeyCodes.KEY_F16 => expr,
// LinuxKeyboardKeyCodes.KEY_F17 => expr,
// LinuxKeyboardKeyCodes.KEY_F18 => expr,
// LinuxKeyboardKeyCodes.KEY_F19 => expr,
// LinuxKeyboardKeyCodes.KEY_F20 => expr,
// LinuxKeyboardKeyCodes.KEY_F21 => expr,
// LinuxKeyboardKeyCodes.KEY_F22 => expr,
// LinuxKeyboardKeyCodes.KEY_F23 => expr,
// LinuxKeyboardKeyCodes.KEY_F24 => expr,
// LinuxKeyboardKeyCodes.KEY_PLAYCD => expr,
// LinuxKeyboardKeyCodes.KEY_PAUSECD => expr,
// LinuxKeyboardKeyCodes.KEY_PROG3 => expr,
// LinuxKeyboardKeyCodes.KEY_PROG4 => expr,
// LinuxKeyboardKeyCodes.KEY_DASHBOARD => expr,
// LinuxKeyboardKeyCodes.KEY_SUSPEND => expr,
// LinuxKeyboardKeyCodes.KEY_CLOSE => expr,
// LinuxKeyboardKeyCodes.KEY_PLAY => expr,
// LinuxKeyboardKeyCodes.KEY_FASTFORWARD => expr,
// LinuxKeyboardKeyCodes.KEY_BASSBOOST => expr,
// LinuxKeyboardKeyCodes.KEY_PRINT => expr,
// LinuxKeyboardKeyCodes.KEY_HP => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA => expr,
// LinuxKeyboardKeyCodes.KEY_SOUND => expr,
// LinuxKeyboardKeyCodes.KEY_QUESTION => expr,
// LinuxKeyboardKeyCodes.KEY_EMAIL => expr,
// LinuxKeyboardKeyCodes.KEY_CHAT => expr,
// LinuxKeyboardKeyCodes.KEY_SEARCH => expr,
// LinuxKeyboardKeyCodes.KEY_CONNECT => expr,
// LinuxKeyboardKeyCodes.KEY_FINANCE => expr,
// LinuxKeyboardKeyCodes.KEY_SPORT => expr,
// LinuxKeyboardKeyCodes.KEY_SHOP => expr,
// LinuxKeyboardKeyCodes.KEY_ALTERASE => expr,
// LinuxKeyboardKeyCodes.KEY_CANCEL => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESSDOWN => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESSUP => expr,
// LinuxKeyboardKeyCodes.KEY_MEDIA => expr,
// LinuxKeyboardKeyCodes.KEY_SWITCHVIDEOMODE => expr,
// LinuxKeyboardKeyCodes.KEY_KBDILLUMTOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_KBDILLUMDOWN => expr,
// LinuxKeyboardKeyCodes.KEY_KBDILLUMUP => expr,
// LinuxKeyboardKeyCodes.KEY_SEND => expr,
// LinuxKeyboardKeyCodes.KEY_REPLY => expr,
// LinuxKeyboardKeyCodes.KEY_FORWARDMAIL => expr,
// LinuxKeyboardKeyCodes.KEY_SAVE => expr,
// LinuxKeyboardKeyCodes.KEY_DOCUMENTS => expr,
// LinuxKeyboardKeyCodes.KEY_BATTERY => expr,
// LinuxKeyboardKeyCodes.KEY_BLUETOOTH => expr,
// LinuxKeyboardKeyCodes.KEY_WLAN => expr,
// LinuxKeyboardKeyCodes.KEY_UWB => expr,
// LinuxKeyboardKeyCodes.KEY_UNKNOWN => expr,
// LinuxKeyboardKeyCodes.KEY_VIDEO_NEXT => expr,
// LinuxKeyboardKeyCodes.KEY_VIDEO_PREV => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESS_CYCLE => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESS_AUTO => expr,
// LinuxKeyboardKeyCodes.KEY_WWAN => expr,
// LinuxKeyboardKeyCodes.KEY_RFKILL => expr,
// LinuxKeyboardKeyCodes.KEY_MICMUTE => expr,
// LinuxKeyboardKeyCodes.BTN_MISC => expr,
// LinuxKeyboardKeyCodes.BTN_1 => expr,
// LinuxKeyboardKeyCodes.BTN_2 => expr,
// LinuxKeyboardKeyCodes.BTN_3 => expr,
// LinuxKeyboardKeyCodes.BTN_4 => expr,
// LinuxKeyboardKeyCodes.BTN_5 => expr,
// LinuxKeyboardKeyCodes.BTN_6 => expr,
// LinuxKeyboardKeyCodes.BTN_7 => expr,
// LinuxKeyboardKeyCodes.BTN_8 => expr,
// LinuxKeyboardKeyCodes.BTN_9 => expr,
// LinuxKeyboardKeyCodes.BTN_MOUSE => expr,
// LinuxKeyboardKeyCodes.BTN_RIGHT => expr,
// LinuxKeyboardKeyCodes.BTN_MIDDLE => expr,
// LinuxKeyboardKeyCodes.BTN_SIDE => expr,
// LinuxKeyboardKeyCodes.BTN_EXTRA => expr,
// LinuxKeyboardKeyCodes.BTN_FORWARD => expr,
// LinuxKeyboardKeyCodes.BTN_BACK => expr,
// LinuxKeyboardKeyCodes.BTN_TASK => expr,
// LinuxKeyboardKeyCodes.BTN_JOYSTICK => expr,
// LinuxKeyboardKeyCodes.BTN_THUMB => expr,
// LinuxKeyboardKeyCodes.BTN_THUMB2 => expr,
// LinuxKeyboardKeyCodes.BTN_TOP => expr,
// LinuxKeyboardKeyCodes.BTN_TOP2 => expr,
// LinuxKeyboardKeyCodes.BTN_PINKIE => expr,
// LinuxKeyboardKeyCodes.BTN_BASE => expr,
// LinuxKeyboardKeyCodes.BTN_BASE2 => expr,
// LinuxKeyboardKeyCodes.BTN_BASE3 => expr,
// LinuxKeyboardKeyCodes.BTN_BASE4 => expr,
// LinuxKeyboardKeyCodes.BTN_BASE5 => expr,
// LinuxKeyboardKeyCodes.BTN_BASE6 => expr,
// LinuxKeyboardKeyCodes.BTN_DEAD => expr,
// LinuxKeyboardKeyCodes.BTN_GAMEPAD => expr,
// LinuxKeyboardKeyCodes.BTN_EAST => expr,
// LinuxKeyboardKeyCodes.BTN_C => expr,
// LinuxKeyboardKeyCodes.BTN_NORTH => expr,
// LinuxKeyboardKeyCodes.BTN_WEST => expr,
// LinuxKeyboardKeyCodes.BTN_Z => expr,
// LinuxKeyboardKeyCodes.BTN_TL => expr,
// LinuxKeyboardKeyCodes.BTN_TR => expr,
// LinuxKeyboardKeyCodes.BTN_TL2 => expr,
// LinuxKeyboardKeyCodes.BTN_TR2 => expr,
// LinuxKeyboardKeyCodes.BTN_SELECT => expr,
// LinuxKeyboardKeyCodes.BTN_START => expr,
// LinuxKeyboardKeyCodes.BTN_MODE => expr,
// LinuxKeyboardKeyCodes.BTN_THUMBL => expr,
// LinuxKeyboardKeyCodes.BTN_THUMBR => expr,
// LinuxKeyboardKeyCodes.BTN_DIGI => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_RUBBER => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_BRUSH => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_PENCIL => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_AIRBRUSH => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_FINGER => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_MOUSE => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_LENS => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_QUINTTAP => expr,
// LinuxKeyboardKeyCodes.BTN_STYLUS3 => expr,
// LinuxKeyboardKeyCodes.BTN_TOUCH => expr,
// LinuxKeyboardKeyCodes.BTN_STYLUS => expr,
// LinuxKeyboardKeyCodes.BTN_STYLUS2 => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_DOUBLETAP => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_TRIPLETAP => expr,
// LinuxKeyboardKeyCodes.BTN_TOOL_QUADTAP => expr,
// LinuxKeyboardKeyCodes.BTN_WHEEL => expr,
// LinuxKeyboardKeyCodes.BTN_GEAR_UP => expr,
// LinuxKeyboardKeyCodes.KEY_OK => expr,
// LinuxKeyboardKeyCodes.KEY_SELECT => expr,
// LinuxKeyboardKeyCodes.KEY_GOTO => expr,
// LinuxKeyboardKeyCodes.KEY_CLEAR => expr,
// LinuxKeyboardKeyCodes.KEY_POWER2 => expr,
// LinuxKeyboardKeyCodes.KEY_OPTION => expr,
// LinuxKeyboardKeyCodes.KEY_INFO => expr,
// LinuxKeyboardKeyCodes.KEY_TIME => expr,
// LinuxKeyboardKeyCodes.KEY_VENDOR => expr,
// LinuxKeyboardKeyCodes.KEY_ARCHIVE => expr,
// LinuxKeyboardKeyCodes.KEY_PROGRAM => expr,
// LinuxKeyboardKeyCodes.KEY_CHANNEL => expr,
// LinuxKeyboardKeyCodes.KEY_FAVORITES => expr,
// LinuxKeyboardKeyCodes.KEY_EPG => expr,
// LinuxKeyboardKeyCodes.KEY_PVR => expr,
// LinuxKeyboardKeyCodes.KEY_MHP => expr,
// LinuxKeyboardKeyCodes.KEY_LANGUAGE => expr,
// LinuxKeyboardKeyCodes.KEY_TITLE => expr,
// LinuxKeyboardKeyCodes.KEY_SUBTITLE => expr,
// LinuxKeyboardKeyCodes.KEY_ANGLE => expr,
// LinuxKeyboardKeyCodes.KEY_FULL_SCREEN => expr,
// LinuxKeyboardKeyCodes.KEY_MODE => expr,
// LinuxKeyboardKeyCodes.KEY_KEYBOARD => expr,
// LinuxKeyboardKeyCodes.KEY_ASPECT_RATIO => expr,
// LinuxKeyboardKeyCodes.KEY_PC => expr,
// LinuxKeyboardKeyCodes.KEY_TV => expr,
// LinuxKeyboardKeyCodes.KEY_TV2 => expr,
// LinuxKeyboardKeyCodes.KEY_VCR => expr,
// LinuxKeyboardKeyCodes.KEY_VCR2 => expr,
// LinuxKeyboardKeyCodes.KEY_SAT => expr,
// LinuxKeyboardKeyCodes.KEY_SAT2 => expr,
// LinuxKeyboardKeyCodes.KEY_CD => expr,
// LinuxKeyboardKeyCodes.KEY_TAPE => expr,
// LinuxKeyboardKeyCodes.KEY_RADIO => expr,
// LinuxKeyboardKeyCodes.KEY_TUNER => expr,
// LinuxKeyboardKeyCodes.KEY_PLAYER => expr,
// LinuxKeyboardKeyCodes.KEY_TEXT => expr,
// LinuxKeyboardKeyCodes.KEY_DVD => expr,
// LinuxKeyboardKeyCodes.KEY_AUX => expr,
// LinuxKeyboardKeyCodes.KEY_MP3 => expr,
// LinuxKeyboardKeyCodes.KEY_AUDIO => expr,
// LinuxKeyboardKeyCodes.KEY_VIDEO => expr,
// LinuxKeyboardKeyCodes.KEY_DIRECTORY => expr,
// LinuxKeyboardKeyCodes.KEY_LIST => expr,
// LinuxKeyboardKeyCodes.KEY_MEMO => expr,
// LinuxKeyboardKeyCodes.KEY_CALENDAR => expr,
// LinuxKeyboardKeyCodes.KEY_RED => expr,
// LinuxKeyboardKeyCodes.KEY_GREEN => expr,
// LinuxKeyboardKeyCodes.KEY_YELLOW => expr,
// LinuxKeyboardKeyCodes.KEY_BLUE => expr,
// LinuxKeyboardKeyCodes.KEY_CHANNELUP => expr,
// LinuxKeyboardKeyCodes.KEY_CHANNELDOWN => expr,
// LinuxKeyboardKeyCodes.KEY_FIRST => expr,
// LinuxKeyboardKeyCodes.KEY_LAST => expr,
// LinuxKeyboardKeyCodes.KEY_AB => expr,
// LinuxKeyboardKeyCodes.KEY_NEXT => expr,
// LinuxKeyboardKeyCodes.KEY_RESTART => expr,
// LinuxKeyboardKeyCodes.KEY_SLOW => expr,
// LinuxKeyboardKeyCodes.KEY_SHUFFLE => expr,
// LinuxKeyboardKeyCodes.KEY_BREAK => expr,
// LinuxKeyboardKeyCodes.KEY_PREVIOUS => expr,
// LinuxKeyboardKeyCodes.KEY_DIGITS => expr,
// LinuxKeyboardKeyCodes.KEY_TEEN => expr,
// LinuxKeyboardKeyCodes.KEY_TWEN => expr,
// LinuxKeyboardKeyCodes.KEY_VIDEOPHONE => expr,
// LinuxKeyboardKeyCodes.KEY_GAMES => expr,
// LinuxKeyboardKeyCodes.KEY_ZOOMIN => expr,
// LinuxKeyboardKeyCodes.KEY_ZOOMOUT => expr,
// LinuxKeyboardKeyCodes.KEY_ZOOMRESET => expr,
// LinuxKeyboardKeyCodes.KEY_WORDPROCESSOR => expr,
// LinuxKeyboardKeyCodes.KEY_EDITOR => expr,
// LinuxKeyboardKeyCodes.KEY_SPREADSHEET => expr,
// LinuxKeyboardKeyCodes.KEY_GRAPHICSEDITOR => expr,
// LinuxKeyboardKeyCodes.KEY_PRESENTATION => expr,
// LinuxKeyboardKeyCodes.KEY_DATABASE => expr,
// LinuxKeyboardKeyCodes.KEY_NEWS => expr,
// LinuxKeyboardKeyCodes.KEY_VOICEMAIL => expr,
// LinuxKeyboardKeyCodes.KEY_ADDRESSBOOK => expr,
// LinuxKeyboardKeyCodes.KEY_MESSENGER => expr,
// LinuxKeyboardKeyCodes.KEY_DISPLAYTOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_SPELLCHECK => expr,
// LinuxKeyboardKeyCodes.KEY_LOGOFF => expr,
// LinuxKeyboardKeyCodes.KEY_DOLLAR => expr,
// LinuxKeyboardKeyCodes.KEY_EURO => expr,
// LinuxKeyboardKeyCodes.KEY_FRAMEBACK => expr,
// LinuxKeyboardKeyCodes.KEY_FRAMEFORWARD => expr,
// LinuxKeyboardKeyCodes.KEY_CONTEXT_MENU => expr,
// LinuxKeyboardKeyCodes.KEY_MEDIA_REPEAT => expr,
// LinuxKeyboardKeyCodes.KEY_10CHANNELSUP => expr,
// LinuxKeyboardKeyCodes.KEY_10CHANNELSDOWN => expr,
// LinuxKeyboardKeyCodes.KEY_IMAGES => expr,
// LinuxKeyboardKeyCodes.KEY_NOTIFICATION_CENTER => expr,
// LinuxKeyboardKeyCodes.KEY_PICKUP_PHONE => expr,
// LinuxKeyboardKeyCodes.KEY_HANGUP_PHONE => expr,
// LinuxKeyboardKeyCodes.KEY_DEL_EOL => expr,
// LinuxKeyboardKeyCodes.KEY_DEL_EOS => expr,
// LinuxKeyboardKeyCodes.KEY_INS_LINE => expr,
// LinuxKeyboardKeyCodes.KEY_DEL_LINE => expr,
// LinuxKeyboardKeyCodes.KEY_FN => expr,
// LinuxKeyboardKeyCodes.KEY_FN_ESC => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F1 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F2 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F3 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F4 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F5 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F6 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F7 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F8 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F9 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F10 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F11 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F12 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_1 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_2 => expr,
// LinuxKeyboardKeyCodes.KEY_FN_D => expr,
// LinuxKeyboardKeyCodes.KEY_FN_E => expr,
// LinuxKeyboardKeyCodes.KEY_FN_F => expr,
// LinuxKeyboardKeyCodes.KEY_FN_S => expr,
// LinuxKeyboardKeyCodes.KEY_FN_B => expr,
// LinuxKeyboardKeyCodes.KEY_FN_RIGHT_SHIFT => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT1 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT2 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT3 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT4 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT5 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT6 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT7 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT8 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT9 => expr,
// LinuxKeyboardKeyCodes.KEY_BRL_DOT10 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_0 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_1 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_2 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_3 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_4 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_5 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_6 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_7 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_8 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_9 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_STAR => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_POUND => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_A => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_B => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_C => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_D => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_FOCUS => expr,
// LinuxKeyboardKeyCodes.KEY_WPS_BUTTON => expr,
// LinuxKeyboardKeyCodes.KEY_TOUCHPAD_TOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_TOUCHPAD_ON => expr,
// LinuxKeyboardKeyCodes.KEY_TOUCHPAD_OFF => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_ZOOMIN => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_ZOOMOUT => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_UP => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_DOWN => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_LEFT => expr,
// LinuxKeyboardKeyCodes.KEY_CAMERA_RIGHT => expr,
// LinuxKeyboardKeyCodes.KEY_ATTENDANT_ON => expr,
// LinuxKeyboardKeyCodes.KEY_ATTENDANT_OFF => expr,
// LinuxKeyboardKeyCodes.KEY_ATTENDANT_TOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_LIGHTS_TOGGLE => expr,
// LinuxKeyboardKeyCodes.BTN_DPAD_UP => expr,
// LinuxKeyboardKeyCodes.BTN_DPAD_DOWN => expr,
// LinuxKeyboardKeyCodes.BTN_DPAD_LEFT => expr,
// LinuxKeyboardKeyCodes.BTN_DPAD_RIGHT => expr,
// LinuxKeyboardKeyCodes.KEY_ALS_TOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_ROTATE_LOCK_TOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_BUTTONCONFIG => expr,
// LinuxKeyboardKeyCodes.KEY_TASKMANAGER => expr,
// LinuxKeyboardKeyCodes.KEY_JOURNAL => expr,
// LinuxKeyboardKeyCodes.KEY_CONTROLPANEL => expr,
// LinuxKeyboardKeyCodes.KEY_APPSELECT => expr,
// LinuxKeyboardKeyCodes.KEY_SCREENSAVER => expr,
// LinuxKeyboardKeyCodes.KEY_VOICECOMMAND => expr,
// LinuxKeyboardKeyCodes.KEY_ASSISTANT => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LAYOUT_NEXT => expr,
// LinuxKeyboardKeyCodes.KEY_EMOJI_PICKER => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESS_MIN => expr,
// LinuxKeyboardKeyCodes.KEY_BRIGHTNESS_MAX => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_PREV => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_NEXT => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_PREVGROUP => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_NEXTGROUP => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_ACCEPT => expr,
// LinuxKeyboardKeyCodes.KEY_KBDINPUTASSIST_CANCEL => expr,
// LinuxKeyboardKeyCodes.KEY_RIGHT_UP => expr,
// LinuxKeyboardKeyCodes.KEY_RIGHT_DOWN => expr,
// LinuxKeyboardKeyCodes.KEY_LEFT_UP => expr,
// LinuxKeyboardKeyCodes.KEY_LEFT_DOWN => expr,
// LinuxKeyboardKeyCodes.KEY_ROOT_MENU => expr,
// LinuxKeyboardKeyCodes.KEY_MEDIA_TOP_MENU => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_11 => expr,
// LinuxKeyboardKeyCodes.KEY_NUMERIC_12 => expr,
// LinuxKeyboardKeyCodes.KEY_AUDIO_DESC => expr,
// LinuxKeyboardKeyCodes.KEY_3D_MODE => expr,
// LinuxKeyboardKeyCodes.KEY_NEXT_FAVORITE => expr,
// LinuxKeyboardKeyCodes.KEY_STOP_RECORD => expr,
// LinuxKeyboardKeyCodes.KEY_PAUSE_RECORD => expr,
// LinuxKeyboardKeyCodes.KEY_VOD => expr,
// LinuxKeyboardKeyCodes.KEY_UNMUTE => expr,
// LinuxKeyboardKeyCodes.KEY_FASTREVERSE => expr,
// LinuxKeyboardKeyCodes.KEY_SLOWREVERSE => expr,
// LinuxKeyboardKeyCodes.KEY_DATA => expr,
// LinuxKeyboardKeyCodes.KEY_ONSCREEN_KEYBOARD => expr,
// LinuxKeyboardKeyCodes.KEY_PRIVACY_SCREEN_TOGGLE => expr,
// LinuxKeyboardKeyCodes.KEY_SELECTIVE_SCREENSHOT => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO1 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO2 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO3 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO4 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO5 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO6 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO7 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO8 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO9 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO10 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO11 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO12 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO13 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO14 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO15 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO16 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO17 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO18 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO19 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO20 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO21 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO22 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO23 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO24 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO25 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO26 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO27 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO28 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO29 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO30 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_RECORD_START => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_RECORD_STOP => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_PRESET_CYCLE => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_PRESET1 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_PRESET2 => expr,
// LinuxKeyboardKeyCodes.KEY_MACRO_PRESET3 => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LCD_MENU1 => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LCD_MENU2 => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LCD_MENU3 => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LCD_MENU4 => expr,
// LinuxKeyboardKeyCodes.KEY_KBD_LCD_MENU5 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY2 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY3 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY4 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY5 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY6 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY7 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY8 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY9 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY10 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY11 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY12 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY13 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY14 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY15 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY16 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY17 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY18 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY19 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY20 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY21 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY22 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY23 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY24 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY25 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY26 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY27 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY28 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY29 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY30 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY31 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY32 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY33 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY34 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY35 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY36 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY37 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY38 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY39 => expr,
// LinuxKeyboardKeyCodes.BTN_TRIGGER_HAPPY40 => expr,
_ => KeyboardKey.None
};
public static MouseButton MouseButtonFromButtonCode(LinuxKeyboardKeyCodes eCode) => eCode switch
{
LinuxKeyboardKeyCodes.BTN_LEFT => MouseButton.Left,
LinuxKeyboardKeyCodes.BTN_RIGHT => MouseButton.Right,
LinuxKeyboardKeyCodes.BTN_MIDDLE => MouseButton.Middle,
//todo: figure these out
// LinuxKeyboardKeyCodes.BTN_SIDE => expr,
// LinuxKeyboardKeyCodes.BTN_EXTRA => expr,
// LinuxKeyboardKeyCodes.BTN_FORWARD => expr,
// LinuxKeyboardKeyCodes.BTN_BACK => expr,
// LinuxKeyboardKeyCodes.BTN_TASK => expr,
_ => throw new ArgumentOutOfRangeException(nameof(eCode), eCode, null)
};
}
}
-454
View File
@@ -1,454 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
-12
View File
@@ -1,12 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:Artemis.UI;assembly=Artemis.UI"
x:Class="Artemis.UI.MacOS.App">
<Application.DataTemplates>
<ui:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<StyleInclude Source="avares://Artemis.UI/Styles/Artemis.axaml" />
</Application.Styles>
</Application>
@@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.MacOS
{
public class App : Application
{
public override void Initialize()
{
ArtemisBootstrapper.Bootstrap(this);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
ArtemisBootstrapper.Initialize();
}
}
}
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup>
</Project>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

-24
View File
@@ -1,24 +0,0 @@
using System;
using Avalonia;
using Avalonia.ReactiveUI;
namespace Artemis.UI.MacOS
{
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
}
}
}
@@ -1,3 +0,0 @@
<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/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -1,506 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Artemis.UI.Shared</name>
</assembly>
<members>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.ArtemisIcon.IconProperty">
<summary>
Gets or sets the currently displayed icon as either a <see cref="T:Material.Icons.MaterialIconKind" /> or an <see cref="T:System.Uri" /> pointing
to an SVG
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.ArtemisIcon.Icon">
<summary>
Gets or sets the currently displayed icon as either a <see cref="T:Material.Icons.MaterialIconKind" /> or an <see cref="T:System.Uri" /> pointing
to an SVG
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer">
<summary>
Visualizes an <see cref="T:Artemis.Core.ArtemisDevice" /> with optional per-LED colors
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.#ctor">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.Render(Avalonia.Media.DrawingContext)">
<inheritdoc />
</member>
<member name="E:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.LedClicked">
<summary>
Occurs when a LED of the device has been clicked
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnLedClicked(Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs)">
<summary>
Invokes the <see cref="E:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.LedClicked" /> event
</summary>
<param name="e"></param>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.DeviceProperty">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ArtemisDevice" /> to display
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.Device">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ArtemisDevice" /> to display
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.ShowColorsProperty">
<summary>
Gets or sets boolean indicating whether or not to show per-LED colors
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.ShowColors">
<summary>
Gets or sets a boolean indicating whether or not to show per-LED colors
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.HighlightedLedsProperty">
<summary>
Gets or sets a list of LEDs to highlight
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.HighlightedLeds">
<summary>
Gets or sets a list of LEDs to highlight
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.OnDetachedFromLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.DeviceVisualizer.MeasureOverride(Avalonia.Size)">
<inheritdoc />
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.ValueProperty">
<summary>
Gets or sets the currently selected value
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.Value">
<summary>
Gets or sets the currently selected value
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.EnumComboBox.OnDetachedFromLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.ProfileConfigurationIcon.ConfigurationIconProperty">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ProfileConfigurationIcon" /> to display
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.ProfileConfigurationIcon.ConfigurationIcon">
<summary>
Gets or sets the <see cref="T:Artemis.Core.ProfileConfigurationIcon" /> to display
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle">
<summary>
Visualizes an <see cref="T:Artemis.Core.ArtemisDevice" /> with optional per-LED colors
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BackgroundProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.Background" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrushProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderThicknessProperty">
<summary>
Defines the <see cref="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush" /> property.
</summary>
</member>
<member name="F:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.InputElementProperty">
<summary>
Defines the <see cref="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.get_InputElement" /> property.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.#ctor">
<inheritdoc />
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.Background">
<summary>
Gets or sets a brush used to paint the control's background.
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderBrush">
<summary>
Gets or sets a brush used to paint the control's border
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.BorderThickness">
<summary>
Gets or sets the width of the control's border
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.OnAttachedToVisualTree(Avalonia.VisualTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Controls.SelectionRectangle.OnDetachedFromVisualTree(Avalonia.VisualTreeAttachmentEventArgs)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter">
<summary>
Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter.Convert(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.ColorToSKColorConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter">
<summary>
Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.Color" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter.Convert(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Converters.SKColorToColorConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.DataModelInputDynamicEventArgs">
<summary>
Provides data about selection events raised by <see cref="!:DataModelDynamicViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.DataModelInputDynamicEventArgs.DataModelPath">
<summary>
Gets the data model path that was selected
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.DataModelInputStaticEventArgs">
<summary>
Provides data about submit events raised by <see cref="!:DataModelStaticViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.DataModelInputStaticEventArgs.Value">
<summary>
The value that was submitted
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs">
<summary>
Provides data on LED click events raised by the device visualizer
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs.Device">
<summary>
The device that was clicked
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.LedClickedEventArgs.Led">
<summary>
The LED that was clicked
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs">
<summary>
Provides data on profile related events raised by the profile editor
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs.ProfileConfiguration">
<summary>
Gets the profile the event was raised for
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.ProfileConfigurationEventArgs.PreviousProfileConfiguration">
<summary>
If applicable, the previous active profile before the event was raised
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs">
<summary>
Provides data on profile element related events raised by the profile editor
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs.RenderProfileElement">
<summary>
Gets the profile element the event was raised for
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.Events.RenderProfileElementEventArgs.PreviousRenderProfileElement">
<summary>
If applicable, the previous active profile element before the event was raised
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Exceptions.ArtemisSharedUIException">
<summary>
Represents errors that occur within the Artemis Shared UI library
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Ninject.SharedUIModule">
<summary>
The main <see cref="T:Ninject.Modules.NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Ninject.SharedUIModule.Load">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog`1">
<inheritdoc />
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog`1.Type">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog">
<summary>
Describes a configuration dialog for a specific plugin
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationDialog.Type">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel">
<summary>
Represents a view model for a plugin configuration window
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.#ctor(Artemis.Core.Plugin)">
<summary>
Creates a new instance of the <see cref="T:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel" /> class
</summary>
<param name="plugin"></param>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.Plugin">
<summary>
Gets the plugin this configuration view model is associated with
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.Close">
<summary>
Closes the window hosting the view model
</summary>
</member>
<member name="E:Artemis.UI.Avalonia.Shared.PluginConfigurationViewModel.CloseRequested">
<summary>
Occurs when the the window hosting the view model should close
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.FileDialogFilter" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder.WithName(System.String)">
<summary>
Sets the name of the filter
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder.WithExtension(System.String)">
<summary>
Adds the provided extension to the filter
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.NotificationBuilder.HavingButton(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.NotificationButtonBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.OpenFileDialog" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithAllowMultiple">
<summary>
Indicate that the user can select multiple files.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithTitle(System.String)">
<summary>
Set the title of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithDirectory(System.String)">
<summary>
Set the initial directory of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.WithInitialFileName(System.String)">
<summary>
Set the initial file name of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.HavingFilter(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.OpenFileDialogBuilder.ShowAsync">
<summary>
Shows the file dialog
</summary>
<returns>
A task that on completion returns an array containing the full path to the selected
files, or null if the dialog was canceled.
</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder">
<summary>
Represents a builder that can create a <see cref="T:Avalonia.Controls.SaveFileDialog" />.
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithTitle(System.String)">
<summary>
Set the title of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithDirectory(System.String)">
<summary>
Set the initial directory of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithInitialFileName(System.String)">
<summary>
Set the initial file name of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.WithDefaultExtension(System.String)">
<summary>
Set the default extension of the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.HavingFilter(System.Action{Artemis.UI.Avalonia.Shared.Services.Builders.FileDialogFilterBuilder})">
<summary>
Add a filter to the dialog
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Builders.SaveFileDialogBuilder.ShowAsync">
<summary>
Shows the save file dialog.
</summary>
<returns>
A task that on completion contains the full path of the save location, or null if the
dialog was canceled.
</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.Services.Interfaces.IArtemisSharedUIService">
<summary>
Represents a service provided by the Artemis Shared UI library
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowWindow``1(System.ValueTuple{System.String,System.Object}[])">
<summary>
Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a window
</summary>
<typeparam name="TViewModel">The type of view model to create</typeparam>
<returns>The created view model</returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowWindow(System.Object)">
<summary>
Given a ViewModel, show its corresponding View as a window
</summary>
<param name="viewModel">ViewModel to show the View for</param>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowExceptionDialog(System.String,System.Exception)">
<summary>
Shows a dialog displaying the given exception
</summary>
<param name="title">The title of the dialog</param>
<param name="exception">The exception to display</param>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowDialogAsync``1(Artemis.UI.Avalonia.Shared.DialogViewModelBase{``0})">
<summary>
Given an existing ViewModel, show its corresponding View as a Dialog
</summary>
<typeparam name="TResult">The return type</typeparam>
<param name="viewModel">ViewModel to show the View for</param>
<returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowDialogAsync``2(System.ValueTuple{System.String,System.Object}[])">
<summary>
Creates a view model instance of type <typeparamref name="TViewModel"/> and shows its corresponding View as a Dialog
</summary>
<typeparam name="TViewModel">The view model type</typeparam>
<typeparam name="TResult">The return type</typeparam>
<returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.ShowConfirmContentDialog(System.String,System.String,System.String,System.String)">
<summary>
Shows a content dialog asking the user to confirm an action
</summary>
<param name="title">The title of the dialog</param>
<param name="message">The message of the dialog</param>
<param name="confirm">The text of the confirm button</param>
<param name="cancel">The text of the cancel button, if <see langword="null"/> the cancel button will not be shown</param>
<returns>A task containing the result of the dialog, <see langword="true"/> if confirmed; otherwise <see langword="false"/></returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.CreateOpenFileDialog">
<summary>
Creates an open file dialog, use the fluent API to configure it
</summary>
<returns>The builder that can be used to configure the dialog</returns>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.Services.Interfaces.IWindowService.CreateSaveFileDialog">
<summary>
Creates a save file dialog, use the fluent API to configure it
</summary>
<returns>The builder that can be used to configure the dialog</returns>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.ViewModelBase">
<summary>
Represents the base class for Artemis view models
</summary>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.ViewModelBase.DisplayName">
<summary>
Gets or sets the display name of the view model
</summary>
</member>
<member name="T:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase">
<summary>
Represents the base class for Artemis view models that are interested in the activated event
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.#ctor">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Dispose(System.Boolean)">
<summary>
Releases the unmanaged resources used by the object and optionally releases the managed resources.
</summary>
<param name="disposing">
<see langword="true" /> to release both managed and unmanaged resources;
<see langword="false" /> to release only unmanaged resources.
</param>
</member>
<member name="P:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Activator">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Avalonia.Shared.ActivatableViewModelBase.Dispose">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1">
<summary>
Represents the base class for Artemis view models used to drive dialogs
</summary>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Close(`0)">
<summary>
Closes the dialog with the given <paramref name="result" />
</summary>
<param name="result">The result of the dialog</param>
</member>
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Cancel">
<summary>
Closes the dialog without a result
</summary>
</member>
</members>
</doc>
@@ -1,49 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationIcon />
<StartupObject />
<OutputPath>bin\</OutputPath>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Artemis.UI.Avalonia.Shared.csproj.DotSettings" />
<None Remove="Artemis.UI.Shared.csproj.DotSettings" />
<None Remove="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
<PackageReference Include="DynamicData" Version="7.5.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" />
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
</ItemGroup>
<ItemGroup>
<Page Include="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Controls\HotkeyBox.axaml.cs">
<DependentUpon>HotkeyBox.axaml</DependentUpon>
</Compile>
<Compile Update="Services\Window\ExceptionDialogView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
</Project>
@@ -1,6 +0,0 @@
<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/=exceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindow/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindows/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -1,55 +0,0 @@
using System.Windows.Input;
using Avalonia;
using FluentAvalonia.UI.Controls;
namespace Artemis.UI.Shared.AttachedProperties;
/// <summary>
/// Helper properties for working with NumberBoxes.
/// </summary>
public class NumberBoxAssist : AvaloniaObject
{
/// <summary>
/// Identifies the <seealso cref="SuffixTextProperty" /> Avalonia attached property.
/// </summary>
/// <value>Provide an <see cref="ICommand" /> derived object or binding.</value>
public static readonly AttachedProperty<string> SuffixTextProperty = AvaloniaProperty.RegisterAttached<NumberBoxAssist, string>("SuffixText", typeof(NumberBox));
/// <summary>
/// Identifies the <seealso cref="PrefixTextProperty" /> Avalonia attached property.
/// </summary>
/// <value>Provide an <see cref="ICommand" /> derived object or binding.</value>
public static readonly AttachedProperty<string> PrefixTextProperty = AvaloniaProperty.RegisterAttached<NumberBoxAssist, string>("PrefixText", typeof(NumberBox));
/// <summary>
/// Accessor for Attached property <see cref="SuffixTextProperty" />.
/// </summary>
public static void SetSuffixText(AvaloniaObject element, string value)
{
element.SetValue(SuffixTextProperty, value);
}
/// <summary>
/// Accessor for Attached property <see cref="SuffixTextProperty" />.
/// </summary>
public static string GetSuffixText(AvaloniaObject element)
{
return element.GetValue(SuffixTextProperty);
}
/// <summary>
/// Accessor for Attached property <see cref="PrefixTextProperty" />.
/// </summary>
public static void SetPrefixText(AvaloniaObject element, string value)
{
element.SetValue(PrefixTextProperty, value);
}
/// <summary>
/// Accessor for Attached property <see cref="PrefixTextProperty" />.
/// </summary>
public static string GetPrefixText(AvaloniaObject element)
{
return element.GetValue(PrefixTextProperty);
}
}
@@ -1,65 +0,0 @@
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
namespace Artemis.UI.Shared.AttachedProperties;
/// <summary>
/// Helper properties for working with TextBoxes.
/// </summary>
public class TextBoxAssist : AvaloniaObject
{
/// <summary>
/// Identifies the <seealso cref="SuffixTextProperty" /> Avalonia attached property.
/// </summary>
/// <value>Provide an <see cref="ICommand" /> derived object or binding.</value>
public static readonly AttachedProperty<string> SuffixTextProperty = AvaloniaProperty.RegisterAttached<TextBoxAssist, string>("SuffixText", typeof(TextBox));
/// <summary>
/// Identifies the <seealso cref="PrefixTextProperty" /> Avalonia attached property.
/// </summary>
/// <value>Provide an <see cref="ICommand" /> derived object or binding.</value>
public static readonly AttachedProperty<string> PrefixTextProperty = AvaloniaProperty.RegisterAttached<TextBoxAssist, string>("PrefixText", typeof(TextBox));
/// <summary>
/// Accessor for Attached property <see cref="SuffixTextProperty" />.
/// </summary>
public static void SetSuffixText(AvaloniaObject element, string value)
{
element.SetValue(SuffixTextProperty, value);
if (!string.IsNullOrWhiteSpace(value) && !((TextBox)element).Classes.Contains("suffixed"))
((TextBox)element).Classes.Add("suffixed");
else
((TextBox)element).Classes.Remove("suffixed");
}
/// <summary>
/// Accessor for Attached property <see cref="SuffixTextProperty" />.
/// </summary>
public static string GetSuffixText(AvaloniaObject element)
{
return element.GetValue(SuffixTextProperty);
}
/// <summary>
/// Accessor for Attached property <see cref="PrefixTextProperty" />.
/// </summary>
public static void SetPrefixText(AvaloniaObject element, string value)
{
element.SetValue(PrefixTextProperty, value);
if (!string.IsNullOrWhiteSpace(value) && !((TextBox)element).Classes.Contains("prefixed"))
((TextBox)element).Classes.Add("prefixed");
else
((TextBox)element).Classes.Remove("prefixed");
}
/// <summary>
/// Accessor for Attached property <see cref="PrefixTextProperty" />.
/// </summary>
public static string GetPrefixText(AvaloniaObject element)
{
return element.GetValue(PrefixTextProperty);
}
}
@@ -1,56 +0,0 @@
using System;
using Avalonia;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using FluentAvalonia.UI.Controls;
namespace Artemis.UI.Shared.Behaviors;
/// <summary>
/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
/// </summary>
public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
{
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
nameof(Value), defaultBindingMode: BindingMode.TwoWay);
static LostFocusNumberBoxBindingBehavior()
{
ValueProperty.Changed.Subscribe(e => ((LostFocusNumberBoxBindingBehavior) e.Sender).OnBindingValueChanged());
}
public double Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
/// <inheritdoc />
protected override void OnAttached()
{
if (AssociatedObject != null)
AssociatedObject.LostFocus += OnLostFocus;
base.OnAttached();
}
/// <inheritdoc />
protected override void OnDetaching()
{
if (AssociatedObject != null)
AssociatedObject.LostFocus -= OnLostFocus;
base.OnDetaching();
}
private void OnLostFocus(object? sender, RoutedEventArgs e)
{
if (AssociatedObject != null)
Value = AssociatedObject.Value;
}
private void OnBindingValueChanged()
{
if (AssociatedObject != null)
AssociatedObject.Value = Value;
}
}
@@ -1,56 +0,0 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
namespace Artemis.UI.Shared.Behaviors;
/// <summary>
/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
/// </summary>
public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
{
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string>(
"Text", defaultBindingMode: BindingMode.TwoWay);
static LostFocusTextBoxBindingBehavior()
{
TextProperty.Changed.Subscribe(e => ((LostFocusTextBoxBindingBehavior) e.Sender).OnBindingValueChanged());
}
public string Text
{
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <inheritdoc />
protected override void OnAttached()
{
if (AssociatedObject != null)
AssociatedObject.LostFocus += OnLostFocus;
base.OnAttached();
}
/// <inheritdoc />
protected override void OnDetaching()
{
if (AssociatedObject != null)
AssociatedObject.LostFocus -= OnLostFocus;
base.OnDetaching();
}
private void OnLostFocus(object? sender, RoutedEventArgs e)
{
if (AssociatedObject != null)
Text = AssociatedObject.Text;
}
private void OnBindingValueChanged()
{
if (AssociatedObject != null)
AssociatedObject.Text = Text;
}
}
@@ -1,8 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.ArtemisIcon">
Welcome to Avalonia!
</UserControl>
@@ -1,115 +0,0 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Svg.Skia;
using Material.Icons;
using Material.Icons.Avalonia;
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Represents a control that can display an arbitrary kind of icon.
/// </summary>
public class ArtemisIcon : UserControl
{
/// <summary>
/// Creates a new instance of the <see cref="ArtemisIcon" /> class.
/// </summary>
public ArtemisIcon()
{
InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
LayoutUpdated += OnLayoutUpdated;
}
private static void IconChanging(IAvaloniaObject sender, bool before)
{
if (before)
((ArtemisIcon) sender).Update();
}
private void Update()
{
try
{
// First look for an enum value instead of a string
if (Icon is MaterialIconKind materialIcon)
{
Content = new MaterialIcon {Kind = materialIcon, Width = Bounds.Width, Height = Bounds.Height};
}
// If it's a string there are several options
else if (Icon is string iconString)
{
// An enum defined as a string
if (Enum.TryParse(iconString, true, out MaterialIconKind parsedIcon))
{
Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height};
}
// An URI pointing to an SVG
else if (iconString.EndsWith(".svg"))
{
SvgSource source = new();
source.Load(iconString);
Content = new SvgImage {Source = source};
}
// An URI pointing to a different kind of image
else
{
Content = new Image {Source = new Bitmap(iconString), Width = Bounds.Width, Height = Bounds.Height};
}
}
}
catch
{
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height};
}
}
private void OnLayoutUpdated(object? sender, EventArgs e)
{
if (Content is Control contentControl)
{
contentControl.Width = Bounds.Width;
contentControl.Height = Bounds.Height;
}
}
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{
if (Content is SvgImage svgImage)
svgImage.Source?.Dispose();
else if (Content is Image image)
((Bitmap) image.Source).Dispose();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Properties
/// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG
/// </summary>
public static readonly StyledProperty<object?> IconProperty =
AvaloniaProperty.Register<ArtemisIcon, object?>(nameof(Icon), notifying: IconChanging);
/// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG
/// </summary>
public object? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
#endregion
}
}
@@ -1,300 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Material.Icons.Avalonia;
using ReactiveUI;
namespace Artemis.UI.Shared.Controls.DataModelPicker;
/// <summary>
/// Represents a data model picker picker that can be used to select a data model path.
/// </summary>
public class DataModelPicker : TemplatedControl
{
/// <summary>
/// The data model UI service this picker should use.
/// </summary>
public static IDataModelUIService? DataModelUIService;
/// <summary>
/// Gets or sets data model path.
/// </summary>
public static readonly StyledProperty<DataModelPath?> DataModelPathProperty =
AvaloniaProperty.Register<DataModelPicker, DataModelPath?>(nameof(DataModelPath), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
/// </summary>
public static readonly StyledProperty<bool> ShowDataModelValuesProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowDataModelValues));
/// <summary>
/// A list of extra modules to show data models of.
/// </summary>
public static readonly StyledProperty<ObservableCollection<Module>?> ModulesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Module>?>(nameof(Modules), new ObservableCollection<Module>());
/// <summary>
/// The data model view model to show, if not provided one will be retrieved by the control.
/// </summary>
public static readonly StyledProperty<DataModelPropertiesViewModel?> DataModelViewModelProperty =
AvaloniaProperty.Register<DataModelPicker, DataModelPropertiesViewModel?>(nameof(DataModelViewModel));
/// <summary>
/// A list of data model view models to show
/// </summary>
public static readonly StyledProperty<ObservableCollection<DataModelPropertiesViewModel>?> ExtraDataModelViewModelsProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<DataModelPropertiesViewModel>?>(nameof(ExtraDataModelViewModels), new ObservableCollection<DataModelPropertiesViewModel>());
/// <summary>
/// A list of types to filter the selectable paths on.
/// </summary>
public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>());
private MaterialIcon? _currentPathIcon;
private TextBlock? _currentPathDisplay;
private TextBlock? _currentPathDescription;
private TreeView? _dataModelTreeView;
static DataModelPicker()
{
ModulesProperty.Changed.Subscribe(ModulesChanged);
DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged);
DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged);
}
/// <summary>
/// Creates a new instance of the <see cref="DataModelPicker" /> class.
/// </summary>
public DataModelPicker()
{
SelectPropertyCommand = ReactiveCommand.Create<DataModelVisualizationViewModel>(selected => ExecuteSelectPropertyCommand(selected));
}
/// <summary>
/// Gets a command that selects the path by it's view model.
/// </summary>
public ReactiveCommand<DataModelVisualizationViewModel, Unit> SelectPropertyCommand { get; }
/// <summary>
/// Gets or sets data model path.
/// </summary>
public DataModelPath? DataModelPath
{
get => GetValue(DataModelPathProperty);
set => SetValue(DataModelPathProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
/// </summary>
public bool ShowDataModelValues
{
get => GetValue(ShowDataModelValuesProperty);
set => SetValue(ShowDataModelValuesProperty, value);
}
/// <summary>
/// A list of extra modules to show data models of.
/// </summary>
public ObservableCollection<Module>? Modules
{
get => GetValue(ModulesProperty);
set => SetValue(ModulesProperty, value);
}
/// <summary>
/// The data model view model to show, if not provided one will be retrieved by the control.
/// </summary>
public DataModelPropertiesViewModel? DataModelViewModel
{
get => GetValue(DataModelViewModelProperty);
private set => SetValue(DataModelViewModelProperty, value);
}
/// <summary>
/// A list of data model view models to show.
/// </summary>
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
{
get => GetValue(ExtraDataModelViewModelsProperty);
set => SetValue(ExtraDataModelViewModelsProperty, value);
}
/// <summary>
/// A list of types to filter the selectable paths on.
/// </summary>
public ObservableCollection<Type>? FilterTypes
{
get => GetValue(FilterTypesProperty);
set => SetValue(FilterTypesProperty, value);
}
/// <summary>
/// Occurs when a new path has been selected
/// </summary>
public event EventHandler<DataModelSelectedEventArgs>? DataModelPathSelected;
/// <summary>
/// Invokes the <see cref="DataModelPathSelected" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
{
DataModelPathSelected?.Invoke(this, e);
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
_currentPathIcon = e.NameScope.Find<MaterialIcon>("CurrentPathIcon");
_currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView");
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
UpdateCurrentPath(true);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
}
#endregion
#endregion
private static void ModulesChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<Module>?> e)
{
if (e.Sender is DataModelPicker dataModelPicker)
dataModelPicker.GetDataModel();
}
private static void DataModelPathPropertyChanged(AvaloniaPropertyChangedEventArgs<DataModelPath?> e)
{
if (e.Sender is DataModelPicker dataModelPicker)
dataModelPicker.UpdateCurrentPath(false);
}
private static void DataModelViewModelPropertyChanged(AvaloniaPropertyChangedEventArgs<DataModelPropertiesViewModel?> e)
{
if (e.Sender is DataModelPicker && e.OldValue.Value != null)
e.OldValue.Value.Dispose();
}
private static void ExtraDataModelViewModelsChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<DataModelPropertiesViewModel>?> e)
{
// TODO, the original did nothing here either and I can't remember why
}
private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected)
{
if (selected.DataModelPath == null)
return;
if (selected.DataModelPath.Equals(DataModelPath))
return;
DataModelPath = new DataModelPath(selected.DataModelPath);
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
}
private void GetDataModel()
{
if (DataModelUIService == null)
return;
ChangeDataModel(DataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List<Module>(), true));
}
private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
{
if (DataModelViewModel != null)
{
DataModelViewModel.Dispose();
DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
}
DataModelViewModel = dataModel;
if (DataModelViewModel != null)
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
}
private void DataModelOnUpdateRequested(object? sender, EventArgs e)
{
DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
if (ExtraDataModelViewModels == null) return;
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
}
private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
// Multi-select isn't a think so grab the first one
object? selected = _dataModelTreeView?.SelectedItems[0];
if (selected == null)
return;
if (selected is DataModelPropertyViewModel property && property.DataModelPath != null)
DataModelPath = new DataModelPath(property.DataModelPath);
else if (selected is DataModelListViewModel list && list.DataModelPath != null)
DataModelPath = new DataModelPath(list.DataModelPath);
else if (selected is DataModelEventViewModel dataModelEvent && dataModelEvent.DataModelPath != null)
DataModelPath = new DataModelPath(dataModelEvent.DataModelPath);
}
private void UpdateCurrentPath(bool selectCurrentPath)
{
if (DataModelPath == null)
return;
if (_dataModelTreeView != null && selectCurrentPath)
{
// Expand the path
DataModel? start = DataModelPath.Target;
DataModelVisualizationViewModel? root = DataModelViewModel?.Children.FirstOrDefault(c => c.DataModel == start);
if (root != null)
{
root.ExpandToPath(DataModelPath);
_dataModelTreeView.SelectedItem = root.GetViewModelForPath(DataModelPath);
}
}
if (_currentPathDisplay != null)
_currentPathDisplay.Text = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
if (_currentPathDescription != null)
_currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description;
if (_currentPathIcon != null)
_currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon();
}
}
@@ -1,338 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Controls.Flyouts;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.Core;
namespace Artemis.UI.Shared.Controls.DataModelPicker;
/// <summary>
/// Represents a button that can be used to pick a data model path in a flyout.
/// </summary>
public class DataModelPickerButton : TemplatedControl
{
/// <summary>
/// Gets or sets the placeholder to show when nothing is selected.
/// </summary>
public static readonly StyledProperty<string> PlaceholderProperty =
AvaloniaProperty.Register<DataModelPicker, string>(nameof(Placeholder), "Click to select");
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show the full path of the selected value.
/// </summary>
public static readonly StyledProperty<bool> ShowFullPathProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowFullPath));
/// <summary>
/// Gets or sets a boolean indicating whether the button should show the icon of the first provided filter type.
/// </summary>
public static readonly StyledProperty<bool> ShowTypeIconProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowTypeIcon));
/// <summary>
/// Gets a boolean indicating whether the data model picker has a value.
/// </summary>
public static readonly StyledProperty<bool> HasValueProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(HasValue));
/// <summary>
/// Gets or sets the desired flyout placement.
/// </summary>
public static readonly StyledProperty<FlyoutPlacementMode> PlacementProperty =
AvaloniaProperty.Register<FlyoutBase, FlyoutPlacementMode>(nameof(Placement));
/// <summary>
/// Gets or sets data model path.
/// </summary>
public static readonly StyledProperty<DataModelPath?> DataModelPathProperty =
AvaloniaProperty.Register<DataModelPicker, DataModelPath?>(nameof(DataModelPath), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
/// </summary>
public static readonly StyledProperty<bool> ShowDataModelValuesProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowDataModelValues));
/// <summary>
/// A list of extra modules to show data models of.
/// </summary>
public static readonly StyledProperty<ObservableCollection<Module>?> ModulesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Module>?>(nameof(Modules), new ObservableCollection<Module>());
/// <summary>
/// A list of data model view models to show
/// </summary>
public static readonly StyledProperty<ObservableCollection<DataModelPropertiesViewModel>?> ExtraDataModelViewModelsProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<DataModelPropertiesViewModel>?>(nameof(ExtraDataModelViewModels), new ObservableCollection<DataModelPropertiesViewModel>());
/// <summary>
/// A list of types to filter the selectable paths on.
/// </summary>
public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>());
private bool _attached;
private bool _flyoutActive;
private Button? _button;
private TextBlock? _label;
private DataModelPickerFlyout? _flyout;
static DataModelPickerButton()
{
ShowFullPathProperty.Changed.Subscribe(ShowFullPathChanged);
DataModelPathProperty.Changed.Subscribe(DataModelPathChanged);
}
/// <summary>
/// Gets or sets the placeholder to show when nothing is selected.
/// </summary>
public string Placeholder
{
get => GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show the full path of the selected value.
/// </summary>
public bool ShowFullPath
{
get => GetValue(ShowFullPathProperty);
set => SetValue(ShowFullPathProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the button should show the icon of the first provided filter type.
/// </summary>
public bool ShowTypeIcon
{
get => GetValue(ShowTypeIconProperty);
set => SetValue(ShowTypeIconProperty, value);
}
/// <summary>
/// Gets a boolean indicating whether the data model picker has a value.
/// </summary>
public bool HasValue
{
get => GetValue(HasValueProperty);
private set => SetValue(HasValueProperty, value);
}
/// <summary>
/// Gets or sets the desired flyout placement.
/// </summary>
public FlyoutPlacementMode Placement
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <summary>
/// Gets or sets data model path.
/// </summary>
public DataModelPath? DataModelPath
{
get => GetValue(DataModelPathProperty);
set => SetValue(DataModelPathProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
/// </summary>
public bool ShowDataModelValues
{
get => GetValue(ShowDataModelValuesProperty);
set => SetValue(ShowDataModelValuesProperty, value);
}
/// <summary>
/// A list of extra modules to show data models of.
/// </summary>
public ObservableCollection<Module>? Modules
{
get => GetValue(ModulesProperty);
set => SetValue(ModulesProperty, value);
}
/// <summary>
/// A list of data model view models to show.
/// </summary>
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
{
get => GetValue(ExtraDataModelViewModelsProperty);
set => SetValue(ExtraDataModelViewModelsProperty, value);
}
/// <summary>
/// A list of types to filter the selectable paths on.
/// </summary>
public ObservableCollection<Type>? FilterTypes
{
get => GetValue(FilterTypesProperty);
set => SetValue(FilterTypesProperty, value);
}
/// <summary>
/// Raised when the flyout opens.
/// </summary>
public event TypedEventHandler<DataModelPickerButton, EventArgs>? FlyoutOpened;
/// <summary>
/// Raised when the flyout closes.
/// </summary>
public event TypedEventHandler<DataModelPickerButton, EventArgs>? FlyoutClosed;
private static void DataModelPathChanged(AvaloniaPropertyChangedEventArgs<DataModelPath?> e)
{
if (e.Sender is not DataModelPickerButton self || !self._attached)
return;
if (e.OldValue.Value != null)
{
e.OldValue.Value.PathInvalidated -= self.PathValidationChanged;
e.OldValue.Value.PathValidated -= self.PathValidationChanged;
e.OldValue.Value.Dispose();
}
if (e.NewValue.Value != null)
{
e.NewValue.Value.PathInvalidated += self.PathValidationChanged;
e.NewValue.Value.PathValidated += self.PathValidationChanged;
}
self.UpdateValueDisplay();
}
private static void ShowFullPathChanged(AvaloniaPropertyChangedEventArgs<bool> e)
{
if (e.Sender is DataModelPickerButton self)
self.UpdateValueDisplay();
}
private void PathValidationChanged(object? sender, EventArgs e)
{
Dispatcher.UIThread.InvokeAsync(UpdateValueDisplay, DispatcherPriority.DataBind);
}
private void UpdateValueDisplay()
{
HasValue = DataModelPath != null && DataModelPath.IsValid;
if (_button == null || _label == null)
return;
if (!HasValue)
{
ToolTip.SetTip(_button, null);
_label.Text = Placeholder;
}
else
{
string? formattedPath = null;
if (DataModelPath != null && DataModelPath.IsValid)
formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
ToolTip.SetTip(_button, formattedPath);
_label.Text = ShowFullPath
? formattedPath
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
}
}
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
if (_flyout == null)
return;
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
_flyout.DataModelPicker.DataModelPath = DataModelPath;
_flyout.DataModelPicker.ExtraDataModelViewModels = ExtraDataModelViewModels;
_flyout.DataModelPicker.FilterTypes = FilterTypes;
_flyout.DataModelPicker.Modules = Modules;
_flyout.DataModelPicker.ShowDataModelValues = ShowDataModelValues;
_flyout.Placement = Placement;
_flyout.ShowAt(_button != null ? _button : this);
_flyoutActive = true;
FlyoutOpened?.Invoke(this, EventArgs.Empty);
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_button != null)
_button.Click -= OnButtonClick;
base.OnApplyTemplate(e);
_button = e.NameScope.Find<Button>("MainButton");
_label = e.NameScope.Find<TextBlock>("MainButtonLabel");
if (_button != null)
_button.Click += OnButtonClick;
UpdateValueDisplay();
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_attached = true;
if (DataModelPath != null)
{
DataModelPath.PathInvalidated += PathValidationChanged;
DataModelPath.PathValidated += PathValidationChanged;
}
_flyout ??= new DataModelPickerFlyout();
_flyout.Confirmed += FlyoutOnConfirmed;
_flyout.Closed += OnFlyoutClosed;
}
private void FlyoutOnConfirmed(DataModelPickerFlyout sender, object args)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
if (_flyout != null)
DataModelPath = _flyout.DataModelPicker.DataModelPath;
}
}
private void OnFlyoutClosed(object? sender, EventArgs e)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
}
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (DataModelPath != null)
{
DataModelPath.PathInvalidated -= PathValidationChanged;
DataModelPath.PathValidated -= PathValidationChanged;
}
if (_flyout != null)
_flyout.Closed -= OnFlyoutClosed;
}
#endregion
}
@@ -1,314 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
/// </summary>
public class DeviceVisualizer : Control
{
private const double UpdateFrameRate = 25.0;
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
private readonly DispatcherTimer _timer;
private Rect _deviceBounds;
private RenderTargetBitmap? _deviceImage;
private List<DeviceVisualizerLed>? _dimmedLeds;
private List<DeviceVisualizerLed>? _highlightedLeds;
private ArtemisDevice? _oldDevice;
/// <inheritdoc />
public DeviceVisualizer()
{
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(1000.0 / UpdateFrameRate)};
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
PointerReleased += OnPointerReleased;
}
/// <inheritdoc />
public override void Render(DrawingContext drawingContext)
{
if (Device == null)
return;
// Determine the scale required to fit the desired size of the control
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
DrawingContext.PushedState? boundsPush = null;
try
{
// Scale the visualization in the desired bounding box
if (Bounds.Width > 0 && Bounds.Height > 0)
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale));
// Apply device rotation
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
// Apply device scale
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
// Render device and LED images
if (_deviceImage != null)
{
drawingContext.DrawImage(
_deviceImage,
new Rect(_deviceImage.Size),
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height),
RenderOptions.GetBitmapInterpolationMode(this)
);
}
if (!ShowColors)
return;
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderGeometry(drawingContext, false);
}
finally
{
boundsPush?.Dispose();
}
}
/// <summary>
/// Occurs when a LED of the device has been clicked
/// </summary>
public event EventHandler<LedClickedEventArgs>? LedClicked;
/// <summary>
/// Invokes the <see cref="LedClicked" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnLedClicked(LedClickedEventArgs e)
{
LedClicked?.Invoke(this, e);
}
private void Update()
{
InvalidateVisual();
}
private Rect MeasureDevice()
{
if (Device == null)
return Rect.Empty;
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
Geometry geometry = new RectangleGeometry(deviceRect);
geometry.Transform = new RotateTransform(Device.Rotation);
return geometry.Bounds;
}
private void TimerOnTick(object? sender, EventArgs e)
{
if (ShowColors && IsVisible && Opacity > 0)
Update();
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (Device == null)
return;
Point position = e.GetPosition(this);
double x = position.X / Bounds.Width;
double y = position.Y / Bounds.Height;
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
if (deviceVisualizerLed != null)
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
}
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
}
private void DeviceUpdated(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
}
#region Properties
/// <summary>
/// Gets or sets the <see cref="ArtemisDevice" /> to display
/// </summary>
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device), notifying: DeviceUpdated);
private static void DeviceUpdated(IAvaloniaObject sender, bool before)
{
if (!before)
((DeviceVisualizer) sender).SetupForDevice();
}
/// <summary>
/// Gets or sets the <see cref="ArtemisDevice" /> to display
/// </summary>
public ArtemisDevice? Device
{
get => GetValue(DeviceProperty);
set => SetValue(DeviceProperty, value);
}
/// <summary>
/// Gets or sets boolean indicating whether or not to show per-LED colors
/// </summary>
public static readonly StyledProperty<bool> ShowColorsProperty =
AvaloniaProperty.Register<DeviceVisualizer, bool>(nameof(ShowColors));
/// <summary>
/// Gets or sets a boolean indicating whether or not to show per-LED colors
/// </summary>
public bool ShowColors
{
get => GetValue(ShowColorsProperty);
set => SetValue(ShowColorsProperty, value);
}
/// <summary>
/// Gets or sets a list of LEDs to highlight
/// </summary>
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
/// <summary>
/// Gets or sets a list of LEDs to highlight
/// </summary>
public ObservableCollection<ArtemisLed>? HighlightedLeds
{
get => GetValue(HighlightedLedsProperty);
set => SetValue(HighlightedLedsProperty, value);
}
#endregion
#region Lifetime management
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_deviceImage?.Dispose();
_deviceImage = null;
if (Device != null)
{
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
Device.DeviceUpdated -= DeviceUpdated;
}
base.OnDetachedFromVisualTree(e);
}
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_timer.Start();
_timer.Tick += TimerOnTick;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_timer.Stop();
_timer.Tick -= TimerOnTick;
base.OnDetachedFromLogicalTree(e);
}
private void SetupForDevice()
{
_deviceImage?.Dispose();
_deviceImage = null;
_deviceVisualizerLeds.Clear();
_highlightedLeds = new List<DeviceVisualizerLed>();
_dimmedLeds = new List<DeviceVisualizerLed>();
if (_oldDevice != null)
{
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
_oldDevice.DeviceUpdated -= DeviceUpdated;
}
_oldDevice = Device;
if (Device == null)
return;
_deviceBounds = MeasureDevice();
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
Device.DeviceUpdated += DeviceUpdated;
// Create all the LEDs
foreach (ArtemisLed artemisLed in Device.Leds)
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
// Load the device main image on a background thread
ArtemisDevice? device = Device;
Task.Run(() =>
{
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
return;
try
{
// Create a bitmap that'll be used to render the device and LED images just once
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.Size.Width * 4, (int) device.RgbDevice.Size.Height * 4));
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.DrawBitmap(context);
_deviceImage = renderTargetBitmap;
Dispatcher.UIThread.Post(InvalidateMeasure);
}
catch
{
// ignored
}
});
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
if (_deviceBounds.Width <= 0 || _deviceBounds.Height <= 0)
return new Size(0, 0);
double availableWidth = double.IsInfinity(availableSize.Width) ? _deviceBounds.Width : availableSize.Width;
double availableHeight = double.IsInfinity(availableSize.Height) ? _deviceBounds.Height : availableSize.Height;
double bestRatio = Math.Min(availableWidth / _deviceBounds.Width, availableHeight / _deviceBounds.Height);
return new Size(_deviceBounds.Width * bestRatio, _deviceBounds.Height * bestRatio);
}
#endregion
}
}
@@ -1,158 +0,0 @@
using System;
using System.IO;
using Artemis.Core;
using Avalonia;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
using RGB.NET.Core;
using Color = Avalonia.Media.Color;
using Point = Avalonia.Point;
using SolidColorBrush = Avalonia.Media.SolidColorBrush;
namespace Artemis.UI.Shared.Controls
{
internal class DeviceVisualizerLed
{
private readonly SolidColorBrush _penBrush;
private readonly SolidColorBrush _fillBrush;
private readonly Pen _pen;
public DeviceVisualizerLed(ArtemisLed led)
{
Led = led;
LedRect = new Rect(
Led.RgbLed.Location.X,
Led.RgbLed.Location.Y,
Led.RgbLed.Size.Width,
Led.RgbLed.Size.Height
);
_fillBrush = new SolidColorBrush();
_penBrush = new SolidColorBrush();
_pen = new Pen(_penBrush) {LineJoin = PenLineJoin.Round};
CreateLedGeometry();
}
public ArtemisLed Led { get; }
public Rect LedRect { get; set; }
public Geometry? DisplayGeometry { get; private set; }
public void DrawBitmap(IDrawingContextImpl drawingContext)
{
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
return;
try
{
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
drawingContext.DrawBitmap(
bitmap.PlatformImpl,
1,
new Rect(bitmap.Size),
new Rect(Led.RgbLed.Location.X * 4, Led.RgbLed.Location.Y * 4, Led.RgbLed.Size.Width * 4, Led.RgbLed.Size.Height * 4),
BitmapInterpolationMode.HighQuality
);
}
catch
{
// ignored
}
}
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
{
byte r = Led.RgbLed.Color.GetR();
byte g = Led.RgbLed.Color.GetG();
byte b = Led.RgbLed.Color.GetB();
if (dimmed)
{
_fillBrush.Color = new Color(50, r, g, b);
_penBrush.Color = new Color(100, r, g, b);
}
else
{
_fillBrush.Color = new Color(100, r, g, b);
_penBrush.Color = new Color(255, r, g, b);
}
// Render the LED geometry
drawingContext.DrawGeometry(_fillBrush, _pen, DisplayGeometry);
}
public bool HitTest(Point position)
{
return DisplayGeometry != null && DisplayGeometry.FillContains(position);
}
private void CreateLedGeometry()
{
// The minimum required size for geometry to be created
if (Led.RgbLed.Size.Width < 2 || Led.RgbLed.Size.Height < 2)
return;
switch (Led.RgbLed.Shape)
{
case Shape.Custom:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateCustomGeometry(2.0);
else
CreateCustomGeometry(1.0);
break;
case Shape.Rectangle:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateKeyCapGeometry();
else
CreateRectangleGeometry();
break;
case Shape.Circle:
CreateCircleGeometry();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void CreateRectangleGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
}
private void CreateCircleGeometry()
{
DisplayGeometry = new EllipseGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
}
private void CreateKeyCapGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 1, Led.RgbLed.Location.Y + 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2));
}
private void CreateCustomGeometry(double deflateAmount)
{
try
{
double width = Led.RgbLed.Size.Width - deflateAmount;
double height = Led.RgbLed.Size.Height - deflateAmount;
Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
geometry.Transform = new TransformGroup
{
Children = new Transforms
{
new ScaleTransform(width, height),
new TranslateTransform(Led.RgbLed.Location.X + deflateAmount / 2, Led.RgbLed.Location.Y + deflateAmount / 2)
}
};
DisplayGeometry = geometry;
}
catch (Exception)
{
CreateRectangleGeometry();
}
}
}
}
@@ -1,14 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.EnumComboBox">
<ComboBox x:Name="EnumComboBox" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding [0]}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</UserControl>
@@ -1,115 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Represents a combobox that can display the values of an enum.
/// </summary>
public class EnumComboBox : UserControl
{
/// <summary>
/// Gets or sets the currently selected value
/// </summary>
public static readonly StyledProperty<object?> ValueProperty =
AvaloniaProperty.Register<EnumComboBox, object?>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged);
private readonly ObservableCollection<(Enum, string)> _currentValues = new();
private ComboBox? _enumComboBox;
/// <summary>
/// Creates a new instance of the <see cref="EnumComboBox" /> class.
/// </summary>
public EnumComboBox()
{
InitializeComponent();
}
/// <summary>
/// Gets or sets the currently selected value
/// </summary>
public object? Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
private static void ValueChanged(IAvaloniaObject sender, bool before)
{
if (sender is EnumComboBox enumCombo && !before)
{
enumCombo.UpdateValues();
enumCombo.UpdateSelection();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1)
return;
(Enum enumValue, _) = _currentValues[_enumComboBox.SelectedIndex];
if (!Equals(Value, enumValue))
Value = enumValue;
}
private void UpdateValues()
{
Type? newType = Value?.GetType();
if (_enumComboBox == null || _currentValues.Any() || newType is not {IsEnum: true})
return;
foreach ((Enum, string) valueDesc in EnumUtilities.GetAllValuesAndDescriptions(newType))
_currentValues.Add(valueDesc);
}
private void UpdateSelection()
{
if (_enumComboBox == null || Value is not Enum)
return;
(Enum, string) value = _currentValues.FirstOrDefault(v => v.Item1.Equals(Value));
if (!Equals(value.Item1, _enumComboBox.SelectedItem))
_enumComboBox.SelectedItem = value;
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_enumComboBox = this.Get<ComboBox>("EnumComboBox");
_enumComboBox.Items = _currentValues;
UpdateValues();
UpdateSelection();
_enumComboBox.SelectionChanged += OnSelectionChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_enumComboBox != null)
_enumComboBox.SelectionChanged -= OnSelectionChanged;
base.OnDetachedFromLogicalTree(e);
}
#endregion
}
}
@@ -1,67 +0,0 @@
using System;
using System.ComponentModel;
using Avalonia.Controls;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Controls.Primitives;
namespace Artemis.UI.Shared.Controls.Flyouts;
/// <summary>
/// Defines a flyout that hosts a data model picker.
/// </summary>
public sealed class DataModelPickerFlyout : PickerFlyoutBase
{
private DataModelPicker.DataModelPicker? _picker;
/// <summary>
/// Gets the data model picker that the flyout hosts.
/// </summary>
public DataModelPicker.DataModelPicker DataModelPicker => _picker ??= new DataModelPicker.DataModelPicker();
/// <summary>
/// Raised when the Confirmed button is tapped indicating the new Color should be applied
/// </summary>
public event TypedEventHandler<DataModelPickerFlyout, object>? Confirmed;
/// <summary>
/// Raised when the Dismiss button is tapped, indicating the new color should not be applied
/// </summary>
public event TypedEventHandler<DataModelPickerFlyout, object>? Dismissed;
/// <inheritdoc />
protected override Control CreatePresenter()
{
_picker ??= new DataModelPicker.DataModelPicker();
PickerFlyoutPresenter presenter = new() {Content = DataModelPicker};
presenter.Confirmed += OnFlyoutConfirmed;
presenter.Dismissed += OnFlyoutDismissed;
return presenter;
}
/// <inheritdoc />
protected override void OnConfirmed()
{
Confirmed?.Invoke(this, EventArgs.Empty);
Hide();
}
/// <inheritdoc />
protected override void OnOpening(CancelEventArgs args)
{
base.OnOpening(args);
(Popup.Child as PickerFlyoutPresenter)?.Classes.Set(":acceptdismiss", true);
}
private void OnFlyoutDismissed(PickerFlyoutPresenter sender, object args)
{
Dismissed?.Invoke(this, EventArgs.Empty);
Hide();
}
private void OnFlyoutConfirmed(PickerFlyoutPresenter sender, object args)
{
OnConfirmed();
}
}
@@ -1,24 +0,0 @@
using Avalonia.Controls;
namespace Artemis.UI.Shared.Controls.Flyouts;
/// <summary>
/// Defines a flyout that hosts a gradient picker.
/// </summary>
public sealed class GradientPickerFlyout : Flyout
{
private GradientPicker.GradientPicker? _picker;
/// <summary>
/// Gets the gradient picker that this flyout hosts.
/// </summary>
public GradientPicker.GradientPicker GradientPicker => _picker ??= new GradientPicker.GradientPicker();
/// <inheritdoc />
protected override Control CreatePresenter()
{
_picker ??= new GradientPicker.GradientPicker();
FlyoutPresenter presenter = new() {Content = GradientPicker};
return presenter;
}
}
@@ -1,325 +0,0 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using Artemis.Core;
using Artemis.UI.Shared.Providers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media;
using ReactiveUI;
using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker;
/// <summary>
/// Represents a gradient picker that can be used to edit a gradient.
/// </summary>
public class GradientPicker : TemplatedControl
{
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public static readonly StyledProperty<ColorGradient> ColorGradientProperty =
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
/// <summary>
/// Gets or sets the currently selected color stop.
/// </summary>
public static readonly StyledProperty<ColorGradientStop?> SelectedColorStopProperty =
AvaloniaProperty.Register<GradientPicker, ColorGradientStop?>(nameof(SelectedColorStop), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public static readonly StyledProperty<bool> IsCompactProperty =
AvaloniaProperty.Register<GradientPicker, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
AvaloniaProperty.Register<GradientPicker, IColorGradientStorageProvider?>(nameof(StorageProvider), notifying: StorageProviderChanged);
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public static readonly DirectProperty<GradientPicker, LinearGradientBrush> LinearGradientBrushProperty =
AvaloniaProperty.RegisterDirect<GradientPicker, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
/// <summary>
/// Gets the command to execute when deleting stops.
/// </summary>
public static readonly DirectProperty<GradientPicker, ICommand> DeleteStopProperty =
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
private readonly ICommand _deleteStop;
private bool _shiftDown;
private Button? _flipStops;
private Border? _gradient;
private Button? _rotateStops;
private Button? _spreadStops;
private Button? _toggleSeamless;
private Button? _randomize;
private ColorGradient? _lastColorGradient;
private ColorPicker? _colorPicker;
public GradientPicker()
{
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>
{
if (ColorGradient.Count <= 2)
return;
int index = ColorGradient.IndexOf(s);
ColorGradient.Remove(s);
if (index > ColorGradient.Count - 1)
index--;
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
});
}
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public ColorGradient ColorGradient
{
get => GetValue(ColorGradientProperty);
set => SetValue(ColorGradientProperty, value);
}
/// <summary>
/// Gets or sets the currently selected color stop.
/// </summary>
public ColorGradientStop? SelectedColorStop
{
get => GetValue(SelectedColorStopProperty);
set
{
if (_colorPicker != null && SelectedColorStop != null)
_colorPicker.PreviousColor = new Color2(SelectedColorStop.Color.Red, SelectedColorStop.Color.Green, SelectedColorStop.Color.Blue, SelectedColorStop.Color.Alpha);
SetValue(SelectedColorStopProperty, value);
}
}
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public bool IsCompact
{
get => GetValue(IsCompactProperty);
set => SetValue(IsCompactProperty, value);
}
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public IColorGradientStorageProvider? StorageProvider
{
get => GetValue(StorageProviderProperty);
set => SetValue(StorageProviderProperty, value);
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public LinearGradientBrush LinearGradientBrush { get; } = new();
/// <summary>
/// Gets the command to execute when deleting stops.
/// </summary>
public ICommand DeleteStop
{
get => _deleteStop;
private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_gradient != null)
_gradient.PointerPressed -= GradientOnPointerPressed;
if (_spreadStops != null)
_spreadStops.Click -= SpreadStopsOnClick;
if (_toggleSeamless != null)
_toggleSeamless.Click -= ToggleSeamlessOnClick;
if (_flipStops != null)
_flipStops.Click -= FlipStopsOnClick;
if (_rotateStops != null)
_rotateStops.Click -= RotateStopsOnClick;
if (_randomize != null)
_randomize.Click -= RandomizeOnClick;
_colorPicker = e.NameScope.Find<ColorPicker>("ColorPicker");
_gradient = e.NameScope.Find<Border>("Gradient");
_spreadStops = e.NameScope.Find<Button>("SpreadStops");
_toggleSeamless = e.NameScope.Find<Button>("ToggleSeamless");
_flipStops = e.NameScope.Find<Button>("FlipStops");
_rotateStops = e.NameScope.Find<Button>("RotateStops");
_randomize = e.NameScope.Find<Button>("Randomize");
if (_gradient != null)
_gradient.PointerPressed += GradientOnPointerPressed;
if (_spreadStops != null)
_spreadStops.Click += SpreadStopsOnClick;
if (_toggleSeamless != null)
_toggleSeamless.Click += ToggleSeamlessOnClick;
if (_flipStops != null)
_flipStops.Click += FlipStopsOnClick;
if (_rotateStops != null)
_rotateStops.Click += RotateStopsOnClick;
if (_randomize != null)
_randomize.Click += RandomizeOnClick;
base.OnApplyTemplate(e);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
Subscribe();
KeyUp += OnKeyUp;
KeyDown += OnKeyDown;
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Unsubscribe();
KeyUp -= OnKeyUp;
KeyDown -= OnKeyDown;
_shiftDown = false;
}
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
(sender as GradientPicker)?.Subscribe();
}
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
{
}
private void Subscribe()
{
Unsubscribe();
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
ColorGradient.StopChanged += ColorGradientOnStopChanged;
SelectedColorStop = ColorGradient.FirstOrDefault();
UpdateGradient();
_lastColorGradient = ColorGradient;
}
private void Unsubscribe()
{
if (_lastColorGradient == null)
return;
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
_lastColorGradient = null;
}
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateGradient();
}
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
{
UpdateGradient();
}
private void UpdateGradient()
{
// Update the display gradient
GradientStops collection = new();
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
LinearGradientBrush.GradientStops = collection;
}
private void GradientOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (_gradient == null)
return;
float position = (float) (e.GetPosition(_gradient).X / _gradient.Bounds.Width);
ColorGradientStop newStop = new(ColorGradient.GetColor(position), position);
ColorGradient.Add(newStop);
SelectedColorStop = newStop;
}
private void OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key is Key.LeftShift or Key.RightShift)
_shiftDown = true;
}
private void OnKeyUp(object? sender, KeyEventArgs e)
{
if (e.Key is Key.LeftShift or Key.RightShift)
_shiftDown = false;
if (e.Key != Key.Delete || SelectedColorStop == null || ColorGradient.Count <= 2)
return;
int index = ColorGradient.IndexOf(SelectedColorStop);
ColorGradient.Remove(SelectedColorStop);
if (index > ColorGradient.Count - 1)
index--;
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
e.Handled = true;
}
private void SpreadStopsOnClick(object? sender, RoutedEventArgs e)
{
ColorGradient.SpreadStops();
}
private void ToggleSeamlessOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.ToggleSeamless();
}
private void FlipStopsOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.FlipStops();
}
private void RotateStopsOnClick(object? sender, RoutedEventArgs e)
{
if (SelectedColorStop == null || ColorGradient.Count < 2)
return;
ColorGradient.RotateStops(_shiftDown);
}
private void RandomizeOnClick(object? sender, RoutedEventArgs e)
{
ColorGradient.Randomize(6);
SelectedColorStop = ColorGradient.First();
}
}
@@ -1,212 +0,0 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Controls.Flyouts;
using Artemis.UI.Shared.Providers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Media;
using FluentAvalonia.Core;
using Button = FluentAvalonia.UI.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker;
/// <summary>
/// Represents a gradient picker box that can be used to edit a gradient
/// </summary>
public class GradientPickerButton : TemplatedControl
{
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public static readonly StyledProperty<ColorGradient?> ColorGradientProperty =
AvaloniaProperty.Register<GradientPickerButton, ColorGradient?>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public static readonly StyledProperty<bool> IsCompactProperty =
AvaloniaProperty.Register<GradientPickerButton, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
AvaloniaProperty.Register<GradientPickerButton, IColorGradientStorageProvider?>(nameof(StorageProvider));
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public static readonly DirectProperty<GradientPickerButton, LinearGradientBrush> LinearGradientBrushProperty =
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
private ColorGradient? _lastColorGradient;
private Button? _button;
private GradientPickerFlyout? _flyout;
private bool _flyoutActive;
/// <summary>
/// Gets or sets the color gradient.
/// </summary>
public ColorGradient? ColorGradient
{
get => GetValue(ColorGradientProperty);
set => SetValue(ColorGradientProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
/// </summary>
public bool IsCompact
{
get => GetValue(IsCompactProperty);
set => SetValue(IsCompactProperty, value);
}
/// <summary>
/// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary>
public IColorGradientStorageProvider? StorageProvider
{
get => GetValue(StorageProviderProperty);
set => SetValue(StorageProviderProperty, value);
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public LinearGradientBrush LinearGradientBrush { get; } = new();
/// <summary>
/// Raised when the flyout opens.
/// </summary>
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutOpened;
/// <summary>
/// Raised when the flyout closes.
/// </summary>
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutClosed;
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_button != null)
_button.Click -= OnButtonClick;
base.OnApplyTemplate(e);
_button = e.NameScope.Find<Button>("MainButton");
if (_button != null)
_button.Click += OnButtonClick;
}
#endregion
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
(sender as GradientPickerButton)?.Subscribe();
}
private void Subscribe()
{
Unsubscribe();
if (ColorGradient != null)
{
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
ColorGradient.StopChanged += ColorGradientOnStopChanged;
}
UpdateGradient();
_lastColorGradient = ColorGradient;
}
private void Unsubscribe()
{
if (_lastColorGradient == null)
return;
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
_lastColorGradient = null;
}
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
UpdateGradient();
}
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
{
UpdateGradient();
}
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
if (_flyout == null || ColorGradient == null)
return;
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
_flyout.GradientPicker.ColorGradient = ColorGradient;
_flyout.GradientPicker.IsCompact = IsCompact;
_flyout.GradientPicker.StorageProvider = StorageProvider;
_flyout.ShowAt(this);
_flyoutActive = true;
FlyoutOpened?.Invoke(this, EventArgs.Empty);
}
private void OnFlyoutClosed(object? sender, EventArgs e)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
}
}
private void UpdateGradient()
{
// Update the display gradient
GradientStops collection = new();
if (ColorGradient != null)
{
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
}
LinearGradientBrush.GradientStops = collection;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
Subscribe();
if (_flyout == null)
{
_flyout = new GradientPickerFlyout();
_flyout.FlyoutPresenterClasses.Add("gradient-picker-presenter");
}
_flyout.Closed += OnFlyoutClosed;
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
Unsubscribe();
if (_flyout != null)
_flyout.Closed -= OnFlyoutClosed;
}
#endregion
}
@@ -1,199 +0,0 @@
using System;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
namespace Artemis.UI.Shared.Controls.GradientPicker;
public class GradientPickerColorStop : TemplatedControl
{
private static ColorGradientStop? _draggingStop;
private static IPointer? _dragPointer;
/// <summary>
/// Gets or sets the gradient picker.
/// </summary>
public static readonly StyledProperty<GradientPicker?> GradientPickerProperty =
AvaloniaProperty.Register<GradientPickerColorStop, GradientPicker?>(nameof(GradientPicker), notifying: Notifying);
private static void Notifying(IAvaloniaObject sender, bool before)
{
if (sender is not GradientPickerColorStop self)
return;
if (before && self.GradientPicker != null)
self.GradientPicker.PropertyChanged -= self.GradientPickerOnPropertyChanged;
else if (self.GradientPicker != null)
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
}
/// <summary>
/// Gets or sets the color stop.
/// </summary>
public static readonly StyledProperty<ColorGradientStop> ColorStopProperty =
AvaloniaProperty.Register<GradientPickerColorStop, ColorGradientStop>(nameof(ColorStop));
/// <summary>
/// Gets or sets the position reference to use when positioning and dragging this color stop.
/// <para>If <see langword="null" /> then dragging is not enabled.</para>
/// </summary>
public static readonly StyledProperty<IControl?> PositionReferenceProperty =
AvaloniaProperty.Register<GradientPickerColorStop, IControl?>(nameof(PositionReference));
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public static readonly DirectProperty<GradientPickerColorStop, bool> IsSelectedProperty =
AvaloniaProperty.RegisterDirect<GradientPickerColorStop, bool>(nameof(IsSelected), g => g.IsSelected);
private bool _isSelected;
private double _dragOffset;
/// <summary>
/// Gets or sets the gradient picker.
/// </summary>
public GradientPicker? GradientPicker
{
get => GetValue(GradientPickerProperty);
set => SetValue(GradientPickerProperty, value);
}
/// <summary>
/// Gets or sets the color stop.
/// </summary>
public ColorGradientStop ColorStop
{
get => GetValue(ColorStopProperty);
set => SetValue(ColorStopProperty, value);
}
/// <summary>
/// Gets or sets the position reference to use when positioning and dragging this color stop.
/// <para>If <see langword="null" /> then dragging is not enabled.</para>
/// </summary>
public IControl? PositionReference
{
get => GetValue(PositionReferenceProperty);
set => SetValue(PositionReferenceProperty, value);
}
/// <summary>
/// Gets the linear gradient brush representing the color gradient.
/// </summary>
public bool IsSelected
{
get => _isSelected;
private set
{
SetAndRaise(IsSelectedProperty, ref _isSelected, value);
if (IsSelected)
PseudoClasses.Add(":selected");
else
PseudoClasses.Remove(":selected");
}
}
private void GradientPickerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (GradientPicker != null && e.Property == GradientPicker.SelectedColorStopProperty)
{
IsSelected = GradientPicker.SelectedColorStop == ColorStop;
}
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || PositionReference == null)
return;
_dragOffset = e.GetCurrentPoint(PositionReference).Position.X - GetPixelPosition();
e.Pointer.Capture(this);
// Store these in case the control is being recreated due to an array resort
// it's a bit ugly but it gives us a way to pick up dragging again with the new control
_dragPointer = e.Pointer;
_draggingStop = ColorStop;
e.Handled = true;
if (GradientPicker != null)
GradientPicker.SelectedColorStop = ColorStop;
}
private void OnPointerMoved(object? sender, PointerEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || !ReferenceEquals(e.Pointer.Captured, this) || PositionReference == null)
{
if (_draggingStop != ColorStop)
return;
_dragOffset = e.GetCurrentPoint(PositionReference).Position.X - GetPixelPosition();
}
double position = e.GetCurrentPoint(PositionReference).Position.X - _dragOffset;
ColorStop.Position = MathF.Round((float) Math.Clamp(position / PositionReference.Bounds.Width, 0, 1), 3, MidpointRounding.AwayFromZero);
e.Handled = true;
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (e.InitialPressMouseButton != MouseButton.Left)
return;
e.Pointer.Capture(null);
e.Handled = true;
_draggingStop = null;
}
private double GetPixelPosition()
{
if (PositionReference == null)
return 0;
return PositionReference.Bounds.Width * ColorStop.Position;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
if (GradientPicker != null)
GradientPicker.PropertyChanged += GradientPickerOnPropertyChanged;
if (PositionReference != null)
{
PointerPressed += OnPointerPressed;
PointerMoved += OnPointerMoved;
PointerReleased += OnPointerReleased;
// If this stop was previously being dragged, carry on dragging again
// This can happen if the control was recreated due to an array sort
if (_draggingStop == ColorStop && _dragPointer != null)
{
_dragPointer.Capture(this);
IsSelected = true;
}
}
base.OnAttachedToVisualTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (GradientPicker != null)
GradientPicker.PropertyChanged -= GradientPickerOnPropertyChanged;
PointerPressed -= OnPointerPressed;
PointerMoved -= OnPointerMoved;
PointerReleased -= OnPointerReleased;
base.OnDetachedFromVisualTree(e);
}
#endregion
}
@@ -1,26 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.HotkeyBox">
<UserControl.Styles>
<Style Selector="TextBox#DisplayTextBox:focus:not(TextBox:empty)">
<Setter Property="InnerRightContent">
<Template>
<Button Classes="textBoxClearButton"
Click="Button_OnClick" />
</Template>
</Setter>
</Style>
</UserControl.Styles>
<controls:NoInputTextBox x:Name="DisplayTextBox"
Watermark="{Binding $parent.Watermark}"
UseFloatingWatermark="{Binding $parent.UseFloatingWatermark}"
Classes="clearButton"
IsReadOnly="True"
HorizontalAlignment="Stretch" />
</UserControl>
@@ -1,138 +0,0 @@
using System;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Humanizer;
using Material.Icons;
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Represents a control that can be used to display or edit <see cref="Core.Hotkey"/> instances.
/// </summary>
public class HotkeyBox : UserControl
{
private readonly TextBox _displayTextBox;
/// <summary>
/// Creates a new instance of the <see cref="HotkeyBox"/> class
/// </summary>
public HotkeyBox()
{
InitializeComponent();
_displayTextBox = this.Find<TextBox>("DisplayTextBox");
_displayTextBox.KeyDown += DisplayTextBoxOnKeyDown;
_displayTextBox.KeyUp += DisplayTextBoxOnKeyUp;
UpdateDisplayTextBox();
}
private static void HotkeyChanging(IAvaloniaObject sender, bool before)
{
((HotkeyBox) sender).UpdateDisplayTextBox();
}
private void DisplayTextBoxOnKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key >= Key.LeftShift && e.Key <= Key.RightAlt)
return;
Hotkey ??= new Hotkey();
Hotkey.Key = (KeyboardKey?) e.Key;
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
UpdateDisplayTextBox();
e.Handled = true;
}
private void DisplayTextBoxOnKeyUp(object? sender, KeyEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.None)
FocusManager.Instance?.Focus(null);
e.Handled = true;
}
private void UpdateDisplayTextBox()
{
string? display = null;
if (Hotkey?.Modifiers != null)
display = string.Join("+", Enum.GetValues<KeyboardModifierKey>().Skip(1).Where(m => Hotkey.Modifiers.Value.HasFlag(m)).Select(v => v.Humanize()));
if (Hotkey?.Key != null)
display = string.IsNullOrEmpty(display) ? Hotkey.Key.ToString() : $"{display}+{Hotkey.Key}";
_displayTextBox.Text = display;
_displayTextBox.CaretIndex = _displayTextBox.Text?.Length ?? 0;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void Button_OnClick(object? sender, RoutedEventArgs e)
{
Hotkey = null;
FocusManager.Instance?.Focus(null);
UpdateDisplayTextBox();
}
#region Properties
/// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG
/// </summary>
public static readonly StyledProperty<Hotkey?> HotkeyProperty =
AvaloniaProperty.Register<HotkeyBox, Hotkey?>(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay, notifying: HotkeyChanging);
/// <summary>
/// Gets or sets the watermark of the hotkey box when it is empty.
/// </summary>
public static readonly StyledProperty<string?> WatermarkProperty =
AvaloniaProperty.Register<HotkeyBox, string?>(nameof(Watermark));
/// <summary>
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
/// </summary>
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
AvaloniaProperty.Register<HotkeyBox, bool>(nameof(UseFloatingWatermark));
/// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG
/// </summary>
public Hotkey? Hotkey
{
get => GetValue(HotkeyProperty);
set => SetValue(HotkeyProperty, value);
}
/// <summary>
/// Gets or sets the watermark of the hotkey box when it is empty.
/// </summary>
public string? Watermark
{
get => GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
/// </summary>
public bool UseFloatingWatermark
{
get => GetValue(UseFloatingWatermarkProperty);
set => SetValue(UseFloatingWatermarkProperty, value);
}
#endregion
}
}
@@ -1,24 +0,0 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Styling;
namespace Artemis.UI.Shared.Controls
{
internal class NoInputTextBox : TextBox, IStyleable
{
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
// Don't call the base method on purpose
}
/// <inheritdoc />
protected override void OnKeyUp(KeyEventArgs e)
{
// Don't call the base method on purpose
}
Type IStyleable.StyleKey => typeof(TextBox);
}
}
@@ -1,7 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.ProfileConfigurationIcon">
</UserControl>
@@ -1,125 +0,0 @@
using System;
using System.IO;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Svg.Skia;
using Material.Icons;
using Material.Icons.Avalonia;
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration"/>.
/// </summary>
public class ProfileConfigurationIcon : UserControl
{
/// <summary>
/// Creates a new instance of the <see cref="ProfileConfigurationIcon" /> class.
/// </summary>
public ProfileConfigurationIcon()
{
InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
PropertyChanged += OnPropertyChanged;
}
private void Update()
{
if (ConfigurationIcon == null)
return;
try
{
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
{
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
? new MaterialIcon {Kind = parsedIcon!}
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
return;
}
Stream? stream = ConfigurationIcon.GetIconStream();
if (stream == null)
{
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
return;
}
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.SvgImage)
{
SvgSource source = new();
source.Load(stream);
Content = new Image {Source = new SvgImage {Source = source}};
}
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.BitmapImage)
{
Content = new Image {Source = new Bitmap(ConfigurationIcon.GetIconStream())};
}
else
{
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
}
catch
{
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{
if (ConfigurationIcon != null)
ConfigurationIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
if (Content is Image image && image.Source is IDisposable disposable)
disposable.Dispose();
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ConfigurationIconProperty)
return;
if (e.OldValue is Core.ProfileConfigurationIcon oldIcon)
oldIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
if (e.NewValue is Core.ProfileConfigurationIcon newIcon)
newIcon.IconUpdated += ConfigurationIconOnIconUpdated;
Update();
}
private void ConfigurationIconOnIconUpdated(object? sender, EventArgs e)
{
Update();
}
#region Properties
/// <summary>
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
/// </summary>
public static readonly StyledProperty<Core.ProfileConfigurationIcon?> ConfigurationIconProperty =
AvaloniaProperty.Register<ProfileConfigurationIcon, Core.ProfileConfigurationIcon?>(nameof(ConfigurationIcon));
/// <summary>
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
/// </summary>
public Core.ProfileConfigurationIcon? ConfigurationIcon
{
get => GetValue(ConfigurationIconProperty);
set => SetValue(ConfigurationIconProperty, value);
}
#endregion
}
}
@@ -1,275 +0,0 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
namespace Artemis.UI.Shared.Controls;
/// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
/// </summary>
public class SelectionRectangle : Control
{
/// <summary>
/// Defines the <see cref="Background" /> property.
/// </summary>
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<SelectionRectangle, IBrush>(nameof(Background), new SolidColorBrush(Colors.CadetBlue, 0.25));
/// <summary>
/// Defines the <see cref="BorderBrush" /> property.
/// </summary>
public static readonly StyledProperty<IBrush> BorderBrushProperty =
AvaloniaProperty.Register<SelectionRectangle, IBrush>(nameof(BorderBrush), new SolidColorBrush(Colors.CadetBlue));
/// <summary>
/// Defines the <see cref="BorderThickness" /> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(BorderThickness), 1);
/// <summary>
/// Defines the <see cref="BorderRadius" /> property.
/// </summary>
public static readonly StyledProperty<double> BorderRadiusProperty =
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(BorderRadius));
/// <summary>
/// Defines the <see cref="InputElement" /> property.
/// </summary>
public static readonly StyledProperty<IControl?> InputElementProperty =
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
/// <summary>
/// Defines the <see cref="ZoomRatio" /> property.
/// </summary>
public static readonly StyledProperty<double> ZoomRatioProperty =
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(ZoomRatio), 1);
/// <summary>
/// Defines the read-only <see cref="IsSelecting" /> property.
/// </summary>
public static readonly DirectProperty<SelectionRectangle, bool> IsSelectingProperty = AvaloniaProperty.RegisterDirect<SelectionRectangle, bool>(nameof(IsSelecting), o => o.IsSelecting);
private Rect? _absoluteRect;
private Point _absoluteStartPosition;
private Rect? _displayRect;
private bool _isSelecting;
private IControl? _oldInputElement;
private Point _startPosition;
private Point _lastPosition;
/// <inheritdoc />
public SelectionRectangle()
{
AffectsRender<TextBlock>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty);
IsHitTestVisible = false;
}
/// <summary>
/// Gets or sets a brush used to paint the control's background.
/// </summary>
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
/// <summary>
/// Gets or sets a brush used to paint the control's border
/// </summary>
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
/// <summary>
/// Gets or sets the width of the control's border
/// </summary>
public double BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
/// <summary>
/// Gets or sets the radius of the control's border
/// </summary>
public double BorderRadius
{
get => GetValue(BorderRadiusProperty);
set => SetValue(BorderRadiusProperty, value);
}
/// <summary>
/// Gets or sets the element that captures input for the selection rectangle.
/// </summary>
public IControl? InputElement
{
get => GetValue(InputElementProperty);
set => SetValue(InputElementProperty, value);
}
/// <summary>
/// Gets or sets the zoom ratio to counteract when drawing
/// </summary>
public double ZoomRatio
{
get => GetValue(ZoomRatioProperty);
set => SetValue(ZoomRatioProperty, value);
}
/// <summary>
/// Gets a boolean indicating whether the selection rectangle is currently performing a selection.
/// </summary>
public bool IsSelecting
{
get => _isSelecting;
private set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);
}
/// <summary>
/// Occurs when the selection rect is being updated, indicating the user is dragging.
/// </summary>
public event EventHandler<SelectionRectangleEventArgs>? SelectionUpdated;
/// <summary>
/// Occurs when the selection has finished, indicating the user stopped dragging.
/// </summary>
public event EventHandler<SelectionRectangleEventArgs>? SelectionFinished;
/// <summary>
/// Invokes the <see cref="SelectionUpdated" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnSelectionUpdated(SelectionRectangleEventArgs e)
{
SelectionUpdated?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="SelectionFinished" /> event
/// </summary>
/// <param name="e"></param>
protected virtual void OnSelectionFinished(SelectionRectangleEventArgs e)
{
SelectionFinished?.Invoke(this, e);
}
private static void OnInputElementChanged(IAvaloniaObject sender, bool before)
{
((SelectionRectangle) sender).SubscribeToInputElement();
}
private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
{
// Point moved seems to trigger when the element under the mouse changes?
// I'm not sure why this is needed but this check makes sure the position really hasn't changed.
Point position = e.GetCurrentPoint(null).Position;
if (position == _lastPosition)
return;
_lastPosition = position;
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
// Capture the pointer and initialize dragging the first time it moves
if (!ReferenceEquals(e.Pointer.Captured, this))
{
e.Pointer.Capture(this);
_startPosition = e.GetPosition(Parent);
_absoluteStartPosition = e.GetPosition(VisualRoot);
_displayRect = null;
}
Point currentPosition = e.GetPosition(Parent);
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
_displayRect = new Rect(
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),
new Point(Math.Max(_startPosition.X, currentPosition.X), Math.Max(_startPosition.Y, currentPosition.Y))
);
_absoluteRect = new Rect(
new Point(Math.Min(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Min(_absoluteStartPosition.Y, absoluteCurrentPosition.Y)),
new Point(Math.Max(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Max(_absoluteStartPosition.Y, absoluteCurrentPosition.Y))
);
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
InvalidateVisual();
IsSelecting = true;
}
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!ReferenceEquals(e.Pointer.Captured, this))
return;
e.Pointer.Capture(null);
if (_displayRect != null && _absoluteRect != null)
{
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
e.Handled = true;
}
_displayRect = null;
InvalidateVisual();
IsSelecting = false;
}
private void SubscribeToInputElement()
{
if (_oldInputElement != null)
{
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
}
_oldInputElement = InputElement;
if (InputElement != null)
{
InputElement.PointerMoved += ParentOnPointerMoved;
InputElement.PointerReleased += ParentOnPointerReleased;
}
}
#region Overrides of Visual
/// <inheritdoc />
public override void Render(DrawingContext drawingContext)
{
if (_displayRect != null)
drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderThickness / ZoomRatio), _displayRect.Value, BorderRadius / ZoomRatio, BorderRadius / ZoomRatio);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
SubscribeToInputElement();
base.OnAttachedToVisualTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (_oldInputElement != null)
{
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
_oldInputElement = null;
}
base.OnDetachedFromVisualTree(e);
}
#endregion
}
@@ -1,41 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using Artemis.Core;
using Avalonia.Data.Converters;
using Avalonia.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters;
/// <summary>
/// Converts <see cref="T:Artemis.Core.ColorGradient" /> into a <see cref="T:Avalonia.Media.GradientStops" />.
/// </summary>
public class ColorGradientToGradientStopsConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
ColorGradient? colorGradient = value as ColorGradient;
GradientStops collection = new();
if (colorGradient == null)
return collection;
foreach (ColorGradientStop c in colorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
return collection;
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
GradientStops? collection = value as GradientStops;
ColorGradient colorGradients = new();
if (collection == null)
return colorGradients;
foreach (GradientStop c in collection.OrderBy(s => s.Offset))
colorGradients.Add(new ColorGradientStop(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset));
return colorGradients;
}
}
@@ -1,38 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FluentAvalonia.UI.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters
{
/// <summary>
/// Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
/// </summary>
public class ColorToSKColorConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Color avaloniaColor)
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
if (value is Color2 fluentAvaloniaColor)
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
return SKColor.Empty;
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
Color result = new(0, 0, 0, 0);
if (value is SKColor skColor)
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
if (targetType == typeof(Color2))
return (Color2) result;
return result;
}
}
}
@@ -1,25 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
namespace Artemis.UI.Shared.Converters
{
/// <summary>
/// Converts an enum into a boolean.
/// </summary>
public class EnumToBooleanConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return Equals(value, parameter);
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;
}
}
}
@@ -1,34 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Data.Converters;
namespace Artemis.UI.Shared.Converters;
/// <summary>
/// Converts the width in percentage to a real number based on the width of the given parent
/// </summary>
public class ParentWidthPercentageConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (parameter is not IControl parent || value is not double percentage)
return value;
return parent.Width / 100.0 * percentage;
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (parameter is not IControl parent || value is not double real)
return value;
return real / parent.Width * 100.0;
}
#endregion
}
@@ -1,29 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters;
/// <summary>
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.SolidColorBrush" />.
/// </summary>
public class SKColorToBrushConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SKColor skColor)
return new SolidColorBrush(new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue));
return new SolidColorBrush(Colors.Transparent);
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SolidColorBrush brush)
return new SKColor(brush.Color.R, brush.Color.G, brush.Color.B, brush.Color.A);
return SKColor.Empty;
}
}
@@ -1,38 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FluentAvalonia.UI.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters
{
/// <summary>
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.Color" />.
/// </summary>
public class SKColorToColorConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
Color result = new(0, 0, 0, 0);
if (value is SKColor skColor)
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
if (targetType == typeof(Color2))
return (Color2) result;
return result;
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Color avaloniaColor)
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
if (value is Color2 fluentAvaloniaColor)
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
return SKColor.Empty;
}
}
}
@@ -1,28 +0,0 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using SkiaSharp;
namespace Artemis.UI.Shared.Converters;
/// <inheritdoc />
/// <summary>
/// Converts <see cref="SKColor" />into <see cref="T:System.String" />.
/// </summary>
public class SKColorToStringConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value?.ToString();
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (string.IsNullOrWhiteSpace(value as string))
return SKColor.Empty;
return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
}
}
@@ -1,29 +0,0 @@
using System;
using System.Globalization;
using Artemis.Core;
using Avalonia.Data.Converters;
namespace Artemis.UI.Shared.Converters
{
/// <summary>
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
/// </summary>
public class TypeToStringConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
if (value is Type type)
return type.GetDisplayName(humanizeProvided && humanize);
return value?.ToString();
}
/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Data.Converters;
namespace Artemis.UI.Shared.Converters;
/// <summary>
/// Converts the width in percentage to a real number based on the width of the given parent
/// </summary>
public class WidthNormalizedConverter : IMultiValueConverter
{
#region Implementation of IMultiValueConverter
/// <inheritdoc />
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
object? first = values.FirstOrDefault();
object? second = values.Skip(1).FirstOrDefault();
if (first is float value && second is double totalWidth)
return (totalWidth / 1.0) * value;
return 0.0;
}
#endregion
}
@@ -1,73 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Artemis.Core.Modules;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a <see cref="DataModel" /> display view model
/// </summary>
/// <typeparam name="T">The type of the data model</typeparam>
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
{
[AllowNull] private T _displayValue = default!;
/// <summary>
/// Gets or sets value that the view model must display
/// </summary>
[AllowNull]
public T DisplayValue
{
get => _displayValue;
set
{
if (Equals(value, _displayValue)) return;
RaiseAndSetIfChanged(ref _displayValue, value);
OnDisplayValueUpdated();
}
}
internal override object InternalGuard => new();
/// <inheritdoc />
public override void UpdateValue(object? model)
{
DisplayValue = model is T value ? value : default;
}
/// <summary>
/// Occurs when the display value is updated
/// </summary>
protected virtual void OnDisplayValueUpdated()
{
}
}
/// <summary>
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
/// </summary>
public abstract class DataModelDisplayViewModel : ViewModelBase
{
private DataModelPropertyAttribute? _propertyDescription;
/// <summary>
/// Gets the property description of this value
/// </summary>
public DataModelPropertyAttribute? PropertyDescription
{
get => _propertyDescription;
internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
}
/// <summary>
/// Prevents this type being implemented directly, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
/// </summary>
internal abstract object InternalGuard { get; }
/// <summary>
/// Updates the display value
/// </summary>
/// <param name="model">The value to set</param>
public abstract void UpdateValue(object? model);
}
}
@@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Artemis.Core.Modules;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a <see cref="DataModel" /> input view model
/// </summary>
/// <typeparam name="T">The type of the data model</typeparam>
public abstract class DataModelInputViewModel<T> : DataModelInputViewModel
{
private bool _closed;
[AllowNull] private T _inputValue = default!;
/// <summary>
/// Creates a new instance of the <see cref="DataModelInputViewModel{T}" /> class
/// </summary>
/// <param name="targetDescription">The description of the property this input VM is representing</param>
/// <param name="initialValue">The initial value to set the input value to</param>
protected DataModelInputViewModel(DataModelPropertyAttribute targetDescription, T initialValue)
{
TargetDescription = targetDescription;
InputValue = initialValue;
}
/// <summary>
/// Gets or sets the value shown in the input
/// </summary>
[AllowNull]
public T InputValue
{
get => _inputValue;
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
}
/// <summary>
/// Gets the description of the property this input VM is representing
/// </summary>
public DataModelPropertyAttribute TargetDescription { get; }
internal override object InternalGuard { get; } = new();
/// <inheritdoc />
public sealed override void Submit()
{
if (_closed)
return;
_closed = true;
OnSubmit();
UpdateCallback(InputValue, true);
}
/// <inheritdoc />
public sealed override void Cancel()
{
if (_closed)
return;
_closed = true;
OnCancel();
UpdateCallback(InputValue, false);
}
}
/// <summary>
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
public abstract class DataModelInputViewModel : ReactiveObject
{
/// <summary>
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
// ReSharper disable once UnusedMember.Global
internal abstract object InternalGuard { get; }
internal Action<object?, bool> UpdateCallback { get; set; } = null!; // Set right after construction
/// <summary>
/// Gets the types this input view model can support through type conversion. This list is defined when registering the
/// view model.
/// </summary>
internal IReadOnlyCollection<Type>? CompatibleConversionTypes { get; set; }
/// <summary>
/// Submits the input value and removes this view model.
/// <para>This is called automatically when the user presses enter or clicks outside the view</para>
/// </summary>
public abstract void Submit();
/// <summary>
/// Discards changes to the input value and removes this view model.
/// <para>This is called automatically when the user presses escape</para>
/// </summary>
public abstract void Cancel();
/// <summary>
/// Called before the current value is submitted
/// </summary>
protected virtual void OnSubmit()
{
}
/// <summary>
/// Called before the current value is discarded
/// </summary>
protected virtual void OnCancel()
{
}
}
}
@@ -1,89 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a layer brush registered through
/// <see cref="IDataModelUIService.RegisterDataModelInput{T}" /> or
/// <see cref="IDataModelUIService.RegisterDataModelDisplay{T}" />
/// </summary>
public class DataModelVisualizationRegistration
{
private readonly IDataModelUIService _dataModelUIService;
internal DataModelVisualizationRegistration(IDataModelUIService dataModelUIService,
RegistrationType registrationType,
Plugin plugin,
Type supportedType,
Type viewModelType)
{
_dataModelUIService = dataModelUIService;
RegistrationType = registrationType;
Plugin = plugin;
SupportedType = supportedType;
ViewModelType = viewModelType;
if (Plugin != Constants.CorePlugin)
Plugin.Disabled += InstanceOnDisabled;
}
/// <summary>
/// Gets the type of registration, either a display or an input
/// </summary>
public RegistrationType RegistrationType { get; }
/// <summary>
/// Gets the plugin that registered the visualization
/// </summary>
public Plugin Plugin { get; }
/// <summary>
/// Gets the type supported by the visualization
/// </summary>
public Type SupportedType { get; }
/// <summary>
/// Gets the view model type of the visualization
/// </summary>
public Type ViewModelType { get; }
/// <summary>
/// Gets a read only collection of types this visualization can convert to and from
/// </summary>
public IReadOnlyCollection<Type>? CompatibleConversionTypes { get; internal set; }
internal void Unsubscribe()
{
if (Plugin != Constants.CorePlugin)
Plugin.Disabled -= InstanceOnDisabled;
}
private void InstanceOnDisabled(object? sender, EventArgs e)
{
if (RegistrationType == RegistrationType.Input)
_dataModelUIService.RemoveDataModelInput(this);
else if (RegistrationType == RegistrationType.Display)
_dataModelUIService.RemoveDataModelDisplay(this);
}
}
/// <summary>
/// Represents a type of data model visualization registration
/// </summary>
public enum RegistrationType
{
/// <summary>
/// A visualization used for displaying values
/// </summary>
Display,
/// <summary>
/// A visualization used for inputting values
/// </summary>
Input
}
}
@@ -1,71 +0,0 @@
using System;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes an event data model property
/// </summary>
public class DataModelEventViewModel : DataModelVisualizationViewModel
{
private Type? _displayValueType;
internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the type of event arguments this event triggers and that must be displayed as children
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
DisplayValueType = DataModelPath?.GetPropertyType();
if (configuration != null)
{
if (configuration.CreateEventChildren)
PopulateProperties(dataModelUIService, configuration);
else if (Children.Any())
Children.Clear();
}
// Only update children if the parent is expanded
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
/// <summary>
/// Always returns <see langword="null"/> for data model events
/// </summary>
public override object? GetCurrentValue()
{
return null;
}
/// <inheritdoc />
public override string? ToString()
{
return DisplayPath ?? Path;
}
internal override int GetChildDepth()
{
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
}
}
}
@@ -1,92 +0,0 @@
using System;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a single data model property contained in a
/// <see cref="DataModelListViewModel" />
/// </summary>
public class DataModelListItemViewModel : DataModelVisualizationViewModel
{
private int _index;
private Type? _listType;
private object? _displayValue;
private DataModelDisplayViewModel? _displayViewModel;
internal DataModelListItemViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
{
ListType = listType;
DisplayViewModel = displayViewModel;
}
internal DataModelListItemViewModel(Type listType, string? name) : base(null, null, null)
{
ListType = listType;
}
/// <summary>
/// Gets the view model used to display the display value
/// </summary>
public DataModelDisplayViewModel? DisplayViewModel
{
get => _displayViewModel;
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
}
/// <summary>
/// Gets the index of the element within the list
/// </summary>
public int Index
{
get => _index;
internal set => this.RaiseAndSetIfChanged(ref _index, value);
}
/// <summary>
/// Gets the type of elements contained in the list
/// </summary>
public Type? ListType
{
get => _listType;
private set => this.RaiseAndSetIfChanged(ref _listType, value);
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object? DisplayValue
{
get => _displayValue;
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <inheritdoc />
public override object? GetCurrentValue()
{
return DisplayValue;
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
// Display value gets updated by parent, don't do anything if it is null
if (DisplayValue == null)
return;
if (DisplayViewModel == null)
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
ListType = DisplayValue.GetType();
DisplayViewModel?.UpdateValue(DisplayValue);
}
/// <inheritdoc />
public override string ToString()
{
return $"[List item {Index}]";
}
}
}
@@ -1,137 +0,0 @@
using System;
using System.Collections;
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a list data model property
/// </summary>
public class DataModelListViewModel : DataModelVisualizationViewModel
{
private string _countDisplay;
private Type? _displayValueType;
private IEnumerable? _list;
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
private int _listCount;
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
: base(dataModel, parent, dataModelPath)
{
_countDisplay = "0 items";
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
}
/// <summary>
/// Gets the instance of the list that is being visualized
/// </summary>
public IEnumerable? List
{
get => _list;
private set => this.RaiseAndSetIfChanged(ref _list, value);
}
/// <summary>
/// Gets amount of elements in the list that is being visualized
/// </summary>
public int ListCount
{
get => _listCount;
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
}
/// <summary>
/// Gets the type of elements this list contains and that must be displayed as children
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets a human readable display count
/// </summary>
public string CountDisplay
{
get => _countDisplay;
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
}
/// <summary>
/// Gets a list of child view models that visualize the elements in the list
/// </summary>
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
{
get => _listChildren;
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
if (Parent != null && !Parent.IsVisualizationExpanded)
return;
List = GetCurrentValue() as IEnumerable;
DisplayValueType = List?.GetType();
if (List == null)
return;
int index = 0;
foreach (object item in List)
{
if (item == null)
continue;
DataModelVisualizationViewModel? child;
if (ListChildren.Count <= index)
{
child = CreateListChild(dataModelUIService, item.GetType(), DataModelPath?.GetPropertyDescription()?.ListItemName);
if (child == null)
continue;
ListChildren.Add(child);
}
else
child = ListChildren[index];
if (child is DataModelListItemViewModel dataModelListPropertyViewModel)
{
dataModelListPropertyViewModel.DisplayValue = item;
dataModelListPropertyViewModel.Index = index;
dataModelListPropertyViewModel.Update(dataModelUIService, configuration);
}
index++;
}
ListCount = index;
while (ListChildren.Count > ListCount)
ListChildren.RemoveAt(ListChildren.Count - 1);
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
}
/// <inheritdoc />
public override string ToString()
{
return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)";
}
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType, string? name)
{
// If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
return typeViewModel != null
? new DataModelListItemViewModel(listType, typeViewModel, name)
: new DataModelListItemViewModel(listType, name);
}
}
}
@@ -1,83 +0,0 @@
using System;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
/// </summary>
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
{
private object? _displayValue;
private Type? _displayValueType;
internal DataModelPropertiesViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
: base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object? DisplayValue
{
get => _displayValue;
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
DisplayValueType = DataModelPath?.GetPropertyType();
// Only set a display value if ToString returns useful information and not just the type name
object? currentValue = GetCurrentValue();
if (currentValue != null && currentValue.ToString() != currentValue.GetType().ToString())
DisplayValue = currentValue.ToString();
else
DisplayValue = null;
// Always populate properties
PopulateProperties(dataModelUIService, configuration);
// Only update children if the parent is expanded
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
/// <inheritdoc />
public override object? GetCurrentValue()
{
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
return DataModel;
return base.GetCurrentValue();
}
/// <inheritdoc />
public override string? ToString()
{
return DisplayPath ?? Path;
}
internal override int GetChildDepth()
{
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
}
}
}
@@ -1,83 +0,0 @@
using System;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a single data model property contained in a
/// <see cref="DataModelPropertiesViewModel" />
/// </summary>
public class DataModelPropertyViewModel : DataModelVisualizationViewModel
{
private object? _displayValue;
private Type? _displayValueType;
private DataModelDisplayViewModel? _displayViewModel;
internal DataModelPropertyViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
: base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object? DisplayValue
{
get => _displayValue;
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets the view model used to display the display value
/// </summary>
public DataModelDisplayViewModel? DisplayViewModel
{
get => _displayViewModel;
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
return;
if (DisplayViewModel == null)
{
Type? propertyType = DataModelPath?.GetPropertyType();
if (propertyType != null)
{
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription, true);
if (DisplayViewModel != null)
DisplayViewModel.PropertyDescription = DataModelPath?.GetPropertyDescription();
}
}
DisplayValue = GetCurrentValue();
DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath?.GetPropertyType();
DisplayViewModel?.UpdateValue(DisplayValue);
}
/// <inheritdoc />
public override string ToString()
{
if (DisplayValueType != null)
return $"[{DisplayValueType.Name}] {DisplayPath ?? Path} - {DisplayValue}";
return $"{DisplayPath ?? Path} - {DisplayValue}";
}
}
}
@@ -1,22 +0,0 @@
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
/// </summary>
public class DataModelUpdateConfiguration
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class
/// </summary>
/// <param name="createEventChildren">A boolean indicating whether or not event children should be created</param>
public DataModelUpdateConfiguration(bool createEventChildren)
{
CreateEventChildren = createEventChildren;
}
/// <summary>
/// Gets a boolean indicating whether or not event children should be created
/// </summary>
public bool CreateEventChildren { get; }
}
}
@@ -1,401 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
/// <summary>
/// Represents a base class for a view model that visualizes a part of the data model
/// </summary>
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
{
private const int MaxDepth = 4;
private ObservableCollection<DataModelVisualizationViewModel> _children;
private DataModel? _dataModel;
private bool _isMatchingFilteredTypes;
private bool _isVisualizationExpanded;
private DataModelVisualizationViewModel? _parent;
private bool _populatedStaticChildren;
private DataModelPropertyAttribute? _propertyDescription;
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
{
_dataModel = dataModel;
_children = new ObservableCollection<DataModelVisualizationViewModel>();
_parent = parent;
DataModelPath = dataModelPath;
IsMatchingFilteredTypes = true;
if (parent == null)
IsRootViewModel = true;
else
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
}
/// <summary>
/// Gets a boolean indicating whether this view model is at the root of the data model
/// </summary>
public bool IsRootViewModel { get; protected set; }
/// <summary>
/// Gets the data model path to the property this view model is visualizing
/// </summary>
public DataModelPath? DataModelPath { get; }
/// <summary>
/// Gets a string representation of the path backing this model
/// </summary>
public string? Path => DataModelPath?.Path;
/// <summary>
/// Gets the property depth of the view model
/// </summary>
public int Depth { get; private set; }
/// <summary>
/// Gets the data model backing this view model
/// </summary>
public DataModel? DataModel
{
get => _dataModel;
protected set => this.RaiseAndSetIfChanged(ref _dataModel, value);
}
/// <summary>
/// Gets the property description of the property this view model is visualizing
/// </summary>
public DataModelPropertyAttribute? PropertyDescription
{
get => _propertyDescription;
protected set => this.RaiseAndSetIfChanged(ref _propertyDescription, value);
}
/// <summary>
/// Gets the parent of this view model
/// </summary>
public DataModelVisualizationViewModel? Parent
{
get => _parent;
protected set => this.RaiseAndSetIfChanged(ref _parent, value);
}
/// <summary>
/// Gets or sets an observable collection containing the children of this view model
/// </summary>
public ObservableCollection<DataModelVisualizationViewModel> Children
{
get => _children;
set => this.RaiseAndSetIfChanged(ref _children, value);
}
/// <summary>
/// Gets a boolean indicating whether the property being visualized matches the types last provided to
/// <see cref="ApplyTypeFilter" />
/// </summary>
public bool IsMatchingFilteredTypes
{
get => _isMatchingFilteredTypes;
private set => this.RaiseAndSetIfChanged(ref _isMatchingFilteredTypes, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the visualization is expanded, exposing the <see cref="Children" />
/// </summary>
public bool IsVisualizationExpanded
{
get => _isVisualizationExpanded;
set
{
if (!this.RaiseAndSetIfChanged(ref _isVisualizationExpanded, value)) return;
RequestUpdate();
}
}
/// <summary>
/// Gets a user-friendly representation of the <see cref="DataModelPath" />
/// </summary>
public virtual string? DisplayPath => DataModelPath != null
? string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier))
: null;
/// <summary>
/// Updates the datamodel and if in an parent, any children
/// </summary>
/// <param name="dataModelUIService">The data model UI service used during update</param>
/// <param name="configuration">The configuration to apply while updating</param>
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
/// <summary>
/// Gets the current value of the property being visualized
/// </summary>
/// <returns>The current value of the property being visualized</returns>
public virtual object? GetCurrentValue()
{
if (IsRootViewModel)
return null;
return DataModelPath?.GetValue();
}
/// <summary>
/// Determines whether the provided types match the type of the property being visualized and sets the result in
/// <see cref="IsMatchingFilteredTypes" />
/// </summary>
/// <param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
/// <param name="filteredTypes">The types to filter</param>
public void ApplyTypeFilter(bool looseMatch, params Type[]? filteredTypes)
{
if (filteredTypes != null)
{
if (filteredTypes.All(t => t == null))
filteredTypes = null;
else
filteredTypes = filteredTypes.Where(t => t != null).ToArray();
}
// If the VM has children, its own type is not relevant
if (Children.Any())
{
foreach (DataModelVisualizationViewModel child in Children)
child?.ApplyTypeFilter(looseMatch, filteredTypes);
IsMatchingFilteredTypes = true;
return;
}
// If null is passed, clear the type filter
if (filteredTypes == null || filteredTypes.Length == 0)
{
IsMatchingFilteredTypes = true;
return;
}
// If the type couldn't be retrieved either way, assume false
Type? type = DataModelPath?.GetPropertyType();
if (type == null)
{
IsMatchingFilteredTypes = false;
return;
}
if (looseMatch)
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
t == typeof(Enum) && type.IsEnum ||
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
type.IsGenericType && t == type.GetGenericTypeDefinition());
else
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
}
/// <summary>
/// Occurs when an update to the property this view model visualizes is requested
/// </summary>
public event EventHandler? UpdateRequested;
/// <summary>
/// Expands this view model and any children to expose the provided <paramref name="dataModelPath" />.
/// </summary>
/// <param name="dataModelPath">The data model path to expose.</param>
public void ExpandToPath(DataModelPath dataModelPath)
{
if (dataModelPath.Target != DataModel)
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
IsVisualizationExpanded = true;
DataModelPathSegment current = dataModelPath.Segments.Skip(1).First();
Children.FirstOrDefault(c => c.Path == current.Path)?.ExpandToPath(current.Next);
}
/// <summary>
/// Finds the view model that hosts the given path.
/// </summary>
/// <param name="dataModelPath">The path to find</param>
/// <returns>The matching view model, may be null if the path doesn't exist or isn't expanded</returns>
public DataModelVisualizationViewModel? GetViewModelForPath(DataModelPath dataModelPath)
{
if (dataModelPath.Target != DataModel)
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
if (DataModelPath?.Path == dataModelPath.Path)
return this;
return Children.Select(c => c.GetViewModelForPath(dataModelPath)).FirstOrDefault(match => match != null);
}
/// <summary>
/// Invokes the <see cref="UpdateRequested" /> event
/// </summary>
protected virtual void OnUpdateRequested()
{
UpdateRequested?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
DataModelPath?.Dispose();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Dispose(true);
}
}
internal virtual int GetChildDepth()
{
return 0;
}
internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? dataModelUpdateConfiguration)
{
if (IsRootViewModel && DataModel == null)
return;
Type? modelType = IsRootViewModel ? DataModel?.GetType() : DataModelPath?.GetPropertyType();
if (modelType == null)
throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type");
// Add missing static children only once, they're static after all
if (!_populatedStaticChildren)
{
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
{
string childPath = AppendToPath(propertyInfo.Name);
if (Children.Any(c => c?.Path != null && c.Path.Equals(childPath)))
continue;
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
continue;
MethodInfo? getMethod = propertyInfo.GetGetMethod();
if (getMethod == null || getMethod.GetParameters().Any())
continue;
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null)
Children.Add(child);
}
_populatedStaticChildren = true;
}
// Remove static children that should be hidden
if (DataModel != null)
{
ReadOnlyCollection<PropertyInfo> hiddenProperties = DataModel.GetHiddenProperties();
foreach (PropertyInfo hiddenProperty in hiddenProperties)
{
string childPath = AppendToPath(hiddenProperty.Name);
DataModelVisualizationViewModel? toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath);
if (toRemove != null)
Children.Remove(toRemove);
}
}
// Add missing dynamic children
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
if (value is DataModel dataModel)
foreach (string key in dataModel.DynamicChildren.Keys.ToList())
{
string childPath = AppendToPath(key);
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue;
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null)
Children.Add(child);
}
// Remove dynamic children that have been removed from the data model
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in toRemoveDynamic)
Children.Remove(dataModelVisualizationViewModel);
}
private DataModelVisualizationViewModel? CreateChild(IDataModelUIService dataModelUIService, string path, int depth)
{
if (DataModel == null)
throw new ArtemisSharedUIException("Cannot create a data model visualization child VM for a parent without a data model");
if (depth > MaxDepth)
return null;
DataModelPath dataModelPath = new(DataModel, path);
if (!dataModelPath.IsValid)
return null;
PropertyInfo? propertyInfo = dataModelPath.GetPropertyInfo();
Type? propertyType = dataModelPath.GetPropertyType();
// Skip properties decorated with DataModelIgnore
if (propertyInfo != null && Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute)))
return null;
// Skip properties that are in the ignored properties list of the respective profile module/data model expansion
if (DataModel.GetHiddenProperties().Any(p => p.Equals(propertyInfo)))
return null;
if (propertyType == null)
return null;
// If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription);
if (typeViewModel != null)
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
// For primitives, create a property view model, it may be null that is fine
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
if (propertyType.IsGenericEnumerable())
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
// For other value types create a child view model
if (propertyType.IsClass || propertyType.IsStruct())
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
return null;
}
private string AppendToPath(string toAppend)
{
if (string.IsNullOrEmpty(Path))
return toAppend;
StringBuilder builder = new();
builder.Append(Path);
builder.Append(".");
builder.Append(toAppend);
return builder.ToString();
}
private void RequestUpdate()
{
Parent?.RequestUpdate();
OnUpdateRequested();
}
private void ExpandToPath(DataModelPathSegment? segment)
{
if (segment == null)
return;
IsVisualizationExpanded = true;
Children.FirstOrDefault(c => c.Path == segment.Path)?.ExpandToPath(segment.Next);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
@@ -1,24 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayView">
<Grid ColumnDefinitions="*,Auto,Auto">
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
IsVisible="{Binding PropertyDescription.Prefix, Converter={x:Static ObjectConverters.IsNotNull}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<TextBlock Grid.Column="1" Text="{Binding Display, Mode=OneWay}" HorizontalAlignment="Right"/>
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
IsVisible="{Binding PropertyDescription.Affix, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>
@@ -1,24 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
{
/// <summary>
/// Represents a default data model display view.
/// </summary>
public class DefaultDataModelDisplayView : UserControl
{
/// <summary>
/// Creates a new instance of the <see cref="DefaultDataModelDisplayView" /> class.
/// </summary>
public DefaultDataModelDisplayView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -1,47 +0,0 @@
<UserControl x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Shared.DefaultTypes.DataModel.Display"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DefaultDataModelDisplayViewModel}">
<UserControl.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<TextBlock Grid.Column="1"
Text="{Binding DisplayValue, Mode=OneWay}"
HorizontalAlignment="Right"
Visibility="{Binding ShowToString, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Text="null"
FontFamily="Consolas"
HorizontalAlignment="Right"
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>
@@ -1,44 +0,0 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared.DataModelVisualization;
using Newtonsoft.Json;
using ReactiveUI;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
{
/// <summary>
/// Represents the default data model display view model that is used when no display viewmodel specific for the type
/// is registered
/// </summary>
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
{
private readonly JsonSerializerSettings _serializerSettings;
private string _display;
public DefaultDataModelDisplayViewModel()
{
_serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
};
_display = "null";
}
public string Display
{
get => _display;
set => RaiseAndSetIfChanged(ref _display, value);
}
protected override void OnDisplayValueUpdated()
{
if (DisplayValue is Enum enumDisplayValue)
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
else if (DisplayValue is not string)
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
else
Display = DisplayValue?.ToString() ?? "null";
}
}
}
@@ -1,22 +0,0 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared.Controls;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data about selection events raised by <see cref="DataModelPicker" />
/// </summary>
public class DataModelSelectedEventArgs : EventArgs
{
/// <summary>
/// Gets the data model path that was selected
/// </summary>
public DataModelPath? Path { get; }
internal DataModelSelectedEventArgs(DataModelPath? path)
{
Path = path;
}
}
}
@@ -1,14 +0,0 @@
using System;
namespace Artemis.UI.Shared.Events
{
internal class DialogClosedEventArgs<TResult> : EventArgs
{
public TResult Result { get; }
public DialogClosedEventArgs(TResult result)
{
Result = result;
}
}
}
@@ -1,27 +0,0 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data on LED click events raised by the device visualizer
/// </summary>
public class LedClickedEventArgs : EventArgs
{
internal LedClickedEventArgs(ArtemisDevice device, ArtemisLed led)
{
Device = device;
Led = led;
}
/// <summary>
/// The device that was clicked
/// </summary>
public ArtemisDevice Device { get; set; }
/// <summary>
/// The LED that was clicked
/// </summary>
public ArtemisLed Led { get; set; }
}
}
@@ -1,32 +0,0 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data on profile related events raised by the profile editor
/// </summary>
public class ProfileConfigurationEventArgs : EventArgs
{
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
{
ProfileConfiguration = profileConfiguration;
}
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
{
ProfileConfiguration = profileConfiguration;
PreviousProfileConfiguration = previousProfileConfiguration;
}
/// <summary>
/// Gets the profile the event was raised for
/// </summary>
public ProfileConfiguration? ProfileConfiguration { get; }
/// <summary>
/// If applicable, the previous active profile before the event was raised
/// </summary>
public ProfileConfiguration? PreviousProfileConfiguration { get; }
}
}
@@ -1,32 +0,0 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data on profile element related events raised by the profile editor
/// </summary>
public class RenderProfileElementEventArgs : EventArgs
{
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement)
{
RenderProfileElement = renderProfileElement;
}
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement)
{
RenderProfileElement = renderProfileElement;
PreviousRenderProfileElement = previousRenderProfileElement;
}
/// <summary>
/// Gets the profile element the event was raised for
/// </summary>
public RenderProfileElement? RenderProfileElement { get; }
/// <summary>
/// If applicable, the previous active profile element before the event was raised
/// </summary>
public RenderProfileElement? PreviousRenderProfileElement { get; }
}
}
@@ -1,38 +0,0 @@
using System;
using Artemis.UI.Shared.Controls;
using Avalonia;
using Avalonia.Input;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data on selection events raised by the <see cref="SelectionRectangle" />.
/// </summary>
public class SelectionRectangleEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
/// </summary>
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
{
KeyModifiers = keyModifiers;
Rectangle = rectangle;
AbsoluteRectangle = absoluteRectangle;
}
/// <summary>
/// Gets the rectangle relative to the parent that was selected when the event occurred.
/// </summary>
public Rect Rectangle { get; }
/// <summary>
/// Gets the rectangle relative to the window that was selected when the event occurred.
/// </summary>
public Rect AbsoluteRectangle { get; }
/// <summary>
/// Gets the key modifiers that where pressed when the event occurred.
/// </summary>
public KeyModifiers KeyModifiers { get; }
}
}
@@ -1,22 +0,0 @@
using System;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents errors that occur within the Artemis Shared UI library
/// </summary>
public class ArtemisSharedUIException : Exception
{
internal ArtemisSharedUIException()
{
}
internal ArtemisSharedUIException(string message) : base(message)
{
}
internal ArtemisSharedUIException(string message, Exception inner) : base(message, inner)
{
}
}
}
@@ -1,25 +0,0 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
namespace Artemis.UI.Shared.Extensions
{
/// <summary>
/// Provides extension methods for Avalonia's <see cref="Control" /> type
/// </summary>
public static class ControlExtensions
{
/// <summary>
/// Clears all data validation errors on the given control and any of it's logical siblings
/// </summary>
/// <param name="target">The target control</param>
public static void ClearAllDataValidationErrors(this Control target)
{
DataValidationErrors.ClearErrors(target);
foreach (ILogical logicalChild in target.GetLogicalChildren())
{
if (logicalChild is Control childControl)
childControl.ClearAllDataValidationErrors();
}
}
}
}
@@ -1,104 +0,0 @@
using System.Linq;
using Artemis.Core;
using SkiaSharp;
namespace Artemis.UI.Shared.Extensions;
/// <summary>
/// Provides utilities when working with layers in UI elements.
/// </summary>
public static class LayerExtensions
{
/// <summary>
/// Returns the layer's bounds in real coordinates.
/// </summary>
public static SKRect GetLayerBounds(this Layer layer)
{
if (!layer.Leds.Any())
return SKRect.Empty;
return new SKRect(
layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.X),
layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.Y),
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.X + l.RgbLed.AbsoluteBoundary.Size.Width),
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.Y + l.RgbLed.AbsoluteBoundary.Size.Height)
);
}
/// <summary>
/// Returns the layer's anchor in real coordinates.
/// </summary>
public static SKPoint GetLayerAnchorPosition(this Layer layer, SKPoint? positionOverride = null)
{
SKRect layerBounds = GetLayerBounds(layer);
SKPoint positionProperty = layer.Transform.Position.CurrentValue;
if (positionOverride != null)
positionProperty = positionOverride.Value;
// Start at the top left of the shape
SKPoint position = new(layerBounds.Left, layerBounds.Top);
// Apply translation
position.X += positionProperty.X * layerBounds.Width;
position.Y += positionProperty.Y * layerBounds.Height;
return position;
}
/// <summary>
/// Returns an absolute and scaled rectangular path for the given layer in real coordinates.
/// </summary>
public static SKPath GetLayerPath(this Layer layer, bool includeTranslation, bool includeScale, bool includeRotation)
{
SKRect layerBounds = GetLayerBounds(layer);
SKMatrix transform = layer.GetTransformMatrix(false, includeTranslation, includeScale, includeRotation, layerBounds);
SKPath path = new();
path.AddRect(layerBounds);
path.Transform(transform);
return path;
}
/// <summary>
/// Returns a new point normalized to 0.0-1.0
/// </summary>
public static SKPoint GetNormalizedPoint(this Layer layer, SKPoint point, bool absolute)
{
SKRect bounds = GetLayerBounds(layer);
if (absolute)
return new SKPoint(
100 / bounds.Width * (point.X - bounds.Left) / 100,
100 / bounds.Height * (point.Y - bounds.Top) / 100
);
return new SKPoint(
100 / bounds.Width * point.X / 100,
100 / bounds.Height * point.Y / 100
);
}
/// <summary>
/// Returns the offset from the given point to the closest sides of the layer's shape bounds
/// </summary>
public static SKPoint GetDragOffset(this Layer layer, SKPoint dragStart)
{
SKRect bounds = layer.GetLayerPath(true, true, false).Bounds;
SKPoint anchor = layer.GetLayerAnchorPosition();
float xOffset = 0f, yOffset = 0f;
// X offset
if (dragStart.X < anchor.X)
xOffset = bounds.Left - dragStart.X;
else if (dragStart.X > anchor.X)
xOffset = bounds.Right - dragStart.X;
// Y offset
if (dragStart.Y < anchor.Y)
yOffset = bounds.Top - dragStart.Y;
else if (dragStart.Y > anchor.Y)
yOffset = bounds.Bottom - dragStart.Y;
return new SKPoint(xOffset, yOffset);
}
}
@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
namespace Artemis.UI.Shared.Extensions
{
public static class PointExtensions
{
public static Point Empty = new(0, 0);
}
}
@@ -1,20 +0,0 @@
using Avalonia.Media;
using SkiaSharp;
namespace Artemis.UI.Shared.Extensions;
/// <summary>
/// Provides extension methods for the <see cref="SKColor"/> type.
/// </summary>
public static class SKColorExtensions
{
/// <summary>
/// Converts a SkiaSharp <see cref="SKColor"/> to an Avalonia <see cref="Color"/>.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>The resulting color.</returns>
public static Color ToColor(this SKColor color)
{
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
}
}
@@ -1,19 +0,0 @@
using Avalonia;
using SkiaSharp;
namespace Artemis.UI.Shared.Extensions;
public static class SKMatrixExtensions
{
public static Matrix ToMatrix(this SKMatrix matrix)
{
return new Matrix(
matrix.ScaleX,
matrix.SkewY,
matrix.SkewX,
matrix.ScaleY,
matrix.TransX,
matrix.TransY
);
}
}
@@ -1,17 +0,0 @@
using Avalonia;
using SkiaSharp;
namespace Artemis.UI.Shared.Extensions;
public static class SKRectExtensions
{
public static Rect ToRect(this SKRect rect)
{
return new Rect(rect.Left, rect.Top, rect.Width, rect.Height);
}
public static Rect ToRect(this SKRectI rect)
{
return new Rect(rect.Left, rect.Top, rect.Width, rect.Height);
}
}
@@ -1,37 +0,0 @@
using Artemis.Core;
using Material.Icons;
using SkiaSharp;
using System;
namespace Artemis.UI.Shared.Extensions
{
/// <summary>
/// Provides utilities when working with types in UI elements.
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// Finds an appropriate Material Design icon for the given <paramref name="type"/>.
/// </summary>
/// <param name="type">The type to retrieve an icon for.</param>
/// <returns>An appropriate Material Design icon for the given <paramref name="type"/>.</returns>
public static MaterialIconKind GetTypeIcon(this Type? type)
{
if (type == null)
return MaterialIconKind.QuestionMarkCircle;
if (type.TypeIsNumber())
return MaterialIconKind.CalculatorVariantOutline;
if (type.IsEnum)
return MaterialIconKind.FormatListBulletedSquare;
if (type == typeof(bool))
return MaterialIconKind.CircleHalfFull;
if (type == typeof(string))
return MaterialIconKind.Text;
if (type == typeof(SKColor))
return MaterialIconKind.Palette;
if (type.IsAssignableTo(typeof(IDataModelEvent)))
return MaterialIconKind.LightningBolt;
return MaterialIconKind.Matrix;
}
}
}
@@ -1,62 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.VisualTree;
namespace Artemis.UI.Shared.Extensions;
/// <summary>
/// Provides extension methods for Avalonia's <see cref="IVisual" /> type
/// </summary>
public static class VisualExtensions
{
/// <summary>
/// Returns a recursive list of all visual children of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfType<T>(this IVisual root)
{
List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any())
return result;
foreach (IVisual visualChild in visualChildren)
{
if (visualChild is T toFind)
result.Add(toFind);
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
}
return result;
}
/// <summary>
/// Returns a recursive list of all visual children with a data context of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of data context the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children with a data context of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfDataContextType<T>(this IVisual root)
{
List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any())
return result;
foreach (IVisual visualChild in visualChildren)
{
if (visualChild is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is T toFind)
result.Add(toFind);
result.AddRange(GetVisualChildrenOfType<T>(visualChild));
}
return result;
}
}
@@ -1,31 +0,0 @@
using System;
using Artemis.UI.Shared.Services.Interfaces;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
namespace Artemis.UI.Shared.Ninject
{
/// <summary>
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
/// </summary>
public class SharedUIModule : NinjectModule
{
/// <inheritdoc />
public override void Load()
{
if (Kernel == null)
throw new ArgumentNullException("Kernel shouldn't be null here.");
// Bind all shared UI services as singletons
Kernel.Bind(x =>
{
x.FromAssemblyContaining<IArtemisSharedUIService>()
.IncludingNonPublicTypes()
.SelectAllClasses()
.InheritedFrom<IArtemisSharedUIService>()
.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
}
}
}
@@ -1,57 +0,0 @@
using System;
using System.Threading.Tasks;
using Artemis.Core.LayerBrushes;
namespace Artemis.UI.Shared.LayerBrushes
{
/// <summary>
/// Represents a view model for a brush configuration window
/// </summary>
public abstract class BrushConfigurationViewModel : ActivatableViewModelBase
{
/// <summary>
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class
/// </summary>
/// <param name="layerBrush"></param>
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush)
{
LayerBrush = layerBrush;
}
/// <summary>
/// Gets the layer brush this view model is associated with
/// </summary>
public BaseLayerBrush LayerBrush { get; }
/// <summary>
/// Closes the dialog
/// </summary>
public void RequestClose()
{
CloseRequested?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
/// </summary>
/// <returns><see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
public virtual bool CanClose()
{
return true;
}
/// <summary>
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
/// </summary>
/// <returns>A task <see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
public virtual Task<bool> CanCloseAsync()
{
return Task.FromResult(true);
}
/// <summary>
/// Occurs when a close was requested
/// </summary>
public event EventHandler? CloseRequested;
}
}
@@ -1,45 +0,0 @@
using System;
using Artemis.Core.LayerBrushes;
namespace Artemis.UI.Shared.LayerBrushes
{
/// <inheritdoc />
public class LayerBrushConfigurationDialog<T> : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel
{
/// <inheritdoc />
public LayerBrushConfigurationDialog()
{
}
/// <inheritdoc />
public LayerBrushConfigurationDialog(int dialogWidth, int dialogHeight)
{
DialogWidth = dialogWidth;
DialogHeight = dialogHeight;
}
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a UI tab for a layer brush
/// </summary>
public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog
{
/// <summary>
/// The default width of the dialog
/// </summary>
public int DialogWidth { get; set; } = 800;
/// <summary>
/// The default height of the dialog
/// </summary>
public int DialogHeight { get; set; } = 800;
/// <summary>
/// The type of view model the dialog contains
/// </summary>
public abstract Type Type { get; }
}
}
@@ -1,57 +0,0 @@
using System;
using System.Threading.Tasks;
using Artemis.Core.LayerEffects;
using Avalonia.Threading;
namespace Artemis.UI.Shared.LayerEffects;
/// <summary>
/// Represents a view model for an effect configuration window
/// </summary>
public abstract class EffectConfigurationViewModel : ActivatableViewModelBase
{
/// <summary>
/// Creates a new instance of the <see cref="EffectConfigurationViewModel" /> class
/// </summary>
/// <param name="layerEffect"></param>
protected EffectConfigurationViewModel(BaseLayerEffect layerEffect)
{
LayerEffect = layerEffect;
}
/// <summary>
/// Gets the layer effect this view model is associated with
/// </summary>
public BaseLayerEffect LayerEffect { get; }
/// <summary>
/// Closes the dialog
/// </summary>
public void RequestClose()
{
CloseRequested?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
/// </summary>
/// <returns><see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
public virtual bool CanClose()
{
return true;
}
/// <summary>
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
/// </summary>
/// <returns>A task <see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
public virtual Task<bool> CanCloseAsync()
{
return Task.FromResult(true);
}
/// <summary>
/// Occurs when a close was requested
/// </summary>
public event EventHandler? CloseRequested;
}
@@ -1,46 +0,0 @@
using System;
using Artemis.Core.LayerEffects;
namespace Artemis.UI.Shared.LayerEffects
{
/// <inheritdoc />
public class LayerEffectConfigurationDialog<T> : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel
{
/// <inheritdoc />
public LayerEffectConfigurationDialog()
{
}
/// <inheritdoc />
public LayerEffectConfigurationDialog(int dialogWidth, int dialogHeight)
{
DialogWidth = dialogWidth;
DialogHeight = dialogHeight;
}
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a UI tab for a specific layer effect
/// </summary>
public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog
{
/// <summary>
/// The default width of the dialog
/// </summary>
public int DialogWidth { get; set; } = 800;
/// <summary>
/// The default height of the dialog
/// </summary>
public int DialogHeight { get; set; } = 800;
/// <summary>
/// The type of view model the dialog contains
/// </summary>
public abstract Type Type { get; }
}
}
@@ -1,21 +0,0 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared
{
/// <inheritdoc />
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
{
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a configuration dialog for a specific plugin
/// </summary>
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
{
/// <inheritdoc />
public abstract Type Type { get; }
}
}
@@ -1,46 +0,0 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a view model for a plugin configuration window
/// </summary>
public abstract class PluginConfigurationViewModel : ViewModelValidationBase, IPluginConfigurationViewModel
{
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
/// </summary>
/// <param name="plugin"></param>
protected PluginConfigurationViewModel(Plugin plugin)
{
Plugin = plugin;
}
/// <summary>
/// Gets the plugin this configuration view model is associated with
/// </summary>
public Plugin Plugin { get; }
/// <summary>
/// Closes the window hosting the view model
/// </summary>
public void Close()
{
CloseRequested?.Invoke(this, EventArgs.Empty);
OnCloseRequested();
}
/// <summary>
/// Called when the the window hosting the view model should close
/// </summary>
public virtual void OnCloseRequested()
{
}
/// <summary>
/// Occurs when the the window hosting the view model should close
/// </summary>
public event EventHandler? CloseRequested;
}
}
@@ -1,56 +0,0 @@
using Artemis.Core.ScriptingProviders;
using ReactiveUI;
namespace Artemis.UI.Shared.ScriptingProviders
{
/// <summary>
/// Represents a Stylet view model containing a script editor
/// </summary>
public class ScriptEditorViewModel : ActivatableViewModelBase, IScriptEditorViewModel
{
private Script? _script;
/// <summary>
/// Creates a new instance of <see cref="ScriptEditorViewModel" />
/// </summary>
/// <param name="scriptType">The script type this view model was created for</param>
public ScriptEditorViewModel(ScriptType scriptType)
{
ScriptType = scriptType;
}
/// <summary>
/// Called just before the script is changed to a different one
/// </summary>
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
protected virtual void OnScriptChanging(Script? script)
{
}
/// <summary>
/// Called after the script was changed to a different one
/// </summary>
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
protected virtual void OnScriptChanged(Script? script)
{
}
/// <inheritdoc />
public ScriptType ScriptType { get; }
/// <inheritdoc />
public Script? Script
{
get => _script;
internal set => RaiseAndSetIfChanged(ref _script, value);
}
/// <inheritdoc />
public void ChangeScript(Script? script)
{
OnScriptChanging(script);
Script = script;
OnScriptChanged(script);
}
}
}
@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Artemis.Core;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for color gradient storage.
/// </summary>
public interface IColorGradientStorageProvider
{
/// <summary>
/// Returns a list containing a copy of all stored color gradients.
/// </summary>
/// <returns>A <see cref="List{T}" /> of <see cref="ColorGradient" /> containing a copy of all stored color gradients.</returns>
public List<ColorGradient> GetColorGradients();
/// <summary>
/// Saves the provided color gradient to storage.
/// </summary>
/// <param name="colorGradient">The color gradient to save.</param>
public void SaveColorGradient(ColorGradient colorGradient);
/// <summary>
/// Deletes the provided color gradient from storage.
/// </summary>
/// <param name="colorGradient">The color gradient to delete.</param>
public void DeleteColorGradient(ColorGradient colorGradient);
}
@@ -1,24 +0,0 @@
using Avalonia.Input;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for custom cursors.
/// </summary>
public interface ICursorProvider
{
/// <summary>
/// A cursor that indicates a rotating.
/// </summary>
public Cursor Rotate { get; }
/// <summary>
/// A cursor that indicates dragging.
/// </summary>
public Cursor Drag { get; }
/// <summary>
/// A cursor that indicates dragging horizontally.
/// </summary>
public Cursor DragHorizontal { get; }
}
@@ -1,258 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
using ReactiveUI;
namespace Artemis.UI.Shared.Services.Builders
{
/// <summary>
/// Represents a builder that can be used to create Fluent UI dialogs.
/// </summary>
public class ContentDialogBuilder
{
private readonly ContentDialog _contentDialog;
private readonly IKernel _kernel;
private readonly Window _parent;
private ContentDialogViewModelBase? _viewModel;
internal ContentDialogBuilder(IKernel kernel, Window parent)
{
_kernel = kernel;
_parent = parent;
_contentDialog = new ContentDialog
{
CloseButtonText = "Close"
};
}
/// <summary>
/// Changes the title of the dialog.
/// </summary>
/// <param name="title">The new title.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithTitle(string? title)
{
_contentDialog.Title = title;
return this;
}
/// <summary>
/// Changes the content of the dialog.
/// </summary>
/// <param name="content">The new content.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithContent(string? content)
{
_contentDialog.Content = content;
return this;
}
/// <summary>
/// Changes the default button of the dialog that is pressed on enter.
/// </summary>
/// <param name="defaultButton">The default button.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton)
{
_contentDialog.DefaultButton = (FluentAvalonia.UI.Controls.ContentDialogButton) defaultButton;
return this;
}
/// <summary>
/// Changes the primary button of the dialog.
/// </summary>
/// <param name="configure">An action to configure the button.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder HavingPrimaryButton(Action<ContentDialogButtonBuilder> configure)
{
ContentDialogButtonBuilder builder = new();
configure(builder);
_contentDialog.IsPrimaryButtonEnabled = true;
_contentDialog.PrimaryButtonText = builder.Text;
_contentDialog.PrimaryButtonCommand = builder.Command;
_contentDialog.PrimaryButtonCommandParameter = builder.CommandParameter;
// I feel like this isn't my responsibility...
if (builder.Command != null)
{
_contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
}
return this;
}
/// <summary>
/// Changes the secondary button of the dialog.
/// </summary>
/// <param name="configure">An action to configure the button.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder HavingSecondaryButton(Action<ContentDialogButtonBuilder> configure)
{
ContentDialogButtonBuilder builder = new();
configure(builder);
_contentDialog.IsSecondaryButtonEnabled = true;
_contentDialog.SecondaryButtonText = builder.Text;
_contentDialog.SecondaryButtonCommand = builder.Command;
_contentDialog.SecondaryButtonCommandParameter = builder.CommandParameter;
// I feel like this isn't my responsibility...
if (builder.Command != null)
{
_contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
}
return this;
}
/// <summary>
/// Changes the text of the close button of the dialog.
/// </summary>
/// <param name="text">The new text.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithCloseButtonText(string? text)
{
_contentDialog.CloseButtonText = text;
return this;
}
/// <summary>
/// Changes the view model of the content dialog, hosting it inside the dialog.
/// </summary>
/// <typeparam name="T">The type of the view model to host.</typeparam>
/// <param name="viewModel">The resulting view model.</param>
/// <param name="parameters">Optional parameters to pass to the constructor of the view model, case and order sensitive.</param>
/// <returns>The builder that can be used to further build the dialog.</returns>
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
viewModel = _kernel.Get<T>(paramsArray);
viewModel.ContentDialog = _contentDialog;
_contentDialog.Content = viewModel;
_viewModel = viewModel;
return this;
}
/// <summary>
/// Asynchronously shows the content dialog.
/// </summary>
/// <returns>A task containing the result of the content dialog.</returns>
/// <exception cref="ArtemisSharedUIException">Thrown when the parent window does not contain a panel at its root.</exception>
public async Task<ContentDialogResult> ShowAsync()
{
if (_parent.Content is not Panel panel)
throw new ArtemisSharedUIException($"The parent window {_parent.GetType().FullName} should contain a panel at its root");
try
{
panel.Children.Add(_contentDialog);
ContentDialogResult result = await _contentDialog.ShowAsync();
// Take the dialog away from the VM in case it's going to try to hide it again or whatever...
if (_viewModel != null)
_viewModel.ContentDialog = null;
return result;
}
finally
{
panel.Children.Remove(_contentDialog);
}
}
}
/// <summary>
/// Represents a content dialog button.
/// </summary>
public enum ContentDialogButton
{
/// <summary>
/// No button.
/// </summary>
None,
/// <summary>
/// The primary button.
/// </summary>
Primary,
/// <summary>
/// The secondary button.
/// </summary>
Secondary,
/// <summary>
/// The close button.
/// </summary>
Close,
}
/// <summary>
/// Represents a builder that can be used to create buttons inside content dialogs.
/// </summary>
public class ContentDialogButtonBuilder
{
internal ContentDialogButtonBuilder()
{
}
internal string? Text { get; set; }
internal ICommand? Command { get; set; }
internal Action? Action { get; set; }
internal object? CommandParameter { get; set; }
/// <summary>
/// Changes text message of the button.
/// </summary>
/// <param name="text">The new text.</param>
/// <returns>The notification builder that can be used to further build the button.</returns>
public ContentDialogButtonBuilder WithText(string? text)
{
Text = text;
return this;
}
/// <summary>
/// Changes action that is called when the button is clicked.
/// </summary>
/// <param name="action">The action to call when the button is clicked.</param>
/// <returns>The builder that can be used to further build the button.</returns>
public ContentDialogButtonBuilder WithAction(Action action)
{
Action = action;
Command = ReactiveCommand.Create(() => Action());
return this;
}
/// <summary>
/// Changes command that is called when the button is clicked.
/// </summary>
/// <param name="command">The command to call when the button is clicked.</param>
/// <returns>The builder that can be used to further build the button.</returns>
public ContentDialogButtonBuilder WithCommand(ICommand? command)
{
Command = command;
return this;
}
/// <summary>
/// Changes parameter of the command that is called when the button is clicked.
/// </summary>
/// <param name="commandParameter">The parameter of the command to call when the button is clicked.</param>
/// <returns>The builder that can be used to further build the button.</returns>
public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter)
{
CommandParameter = commandParameter;
return this;
}
}
}
@@ -1,40 +0,0 @@
using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Builders
{
/// <summary>
/// Represents a builder that can create a <see cref="FileDialogFilter" />.
/// </summary>
public class FileDialogFilterBuilder
{
private readonly FileDialogFilter _filter;
internal FileDialogFilterBuilder()
{
_filter = new FileDialogFilter();
}
/// <summary>
/// Sets the name of the filter
/// </summary>
public FileDialogFilterBuilder WithName(string name)
{
_filter.Name = name;
return this;
}
/// <summary>
/// Adds the provided extension to the filter
/// </summary>
public FileDialogFilterBuilder WithExtension(string extension)
{
_filter.Extensions.Add(extension);
return this;
}
internal FileDialogFilter Build()
{
return _filter;
}
}
}
Loaded 100 of 1110 files, more files were not shown because too many files have changed in this diff. Show more