Rename Avalonia projects

Add Avalonia platform projects
This commit is contained in:
Robert committed 2021-11-18 00:18:13 +01:00
1 parent 1e36297c61
commit f38a9e9e55
205 files changed
+7871 -578

No files matched your search

+454
View File
@@ -0,0 +1,454 @@
## 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
+13
View File
@@ -0,0 +1,13 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Artemis.UI.Linux"
xmlns:ui="clr-namespace:Artemis.UI;assembly=Artemis.UI"
x:Class="Artemis.UI.Linux.App">
<Application.DataTemplates>
<ui:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
@@ -0,0 +1,50 @@
using Artemis.Core.Ninject;
using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root.ViewModels;
using Artemis.UI.Shared.Ninject;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Ninject;
using ReactiveUI;
using Splat.Ninject;
namespace Artemis.UI.Linux
{
public class App : Application
{
private StandardKernel _kernel = null!;
public override void Initialize()
{
InitializeNinject();
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get<RootViewModel>()
};
base.OnFrameworkInitializationCompleted();
}
private void InitializeNinject()
{
_kernel = new StandardKernel();
_kernel.Settings.InjectNonPublic = true;
_kernel.Load<CoreModule>();
_kernel.Load<UIModule>();
_kernel.Load<SharedUIModule>();
_kernel.UseNinjectDependencyResolver();
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<!--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.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+24
View File
@@ -0,0 +1,24 @@
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();
}
}
}
File diff suppressed because it is too large. Load diff
+454
View File
@@ -0,0 +1,454 @@
## 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
+13
View File
@@ -0,0 +1,13 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Artemis.UI.MacOS"
xmlns:ui="clr-namespace:Artemis.UI;assembly=Artemis.UI"
x:Class="Artemis.UI.MacOS.App">
<Application.DataTemplates>
<ui:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>
@@ -0,0 +1,50 @@
using Artemis.Core.Ninject;
using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root.ViewModels;
using Artemis.UI.Shared.Ninject;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Ninject;
using ReactiveUI;
using Splat.Ninject;
namespace Artemis.UI.MacOS
{
public class App : Application
{
private StandardKernel _kernel = null!;
public override void Initialize()
{
InitializeNinject();
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get<RootViewModel>()
};
base.OnFrameworkInitializationCompleted();
}
private void InitializeNinject()
{
_kernel = new StandardKernel();
_kernel.Settings.InjectNonPublic = true;
_kernel.Load<CoreModule>();
_kernel.Load<UIModule>();
_kernel.Load<SharedUIModule>();
_kernel.UseNinjectDependencyResolver();
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<!--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.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+24
View File
@@ -0,0 +1,24 @@
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();
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,3 @@
<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>
@@ -0,0 +1,506 @@
<?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>
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationIcon />
<StartupObject />
<OutputPath>bin\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>C:\Repos\Artemis\src\Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.8.3" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.10" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.10" />
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="0.10.10" />
<PackageReference Include="FluentAvaloniaUI" Version="1.1.5" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Material.Icons.Avalonia">
<HintPath>..\..\..\..\Users\Robert\.nuget\packages\material.icons.avalonia\1.0.2\lib\netstandard2.0\Material.Icons.Avalonia.dll</HintPath>
</Reference>
<Reference Include="RGB.NET.Core">
<HintPath>..\..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Services\WindowService\ExceptionDialogView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
</Project>
@@ -0,0 +1,3 @@
<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>
@@ -0,0 +1,8 @@
<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>
@@ -0,0 +1,102 @@
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
{
public partial class ArtemisIcon : UserControl
{
#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);
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 };
}
}
/// <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
public ArtemisIcon()
{
InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
LayoutUpdated += OnLayoutUpdated;
}
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);
}
}
}
@@ -0,0 +1,306 @@
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(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height));
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;
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_deviceImage?.Dispose();
_deviceImage = null;
if (Device != null)
{
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
Device.DeviceUpdated -= DeviceUpdated;
}
base.OnDetachedFromVisualTree(e);
}
/// <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 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
}
}
@@ -0,0 +1,158 @@
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();
}
}
}
}
@@ -0,0 +1,14 @@
<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>
@@ -0,0 +1,108 @@
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
{
public partial 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);
/// <summary>
/// Gets or sets the currently selected value
/// </summary>
public object? Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
private ComboBox? _enumComboBox;
private readonly ObservableCollection<(Enum, string)> _currentValues = new();
private static void ValueChanged(IAvaloniaObject sender, bool before)
{
if (sender is EnumComboBox enumCombo && !before)
{
enumCombo.UpdateValues();
enumCombo.UpdateSelection();
}
}
public EnumComboBox()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_enumComboBox == null)
return;
var (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
}
}
@@ -0,0 +1,7 @@
<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>
@@ -0,0 +1,111 @@
using System;
using System.ComponentModel;
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
{
public class ProfileConfigurationIcon : UserControl
{
#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
public ProfileConfigurationIcon()
{
InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
PropertyChanged += OnPropertyChanged;
}
private void Update()
{
if (ConfigurationIcon == null)
return;
try
{
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.SvgImage && ConfigurationIcon.FileIcon != null)
{
SvgSource source = new();
source.Load(ConfigurationIcon.FileIcon);
Content = new SvgImage {Source = source};
}
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon && ConfigurationIcon.MaterialIcon != null)
{
Content = Enum.TryParse(ConfigurationIcon.MaterialIcon, true, out MaterialIconKind parsedIcon)
? new MaterialIcon {Kind = parsedIcon!}
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.BitmapImage && ConfigurationIcon.FileIcon != null)
Content = new Image {Source = new Bitmap(ConfigurationIcon.FileIcon)};
else
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
catch
{
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Event handlers
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{
if (ConfigurationIcon != null)
ConfigurationIcon.PropertyChanged -= IconOnPropertyChanged;
if (Content is SvgImage svgImage)
svgImage.Source?.Dispose();
else if (Content is Image image)
((Bitmap) image.Source).Dispose();
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ConfigurationIconProperty)
{
if (e.OldValue is Core.ProfileConfigurationIcon oldIcon)
oldIcon.PropertyChanged -= IconOnPropertyChanged;
if (e.NewValue is Core.ProfileConfigurationIcon newIcon)
newIcon.PropertyChanged += IconOnPropertyChanged;
Update();
}
}
private void IconOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Update();
}
#endregion
}
}
@@ -0,0 +1,200 @@
using System;
using System.Windows.Input;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using FluentAvalonia.Styling;
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(AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>().CustomAccentColor ?? Colors.Transparent, 0.25));
/// <summary>
/// Defines the <see cref="BorderBrush" /> property.
/// </summary>
public static readonly StyledProperty<IBrush> BorderBrushProperty =
AvaloniaProperty.Register<SelectionRectangle, IBrush>(nameof(BorderBrush),
new SolidColorBrush(AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>().CustomAccentColor ?? Colors.Transparent));
/// <summary>
/// Defines the <see cref="BorderBrush" /> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(BorderThickness), 1);
/// <summary>
/// Defines the <see cref="get_InputElement" /> property.
/// </summary>
public static readonly StyledProperty<IControl?> InputElementProperty =
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
public static readonly StyledProperty<ICommand?> SelectionUpdatedProperty
= AvaloniaProperty.Register<SelectionRectangle, ICommand?>(nameof(SelectionUpdated));
public static readonly StyledProperty<ICommand?> SelectionFinishedProperty
= AvaloniaProperty.Register<SelectionRectangle, ICommand?>(nameof(SelectionUpdated));
private Rect? _displayRect;
private IControl? _oldInputElement;
private Point _startPosition;
/// <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);
}
public IControl? InputElement
{
get => GetValue(InputElementProperty);
set => SetValue(InputElementProperty, value);
}
public ICommand? SelectionUpdated
{
get => GetValue(SelectionUpdatedProperty);
set => SetValue(SelectionUpdatedProperty, value);
}
public ICommand? SelectionFinished
{
get => GetValue(SelectionFinishedProperty);
set => SetValue(SelectionFinishedProperty, value);
}
private static void OnInputElementChanged(IAvaloniaObject sender, bool before)
{
((SelectionRectangle) sender).SubscribeToInputElement();
}
private void ParentOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Pointer.Capture(this);
_startPosition = e.GetPosition(Parent);
_displayRect = null;
}
private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
{
if (!ReferenceEquals(e.Pointer.Captured, this))
return;
Point currentPosition = e.GetPosition(Parent);
_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))
);
SelectionUpdated?.Execute(_displayRect.Value);
InvalidateVisual();
}
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!ReferenceEquals(e.Pointer.Captured, this))
return;
e.Pointer.Capture(null);
if (_displayRect != null)
SelectionFinished?.Execute(_displayRect.Value);
_displayRect = null;
InvalidateVisual();
}
private void SubscribeToInputElement()
{
if (_oldInputElement != null)
{
_oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
}
_oldInputElement = InputElement;
if (InputElement != null)
{
InputElement.PointerPressed += ParentOnPointerPressed;
InputElement.PointerMoved += ParentOnPointerMoved;
InputElement.PointerReleased += ParentOnPointerReleased;
}
}
#region Overrides of Visual
public override void Render(DrawingContext drawingContext)
{
if (_displayRect != null)
drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderThickness), _displayRect.Value);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
SubscribeToInputElement();
base.OnAttachedToVisualTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (_oldInputElement != null)
{
_oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
_oldInputElement = null;
}
base.OnDetachedFromVisualTree(e);
}
#endregion
}
}
@@ -0,0 +1,38 @@
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;
}
}
}
@@ -0,0 +1,38 @@
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;
}
}
}
@@ -0,0 +1,21 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data about selection events raised by <see cref="DataModelDynamicViewModel" />
/// </summary>
public class DataModelInputDynamicEventArgs : EventArgs
{
internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath)
{
DataModelPath = dataModelPath;
}
/// <summary>
/// Gets the data model path that was selected
/// </summary>
public DataModelPath? DataModelPath { get; }
}
}
@@ -0,0 +1,20 @@
using System;
namespace Artemis.UI.Shared.Events
{
/// <summary>
/// Provides data about submit events raised by <see cref="DataModelStaticViewModel" />
/// </summary>
public class DataModelInputStaticEventArgs : EventArgs
{
internal DataModelInputStaticEventArgs(object? value)
{
Value = value;
}
/// <summary>
/// The value that was submitted
/// </summary>
public object? Value { get; }
}
}
@@ -0,0 +1,14 @@
using System;
namespace Artemis.UI.Shared.Events
{
internal class DialogClosedEventArgs<TResult> : EventArgs
{
public TResult Result { get; }
public DialogClosedEventArgs(TResult result)
{
Result = result;
}
}
}
@@ -0,0 +1,27 @@
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; }
}
}
@@ -0,0 +1,32 @@
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; }
}
}
@@ -0,0 +1,32 @@
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; }
}
}
@@ -0,0 +1,15 @@
using System;
using Avalonia;
namespace Artemis.UI.Shared.Events
{
public class SelectionRectangleEventArgs : EventArgs
{
public SelectionRectangleEventArgs(Rect rect)
{
Rect = rect;
}
public Rect Rect { get; }
}
}
@@ -0,0 +1,22 @@
using System;
namespace Artemis.UI.Shared.Exceptions
{
/// <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)
{
}
}
}
@@ -0,0 +1,31 @@
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());
});
}
}
}
@@ -0,0 +1,21 @@
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; }
}
}
@@ -0,0 +1,38 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a view model for a plugin configuration window
/// </summary>
public abstract class PluginConfigurationViewModel : ViewModelBase, 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);
}
/// <summary>
/// Occurs when the the window hosting the view model should close
/// </summary>
public event EventHandler? CloseRequested;
}
}
@@ -0,0 +1,132 @@
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;
namespace Artemis.UI.Shared.Services.Builders
{
public class ContentDialogBuilder
{
private readonly ContentDialog _contentDialog;
private readonly IKernel _kernel;
private readonly Window _parent;
internal ContentDialogBuilder(IKernel kernel, Window parent)
{
_kernel = kernel;
_parent = parent;
_contentDialog = new ContentDialog
{
CloseButtonText = "Close"
};
}
public ContentDialogBuilder WithTitle(string? title)
{
_contentDialog.Title = title;
return this;
}
public ContentDialogBuilder WithContent(string? content)
{
_contentDialog.Content = content;
return this;
}
public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton)
{
_contentDialog.DefaultButton = defaultButton;
return this;
}
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;
return this;
}
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;
return this;
}
public ContentDialogBuilder WithCloseButtonText(string? text)
{
_contentDialog.CloseButtonText = text;
return this;
}
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object value)[] parameters) where T : ViewModelBase
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
viewModel = _kernel.Get<T>(paramsArray);
_contentDialog.Content = viewModel;
return this;
}
public async Task<ContentDialogResult> ShowAsync()
{
if (_parent.Content is not Panel panel)
return ContentDialogResult.None;
try
{
panel.Children.Add(_contentDialog);
return await _contentDialog.ShowAsync();
}
finally
{
panel.Children.Remove(_contentDialog);
}
}
}
public class ContentDialogButtonBuilder
{
internal ContentDialogButtonBuilder()
{
}
internal string? Text { get; set; }
internal ICommand? Command { get; set; }
internal object? CommandParameter { get; set; }
public ContentDialogButtonBuilder WithText(string? text)
{
Text = text;
return this;
}
public ContentDialogButtonBuilder WithCommand(ICommand? command)
{
Command = command;
return this;
}
public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter)
{
CommandParameter = commandParameter;
return this;
}
}
}
@@ -0,0 +1,40 @@
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;
}
}
}
@@ -0,0 +1,138 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Shared.Services.Builders
{
public class NotificationBuilder
{
private readonly InfoBar _infoBar;
private readonly Window _parent;
private TimeSpan _timeout = TimeSpan.FromSeconds(5);
public NotificationBuilder(Window parent)
{
_parent = parent;
_infoBar = new InfoBar
{
Classes = Classes.Parse("notification-info-bar"),
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalAlignment = HorizontalAlignment.Right
};
}
public NotificationBuilder WithTitle(string? title)
{
_infoBar.Title = title;
return this;
}
public NotificationBuilder WithMessage(string? content)
{
_infoBar.Message = content;
return this;
}
public NotificationBuilder WithTimeout(TimeSpan timeout)
{
_timeout = timeout;
return this;
}
public NotificationBuilder WithVerticalPosition(VerticalAlignment position)
{
_infoBar.VerticalAlignment = position;
return this;
}
public NotificationBuilder WithHorizontalPosition(HorizontalAlignment position)
{
_infoBar.HorizontalAlignment = position;
return this;
}
/// <summary>
/// Add a filter to the dialog
/// </summary>
public NotificationBuilder HavingButton(Action<NotificationButtonBuilder> configure)
{
NotificationButtonBuilder builder = new();
configure(builder);
_infoBar.ActionButton = builder.Build();
return this;
}
public NotificationBuilder WithSeverity(NotificationSeverity severity)
{
_infoBar.Severity = (InfoBarSeverity) severity;
return this;
}
public void Show()
{
if (_parent.Content is not Panel panel)
return;
Dispatcher.UIThread.Post(() =>
{
panel.Children.Add(_infoBar);
_infoBar.Closed += InfoBarOnClosed;
_infoBar.IsOpen = true;
});
Task.Run(async () =>
{
await Task.Delay(_timeout);
Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false);
});
}
private void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args)
{
_infoBar.Closed -= InfoBarOnClosed;
if (_parent.Content is not Panel panel)
return;
panel.Children.Remove(_infoBar);
}
}
public class NotificationButtonBuilder
{
private string _text = "Text";
private Action? _action;
public NotificationButtonBuilder WithText(string text)
{
_text = text;
return this;
}
public NotificationButtonBuilder WithAction(Action action)
{
_action = action;
return this;
}
public IControl Build()
{
return _action != null
? new Button {Content = _text, Command = ReactiveCommand.Create(() => _action)}
: new Button {Content = _text};
}
}
public enum NotificationSeverity
{
Informational,
Success,
Warning,
Error
}
}
@@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Builders
{
/// <summary>
/// Represents a builder that can create a <see cref="OpenFileDialog" />.
/// </summary>
public class OpenFileDialogBuilder
{
private readonly OpenFileDialog _openFileDialog;
private readonly Window _parent;
public OpenFileDialogBuilder(Window parent)
{
_parent = parent;
_openFileDialog = new OpenFileDialog();
}
/// <summary>
/// Indicate that the user can select multiple files.
/// </summary>
public OpenFileDialogBuilder WithAllowMultiple()
{
_openFileDialog.AllowMultiple = true;
return this;
}
/// <summary>
/// Set the title of the dialog
/// </summary>
public OpenFileDialogBuilder WithTitle(string? title)
{
_openFileDialog.Title = title;
return this;
}
/// <summary>
/// Set the initial directory of the dialog
/// </summary>
public OpenFileDialogBuilder WithDirectory(string? directory)
{
_openFileDialog.Directory = directory;
return this;
}
/// <summary>
/// Set the initial file name of the dialog
/// </summary>
public OpenFileDialogBuilder WithInitialFileName(string? initialFileName)
{
_openFileDialog.InitialFileName = initialFileName;
return this;
}
/// <summary>
/// Add a filter to the dialog
/// </summary>
public OpenFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
{
FileDialogFilterBuilder builder = new();
configure(builder);
_openFileDialog.Filters.Add(builder.Build());
return this;
}
/// <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>
public async Task<string[]?> ShowAsync()
{
return await _openFileDialog.ShowAsync(_parent);
}
}
}
@@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Builders
{
/// <summary>
/// Represents a builder that can create a <see cref="SaveFileDialog" />.
/// </summary>
public class SaveFileDialogBuilder
{
private readonly Window _parent;
private readonly SaveFileDialog _saveFileDialog;
public SaveFileDialogBuilder(Window parent)
{
_parent = parent;
_saveFileDialog = new SaveFileDialog();
}
/// <summary>
/// Set the title of the dialog
/// </summary>
public SaveFileDialogBuilder WithTitle(string? title)
{
_saveFileDialog.Title = title;
return this;
}
/// <summary>
/// Set the initial directory of the dialog
/// </summary>
public SaveFileDialogBuilder WithDirectory(string? directory)
{
_saveFileDialog.Directory = directory;
return this;
}
/// <summary>
/// Set the initial file name of the dialog
/// </summary>
public SaveFileDialogBuilder WithInitialFileName(string? initialFileName)
{
_saveFileDialog.InitialFileName = initialFileName;
return this;
}
/// <summary>
/// Set the default extension of the dialog
/// </summary>
public SaveFileDialogBuilder WithDefaultExtension(string? defaultExtension)
{
_saveFileDialog.DefaultExtension = defaultExtension;
return this;
}
/// <summary>
/// Add a filter to the dialog
/// </summary>
public SaveFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
{
FileDialogFilterBuilder builder = new();
configure(builder);
_saveFileDialog.Filters.Add(builder.Build());
return this;
}
/// <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>
public async Task<string?> ShowAsync()
{
return await _saveFileDialog.ShowAsync(_parent);
}
}
}
@@ -0,0 +1,9 @@
namespace Artemis.UI.Shared.Services.Interfaces
{
/// <summary>
/// Represents a service provided by the Artemis Shared UI library
/// </summary>
public interface IArtemisSharedUIService
{
}
}
@@ -0,0 +1,9 @@
using Artemis.UI.Shared.Services.Builders;
namespace Artemis.UI.Shared.Services.Interfaces
{
public interface INotificationService : IArtemisSharedUIService
{
NotificationBuilder CreateNotification();
}
}
@@ -0,0 +1,72 @@
using System;
using System.Threading.Tasks;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Interfaces
{
public interface IWindowService : IArtemisSharedUIService
{
/// <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>
TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
/// <summary>
/// Given a ViewModel, show its corresponding View as a window
/// </summary>
/// <param name="viewModel">ViewModel to show the View for</param>
void ShowWindow(object viewModel);
/// <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>
void ShowExceptionDialog(string title, Exception exception);
/// <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>
Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel);
/// <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>
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
/// <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>
Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
/// <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>
OpenFileDialogBuilder CreateOpenFileDialog();
/// <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>
SaveFileDialogBuilder CreateSaveFileDialog();
ContentDialogBuilder CreateContentDialog();
Window GetCurrentWindow();
}
}
@@ -0,0 +1,20 @@
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.Services
{
public class NotificationService : INotificationService
{
private readonly IWindowService _windowService;
public NotificationService(IWindowService windowService)
{
_windowService = windowService;
}
public NotificationBuilder CreateNotification()
{
return new NotificationBuilder(_windowService.GetCurrentWindow());
}
}
}
@@ -0,0 +1,48 @@
<Window 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="800"
x:Class="Artemis.UI.Shared.Services.ExceptionDialogView"
Title="{Binding Title}"
ExtendClientAreaToDecorationsHint="True"
Width="800"
Height="800"
WindowStartupLocation="CenterOwner">
<Grid>
<TextBlock Margin="10" IsHitTestVisible="False" Text="{Binding Title}" />
<Grid Margin="0 32 0 0" ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<Border Classes="card" Grid.Row="0" Grid.ColumnSpan="2" Margin="10">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0 5">
<Grid RowDefinitions="Auto,Auto,*">
<TextBlock Grid.Row="0" Classes="h3">Awww :(</TextBlock>
<TextBlock Grid.Row="1">
It looks like Artemis ran into an unhandled exception. If this keeps happening feel free to hit us up on Discord.
</TextBlock>
<TextBox Grid.Row="2" Text="{Binding Exception, Mode=OneTime}"
Margin="0 10"
AcceptsReturn="True"
IsReadOnly="True"
FontFamily="Consolas"
FontSize="12"
BorderThickness="0" />
</Grid>
</ScrollViewer>
</Border>
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" Margin="15" VerticalAlignment="Center">
When reporting errors please don't take a screenshot of the error, instead copy the text, thanks!
</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="15">
<Button Command="{Binding CopyException}" Classes="AppBarButton" Width="150" Margin="0 0 5 0">
Copy exception
</Button>
<Button Command="{Binding Close}" Width="150" Margin="5 0 0 0">
Close
</Button>
</StackPanel>
</Grid>
</Grid>
</Window>
@@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Shared.Services
{
internal class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
{
public ExceptionDialogView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Layout;
namespace Artemis.UI.Shared.Services
{
internal class ExceptionDialogViewModel : DialogViewModelBase<object>
{
private readonly INotificationService _notificationService;
public ExceptionDialogViewModel(string title, Exception exception, INotificationService notificationService)
{
_notificationService = notificationService;
Title = $"Artemis | {title}";
Exception = exception;
}
public string Title { get; }
public Exception Exception { get; }
public async Task CopyException()
{
await Application.Current.Clipboard.SetTextAsync(Exception.ToString());
_notificationService.CreateNotification()
.WithMessage("Copied stack trace to clipboard.")
.WithSeverity(NotificationSeverity.Success)
.WithHorizontalPosition(HorizontalAlignment.Center)
.Show();
}
}
}
@@ -0,0 +1,147 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Artemis.UI.Shared.Exceptions;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
namespace Artemis.UI.Shared.Services
{
internal class WindowService : IWindowService
{
private readonly IKernel _kernel;
private bool _exceptionDialogOpen;
public WindowService(IKernel kernel)
{
_kernel = kernel;
}
public T ShowWindow<T>(params (string name, object value)[] parameters)
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
T viewModel = _kernel.Get<T>(paramsArray)!;
ShowWindow(viewModel);
return viewModel;
}
public void ShowWindow(object viewModel)
{
Window parent = GetCurrentWindow();
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name);
if (type == null)
{
throw new ArtemisSharedUIException($"Failed to find a window named {name}.");
}
if (!type.IsAssignableTo(typeof(Window)))
{
throw new ArtemisSharedUIException($"Type {name} is not a window.");
}
Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel;
window.Show(parent);
}
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
{
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
TViewModel viewModel = _kernel.Get<TViewModel>(paramsArray)!;
return await ShowDialogAsync(viewModel);
}
public async Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel")
{
ContentDialogResult contentDialogResult = await CreateContentDialog()
.WithTitle(title)
.WithContent(message)
.HavingPrimaryButton(b => b.WithText(confirm))
.WithCloseButtonText(cancel)
.ShowAsync();
return contentDialogResult == ContentDialogResult.Primary;
}
public async Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel)
{
Window parent = GetCurrentWindow();
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = viewModel.GetType().Assembly.GetType(name);
if (type == null)
{
throw new ArtemisSharedUIException($"Failed to find a window named {name}.");
}
if (!type.IsAssignableTo(typeof(Window)))
{
throw new ArtemisSharedUIException($"Type {name} is not a window.");
}
Window window = (Window) Activator.CreateInstance(type)!;
window.DataContext = viewModel;
viewModel.CloseRequested += (_, args) => window.Close(args.Result);
viewModel.CancelRequested += (_, _) => window.Close();
return await window.ShowDialog<TResult>(parent);
}
public void ShowExceptionDialog(string title, Exception exception)
{
if (_exceptionDialogOpen)
return;
_exceptionDialogOpen = true;
// Fire and forget the dialog
Dispatcher.UIThread.InvokeAsync(async () =>
{
try
{
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _kernel.Get<INotificationService>()));
}
finally
{
_exceptionDialogOpen = false;
}
});
}
public ContentDialogBuilder CreateContentDialog()
{
return new ContentDialogBuilder(_kernel, GetCurrentWindow());
}
public OpenFileDialogBuilder CreateOpenFileDialog()
{
return new OpenFileDialogBuilder(GetCurrentWindow());
}
public SaveFileDialogBuilder CreateSaveFileDialog()
{
return new SaveFileDialogBuilder(GetCurrentWindow());
}
public Window GetCurrentWindow()
{
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
{
throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
}
Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
return parent;
}
}
}
@@ -0,0 +1,32 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel >
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Informational"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Success"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Warning"/>
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True" Severity="Error"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<!-- Add Styles Here -->
<Style Selector="controls|InfoBar.notification-info-bar">
<Setter Property="Opacity" Value="0" />
<Setter Property="MaxWidth" Value="600" />
<Setter Property="Margin" Value="15"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
</Transitions>
</Setter>
</Style>
<Style Selector="controls|InfoBar.notification-info-bar[IsOpen=True]">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="controls|InfoBar.notification-info-bar:informational /template/ Border#ContentRoot">
<Setter Property="Background" Value="#ff3c3c3c" />
</Style>
</Styles>
@@ -0,0 +1,83 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Shared.Events;
using ReactiveUI;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents the base class for Artemis view models
/// </summary>
public abstract class ViewModelBase : ReactiveObject
{
private string? _displayName;
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
}
/// <summary>
/// Represents the base class for Artemis view models that are interested in the activated event
/// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
{
/// <inheritdoc />
protected ActivatableViewModelBase()
{
this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
}
/// <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)
{
}
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Represents the base class for Artemis view models used to drive dialogs
/// </summary>
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
{
/// <summary>
/// Closes the dialog with the given <paramref name="result" />
/// </summary>
/// <param name="result">The result of the dialog</param>
public void Close(TResult result)
{
CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result));
}
/// <summary>
/// Closes the dialog without a result
/// </summary>
public void Cancel()
{
CancelRequested?.Invoke(this, EventArgs.Empty);
}
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
internal event EventHandler? CancelRequested;
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
To use the Avalonia CI feed to get unstable packages, move this file to the root of your solution.
-->
<configuration>
<packageSources>
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" />
</packageSources>
</configuration>
File diff suppressed because it is too large. Load diff
+454
View File
@@ -0,0 +1,454 @@
## 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
+31
View File
@@ -0,0 +1,31 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Artemis.UI.Windows"
xmlns:ui="clr-namespace:Artemis.UI;assembly=Artemis.UI"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
x:Class="Artemis.UI.Windows.App">
<Application.DataTemplates>
<ui:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<!-- Third party styles -->
<styling:FluentAvaloniaTheme RequestedTheme="Dark" CustomAccentColor="#4db6ac" />
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" />
<!-- Grab the window styling from Avalonia -->
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Window.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TitleBar.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml" />
<!-- Global styles -->
<!-- <StyleInclude Source="/Styles/Border.axaml" /> -->
<!-- <StyleInclude Source="/Styles/Button.axaml" /> -->
<!-- <StyleInclude Source="/Styles/TextBlock.axaml" /> -->
<!-- <StyleInclude Source="/Styles/Sidebar.axaml" /> -->
<!-- Shared styles -->
<!-- TODO: Make a single file-->
<StyleInclude Source="avares://Artemis.UI.Shared/Styles/InfoBar.axaml" />
</Application.Styles>
</Application>
@@ -0,0 +1,54 @@
using Artemis.Core.Ninject;
using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root.ViewModels;
using Artemis.UI.Shared.Ninject;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ninject;
using ReactiveUI;
using Splat.Ninject;
namespace Artemis.UI.Windows
{
public class App : Application
{
private StandardKernel _kernel = null!;
public override void Initialize()
{
InitializeNinject();
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get<RootViewModel>()
};
AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>().ForceNativeTitleBarToTheme(desktop.MainWindow, "Dark");
}
base.OnFrameworkInitializationCompleted();
}
private void InitializeNinject()
{
_kernel = new StandardKernel();
_kernel.Settings.InjectNonPublic = true;
_kernel.Load<CoreModule>();
_kernel.Load<UIModule>();
_kernel.Load<SharedUIModule>();
_kernel.UseNinjectDependencyResolver();
}
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<!--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.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

@@ -0,0 +1,24 @@
using System;
using Avalonia;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Windows
{
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();
}
}
}
File diff suppressed because it is too large. Load diff
+30
View File
@@ -0,0 +1,30 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:ui="clr-namespace:Artemis.UI"
x:Class="Artemis.UI.App">
<Application.DataTemplates>
<ui:ViewLocator />
</Application.DataTemplates>
<Application.Styles>
<!-- Third party styles -->
<sty:FluentAvaloniaTheme RequestedTheme="Dark" CustomAccentColor="#4db6ac" />
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" />
<!-- Grab the window styling from Avalonia -->
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Window.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TitleBar.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml" />
<!-- Global styles -->
<StyleInclude Source="/Styles/Border.axaml" />
<StyleInclude Source="/Styles/Button.axaml" />
<StyleInclude Source="/Styles/TextBlock.axaml" />
<StyleInclude Source="/Styles/Sidebar.axaml" />
<!-- Shared styles -->
<!-- TODO: Make a single file-->
<StyleInclude Source="avares://Artemis.UI.Avalonia.Shared/Styles/InfoBar.axaml" />
</Application.Styles>
</Application>
+54
View File
@@ -0,0 +1,54 @@
using Artemis.Core.Ninject;
using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root.ViewModels;
using Artemis.UI.Shared.Ninject;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ninject;
using ReactiveUI;
using Splat.Ninject;
namespace Artemis.UI
{
public class App : Application
{
private StandardKernel _kernel = null!;
public override void Initialize()
{
InitializeNinject();
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get<RootViewModel>()
};
AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>().ForceNativeTitleBarToTheme(desktop.MainWindow, "Dark");
}
base.OnFrameworkInitializationCompleted();
}
private void InitializeNinject()
{
_kernel = new StandardKernel();
_kernel.Settings.InjectNonPublic = true;
_kernel.Load<CoreModule>();
_kernel.Load<UIModule>();
_kernel.Load<SharedUIModule>();
_kernel.UseNinjectDependencyResolver();
}
}
}
@@ -0,0 +1,2 @@
<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/=events/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+45
View File
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputPath>bin\</OutputPath>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="4.2.0" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.8.3" />
<PackageReference Include="FluentAvaloniaUI" Version="1.1.5" />
<PackageReference Include="Flurl.Http" Version="3.2.0" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="Splat.Ninject" Version="13.1.30" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="RGB.NET.Core">
<HintPath>..\..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Images\Logo\bow-black.ico" />
<Content Include="Assets\Images\Logo\bow-white.ico" />
<Content Include="Assets\Images\Logo\bow.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\Images\Logo\bow-black.ico" />
<Resource Include="Assets\Images\Logo\bow-white.ico" />
<Resource Include="Assets\Images\Logo\bow-white.svg" />
<Resource Include="Assets\Images\Logo\bow.ico" />
<Resource Include="Assets\Images\Logo\bow.svg" />
</ItemGroup>
</Project>
@@ -0,0 +1,2 @@
<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/=events/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>.cls-1,.cls-2{fill:#fff;}.cls-2{fill-rule:evenodd;}</style>
</defs>
<g id="Layer_10" data-name="Layer 10">
<path class="cls-1" d="M52.32,17.68l-2.83,5a1.16,1.16,0,0,1-2,0l-.57-1-.54.32c-.22-.44-.46-.88-.71-1.31s-.66-1.1-1-1.63l.53-.3-.63-1.06a1.16,1.16,0,0,1,1-1.74l5.75,0A1.15,1.15,0,0,1,52.32,17.68Z"/>
<path class="cls-1" d="M24.71,25.63l-4.84,2.58-.53.28a38.15,38.15,0,0,1-5.54-2.18c-4.51-1.49-1.48-7,1.93-4.31.34.22.69.44,1,.64.59.35,1.21.67,1.82,1A25.88,25.88,0,0,0,24.71,25.63Z"/>
<path class="cls-1" d="M30.83,36.22c-.15.43-.28.86-.41,1.29a25.74,25.74,0,0,0-.81,4.09,26.72,26.72,0,0,0-.17,3.1c0,.37,0,.75,0,1.12A2.45,2.45,0,0,1,25,47.72c-.56-1-.22-2-.18-3.08s.21-2,.39-3c.12-.74.27-1.47.43-2.2l.53-.33,3.63-2.26Z"/>
<path class="cls-1" d="M35,28.71l-.91.57L31.3,31,24,35.59l-4.45,2.78-2.22,1.37a2.6,2.6,0,0,1-1.26.34,2.45,2.45,0,0,1-.8-4.72l.58-.31,1.3-.69,4.67-2.5,7.6-4.05,2.94-1.57.94-.5a17.91,17.91,0,0,1,1,1.55C34.57,27.75,34.82,28.23,35,28.71Z"/>
<path class="cls-2" d="M39.29,53.89a2.56,2.56,0,0,1-1.09.74l-.34.08a.13.13,0,0,1-.09,0,1.84,1.84,0,0,1-.33,0,2.41,2.41,0,0,1-1.84-4,22.32,22.32,0,0,0,5-18.09c0-.24-.08-.48-.13-.72s-.1-.48-.16-.73-.11-.48-.18-.72-.12-.45-.2-.68a20.49,20.49,0,0,0-.7-1.94c-.06-.18-.14-.34-.21-.51a21.83,21.83,0,0,0-1.09-2.16c-.14-.22-.27-.45-.4-.66-.25-.4-.51-.78-.77-1.16s-.63-.85-1-1.26l-.48-.56c-.35-.4-.72-.78-1.09-1.14a6.51,6.51,0,0,0-.54-.51l-.45-.4a22.08,22.08,0,0,0-3-2.2c-.17-.11-.36-.21-.54-.31s-.42-.24-.63-.35l-.46-.23a19.7,19.7,0,0,0-2.31-1l-.44-.15-.1,0c-.53-.18-1.07-.34-1.63-.48l-.25-.06a19.61,19.61,0,0,0-2-.39c-.35-.06-.7-.1-1-.13s-.8-.07-1.2-.08-.65,0-1,0h0a22.18,22.18,0,0,0-4,.36,3.28,3.28,0,0,1-.43,0,2.42,2.42,0,0,1-.42-4.8A26,26,0,0,1,18,9.26h.62c.43,0,.86,0,1.28,0l1,.07,1.07.11c.52.07,1,.14,1.53.24.23,0,.46.08.7.13l.14,0c.35.08.69.15,1,.25a20.61,20.61,0,0,1,2.16.65c.48.16.94.33,1.4.52h0c.33.14.67.28,1,.44s.58.27.86.42l.27.13c.28.14.56.29.82.45s.64.36,1,.55c.49.31,1,.62,1.45,1l.15.11c.31.22.62.46.93.7l.11.08c.36.28.71.58,1.06.89l0,0c.33.28.64.57,1,.88s.64.62.94.95c.1.1.19.21.29.32.26.29.52.58.77.88,0,0,0,.05.06.09.28.34.55.68.81,1s.55.75.82,1.15l.28.44c.21.33.42.67.62,1A27.14,27.14,0,0,1,39.29,53.89Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
<defs>
<style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:url(#linear-gradient-3);}.cls-4{fill:url(#linear-gradient-4);}.cls-5{fill:url(#linear-gradient-5);}.cls-6{fill:url(#linear-gradient-6);}.cls-7{fill:url(#linear-gradient-7);}.cls-8{fill-rule:evenodd;fill:url(#linear-gradient-8);}</style>
<linearGradient id="linear-gradient" x1="151.25" y1="94.74" x2="810.05" y2="369.54" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f19d25"/>
<stop offset="0.12" stop-color="#f63d3d"/>
<stop offset="0.28" stop-color="#c93cec"/>
<stop offset="0.44" stop-color="#2667f4"/>
<stop offset="0.56" stop-color="#1cb6e7"/>
<stop offset="0.7" stop-color="#2df4b5"/>
<stop offset="0.87" stop-color="#70ea37"/>
<stop offset="1" stop-color="#cfe726"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="132.17" y1="204.4" x2="234.7" y2="230.88" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ef3d3d"/>
<stop offset="1" stop-color="#b72222"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="217.07" y1="354.42" x2="253.03" y2="258.52" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2b75f6"/>
<stop offset="1" stop-color="#1452aa"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="105.18" y1="320.77" x2="311.18" y2="203.81" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#22f15e"/>
<stop offset="1" stop-color="#29af4d"/>
</linearGradient>
<linearGradient id="linear-gradient-5" x1="112.68" y1="187.21" x2="771.48" y2="462.01" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-6" x1="88.75" y1="244.59" x2="747.54" y2="519.39" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-7" x1="102.97" y1="210.49" x2="761.77" y2="485.29" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-8" x1="141.26" y1="116.54" x2="347.22" y2="356.89" gradientTransform="translate(11.74 6.94)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#009385"/>
<stop offset="1" stop-color="#045e53"/>
</linearGradient>
</defs>
<g id="Teal_Bow" data-name="Teal Bow">
<g id="TealRainbow">
<path class="cls-1" d="M375.54,156.66,340,157a7.14,7.14,0,0,0-6.1,10.76l3.85,6.55-16.8,9.84c2.18,3.26,4.26,6.61,6.24,10,1.54,2.67,3,5.37,4.39,8.09l16.82-9.85,3.53,6a7.14,7.14,0,0,0,12.37-.1l17.53-30.94A7.15,7.15,0,0,0,375.54,156.66Z"/>
<path class="cls-5" d="M235.3,221.27l-34.52,18.41c-6.08-1-12.11-2.36-18.05-4a230.91,230.91,0,0,1-39.4-14.9c-27.91-9.26-9.21-43.22,11.93-26.76,2.11,1.41,4.28,2.76,6.48,4,3.65,2.15,7.42,4.09,11.24,5.91a156.68,156.68,0,0,0,42.24,13.31A130.42,130.42,0,0,0,239.63,219Z"/>
<path class="cls-6" d="M257,258.59l4.09-2.55a135.41,135.41,0,0,0-14.8,34.07,160.44,160.44,0,0,0-5,25.32,158,158,0,0,0-1.06,19.21c0,2.32.11,4.63.26,6.94,4.06,16.9-18.44,27-27.64,11.76-3.51-6-1.35-12.66-1.14-19.08.58-6.21,1.35-12.39,2.39-18.55a200.68,200.68,0,0,1,9.63-36.38l10.53-6.56Z"/>
<path class="cls-7" d="M284,219.06c-1.92-3.33-4-6.55-6.21-9.68h0l-9.46,5.54a17.12,17.12,0,0,0-2.69,1.52l-7.34,3.92L240,230.1q-23.52,12.53-47,25.08L164,270.62l-8,4.27c-1.21.65-2.43,1.29-3.64,1.95C132.81,285.25,146.31,314,165.2,304l13.72-8.55,27.53-17.15,45.22-28.18,17.55-10.94,7-4.39a15.73,15.73,0,0,0,3.58-2.29h0l8.56-5Q286.34,223.21,284,219.06Z"/>
<path class="cls-8" d="M169.2,115.38A169,169,0,0,0,143.28,118a15,15,0,0,0,5.26,29.44A137.81,137.81,0,0,1,278.31,372.15a14.95,14.95,0,0,0,22.82,19.31A167.69,167.69,0,0,0,169.2,115.38Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

@@ -0,0 +1,30 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using RGBColor = RGB.NET.Core.Color;
namespace Artemis.UI.Converters
{
/// <summary>
/// Converts <see cref="T:RGB.NET.Core.Color" /> into <see cref="T:Avalonia.Media.Color" />.
/// </summary>
public class ColorToSolidColorBrushConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new SolidColorBrush(!(value is RGBColor color)
? new Color(0, 0, 0, 0)
: new Color((byte) color.A, (byte) color.R, (byte) color.G, (byte) color.B));
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(value is SolidColorBrush brush)
? RGBColor.Transparent
: new RGBColor(brush.Color.A, brush.Color.R, brush.Color.G, brush.Color.B);
}
}
}
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Converters
{
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
private static string Description(Enum value)
{
object[] attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute)?.Description;
// If no description is found, the least we can do is replace underscores with spaces
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
private static IEnumerable<Tuple<object, object>> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select(e => new Tuple<object, object>(e, Description(e))).ToList();
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using RGB.NET.Core;
namespace Artemis.UI.Converters
{
public class LedIdToStringConverter : IValueConverter
{
#region Implementation of 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 (Enum.TryParse(typeof(LedId), value?.ToString(), true, out object parsedLedId))
return parsedLedId;
return LedId.Unknown1;
}
#endregion
}
}
@@ -0,0 +1,29 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Artemis.UI.Converters
{
public class NormalizedPercentageConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double number)
return number * 100.0;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double number)
return number / 100.0;
return value;
}
#endregion
}
}
@@ -0,0 +1,22 @@
using System;
using System.Globalization;
using System.IO;
using Avalonia.Data.Converters;
namespace Artemis.UI.Converters
{
public class UriToFileNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Uri uri && uri.IsFile)
return Path.GetFileName(uri.LocalPath);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Data.Converters;
namespace Artemis.UI.Converters
{
public class ValuesAdditionConverter : IMultiValueConverter
{
/// <inheritdoc />
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return values.Where(v => v is double).Cast<double>().Sum();
}
}
}
@@ -0,0 +1,22 @@
using System;
namespace Artemis.UI.Exceptions
{
public class ArtemisGraphicsContextException : Exception
{
/// <inheritdoc />
public ArtemisGraphicsContextException()
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message) : base(message)
{
}
/// <inheritdoc />
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace Artemis.UI.Exceptions
{
public class ArtemisUIException : Exception
{
public ArtemisUIException()
{
}
public ArtemisUIException(string message) : base(message)
{
}
public ArtemisUIException(string message, Exception inner) : base(message, inner)
{
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Artemis.UI.Extensions
{
public static class ObservableCollectionExtensions
{
public static void Sort<T>(this ObservableCollection<T> collection, Func<T, object> order)
{
List<T> ordered = collection.OrderBy(order).ToList();
for (int index = 0; index < ordered.Count; index++)
{
T dataBindingConditionViewModel = ordered[index];
if (collection.IndexOf(dataBindingConditionViewModel) != index)
collection.Move(collection.IndexOf(dataBindingConditionViewModel), index);
}
}
}
}
+26
View File
@@ -0,0 +1,26 @@
<Window 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.MainWindow"
Icon="/Assets/Images/Logo/bow.ico"
Title="Artemis.UI.Avalonia"
ExtendClientAreaToDecorationsHint="True"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent">
<Panel>
<ExperimentalAcrylicBorder IsHitTestVisible="False">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
BackgroundSource="Digger"
TintColor="Black"
TintOpacity="1"
MaterialOpacity="0.85" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ContentControl Content="{Binding}" />
</Panel>
</Window>
@@ -0,0 +1,23 @@
using Artemis.UI.Screens.Root.ViewModels;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI
{
public class MainWindow : ReactiveWindow<RootViewModel>
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Screens.Device.Tabs.ViewModels;
using Artemis.UI.Screens.Device.ViewModels;
using Artemis.UI.Screens.Plugins.ViewModels;
using Artemis.UI.Screens.Root.ViewModels;
using Artemis.UI.Screens.Settings.Tabs.ViewModels;
using Artemis.UI.Screens.SurfaceEditor.ViewModels;
using ReactiveUI;
namespace Artemis.UI.Ninject.Factories
{
public interface IVmFactory
{
}
public interface IDeviceVmFactory : IVmFactory
{
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel);
DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device);
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
}
public interface ISettingsVmFactory : IVmFactory
{
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
// DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device);
}
public interface ISidebarVmFactory : IVmFactory
{
SidebarViewModel SidebarViewModel(IScreen hostScreen);
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
}
public interface SurfaceVmFactory : IVmFactory
{
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device);
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device);
}
public interface IPrerequisitesVmFactory : IVmFactory
{
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
}
}
@@ -0,0 +1,57 @@
using System;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;
namespace Artemis.UI.Ninject
{
public class UIModule : NinjectModule
{
public override void Load()
{
if (Kernel == null)
throw new ArgumentNullException("Kernel shouldn't be null here.");
Kernel.Components.Add<IMissingBindingResolver, SelfBindingResolver>();
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<ViewModelBase>()
.BindToSelf();
});
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<MainScreenViewModel>()
.BindAllBaseClasses();
});
// Bind UI factories
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllInterfaces()
.InheritedFrom<IVmFactory>()
.BindToFactory();
});
// Bind all UI services as singletons
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<IArtemisUIService>()
.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
}
}
}
@@ -0,0 +1,8 @@
using Artemis.Core.Services;
namespace Artemis.UI.Providers
{
public class AvaloniaInputProvider : InputProvider
{
}
}
@@ -0,0 +1,18 @@
<Window 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:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Debugger.DebugView"
Title="Artemis | Debugger"
Width="1200"
Height="800"
ExtendClientAreaToDecorationsHint="True">
<controls:NavigationView x:Name="Navigation">
</controls:NavigationView>
</Window>
@@ -0,0 +1,53 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Shared.Events;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger
{
public class DebugView : ReactiveWindow<DebugViewModel>
{
private readonly NavigationView _navigation;
public DebugView()
{
Activated += OnActivated;
Deactivated += OnDeactivated;
InitializeComponent();
_navigation = this.Get<NavigationView>("Navigation");
this.WhenActivated(d =>
{
ViewModel!.WhenAnyValue(vm => vm!.IsActive).Subscribe(_ => Activate()).DisposeWith(d);
ViewModel!.SelectedItem = (NavigationViewItem) _navigation.MenuItems.ElementAt(0);
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnDeactivated(object? sender, EventArgs e)
{
if (ViewModel != null)
ViewModel.IsActive = false;
}
private void OnActivated(object? sender, EventArgs e)
{
if (ViewModel != null)
ViewModel.IsActive = true;
}
private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e)
{
}
}
}
@@ -0,0 +1,77 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Screens.Debugger.Tabs.DataModel;
using Artemis.UI.Screens.Debugger.Tabs.Logs;
using Artemis.UI.Screens.Debugger.Tabs.Performance;
using Artemis.UI.Screens.Debugger.Tabs.Render;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using Ninject;
using Ninject.Parameters;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger
{
public class DebugViewModel : ActivatableViewModelBase, IScreen
{
private readonly IKernel _kernel;
private readonly IDebugService _debugService;
private bool _isActive;
private NavigationViewItem? _selectedItem;
public DebugViewModel(IKernel kernel, IDebugService debugService)
{
_kernel = kernel;
_debugService = debugService;
this.WhenAnyValue(x => x.SelectedItem).WhereNotNull().Subscribe(NavigateToSelectedItem);
this.WhenActivated(disposables =>
{
Disposable
.Create(HandleDeactivation)
.DisposeWith(disposables);
});
}
public bool IsActive
{
get => _isActive;
set => this.RaiseAndSetIfChanged(ref _isActive, value);
}
public NavigationViewItem? SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
private void NavigateToSelectedItem(NavigationViewItem item)
{
// Kind of a lame way to do this but it's so static idc
ConstructorArgument hostScreen = new("hostScreen", this);
switch ((string) item.Content)
{
case "Rendering":
Router.Navigate.Execute(_kernel.Get<RenderDebugViewModel>(hostScreen));
break;
case "Logs":
Router.Navigate.Execute(_kernel.Get<LogsDebugViewModel>(hostScreen));
break;
case "Data Model":
Router.Navigate.Execute(_kernel.Get<DataModelDebugViewModel>(hostScreen));
break;
case "Performance":
Router.Navigate.Execute(_kernel.Get<PerformanceDebugViewModel>(hostScreen));
break;
}
}
private void HandleDeactivation()
{
_debugService.ClearDebugger();
}
public RoutingState Router { get; } = new();
}
}
@@ -0,0 +1,8 @@
<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.Screens.Debugger.Tabs.DataModel.DataModelDebugView">
<TextBlock>Data Model</TextBlock>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.DataModel
{
public class DataModelDebugView : ReactiveUserControl<DataModelDebugViewModel>
{
public DataModelDebugView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,16 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.DataModel
{
public class DataModelDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{
public DataModelDebugViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
}
public string UrlPathSegment => "data-model";
public IScreen HostScreen { get; }
}
}
@@ -0,0 +1,8 @@
<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.Screens.Debugger.Tabs.Logs.LogsDebugView">
<TextBlock>Logs</TextBlock>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.Logs
{
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
{
public LogsDebugView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,16 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.Logs
{
public class LogsDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{
public LogsDebugViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
}
public string UrlPathSegment => "logs";
public IScreen HostScreen { get; }
}
}
@@ -0,0 +1,8 @@
<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.Screens.Debugger.Tabs.Performance.PerformanceDebugView">
<TextBlock>Performance</TextBlock>
</UserControl>
Loaded 100 of 205 files, more files were not shown because too many files have changed in this diff. Show more