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

Merge branch 'development'

This commit is contained in:
Robert 2023-04-10 20:12:08 +02:00
commit 4e700a7ced
317 changed files with 1503 additions and 3789 deletions

View File

@ -58,7 +58,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v2
with: with:
dotnet-version: '6.0.x' dotnet-version: '7.0.x'
- name: Publish Artemis - name: Publish Artemis
run: dotnet publish --configuration Release -p:Version=${{ needs.version.outputs.version-number }} --runtime ${{ matrix.rid }} --output build/${{ matrix.rid }} --self-contained Artemis/src/Artemis.UI.${{ matrix.csproj }}/Artemis.UI.${{ matrix.csproj }}.csproj run: dotnet publish --configuration Release -p:Version=${{ needs.version.outputs.version-number }} --runtime ${{ matrix.rid }} --output build/${{ matrix.rid }} --self-contained Artemis/src/Artemis.UI.${{ matrix.csproj }}/Artemis.UI.${{ matrix.csproj }}.csproj
- name: Publish Plugins - name: Publish Plugins

View File

@ -3,8 +3,6 @@ name: Publish Nuget packages
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches:
- master
jobs: jobs:
version: version:
@ -37,7 +35,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v2
with: with:
dotnet-version: '6.0.x' dotnet-version: '7.0.x'
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Pack Artemis.Core - name: Pack Artemis.Core

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext> <PreserveCompilationContext>false</PreserveCompilationContext>
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp> <ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
<AssemblyTitle>Artemis.Core</AssemblyTitle> <AssemblyTitle>Artemis.Core</AssemblyTitle>
@ -35,21 +35,22 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.3.1" /> <PackageReference Include="DryIoc.dll" Version="5.3.4" />
<PackageReference Include="EmbedIO" Version="3.5.0" /> <PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="HidSharp" Version="2.1.0" /> <PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="LiteDB" Version="5.0.12" /> <PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="LiteDB" Version="5.0.16" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.29" />
<PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.29" />
<PackageReference Include="Serilog" Version="2.11.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" /> <PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />

View File

@ -1,4 +1,5 @@
<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"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Cquantization/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Cquantization/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Csorting/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=colorscience_005Csorting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=defaulttypes/@EntryIndexedValue">True</s:Boolean>

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.JsonConverters; using Artemis.Core.JsonConverters;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Core.Services.Core;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using Newtonsoft.Json; using Newtonsoft.Json;

View File

@ -37,9 +37,9 @@ public static class ContainerExtensions
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton); container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
container.Register(made: Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType)); container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton); container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);
container.Register(made: Made.Of(_ => ServiceInfo.Of<ILoggerFactory>(), f => f.CreateLogger(Arg.Index<Type>(0)), r => r.Parent.ImplementationType)); container.Register(Made.Of(_ => ServiceInfo.Of<ILoggerFactory>(), f => f.CreateLogger(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
} }
/// <summary> /// <summary>

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
namespace Artemis.Core; namespace Artemis.Core;

View File

@ -10,7 +10,7 @@ public class ArtemisPluginException : Exception
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class /// Creates a new instance of the <see cref="ArtemisPluginException" /> class
/// </summary> /// </summary>
public ArtemisPluginException(Plugin plugin) internal ArtemisPluginException(Plugin plugin)
{ {
Plugin = plugin; Plugin = plugin;
} }
@ -18,7 +18,7 @@ public class ArtemisPluginException : Exception
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class /// Creates a new instance of the <see cref="ArtemisPluginException" /> class
/// </summary> /// </summary>
public ArtemisPluginException(Plugin plugin, string message) : base(message) internal ArtemisPluginException(Plugin plugin, string message) : base(message)
{ {
Plugin = plugin; Plugin = plugin;
} }
@ -26,7 +26,7 @@ public class ArtemisPluginException : Exception
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class /// Creates a new instance of the <see cref="ArtemisPluginException" /> class
/// </summary> /// </summary>
public ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner) internal ArtemisPluginException(Plugin plugin, string message, Exception inner) : base(message, inner)
{ {
Plugin = plugin; Plugin = plugin;
} }
@ -45,8 +45,30 @@ public class ArtemisPluginException : Exception
{ {
} }
/// <summary>
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
/// </summary>
public ArtemisPluginException(string message, string helpDocument) : base(message)
{
HelpDocument = helpDocument;
}
/// <summary>
/// Creates a new instance of the <see cref="ArtemisPluginException" /> class
/// </summary>
public ArtemisPluginException(string message, Exception inner, string helpDocument) : base(message, inner)
{
HelpDocument = helpDocument;
}
/// <summary> /// <summary>
/// Gets the plugin the error is related to /// Gets the plugin the error is related to
/// </summary> /// </summary>
public Plugin? Plugin { get; } public Plugin? Plugin { get; }
/// <summary>
/// Gets or sets the help document related to this exception.
/// </summary>
public string? HelpDocument { get; }
} }

View File

@ -21,17 +21,17 @@ public static class ProcessExtensions
{ {
int capacity = 2000; int capacity = 2000;
StringBuilder builder = new(capacity); StringBuilder builder = new(capacity);
IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id); nint ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id);
if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty; if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) return string.Empty;
return builder.ToString(); return builder.ToString();
} }
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize); private static extern bool QueryFullProcessImageName([In] nint hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId); private static extern nint OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
[Flags] [Flags]
private enum ProcessAccessFlags : uint private enum ProcessAccessFlags : uint

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Artemis.Core.Properties; using JetBrains.Annotations;
namespace Artemis.Core; namespace Artemis.Core;

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -36,9 +34,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
/// <summary> /// <summary>
/// The value of the setting /// The value of the setting
/// </summary> /// </summary>
[AllowNull] public T? Value
[CanBeNull]
public T Value
{ {
get => _value; get => _value;
set set

File diff suppressed because it is too large Load Diff

View File

@ -127,6 +127,13 @@ public interface IPluginManagementService : IArtemisService, IDisposable
/// <returns>If the current call stack contains a plugin, the plugin. Otherwise null</returns> /// <returns>If the current call stack contains a plugin, the plugin. Otherwise null</returns>
Plugin? GetCallingPlugin(); Plugin? GetCallingPlugin();
/// <summary>
/// Returns the plugin that threw the provided exception.
/// </summary>
/// <param name="exception"></param>
/// <returns>If the exception was thrown by a plugin, the plugin. Otherwise null</returns>
Plugin? GetPluginFromException(Exception exception);
/// <summary> /// <summary>
/// Gets the plugin that defined the specified device /// Gets the plugin that defined the specified device
/// </summary> /// </summary>

View File

@ -192,7 +192,19 @@ internal class PluginManagementService : IPluginManagementService
public Plugin? GetCallingPlugin() public Plugin? GetCallingPlugin()
{ {
StackTrace stackTrace = new(); // get call stack return GetPluginFromStackTrace(new StackTrace());
}
public Plugin? GetPluginFromException(Exception exception)
{
if (exception is ArtemisPluginException pluginException && pluginException.Plugin != null)
return pluginException.Plugin;
return GetPluginFromStackTrace(new StackTrace(exception));
}
private Plugin? GetPluginFromStackTrace(StackTrace stackTrace)
{
StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames)
foreach (StackFrame stackFrame in stackFrames) foreach (StackFrame stackFrame in stackFrames)

View File

@ -1,6 +1,5 @@
using System; using System;
using EmbedIO; using EmbedIO;
using DryIoc;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;

View File

@ -4,8 +4,8 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.Events; using Artemis.Core.Events;
using Artemis.Core.Internal; using Artemis.Core.Internal;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile.Nodes; using Artemis.Storage.Entities.Profile.Nodes;
using JetBrains.Annotations;
namespace Artemis.Core; namespace Artemis.Core;

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext> <PreserveCompilationContext>false</PreserveCompilationContext>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.12" /> <PackageReference Include="LiteDB" Version="5.0.16" />
<PackageReference Include="Serilog" Version="2.11.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,6 +5,11 @@ namespace Artemis.Storage.Entities.Profile;
public class LedEntity public class LedEntity
{ {
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public int? PhysicalLayout { get; set; }
#region LedEntityEqualityComparer #region LedEntityEqualityComparer
private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity> private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity>
@ -31,9 +36,4 @@ public class LedEntity
public static IEqualityComparer<LedEntity> LedEntityComparer { get; } = new LedEntityEqualityComparer(); public static IEqualityComparer<LedEntity> LedEntityComparer { get; } = new LedEntityEqualityComparer();
#endregion #endregion
public string LedName { get; set; }
public string DeviceIdentifier { get; set; }
public int? PhysicalLayout { get; set; }
} }

View File

@ -51,7 +51,7 @@ public class M0021GradientNodes : IStorageMigration
TargetType = "ColorGradient", TargetType = "ColorGradient",
TargetNode = gradientNode.Id, TargetNode = gradientNode.Id,
TargetPinCollectionId = -1, TargetPinCollectionId = -1,
TargetPinId = 0, TargetPinId = 0
}); });
// Move the exit node to the right // Move the exit node to the right
@ -59,6 +59,18 @@ public class M0021GradientNodes : IStorageMigration
exitNode.Y += 30; exitNode.Y += 30;
} }
private void MigrateDataBinding(PropertyGroupEntity propertyGroup)
{
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
foreach (PropertyEntity property in propertyGroup.Properties)
{
if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled)
MigrateDataBinding(property);
}
}
public int UserVersion => 21; public int UserVersion => 21;
public void Apply(LiteRepository repository) public void Apply(LiteRepository repository)
@ -73,16 +85,4 @@ public class M0021GradientNodes : IStorageMigration
repository.Update(profileEntity); repository.Update(profileEntity);
} }
} }
private void MigrateDataBinding(PropertyGroupEntity propertyGroup)
{
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
foreach (PropertyEntity property in propertyGroup.Properties)
{
if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled)
MigrateDataBinding(property);
}
}
} }

View File

@ -5,7 +5,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.ReactiveUI;
using DryIoc; using DryIoc;
using ReactiveUI; using ReactiveUI;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
@ -16,12 +16,12 @@
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.18" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -1,7 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.ReactiveUI;
using DryIoc; using DryIoc;
using ReactiveUI; using ReactiveUI;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
@ -15,12 +15,12 @@
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.18" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputPath>bin\</OutputPath> <OutputPath>bin\</OutputPath>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
@ -10,18 +10,18 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.18" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.18" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview6" />
<PackageReference Include="DynamicData" Version="7.9.14" /> <PackageReference Include="DynamicData" Version="7.13.1" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.0-preview6" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" /> <PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -1,4 +1,5 @@
<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"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindow/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindow/@EntryIndexedValue">True</s:Boolean>

View File

@ -15,7 +15,7 @@ public class LostFocusNumericUpDownBindingBehavior : Behavior<NumericUpDown>
/// <summary> /// <summary>
/// Gets or sets the value of the binding. /// Gets or sets the value of the binding.
/// </summary> /// </summary>
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusNumericUpDownBindingBehavior, double>( public static readonly StyledProperty<decimal?> ValueProperty = AvaloniaProperty.Register<LostFocusNumericUpDownBindingBehavior, decimal?>(
nameof(Value), defaultBindingMode: BindingMode.TwoWay); nameof(Value), defaultBindingMode: BindingMode.TwoWay);
static LostFocusNumericUpDownBindingBehavior() static LostFocusNumericUpDownBindingBehavior()
@ -26,7 +26,7 @@ public class LostFocusNumericUpDownBindingBehavior : Behavior<NumericUpDown>
/// <summary> /// <summary>
/// Gets or sets the value of the binding. /// Gets or sets the value of the binding.
/// </summary> /// </summary>
public double Value public decimal? Value
{ {
get => GetValue(ValueProperty); get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value); set => SetValue(ValueProperty, value);

View File

@ -2,12 +2,12 @@ using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Visuals.Media.Imaging;
using Material.Icons; using Material.Icons;
using Material.Icons.Avalonia; using Material.Icons.Avalonia;
@ -16,9 +16,9 @@ namespace Artemis.UI.Shared;
/// <summary> /// <summary>
/// Represents a control that can display an arbitrary kind of icon. /// Represents a control that can display an arbitrary kind of icon.
/// </summary> /// </summary>
public class ArtemisIcon : UserControl public partial class ArtemisIcon : UserControl
{ {
private static readonly Regex _imageRegex = new(@"[\/.](gif|jpg|jpeg|tiff|png)$", RegexOptions.Compiled); private static readonly Regex ImageRegex = new(@"[\/.](gif|jpg|jpeg|tiff|png)$", RegexOptions.Compiled);
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ArtemisIcon" /> class. /// Creates a new instance of the <see cref="ArtemisIcon" /> class.
@ -28,12 +28,7 @@ public class ArtemisIcon : UserControl
InitializeComponent(); InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree; DetachedFromLogicalTree += OnDetachedFromLogicalTree;
LayoutUpdated += OnLayoutUpdated; LayoutUpdated += OnLayoutUpdated;
} PropertyChanged += OnPropertyChanged;
private static void IconChanging(IAvaloniaObject sender, bool before)
{
if (before)
((ArtemisIcon) sender).Update();
} }
private void Update() private void Update()
@ -54,7 +49,7 @@ public class ArtemisIcon : UserControl
Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height}; Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height};
} }
// An URI pointing to an image // An URI pointing to an image
else if (_imageRegex.IsMatch(iconString)) else if (ImageRegex.IsMatch(iconString))
{ {
if (!Fill) if (!Fill)
Content = new Image Content = new Image
@ -66,7 +61,7 @@ public class ArtemisIcon : UserControl
else else
Content = new Border Content = new Border
{ {
Background = TextBlock.GetForeground(this), Background = TextElement.GetForeground(this),
VerticalAlignment = VerticalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch,
OpacityMask = new ImageBrush(new Bitmap(iconString)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality} OpacityMask = new ImageBrush(new Bitmap(iconString)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
@ -93,25 +88,25 @@ public class ArtemisIcon : UserControl
} }
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IconProperty || e.Property == FillProperty)
Update();
}
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{ {
if (Content is Image image && image.Source is IDisposable disposable) if (Content is Image image && image.Source is IDisposable disposable)
disposable.Dispose(); disposable.Dispose();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Properties #region Properties
/// <summary> /// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" /> /// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG /// pointing to an SVG
/// </summary> /// </summary>
public static readonly StyledProperty<object?> IconProperty = public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<ArtemisIcon, object?>(nameof(Icon));
AvaloniaProperty.Register<ArtemisIcon, object?>(nameof(Icon), notifying: IconChanging);
/// <summary> /// <summary>
@ -128,8 +123,7 @@ public class ArtemisIcon : UserControl
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the /// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
/// theme /// theme
/// </summary> /// </summary>
public static readonly StyledProperty<bool> FillProperty = public static readonly StyledProperty<bool> FillProperty = AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon));
AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon), false, notifying: IconChanging);
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the /// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the

View File

@ -46,8 +46,8 @@ public class DataModelPickerButton : TemplatedControl
/// <summary> /// <summary>
/// Gets or sets the desired flyout placement. /// Gets or sets the desired flyout placement.
/// </summary> /// </summary>
public static readonly StyledProperty<FlyoutPlacementMode> PlacementProperty = public static readonly StyledProperty<PlacementMode> PlacementProperty =
AvaloniaProperty.Register<FlyoutBase, FlyoutPlacementMode>(nameof(Placement)); AvaloniaProperty.Register<FlyoutBase, PlacementMode>(nameof(Placement));
/// <summary> /// <summary>
/// Gets or sets data model path. /// Gets or sets data model path.
@ -133,7 +133,7 @@ public class DataModelPickerButton : TemplatedControl
/// <summary> /// <summary>
/// Gets or sets the desired flyout placement. /// Gets or sets the desired flyout placement.
/// </summary> /// </summary>
public FlyoutPlacementMode Placement public PlacementMode Placement
{ {
get => GetValue(PlacementProperty); get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value); set => SetValue(PlacementProperty, value);

View File

@ -12,10 +12,7 @@ using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
namespace Artemis.UI.Shared; namespace Artemis.UI.Shared;
@ -41,12 +38,14 @@ public class DeviceVisualizer : Control
_deviceVisualizerLeds = new List<DeviceVisualizerLed>(); _deviceVisualizerLeds = new List<DeviceVisualizerLed>();
PointerReleased += OnPointerReleased; PointerReleased += OnPointerReleased;
PropertyChanged += OnPropertyChanged;
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(DrawingContext drawingContext) public override void Render(DrawingContext drawingContext)
{ {
if (Device == null) if (Device == null || _deviceBounds.Width == 0 || _deviceBounds.Height == 0 || _loading)
return; return;
// Determine the scale required to fit the desired size of the control // Determine the scale required to fit the desired size of the control
@ -57,11 +56,11 @@ public class DeviceVisualizer : Control
{ {
// Scale the visualization in the desired bounding box // Scale the visualization in the desired bounding box
if (Bounds.Width > 0 && Bounds.Height > 0) if (Bounds.Width > 0 && Bounds.Height > 0)
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale)); boundsPush = drawingContext.PushTransform(Matrix.CreateScale(scale, scale));
// Apply device rotation // Apply device rotation
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top)); using DrawingContext.PushedState translationPush = drawingContext.PushTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation))); using DrawingContext.PushedState rotationPush = drawingContext.PushTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
// Render device and LED images // Render device and LED images
if (_deviceImage != null) if (_deviceImage != null)
@ -78,7 +77,7 @@ public class DeviceVisualizer : Control
lock (_deviceVisualizerLeds) lock (_deviceVisualizerLeds)
{ {
// Apply device scale // Apply device scale
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale)); using DrawingContext.PushedState scalePush = drawingContext.PushTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderGeometry(drawingContext, false); deviceVisualizerLed.RenderGeometry(drawingContext, false);
} }
@ -123,7 +122,7 @@ public class DeviceVisualizer : Control
private Rect MeasureDevice() private Rect MeasureDevice()
{ {
if (Device == null) if (Device == null)
return Rect.Empty; return new Rect();
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height); Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
Geometry geometry = new RectangleGeometry(deviceRect); Geometry geometry = new RectangleGeometry(deviceRect);
@ -155,6 +154,12 @@ public class DeviceVisualizer : Control
OnClicked(e); OnClicked(e);
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == DeviceProperty)
SetupForDevice();
}
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background); Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
@ -171,13 +176,7 @@ public class DeviceVisualizer : Control
/// Gets or sets the <see cref="ArtemisDevice" /> to display /// Gets or sets the <see cref="ArtemisDevice" /> to display
/// </summary> /// </summary>
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty = public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device), notifying: DeviceUpdated); AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device));
private static void DeviceUpdated(IAvaloniaObject sender, bool before)
{
if (!before)
((DeviceVisualizer) sender).SetupForDevice();
}
/// <summary> /// <summary>
/// Gets or sets the <see cref="ArtemisDevice" /> to display /// Gets or sets the <see cref="ArtemisDevice" /> to display
@ -209,6 +208,8 @@ public class DeviceVisualizer : Control
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty = public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds)); AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
private bool _loading;
/// <summary> /// <summary>
/// Gets or sets a list of LEDs to highlight /// Gets or sets a list of LEDs to highlight
/// </summary> /// </summary>
@ -274,6 +275,7 @@ public class DeviceVisualizer : Control
return; return;
_deviceBounds = MeasureDevice(); _deviceBounds = MeasureDevice();
_loading = true;
Device.RgbDevice.PropertyChanged += DevicePropertyChanged; Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
Device.DeviceUpdated += DeviceUpdated; Device.DeviceUpdated += DeviceUpdated;
@ -288,6 +290,8 @@ public class DeviceVisualizer : Control
// Load the device main image on a background thread // Load the device main image on a background thread
ArtemisDevice? device = Device; ArtemisDevice? device = Device;
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{
try
{ {
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath)) if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
{ {
@ -296,15 +300,13 @@ public class DeviceVisualizer : Control
return; return;
} }
try
{
// Create a bitmap that'll be used to render the device and LED images just once // Create a bitmap that'll be used to render the device and LED images just once
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in // Render 4 times the actual size of the device to make sure things look sharp when zoomed in
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2)); RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2));
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this)); using DrawingContext context = renderTargetBitmap.CreateDrawingContext();
using Bitmap bitmap = new(device.Layout.Image.LocalPath); using Bitmap bitmap = new(device.Layout.Image.LocalPath);
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality); context.DrawImage(bitmap, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
lock (_deviceVisualizerLeds) lock (_deviceVisualizerLeds)
{ {
@ -315,12 +317,16 @@ public class DeviceVisualizer : Control
_deviceImage?.Dispose(); _deviceImage?.Dispose();
_deviceImage = renderTargetBitmap; _deviceImage = renderTargetBitmap;
Dispatcher.UIThread.Post(InvalidateMeasure); InvalidateMeasure();
} }
catch (Exception) catch (Exception)
{ {
// ignored // ignored
} }
finally
{
_loading = false;
}
}); });
} }

View File

@ -4,8 +4,6 @@ using Artemis.Core;
using Avalonia; using Avalonia;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
using RGB.NET.Core; using RGB.NET.Core;
using Color = Avalonia.Media.Color; using Color = Avalonia.Media.Color;
using Point = Avalonia.Point; using Point = Avalonia.Point;
@ -40,7 +38,7 @@ internal class DeviceVisualizerLed
public Rect LedRect { get; set; } public Rect LedRect { get; set; }
public Geometry? DisplayGeometry { get; private set; } public Geometry? DisplayGeometry { get; private set; }
public void DrawBitmap(IDrawingContextImpl drawingContext, double scale) public void DrawBitmap(DrawingContext drawingContext, double scale)
{ {
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath)) if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
return; return;
@ -48,9 +46,8 @@ internal class DeviceVisualizerLed
try try
{ {
using Bitmap bitmap = new(Led.Layout.Image.LocalPath); using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
drawingContext.DrawBitmap( drawingContext.DrawImage(
bitmap.PlatformImpl, bitmap,
1,
new Rect(bitmap.Size), new Rect(bitmap.Size),
new Rect(Led.RgbLed.Location.X * scale, Led.RgbLed.Location.Y * scale, Led.RgbLed.Size.Width * scale, Led.RgbLed.Size.Height * scale), new Rect(Led.RgbLed.Location.X * scale, Led.RgbLed.Location.Y * scale, Led.RgbLed.Size.Width * scale, Led.RgbLed.Size.Height * scale),
BitmapInterpolationMode.HighQuality BitmapInterpolationMode.HighQuality

View File

@ -23,7 +23,7 @@
</UserControl.Styles> </UserControl.Styles>
<Panel> <Panel>
<controls:NumberBox Name="NumberBox" <controls:NumberBox Name="InnerNumberBox"
AcceptsExpression="True" AcceptsExpression="True"
LargeChange="{Binding $parent[sharedControls:DraggableNumberBox].LargeChange}" LargeChange="{Binding $parent[sharedControls:DraggableNumberBox].LargeChange}"
SmallChange="{Binding $parent[sharedControls:DraggableNumberBox].SmallChange}" SmallChange="{Binding $parent[sharedControls:DraggableNumberBox].SmallChange}"

View File

@ -14,12 +14,12 @@ namespace Artemis.UI.Shared.Controls;
/// <summary> /// <summary>
/// Represents a number box that can be mutated by dragging over it horizontally /// Represents a number box that can be mutated by dragging over it horizontally
/// </summary> /// </summary>
public class DraggableNumberBox : UserControl public partial class DraggableNumberBox : UserControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Value" /> property. /// Defines the <see cref="Value" /> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<DraggableNumberBox, double>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged); public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<DraggableNumberBox, double>(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Defines the <see cref="Minimum" /> property. /// Defines the <see cref="Minimum" /> property.
@ -56,7 +56,6 @@ public class DraggableNumberBox : UserControl
/// </summary> /// </summary>
public static readonly StyledProperty<string?> SuffixProperty = AvaloniaProperty.Register<DraggableNumberBox, string?>(nameof(Suffix)); public static readonly StyledProperty<string?> SuffixProperty = AvaloniaProperty.Register<DraggableNumberBox, string?>(nameof(Suffix));
private readonly NumberBox _numberBox;
private TextBox? _inputTextBox; private TextBox? _inputTextBox;
private double _lastX; private double _lastX;
private bool _moved; private bool _moved;
@ -69,12 +68,12 @@ public class DraggableNumberBox : UserControl
public DraggableNumberBox() public DraggableNumberBox()
{ {
InitializeComponent(); InitializeComponent();
_numberBox = this.Get<NumberBox>("NumberBox"); InnerNumberBox.Value = Value;
_numberBox.Value = Value;
PointerPressed += OnPointerPressed; PointerPressed += OnPointerPressed;
PointerMoved += OnPointerMoved; PointerMoved += OnPointerMoved;
PointerReleased += OnPointerReleased; PointerReleased += OnPointerReleased;
PropertyChanged += OnPropertyChanged;
AddHandler(KeyUpEvent, HandleKeyUp, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); AddHandler(KeyUpEvent, HandleKeyUp, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
} }
@ -161,35 +160,26 @@ public class DraggableNumberBox : UserControl
/// </summary> /// </summary>
public event TypedEventHandler<DraggableNumberBox, EventArgs>? DragFinished; public event TypedEventHandler<DraggableNumberBox, EventArgs>? DragFinished;
private static void ValueChanged(IAvaloniaObject sender, bool before) private void SetNumberBoxValue(double value)
{ {
if (before) if (!(Math.Abs(InnerNumberBox.Value - Value) > 0.00001))
return; return;
DraggableNumberBox draggable = (DraggableNumberBox) sender; _updating = true;
if (!(Math.Abs(draggable._numberBox.Value - draggable.Value) > 0.00001)) InnerNumberBox.Value = Value;
return; _updating = false;
draggable._updating = true;
draggable._numberBox.Value = draggable.Value;
draggable._updating = false;
} }
private void HandleKeyUp(object? sender, KeyEventArgs e) private void HandleKeyUp(object? sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter || e.Key == Key.Escape) if (e.Key == Key.Enter || e.Key == Key.Escape)
Parent?.Focus(); FocusManager.Instance?.Focus(Parent as IInputElement);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
private void OnPointerPressed(object? sender, PointerPressedEventArgs e) private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
PointerPoint point = e.GetCurrentPoint(this); PointerPoint point = e.GetCurrentPoint(this);
_inputTextBox = _numberBox.FindDescendantOfType<TextBox>(); _inputTextBox = InnerNumberBox.FindDescendantOfType<TextBox>();
_moved = false; _moved = false;
_startX = point.Position.X; _startX = point.Position.X;
_lastX = point.Position.X; _lastX = point.Position.X;
@ -211,7 +201,7 @@ public class DraggableNumberBox : UserControl
if (!_moved) if (!_moved)
{ {
// Let our parent take focus, it would make more sense to take focus ourselves but that hides the collider // Let our parent take focus, it would make more sense to take focus ourselves but that hides the collider
Parent?.Focus(); FocusManager.Instance?.Focus(Parent as IInputElement);
_moved = true; _moved = true;
e.Pointer.Capture(this); e.Pointer.Capture(this);
DragStarted?.Invoke(this, EventArgs.Empty); DragStarted?.Invoke(this, EventArgs.Empty);
@ -254,6 +244,12 @@ public class DraggableNumberBox : UserControl
e.Handled = true; e.Handled = true;
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ValueProperty)
SetNumberBoxValue(Value);
}
private void NumberBox_OnValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) private void NumberBox_OnValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args)
{ {
if (_updating) if (_updating)
@ -261,17 +257,17 @@ public class DraggableNumberBox : UserControl
if (args.NewValue < Minimum) if (args.NewValue < Minimum)
{ {
_numberBox.Value = Minimum; InnerNumberBox.Value = Minimum;
return; return;
} }
if (args.NewValue > Maximum) if (args.NewValue > Maximum)
{ {
_numberBox.Value = Maximum; InnerNumberBox.Value = Maximum;
return; return;
} }
if (Math.Abs(Value - _numberBox.Value) > 0.00001) if (Math.Abs(Value - InnerNumberBox.Value) > 0.00001)
Value = _numberBox.Value; Value = InnerNumberBox.Value;
} }
} }

View File

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.EnumComboBox"> x:Class="Artemis.UI.Shared.EnumComboBox">
<ComboBox x:Name="EnumComboBox" HorizontalAlignment="Stretch"> <ComboBox x:Name="ChildEnumComboBox" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding [1]}" /> <TextBlock Text="{Binding [1]}" />

View File

@ -13,13 +13,12 @@ namespace Artemis.UI.Shared;
/// <summary> /// <summary>
/// Represents a combobox that can display the values of an enum. /// Represents a combobox that can display the values of an enum.
/// </summary> /// </summary>
public class EnumComboBox : UserControl public partial class EnumComboBox : UserControl
{ {
/// <summary> /// <summary>
/// Gets or sets the currently selected value /// Gets or sets the currently selected value
/// </summary> /// </summary>
public static readonly StyledProperty<object?> ValueProperty = public static readonly StyledProperty<object?> ValueProperty = AvaloniaProperty.Register<EnumComboBox, object?>(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
AvaloniaProperty.Register<EnumComboBox, object?>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged);
private readonly ObservableCollection<(Enum, string)> _currentValues = new(); private readonly ObservableCollection<(Enum, string)> _currentValues = new();
private Type? _currentType; private Type? _currentType;
@ -31,9 +30,19 @@ public class EnumComboBox : UserControl
/// </summary> /// </summary>
public EnumComboBox() public EnumComboBox()
{ {
PropertyChanged += OnPropertyChanged;
InitializeComponent(); InitializeComponent();
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ValueProperty)
{
UpdateValues();
UpdateSelection();
}
}
/// <summary> /// <summary>
/// Gets or sets the currently selected value /// Gets or sets the currently selected value
/// </summary> /// </summary>
@ -43,20 +52,6 @@ public class EnumComboBox : UserControl
set => SetValue(ValueProperty, value); set => SetValue(ValueProperty, value);
} }
private static void ValueChanged(IAvaloniaObject sender, bool before)
{
if (sender is EnumComboBox enumCombo && !before)
{
enumCombo.UpdateValues();
enumCombo.UpdateSelection();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e) private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1) if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1)
@ -95,7 +90,7 @@ public class EnumComboBox : UserControl
/// <inheritdoc /> /// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_enumComboBox = this.Get<ComboBox>("EnumComboBox"); _enumComboBox = this.Get<ComboBox>("ChildEnumComboBox");
_enumComboBox.Items = _currentValues; _enumComboBox.Items = _currentValues;
UpdateValues(); UpdateValues();

View File

@ -27,7 +27,7 @@ public class GradientPicker : TemplatedControl
/// Gets or sets the color gradient. /// Gets or sets the color gradient.
/// </summary> /// </summary>
public static readonly StyledProperty<ColorGradient> ColorGradientProperty = public static readonly StyledProperty<ColorGradient> ColorGradientProperty =
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf()); AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(ColorGradient), defaultValue: ColorGradient.GetUnicornBarf());
/// <summary> /// <summary>
/// Gets or sets the currently selected color stop. /// Gets or sets the currently selected color stop.
@ -45,7 +45,7 @@ public class GradientPicker : TemplatedControl
/// Gets or sets a storage provider to use for storing and loading gradients. /// Gets or sets a storage provider to use for storing and loading gradients.
/// </summary> /// </summary>
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty = public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
AvaloniaProperty.Register<GradientPicker, IColorGradientStorageProvider?>(nameof(StorageProvider), notifying: StorageProviderChanged); AvaloniaProperty.Register<GradientPicker, IColorGradientStorageProvider?>(nameof(StorageProvider));
/// <summary> /// <summary>
/// Gets the linear gradient brush representing the color gradient. /// Gets the linear gradient brush representing the color gradient.
@ -66,7 +66,7 @@ public class GradientPicker : TemplatedControl
AvaloniaProperty.RegisterDirect<GradientPicker, ColorGradient>(nameof(EditingColorGradient), g => g.EditingColorGradient); AvaloniaProperty.RegisterDirect<GradientPicker, ColorGradient>(nameof(EditingColorGradient), g => g.EditingColorGradient);
private readonly ICommand _deleteStop; private readonly ICommand _deleteStop;
private ColorPicker? _colorPicker; private FAColorPicker? _colorPicker;
private Button? _flipStops; private Button? _flipStops;
private Border? _gradient; private Border? _gradient;
private Button? _randomize; private Button? _randomize;
@ -94,6 +94,8 @@ public class GradientPicker : TemplatedControl
SelectedColorStop = EditingColorGradient.ElementAtOrDefault(index); SelectedColorStop = EditingColorGradient.ElementAtOrDefault(index);
}); });
PropertyChanged += OnPropertyChanged;
} }
/// <summary> /// <summary>
@ -176,7 +178,7 @@ public class GradientPicker : TemplatedControl
if (_randomize != null) if (_randomize != null)
_randomize.Click -= RandomizeOnClick; _randomize.Click -= RandomizeOnClick;
_colorPicker = e.NameScope.Find<ColorPicker>("ColorPicker"); _colorPicker = e.NameScope.Find<FAColorPicker>("ColorPicker");
_gradient = e.NameScope.Find<Border>("Gradient"); _gradient = e.NameScope.Find<Border>("Gradient");
_spreadStops = e.NameScope.Find<Button>("SpreadStops"); _spreadStops = e.NameScope.Find<Button>("SpreadStops");
_toggleSeamless = e.NameScope.Find<Button>("ToggleSeamless"); _toggleSeamless = e.NameScope.Find<Button>("ToggleSeamless");
@ -220,16 +222,6 @@ public class GradientPicker : TemplatedControl
_shiftDown = false; _shiftDown = false;
} }
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
(sender as GradientPicker)?.ApplyToField();
}
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
{
}
private void ApplyToField() private void ApplyToField()
{ {
EditingColorGradient = new ColorGradient(ColorGradient); EditingColorGradient = new ColorGradient(ColorGradient);
@ -348,4 +340,10 @@ public class GradientPicker : TemplatedControl
EditingColorGradient.Randomize(6); EditingColorGradient.Randomize(6);
SelectedColorStop = EditingColorGradient.First(); SelectedColorStop = EditingColorGradient.First();
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ColorGradientProperty)
ApplyToField();
}
} }

View File

@ -12,7 +12,6 @@ using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using Button = FluentAvalonia.UI.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker; namespace Artemis.UI.Shared.Controls.GradientPicker;
@ -25,7 +24,7 @@ public class GradientPickerButton : TemplatedControl
/// Gets or sets the color gradient. /// Gets or sets the color gradient.
/// </summary> /// </summary>
public static readonly StyledProperty<ColorGradient?> ColorGradientProperty = public static readonly StyledProperty<ColorGradient?> ColorGradientProperty =
AvaloniaProperty.Register<GradientPickerButton, ColorGradient?>(nameof(ColorGradient), notifying: ColorGradientChanged); AvaloniaProperty.Register<GradientPickerButton, ColorGradient?>(nameof(ColorGradient));
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not. /// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
@ -48,6 +47,12 @@ public class GradientPickerButton : TemplatedControl
private Button? _button; private Button? _button;
private ColorGradient? _lastColorGradient; private ColorGradient? _lastColorGradient;
/// <inheritdoc />
public GradientPickerButton()
{
PropertyChanged += OnPropertyChanged;
}
/// <summary> /// <summary>
/// Gets or sets the color gradient. /// Gets or sets the color gradient.
/// </summary> /// </summary>
@ -105,12 +110,6 @@ public class GradientPickerButton : TemplatedControl
#endregion #endregion
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
{
if (!before)
(sender as GradientPickerButton)?.Subscribe();
}
private void Subscribe() private void Subscribe()
{ {
Unsubscribe(); Unsubscribe();
@ -178,6 +177,12 @@ public class GradientPickerButton : TemplatedControl
LinearGradientBrush.GradientStops = collection; LinearGradientBrush.GradientStops = collection;
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ColorGradientProperty)
Subscribe();
}
#region Overrides of Visual #region Overrides of Visual
/// <inheritdoc /> /// <inheritdoc />

View File

@ -16,7 +16,7 @@ internal class GradientPickerColorStop : TemplatedControl
/// Gets or sets the gradient picker. /// Gets or sets the gradient picker.
/// </summary> /// </summary>
public static readonly StyledProperty<GradientPicker?> GradientPickerProperty = public static readonly StyledProperty<GradientPicker?> GradientPickerProperty =
AvaloniaProperty.Register<GradientPickerColorStop, GradientPicker?>(nameof(GradientPicker), notifying: Notifying); AvaloniaProperty.Register<GradientPickerColorStop, GradientPicker?>(nameof(GradientPicker));
/// <summary> /// <summary>
/// Gets or sets the color stop. /// Gets or sets the color stop.
@ -28,8 +28,8 @@ internal class GradientPickerColorStop : TemplatedControl
/// Gets or sets the position reference to use when positioning and dragging this color stop. /// Gets or sets the position reference to use when positioning and dragging this color stop.
/// <para>If <see langword="null" /> then dragging is not enabled.</para> /// <para>If <see langword="null" /> then dragging is not enabled.</para>
/// </summary> /// </summary>
public static readonly StyledProperty<IControl?> PositionReferenceProperty = public static readonly StyledProperty<Control?> PositionReferenceProperty =
AvaloniaProperty.Register<GradientPickerColorStop, IControl?>(nameof(PositionReference)); AvaloniaProperty.Register<GradientPickerColorStop, Control?>(nameof(PositionReference));
/// <summary> /// <summary>
/// Gets the linear gradient brush representing the color gradient. /// Gets the linear gradient brush representing the color gradient.
@ -47,7 +47,16 @@ internal class GradientPickerColorStop : TemplatedControl
public GradientPicker? GradientPicker public GradientPicker? GradientPicker
{ {
get => GetValue(GradientPickerProperty); get => GetValue(GradientPickerProperty);
set => SetValue(GradientPickerProperty, value); set
{
if (GradientPicker != null)
GradientPicker.PropertyChanged -= GradientPickerOnPropertyChanged;
SetValue(GradientPickerProperty, value);
if (GradientPicker != null)
GradientPicker.PropertyChanged += GradientPickerOnPropertyChanged;
IsSelected = ReferenceEquals(GradientPicker?.SelectedColorStop, ColorStop);
}
} }
/// <summary> /// <summary>
@ -63,7 +72,7 @@ internal class GradientPickerColorStop : TemplatedControl
/// Gets or sets the position reference to use when positioning and dragging this color stop. /// Gets or sets the position reference to use when positioning and dragging this color stop.
/// <para>If <see langword="null" /> then dragging is not enabled.</para> /// <para>If <see langword="null" /> then dragging is not enabled.</para>
/// </summary> /// </summary>
public IControl? PositionReference public Control? PositionReference
{ {
get => GetValue(PositionReferenceProperty); get => GetValue(PositionReferenceProperty);
set => SetValue(PositionReferenceProperty, value); set => SetValue(PositionReferenceProperty, value);
@ -85,19 +94,6 @@ internal class GradientPickerColorStop : TemplatedControl
} }
} }
private static void Notifying(IAvaloniaObject sender, bool before)
{
if (sender is not GradientPickerColorStop self)
return;
if (before && self.GradientPicker != null)
self.GradientPicker.PropertyChanged -= self.GradientPickerOnPropertyChanged;
else if (self.GradientPicker != null)
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
}
private void GradientPickerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void GradientPickerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (GradientPicker != null && e.Property == GradientPicker.SelectedColorStopProperty) if (GradientPicker != null && e.Property == GradientPicker.SelectedColorStopProperty)

View File

@ -10,8 +10,16 @@
<Style Selector="TextBox#DisplayTextBox:focus:not(TextBox:empty)"> <Style Selector="TextBox#DisplayTextBox:focus:not(TextBox:empty)">
<Setter Property="InnerRightContent"> <Setter Property="InnerRightContent">
<Template> <Template>
<Button Classes="textBoxClearButton" <Button Theme="{StaticResource TextBoxDeleteButtonStyle}"
Click="Button_OnClick" /> Click="Button_OnClick"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}"
Padding="{StaticResource HelperButtonThemePadding}"
IsTabStop="False"
Focusable="False"
FontSize="{TemplateBinding FontSize}"
Width="30"
VerticalAlignment="Stretch" />
</Template> </Template>
</Setter> </Setter>
</Style> </Style>

View File

@ -8,6 +8,8 @@ using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using DryIoc;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using Humanizer; using Humanizer;
using Material.Icons; using Material.Icons;
@ -17,48 +19,64 @@ namespace Artemis.UI.Shared;
/// <summary> /// <summary>
/// Represents a control that can be used to display or edit <see cref="Core.Hotkey" /> instances. /// Represents a control that can be used to display or edit <see cref="Core.Hotkey" /> instances.
/// </summary> /// </summary>
public class HotkeyBox : UserControl public partial class HotkeyBox : UserControl
{ {
private readonly TextBox _displayTextBox; private readonly IInputService _inputService;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="HotkeyBox" /> class /// Creates a new instance of the <see cref="HotkeyBox" /> class
/// </summary> /// </summary>
public HotkeyBox() public HotkeyBox()
{ {
InitializeComponent(); _inputService = UI.Locator.Resolve<IInputService>();
_displayTextBox = this.Find<TextBox>("DisplayTextBox"); InitializeComponent();
_displayTextBox.KeyDown += DisplayTextBoxOnKeyDown; PropertyChanged += OnPropertyChanged;
_displayTextBox.KeyUp += DisplayTextBoxOnKeyUp;
UpdateDisplayTextBox(); UpdateDisplayTextBox();
} }
private static void HotkeyChanging(IAvaloniaObject sender, bool before) protected override void OnGotFocus(GotFocusEventArgs e)
{ {
((HotkeyBox) sender).UpdateDisplayTextBox(); _inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
base.OnGotFocus(e);
} }
private void DisplayTextBoxOnKeyDown(object? sender, KeyEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
{ {
if (e.Key >= Key.LeftShift && e.Key <= Key.RightAlt) _inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
base.OnLostFocus(e);
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == HotkeyProperty)
UpdateDisplayTextBox();
}
private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e)
{
if (e.Key >= KeyboardKey.LeftShift && e.Key <= KeyboardKey.RightAlt)
return; return;
Hotkey ??= new Hotkey(); Hotkey ??= new Hotkey();
Hotkey.Key = (KeyboardKey?) e.Key; Hotkey.Key = e.Key;
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers; Hotkey.Modifiers = e.Modifiers;
Dispatcher.UIThread.Post(() =>
{
UpdateDisplayTextBox(); UpdateDisplayTextBox();
HotkeyChanged?.Invoke(this, EventArgs.Empty); HotkeyChanged?.Invoke(this, EventArgs.Empty);
});
e.Handled = true;
} }
private void DisplayTextBoxOnKeyUp(object? sender, KeyEventArgs e) private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{ {
if (e.KeyModifiers == KeyModifiers.None) if (e.Modifiers == KeyboardModifierKey.None)
FocusManager.Instance?.Focus(null); Dispatcher.UIThread.Post(() => FocusManager.Instance?.Focus(null));
e.Handled = true;
} }
private void UpdateDisplayTextBox() private void UpdateDisplayTextBox()
@ -69,13 +87,8 @@ public class HotkeyBox : UserControl
if (Hotkey?.Key != null) if (Hotkey?.Key != null)
display = string.IsNullOrEmpty(display) ? Hotkey.Key.ToString() : $"{display}+{Hotkey.Key}"; display = string.IsNullOrEmpty(display) ? Hotkey.Key.ToString() : $"{display}+{Hotkey.Key}";
_displayTextBox.Text = display; DisplayTextBox.Text = display;
_displayTextBox.CaretIndex = _displayTextBox.Text?.Length ?? 0; DisplayTextBox.CaretIndex = DisplayTextBox.Text?.Length ?? 0;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
} }
private void Button_OnClick(object? sender, RoutedEventArgs e) private void Button_OnClick(object? sender, RoutedEventArgs e)
@ -92,20 +105,17 @@ public class HotkeyBox : UserControl
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" /> /// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
/// pointing to an SVG /// pointing to an SVG
/// </summary> /// </summary>
public static readonly StyledProperty<Hotkey?> HotkeyProperty = public static readonly StyledProperty<Hotkey?> HotkeyProperty = AvaloniaProperty.Register<HotkeyBox, Hotkey?>(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay);
AvaloniaProperty.Register<HotkeyBox, Hotkey?>(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay, notifying: HotkeyChanging);
/// <summary> /// <summary>
/// Gets or sets the watermark of the hotkey box when it is empty. /// Gets or sets the watermark of the hotkey box when it is empty.
/// </summary> /// </summary>
public static readonly StyledProperty<string?> WatermarkProperty = public static readonly StyledProperty<string?> WatermarkProperty = AvaloniaProperty.Register<HotkeyBox, string?>(nameof(Watermark));
AvaloniaProperty.Register<HotkeyBox, string?>(nameof(Watermark));
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty. /// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty = public static readonly StyledProperty<bool> UseFloatingWatermarkProperty = AvaloniaProperty.Register<HotkeyBox, bool>(nameof(UseFloatingWatermark));
AvaloniaProperty.Register<HotkeyBox, bool>(nameof(UseFloatingWatermark));
/// <summary> /// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" /> /// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />

View File

@ -3,13 +3,13 @@ using System.IO;
using Artemis.Core; using Artemis.Core;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
using Material.Icons; using Material.Icons;
using Material.Icons.Avalonia; using Material.Icons.Avalonia;
@ -18,7 +18,7 @@ namespace Artemis.UI.Shared;
/// <summary> /// <summary>
/// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration" />. /// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration" />.
/// </summary> /// </summary>
public class ProfileConfigurationIcon : UserControl, IDisposable public partial class ProfileConfigurationIcon : UserControl, IDisposable
{ {
private Stream? _stream; private Stream? _stream;
@ -72,18 +72,13 @@ public class ProfileConfigurationIcon : UserControl, IDisposable
Content = new Border Content = new Border
{ {
Background = TextBlock.GetForeground(this), Background = TextElement.GetForeground(this),
VerticalAlignment = VerticalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch,
OpacityMask = new ImageBrush(new Bitmap(stream)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality} OpacityMask = new ImageBrush(new Bitmap(stream)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
}; };
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{ {
if (ConfigurationIcon != null) if (ConfigurationIcon != null)

View File

@ -40,8 +40,8 @@ public class SelectionRectangle : Control
/// <summary> /// <summary>
/// Defines the <see cref="InputElement" /> property. /// Defines the <see cref="InputElement" /> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IControl?> InputElementProperty = public static readonly StyledProperty<InputElement?> InputElementProperty =
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged); AvaloniaProperty.Register<SelectionRectangle, InputElement?>(nameof(InputElement));
/// <summary> /// <summary>
/// Defines the <see cref="ZoomRatio" /> property. /// Defines the <see cref="ZoomRatio" /> property.
@ -61,7 +61,7 @@ public class SelectionRectangle : Control
private Rect? _displayRect; private Rect? _displayRect;
private bool _isSelecting; private bool _isSelecting;
private Point _lastPosition; private Point _lastPosition;
private IControl? _oldInputElement; private InputElement? _oldInputElement;
private Point _startPosition; private Point _startPosition;
/// <inheritdoc /> /// <inheritdoc />
@ -69,6 +69,14 @@ public class SelectionRectangle : Control
{ {
AffectsRender<TextBlock>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty); AffectsRender<TextBlock>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty);
IsHitTestVisible = false; IsHitTestVisible = false;
PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == InputElementProperty)
SubscribeToInputElement();
} }
/// <summary> /// <summary>
@ -110,7 +118,7 @@ public class SelectionRectangle : Control
/// <summary> /// <summary>
/// Gets or sets the element that captures input for the selection rectangle. /// Gets or sets the element that captures input for the selection rectangle.
/// </summary> /// </summary>
public IControl? InputElement public InputElement? InputElement
{ {
get => GetValue(InputElementProperty); get => GetValue(InputElementProperty);
set => SetValue(InputElementProperty, value); set => SetValue(InputElementProperty, value);
@ -162,11 +170,6 @@ public class SelectionRectangle : Control
SelectionFinished?.Invoke(this, e); SelectionFinished?.Invoke(this, e);
} }
private static void OnInputElementChanged(IAvaloniaObject sender, bool before)
{
((SelectionRectangle) sender).SubscribeToInputElement();
}
private void ParentOnPointerMoved(object? sender, PointerEventArgs e) private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
{ {
// Point moved seems to trigger when the element under the mouse changes? // Point moved seems to trigger when the element under the mouse changes?
@ -185,13 +188,13 @@ public class SelectionRectangle : Control
{ {
e.Pointer.Capture(this); e.Pointer.Capture(this);
_startPosition = e.GetPosition(Parent); _startPosition = e.GetPosition(Parent as Visual);
_absoluteStartPosition = e.GetPosition(VisualRoot); _absoluteStartPosition = e.GetPosition(VisualRoot as Visual);
_displayRect = null; _displayRect = null;
} }
Point currentPosition = e.GetPosition(Parent); Point currentPosition = e.GetPosition(Parent as Visual);
Point absoluteCurrentPosition = e.GetPosition(VisualRoot); Point absoluteCurrentPosition = e.GetPosition(VisualRoot as Visual);
_displayRect = new Rect( _displayRect = new Rect(
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)), new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),

View File

@ -15,7 +15,7 @@ public class ParentWidthPercentageConverter : IValueConverter
/// <inheritdoc /> /// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (parameter is not IControl parent || value is not double percentage) if (parameter is not Control parent || value is not double percentage)
return value; return value;
return parent.Width / 100.0 * percentage; return parent.Width / 100.0 * percentage;
@ -24,7 +24,7 @@ public class ParentWidthPercentageConverter : IValueConverter
/// <inheritdoc /> /// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (parameter is not IControl parent || value is not double real) if (parameter is not Control parent || value is not double real)
return value; return value;
return real / parent.Width * 100.0; return real / parent.Width * 100.0;

View File

@ -6,7 +6,7 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
/// <summary> /// <summary>
/// Represents a default data model display view. /// Represents a default data model display view.
/// </summary> /// </summary>
public class DefaultDataModelDisplayView : UserControl public partial class DefaultDataModelDisplayView : UserControl
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DefaultDataModelDisplayView" /> class. /// Creates a new instance of the <see cref="DefaultDataModelDisplayView" /> class.
@ -15,9 +15,4 @@ public class DefaultDataModelDisplayView : UserControl
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -7,7 +7,7 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
/// <summary> /// <summary>
/// Represents a data model display view used to display <see cref="SKColor" /> values. /// Represents a data model display view used to display <see cref="SKColor" /> values.
/// </summary> /// </summary>
public class SKColorDataModelDisplayView : UserControl public partial class SKColorDataModelDisplayView : UserControl
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="SKColorDataModelDisplayView" /> class. /// Creates a new instance of the <see cref="SKColorDataModelDisplayView" /> class.
@ -17,8 +17,4 @@ public class SKColorDataModelDisplayView : UserControl
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -17,5 +17,7 @@ public static class ContainerExtensions
{ {
Assembly artemisShared = typeof(IArtemisSharedUIService).GetAssembly(); Assembly artemisShared = typeof(IArtemisSharedUIService).GetAssembly();
container.RegisterMany(new[] { artemisShared }, type => type.IsAssignableTo<IArtemisSharedUIService>(), Reuse.Singleton); container.RegisterMany(new[] { artemisShared }, type => type.IsAssignableTo<IArtemisSharedUIService>(), Reuse.Singleton);
UI.Locator = container;
} }
} }

View File

@ -6,7 +6,7 @@ using Avalonia.VisualTree;
namespace Artemis.UI.Shared.Extensions; namespace Artemis.UI.Shared.Extensions;
/// <summary> /// <summary>
/// Provides extension methods for Avalonia's <see cref="IVisual" /> type /// Provides extension methods for Avalonia's <see cref="Visual" /> type
/// </summary> /// </summary>
public static class VisualExtensions public static class VisualExtensions
{ {
@ -16,15 +16,15 @@ public static class VisualExtensions
/// <typeparam name="T">The type the children should have.</typeparam> /// <typeparam name="T">The type the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param> /// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children of type <typeparamref name="T" />.</returns> /// <returns>A recursive list of all visual children of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfType<T>(this IVisual root) public static List<T> GetVisualChildrenOfType<T>(this Visual root)
{ {
List<T> result = new(); List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList(); List<Visual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any()) if (visualChildren == null || !visualChildren.Any())
return result; return result;
foreach (IVisual visualChild in visualChildren) foreach (Visual visualChild in visualChildren)
{ {
if (visualChild is T toFind) if (visualChild is T toFind)
result.Add(toFind); result.Add(toFind);
@ -41,15 +41,15 @@ public static class VisualExtensions
/// <typeparam name="T">The type of data context the children should have.</typeparam> /// <typeparam name="T">The type of data context the children should have.</typeparam>
/// <param name="root">The root visual at which to start searching.</param> /// <param name="root">The root visual at which to start searching.</param>
/// <returns>A recursive list of all visual children with a data context of type <typeparamref name="T" />.</returns> /// <returns>A recursive list of all visual children with a data context of type <typeparamref name="T" />.</returns>
public static List<T> GetVisualChildrenOfDataContextType<T>(this IVisual root) public static List<T> GetVisualChildrenOfDataContextType<T>(this Visual root)
{ {
List<T> result = new(); List<T> result = new();
List<IVisual>? visualChildren = root.GetVisualChildren()?.ToList(); List<Visual>? visualChildren = root.GetVisualChildren()?.ToList();
if (visualChildren == null || !visualChildren.Any()) if (visualChildren == null || !visualChildren.Any())
return result; return result;
foreach (IVisual visualChild in visualChildren) foreach (Visual visualChild in visualChildren)
{ {
if (visualChild is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is T toFind) if (visualChild is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is T toFind)
result.Add(toFind); result.Add(toFind);

View File

@ -4,8 +4,9 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using FluentAvalonia.UI.Controls; using Avalonia.Styling;
using FluentAvalonia.UI.Media; using FluentAvalonia.UI.Media;
using FluentAvalonia.UI.Windowing;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared; namespace Artemis.UI.Shared;
@ -17,18 +18,18 @@ namespace Artemis.UI.Shared;
/// and vice versa. /// and vice versa.
/// </summary> /// </summary>
/// <typeparam name="TViewModel">ViewModel type.</typeparam> /// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveCoreWindow<TViewModel> : CoreWindow, IViewFor<TViewModel> where TViewModel : class public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel> where TViewModel : class
{ {
/// <summary> /// <summary>
/// The ViewModel. /// The ViewModel.
/// </summary> /// </summary>
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
.Register<ReactiveCoreWindow<TViewModel>, TViewModel?>(nameof(ViewModel)); .Register<ReactiveAppWindow<TViewModel>, TViewModel?>(nameof(ViewModel));
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ReactiveCoreWindow{TViewModel}" /> class. /// Initializes a new instance of the <see cref="ReactiveAppWindow{TViewModel}" /> class.
/// </summary> /// </summary>
public ReactiveCoreWindow() public ReactiveAppWindow()
{ {
// This WhenActivated block calls ViewModel's WhenActivated // This WhenActivated block calls ViewModel's WhenActivated
// block if the ViewModel implements IActivatableViewModel. // block if the ViewModel implements IActivatableViewModel.
@ -46,13 +47,9 @@ public class ReactiveCoreWindow<TViewModel> : CoreWindow, IViewFor<TViewModel> w
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11)
return; return;
// Enable Mica on Windows 11, based on the FluentAvalonia sample application
TransparencyBackgroundFallback = Brushes.Transparent; TransparencyBackgroundFallback = Brushes.Transparent;
TransparencyLevelHint = WindowTransparencyLevel.Mica; TransparencyLevelHint = WindowTransparencyLevel.Mica;
TryEnableMicaEffect();
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", out object? value) ? (Color) value! : new Color2(32, 32, 32);
color = color.LightenPercent(-0.5f);
Background = new ImmutableSolidColorBrush(color, 0.82);
} }
private void OnDataContextChanged(object? value) private void OnDataContextChanged(object? value)
@ -70,6 +67,31 @@ public class ReactiveCoreWindow<TViewModel> : CoreWindow, IViewFor<TViewModel> w
else if (DataContext != value) DataContext = value; else if (DataContext != value) DataContext = value;
} }
private void TryEnableMicaEffect()
{
// The background colors for the Mica brush are still based around SolidBackgroundFillColorBase resource
// BUT since we can't control the actual Mica brush color, we have to use the window background to create
// the same effect. However, we can't use SolidBackgroundFillColorBase directly since its opaque, and if
// we set the opacity the color become lighter than we want. So we take the normal color, darken it and
// apply the opacity until we get the roughly the correct color
// NOTE that the effect still doesn't look right, but it suffices. Ideally we need access to the Mica
// CompositionBrush to properly change the color but I don't know if we can do that or not
if (ActualThemeVariant == ThemeVariant.Dark)
{
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Dark, out object? value) ? (Color) value : new Color2(32, 32, 32);
color = color.LightenPercent(-0.5f);
Background = new ImmutableSolidColorBrush(color, 0.78);
}
else if (ActualThemeVariant == ThemeVariant.Light)
{
// Similar effect here
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out object? value) ? (Color) value : new Color2(243, 243, 243);
color = color.LightenPercent(0.5f);
Background = new ImmutableSolidColorBrush(color, 0.9);
}
}
/// <summary> /// <summary>
/// The ViewModel. /// The ViewModel.

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -1,4 +1,7 @@
using Avalonia.Controls; using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
namespace Artemis.UI.Shared.Services.Builders; namespace Artemis.UI.Shared.Services.Builders;
@ -7,11 +10,12 @@ namespace Artemis.UI.Shared.Services.Builders;
/// </summary> /// </summary>
public class FileDialogFilterBuilder public class FileDialogFilterBuilder
{ {
private readonly FileDialogFilter _filter; private string _name;
private readonly List<string> _extensions = new();
internal FileDialogFilterBuilder() internal FileDialogFilterBuilder()
{ {
_filter = new FileDialogFilter(); _name = "Unknown";
} }
/// <summary> /// <summary>
@ -19,7 +23,7 @@ public class FileDialogFilterBuilder
/// </summary> /// </summary>
public FileDialogFilterBuilder WithName(string name) public FileDialogFilterBuilder WithName(string name)
{ {
_filter.Name = name; _name = name;
return this; return this;
} }
@ -28,12 +32,16 @@ public class FileDialogFilterBuilder
/// </summary> /// </summary>
public FileDialogFilterBuilder WithExtension(string extension) public FileDialogFilterBuilder WithExtension(string extension)
{ {
_filter.Extensions.Add(extension); if (!_extensions.Contains(extension))
_extensions.Add(extension);
return this; return this;
} }
internal FileDialogFilter Build() internal FilePickerFileType Build()
{ {
return _filter; return new FilePickerFileType(_name)
{
Patterns = _extensions.Select(e => "*." + e).ToList()
};
} }
} }

View File

@ -116,7 +116,7 @@ public class NotificationBuilder
/// <exception cref="ArtemisSharedUIException" /> /// <exception cref="ArtemisSharedUIException" />
public Action Show() public Action Show()
{ {
IPanel? panel = _parent.Find<IPanel>("NotificationContainer"); Panel? panel = _parent.Find<Panel>("NotificationContainer");
if (panel == null) if (panel == null)
throw new ArtemisSharedUIException("Can't display a notification on a window without a NotificationContainer."); throw new ArtemisSharedUIException("Can't display a notification on a window without a NotificationContainer.");
@ -202,7 +202,7 @@ public class NotificationButtonBuilder
return this; return this;
} }
internal IControl Build() internal Control Build()
{ {
if (_action != null) if (_action != null)
return new Button {Content = _text, Command = ReactiveCommand.Create(() => _action()), Classes = new Classes("AppBarButton")}; return new Button {Content = _text, Command = ReactiveCommand.Create(() => _action()), Classes = new Classes("AppBarButton")};

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform.Storage;
namespace Artemis.UI.Shared.Services.Builders; namespace Artemis.UI.Shared.Services.Builders;
@ -10,8 +12,9 @@ namespace Artemis.UI.Shared.Services.Builders;
/// </summary> /// </summary>
public class OpenFileDialogBuilder public class OpenFileDialogBuilder
{ {
private readonly OpenFileDialog _openFileDialog;
private readonly Window _parent; private readonly Window _parent;
private readonly FilePickerOpenOptions _options;
private List<FilePickerFileType>? _fileTypeFilters;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenFileDialogBuilder" /> class. /// Creates a new instance of the <see cref="OpenFileDialogBuilder" /> class.
@ -20,7 +23,7 @@ public class OpenFileDialogBuilder
internal OpenFileDialogBuilder(Window parent) internal OpenFileDialogBuilder(Window parent)
{ {
_parent = parent; _parent = parent;
_openFileDialog = new OpenFileDialog(); _options = new FilePickerOpenOptions();
} }
/// <summary> /// <summary>
@ -28,7 +31,7 @@ public class OpenFileDialogBuilder
/// </summary> /// </summary>
public OpenFileDialogBuilder WithAllowMultiple() public OpenFileDialogBuilder WithAllowMultiple()
{ {
_openFileDialog.AllowMultiple = true; _options.AllowMultiple = true;
return this; return this;
} }
@ -37,7 +40,7 @@ public class OpenFileDialogBuilder
/// </summary> /// </summary>
public OpenFileDialogBuilder WithTitle(string? title) public OpenFileDialogBuilder WithTitle(string? title)
{ {
_openFileDialog.Title = title; _options.Title = title;
return this; return this;
} }
@ -46,16 +49,7 @@ public class OpenFileDialogBuilder
/// </summary> /// </summary>
public OpenFileDialogBuilder WithDirectory(string? directory) public OpenFileDialogBuilder WithDirectory(string? directory)
{ {
_openFileDialog.Directory = directory; _options.SuggestedStartLocation = directory != null ? _parent.StorageProvider.TryGetFolderFromPathAsync(directory).GetAwaiter().GetResult() : null;
return this;
}
/// <summary>
/// Set the initial file name of the dialog
/// </summary>
public OpenFileDialogBuilder WithInitialFileName(string? initialFileName)
{
_openFileDialog.InitialFileName = initialFileName;
return this; return this;
} }
@ -67,8 +61,9 @@ public class OpenFileDialogBuilder
FileDialogFilterBuilder builder = new(); FileDialogFilterBuilder builder = new();
configure(builder); configure(builder);
_openFileDialog.Filters ??= new List<FileDialogFilter>(); _fileTypeFilters ??= new List<FilePickerFileType>();
_openFileDialog.Filters.Add(builder.Build()); _fileTypeFilters.Add(builder.Build());
_options.FileTypeFilter = _fileTypeFilters;
return this; return this;
} }
@ -82,6 +77,7 @@ public class OpenFileDialogBuilder
/// </returns> /// </returns>
public async Task<string[]?> ShowAsync() public async Task<string[]?> ShowAsync()
{ {
return await _openFileDialog.ShowAsync(_parent); IReadOnlyList<IStorageFile> files = await _parent.StorageProvider.OpenFilePickerAsync(_options);
return files.Select(f => f.Path.AbsolutePath).ToArray();
} }
} }

View File

@ -1,5 +1,8 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform.Storage;
namespace Artemis.UI.Shared.Services.Builders; namespace Artemis.UI.Shared.Services.Builders;
@ -8,8 +11,8 @@ namespace Artemis.UI.Shared.Services.Builders;
/// </summary> /// </summary>
public class OpenFolderDialogBuilder public class OpenFolderDialogBuilder
{ {
private readonly OpenFolderDialog _openFolderDialog;
private readonly Window _parent; private readonly Window _parent;
private readonly FolderPickerOpenOptions _options;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="OpenFolderDialogBuilder" /> class. /// Creates a new instance of the <see cref="OpenFolderDialogBuilder" /> class.
@ -18,16 +21,15 @@ public class OpenFolderDialogBuilder
internal OpenFolderDialogBuilder(Window parent) internal OpenFolderDialogBuilder(Window parent)
{ {
_parent = parent; _parent = parent;
_openFolderDialog = new OpenFolderDialog(); _options = new FolderPickerOpenOptions {AllowMultiple = false};
} }
/// <summary> /// <summary>
/// Set the title of the dialog /// Set the title of the dialog
/// </summary> /// </summary>
public OpenFolderDialogBuilder WithTitle(string? title) public OpenFolderDialogBuilder WithTitle(string? title)
{ {
_openFolderDialog.Title = title; _options.Title = title;
return this; return this;
} }
@ -36,7 +38,7 @@ public class OpenFolderDialogBuilder
/// </summary> /// </summary>
public OpenFolderDialogBuilder WithDirectory(string? directory) public OpenFolderDialogBuilder WithDirectory(string? directory)
{ {
_openFolderDialog.Directory = directory; _options.SuggestedStartLocation = directory != null ? _parent.StorageProvider.TryGetFolderFromPathAsync(directory).GetAwaiter().GetResult() : null;
return this; return this;
} }
@ -49,6 +51,7 @@ public class OpenFolderDialogBuilder
/// </returns> /// </returns>
public async Task<string?> ShowAsync() public async Task<string?> ShowAsync()
{ {
return await _openFolderDialog.ShowAsync(_parent); IReadOnlyList<IStorageFolder> folder = await _parent.StorageProvider.OpenFolderPickerAsync(_options);
return folder.FirstOrDefault()?.Path.AbsolutePath;
} }
} }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform.Storage;
namespace Artemis.UI.Shared.Services.Builders; namespace Artemis.UI.Shared.Services.Builders;
@ -11,7 +12,8 @@ namespace Artemis.UI.Shared.Services.Builders;
public class SaveFileDialogBuilder public class SaveFileDialogBuilder
{ {
private readonly Window _parent; private readonly Window _parent;
private readonly SaveFileDialog _saveFileDialog; private readonly FilePickerSaveOptions _options;
private List<FilePickerFileType>? _fileTypeFilters;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class. /// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class.
@ -20,7 +22,7 @@ public class SaveFileDialogBuilder
internal SaveFileDialogBuilder(Window parent) internal SaveFileDialogBuilder(Window parent)
{ {
_parent = parent; _parent = parent;
_saveFileDialog = new SaveFileDialog(); _options = new FilePickerSaveOptions();
} }
/// <summary> /// <summary>
@ -28,7 +30,7 @@ public class SaveFileDialogBuilder
/// </summary> /// </summary>
public SaveFileDialogBuilder WithTitle(string? title) public SaveFileDialogBuilder WithTitle(string? title)
{ {
_saveFileDialog.Title = title; _options.Title = title;
return this; return this;
} }
@ -37,7 +39,7 @@ public class SaveFileDialogBuilder
/// </summary> /// </summary>
public SaveFileDialogBuilder WithDirectory(string? directory) public SaveFileDialogBuilder WithDirectory(string? directory)
{ {
_saveFileDialog.Directory = directory; _options.SuggestedStartLocation = directory != null ? _parent.StorageProvider.TryGetFolderFromPathAsync(directory).GetAwaiter().GetResult() : null;
return this; return this;
} }
@ -46,16 +48,7 @@ public class SaveFileDialogBuilder
/// </summary> /// </summary>
public SaveFileDialogBuilder WithInitialFileName(string? initialFileName) public SaveFileDialogBuilder WithInitialFileName(string? initialFileName)
{ {
_saveFileDialog.InitialFileName = initialFileName; _options.SuggestedFileName = initialFileName;
return this;
}
/// <summary>
/// Set the default extension of the dialog
/// </summary>
public SaveFileDialogBuilder WithDefaultExtension(string? defaultExtension)
{
_saveFileDialog.DefaultExtension = defaultExtension;
return this; return this;
} }
@ -67,8 +60,9 @@ public class SaveFileDialogBuilder
FileDialogFilterBuilder builder = new(); FileDialogFilterBuilder builder = new();
configure(builder); configure(builder);
_saveFileDialog.Filters ??= new List<FileDialogFilter>(); _fileTypeFilters ??= new List<FilePickerFileType>();
_saveFileDialog.Filters.Add(builder.Build()); _fileTypeFilters.Add(builder.Build());
_options.FileTypeChoices = _fileTypeFilters;
return this; return this;
} }
@ -82,6 +76,7 @@ public class SaveFileDialogBuilder
/// </returns> /// </returns>
public async Task<string?> ShowAsync() public async Task<string?> ShowAsync()
{ {
return await _saveFileDialog.ShowAsync(_parent); IStorageFile? path = await _parent.StorageProvider.SaveFilePickerAsync(_options);
return path?.Path.AbsolutePath;
} }
} }

View File

@ -4,7 +4,7 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.Shared.Services; namespace Artemis.UI.Shared.Services;
internal class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel> internal partial class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
{ {
public ExceptionDialogView() public ExceptionDialogView()
{ {
@ -14,8 +14,4 @@ internal class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
#endif #endif
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -32,15 +32,4 @@
<StyleInclude Source="/Styles/Notifications.axaml" /> <StyleInclude Source="/Styles/Notifications.axaml" />
<StyleInclude Source="/Styles/NumberBox.axaml" /> <StyleInclude Source="/Styles/NumberBox.axaml" />
<StyleInclude Source="/Styles/TreeView.axaml" /> <StyleInclude Source="/Styles/TreeView.axaml" />
<!-- <Style Selector="Window:windows:windows10 /template/ Border#RootBorder"> -->
<!-- ~1~ This will show if custom accent color setting is used in Settings page@1@ -->
<!-- <Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" /> -->
<!-- <Setter Property="BorderThickness" Value="0 1 0 0" /> -->
<!-- </Style> -->
<!-- -->
<!-- <Style Selector="Window[IsActive=False]:windows:windows10 /template/ Border#RootBorder"> -->
<!-- <Setter Property="BorderBrush" Value="#3d3d3d" /> -->
<!-- <Setter Property="BorderThickness" Value="0 1 0 0" /> -->
<!-- </Style> -->
</Styles> </Styles>

View File

@ -8,7 +8,7 @@
<Border Classes="card" Margin="20"> <Border Classes="card" Margin="20">
<StackPanel> <StackPanel>
<TextBlock>I'm in a panel yo!</TextBlock> <TextBlock>I'm in a panel yo!</TextBlock>
<Separator Classes="card-separator" /> <Border Classes="card-separator" />
<TextBlock>I'm in a panel yo!</TextBlock> <TextBlock>I'm in a panel yo!</TextBlock>
</StackPanel> </StackPanel>
</Border> </Border>
@ -28,13 +28,6 @@
<Setter Property="ClipToBounds" Value="True" /> <Setter Property="ClipToBounds" Value="True" />
</Style> </Style>
<Style Selector="Border#TitleBar">
<Setter Property="Height" Value="40"></Setter>
</Style>
<Style Selector="Window:windows Border#TitleBar">
<Setter Property="Margin" Value="0 0 138 0"></Setter>
</Style>
<Style Selector="Border.card"> <Style Selector="Border.card">
<Setter Property="Padding" Value="16" /> <Setter Property="Padding" Value="16" />
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" /> <Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
@ -51,7 +44,7 @@
<Setter Property="CornerRadius" Value="{DynamicResource CardCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource CardCornerRadius}" />
</Style> </Style>
<Style Selector="Separator.card-separator"> <Style Selector="Border.card-separator">
<Setter Property="Background" Value="{DynamicResource ButtonBorderBrush}" /> <Setter Property="Background" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="Margin" Value="-12 15" /> <Setter Property="Margin" Value="-12 15" />
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />

View File

@ -2,14 +2,17 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20" Width="600" Height="600">
<Grid Height="24" ColumnDefinitions="*"> <Grid Height="24" ColumnDefinitions="*" Width="200">
<TextBox Classes="condensed" <TextBox Classes="condensed"
Text="#FFFF0000" Text="#FFFF0000"
Padding="2 2 30 2"> Padding="2 2 30 2"
>
</TextBox> </TextBox>
<controls:ColorPickerButton Classes="contained-color-picker-button" <controls:ColorPickerButton Classes="contained-color-picker-button"
ShowAcceptDismissButtons="False"/> Color="Red"
ShowAcceptDismissButtons="False"
/>
</Grid> </Grid>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
@ -25,23 +28,21 @@
<Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Cursor" Value="Hand" /> <Setter Property="Cursor" Value="Hand" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value>
<ControlTemplate> <ControlTemplate>
<controls:Button Name="MainButton" <Button Name="ShowFlyoutButton"
Padding="0" CornerRadius="{TemplateBinding CornerRadius}"
BorderThickness="0" HorizontalContentAlignment="Stretch"
CornerRadius="{TemplateBinding CornerRadius}"> Padding="0">
<Grid ColumnDefinitions="*,Auto">
<Border BorderBrush="{DynamicResource ColorPickerButtonOutline}" <Border BorderBrush="{DynamicResource ColorPickerButtonOutline}"
BorderThickness="1" BorderThickness="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
MinWidth="18" MinHeight="18" MinWidth="18" MinHeight="18"
Background="{TemplateBinding Color, Converter={StaticResource ColorBrushConv}}" Background="{TemplateBinding Color, Converter={StaticResource ColorBrushConv}}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}"/>
<controls:ColorPicker Name="ColorPicker" IsVisible="False" /> </Grid>
</Border> </Button>
</controls:Button>
</ControlTemplate> </ControlTemplate>
</Setter.Value>
</Setter> </Setter>
</Style> </Style>

View File

@ -102,7 +102,7 @@
<Setter Property="MinHeight" Value="24" /> <Setter Property="MinHeight" Value="24" />
</Style> </Style>
<Style Selector="dataModelPicker|DataModelPickerButton.condensed /template/ controls|Button"> <Style Selector="dataModelPicker|DataModelPickerButton.condensed /template/ Button">
<Setter Property="Padding" Value="6 3 11 3" /> <Setter Property="Padding" Value="6 3 11 3" />
<Setter Property="Height" Value="24" /> <Setter Property="Height" Value="24" />
</Style> </Style>

View File

@ -27,7 +27,7 @@
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<controls:Button Name="MainButton" <Button Name="MainButton"
CornerRadius="{TemplateBinding CornerRadius}" CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"
@ -51,7 +51,7 @@
Padding="2 2 2 0" Padding="2 2 2 0"
Margin="5 0" /> Margin="5 0" />
</Grid> </Grid>
</controls:Button> </Button>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

View File

@ -168,7 +168,7 @@
BorderBrush="{DynamicResource ButtonBorderBrush}" BorderBrush="{DynamicResource ButtonBorderBrush}"
BorderThickness="0 0 1 0" BorderThickness="0 0 1 0"
Padding="0 0 10 0"> Padding="0 0 10 0">
<fluent:ColorPicker Name="ColorPicker" <fluent:FAColorPicker Name="ColorPicker"
ColorTextType="HexAlpha" ColorTextType="HexAlpha"
UseColorWheel="True" UseColorWheel="True"
UseColorTriangle="True" UseColorTriangle="True"

View File

@ -1,15 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"> xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Width="200"> <Border Padding="20" Width="200">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<gradientPicker:GradientPickerButton IsCompact="True" /> <gradientPicker:GradientPickerButton IsCompact="True" />
<ComboBox HorizontalAlignment="Stretch"></ComboBox> <ComboBox HorizontalAlignment="Stretch"></ComboBox>
<controls:DropDownButton HorizontalAlignment="Stretch"></controls:DropDownButton> <DropDownButton HorizontalAlignment="Stretch"></DropDownButton>
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
@ -31,7 +29,7 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<controls:Button <Button
Name="MainButton" Name="MainButton"
Padding="0 0 12 0" Padding="0 0 12 0"
CornerRadius="{TemplateBinding CornerRadius}" CornerRadius="{TemplateBinding CornerRadius}"
@ -54,7 +52,7 @@
Margin="4,0,0,0" /> Margin="4,0,0,0" />
</Grid> </Grid>
</controls:Button> </Button>
</ControlTemplate> </ControlTemplate>

View File

@ -35,22 +35,20 @@
<ControlTemplate> <ControlTemplate>
<DataValidationErrors> <DataValidationErrors>
<Panel> <Panel>
<!-- This is flipped (scaleY(-1)) for the elevation brush effect <Border Name="PART_BorderElement"
-->
<Border
Name="PART_BorderElement"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
MinWidth="{TemplateBinding MinWidth}" MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}" MinHeight="{TemplateBinding MinHeight}">
RenderTransform="scaleY(-1)" </Border>
CornerRadius="{TemplateBinding CornerRadius}" />
<Border <Border Margin="{TemplateBinding BorderThickness}">
Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto" > <Grid ColumnDefinitions="Auto,*,Auto" >
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" /> <ContentPresenter Grid.Column="0"
Grid.ColumnSpan="1"
Content="{TemplateBinding InnerLeftContent}"/>
<Grid x:Name="PART_InnerGrid" <Grid x:Name="PART_InnerGrid"
Grid.Column="1" Grid.Column="1"
RowDefinitions="Auto,Auto" RowDefinitions="Auto,Auto"
@ -62,6 +60,7 @@
Name="PART_FloatingWatermark" Name="PART_FloatingWatermark"
Foreground="{DynamicResource SystemAccentColor}" Foreground="{DynamicResource SystemAccentColor}"
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"
IsVisible="False"
Text="{TemplateBinding Watermark}" /> Text="{TemplateBinding Watermark}" />
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
@ -75,16 +74,18 @@
<ScrollViewer Grid.Row="1" <ScrollViewer Grid.Row="1"
Grid.Column="1" Grid.Column="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<Panel> <Panel>
<TextBlock Name="PART_Watermark" <TextBlock Name="PART_Watermark"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
IsHitTestVisible="False" />
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
@ -92,6 +93,7 @@
SelectionEnd="{TemplateBinding SelectionEnd}" SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}" TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}" PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}" RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}" SelectionBrush="{TemplateBinding SelectionBrush}"

View File

@ -53,20 +53,23 @@
TemplatedControl.IsTemplateFocusTarget="True" TemplatedControl.IsTemplateFocusTarget="True"
Margin="2 2 0 2"> Margin="2 2 0 2">
<Panel> <Panel>
<Border Name="SelectionIndicator" <Rectangle Name="SelectionIndicator"
Width="3" Width="3"
Height="16" Height="16"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
CornerRadius="2" /> RadiusX="2"
RadiusY="2"
IsVisible="False"
Fill="{DynamicResource TreeViewItemSelectionIndicatorForeground}" />
<Grid Name="PART_Header" <Grid Name="PART_Header"
ColumnDefinitions="Auto, *" ColumnDefinitions="Auto, *"
Margin="{TemplateBinding Level, Mode=OneWay, Converter={StaticResource TreeViewItemLeftMarginConverter}}"> Margin="{TemplateBinding Level, Mode=OneWay, Converter={StaticResource TreeViewItemLeftMarginConverter}}">
<Panel Name="PART_ExpandCollapseChevronContainer" <Panel Name="PART_ExpandCollapseChevronContainer"
Margin="8 0 4 0"> Margin="{StaticResource TreeViewItemExpandCollapseChevronMargin}">
<ToggleButton Name="PART_ExpandCollapseChevron" <ToggleButton Name="PART_ExpandCollapseChevron"
Classes="ExpandCollapseChevron" Theme="{StaticResource TreeViewChevronButton}"
Focusable="False" Focusable="False"
IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" /> IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
</Panel> </Panel>
@ -74,6 +77,7 @@
Grid.Column="1" Grid.Column="1"
Focusable="False" Focusable="False"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />
@ -82,7 +86,6 @@
</Border> </Border>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
Margin="2 2 0 2"/> Margin="2 2 0 2"/>
</StackPanel> </StackPanel>

View File

@ -0,0 +1,8 @@
using DryIoc;
namespace Artemis.UI.Shared;
internal static class UI
{
public static IContainer Locator { get; set; } = null!;
}

View File

@ -13,7 +13,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.ReactiveUI;
using DryIoc; using DryIoc;
using ReactiveUI; using ReactiveUI;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework> <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<OutputPath>bin</OutputPath> <OutputPath>bin</OutputPath>
@ -21,18 +21,18 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.18" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Win32" Version="0.10.18" /> <PackageReference Include="Avalonia.Win32" Version="11.0.0-preview6" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1" /> <PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="6.0.0" /> <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" />
<PackageReference Include="RawInput.Sharp" Version="0.0.4" /> <PackageReference Include="RawInput.Sharp" Version="0.1.1" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.1-preview.108" /> <PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" /> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Artemis.UI.Windows": { "Artemis.UI.Windows": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "--force-elevation --disable-forced-shutdown --pcmr" "commandLineArgs": "--disable-forced-shutdown --pcmr"
} }
} }
} }

View File

@ -1,25 +0,0 @@
using System;
using Avalonia.Win32;
namespace Artemis.UI.Windows.Providers.Input;
public class SpongeWindow : WindowImpl
{
public event EventHandler<SpongeWindowEventArgs>? WndProcCalled;
#region Overrides of WindowImpl
/// <inheritdoc />
protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
OnWndProcCalled(new SpongeWindowEventArgs(hWnd, msg, wParam, lParam));
return base.WndProc(hWnd, msg, wParam, lParam);
}
#endregion
protected virtual void OnWndProcCalled(SpongeWindowEventArgs e)
{
WndProcCalled?.Invoke(this, e);
}
}

View File

@ -1,19 +0,0 @@
using System;
namespace Artemis.UI.Windows.Providers.Input;
public class SpongeWindowEventArgs : EventArgs
{
public SpongeWindowEventArgs(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
HWnd = hWnd;
Msg = msg;
WParam = wParam;
LParam = lParam;
}
public IntPtr HWnd { get; }
public uint Msg { get; }
public IntPtr WParam { get; }
public IntPtr LParam { get; }
}

View File

@ -5,6 +5,8 @@ using System.Timers;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Windows.Utilities; using Artemis.UI.Windows.Utilities;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Linearstar.Windows.RawInput; using Linearstar.Windows.RawInput;
using Linearstar.Windows.RawInput.Native; using Linearstar.Windows.RawInput.Native;
using Serilog; using Serilog;
@ -13,28 +15,43 @@ namespace Artemis.UI.Windows.Providers.Input;
public class WindowsInputProvider : InputProvider public class WindowsInputProvider : InputProvider
{ {
private const int GWL_WNDPROC = -4;
private const int WM_INPUT = 0x00FF; private const int WM_INPUT = 0x00FF;
private readonly IWindowImpl _window;
private readonly nint _hWndProcHook;
private readonly WndProc? _fnWndProcHook;
private readonly IInputService _inputService; private readonly IInputService _inputService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly SpongeWindow _sponge;
private readonly Timer _taskManagerTimer; private readonly Timer _taskManagerTimer;
private int _lastProcessId; private int _lastProcessId;
delegate nint WndProc(nint hWnd, uint msg, nint wParam, nint lParam);
private nint CustomWndProc(nint hWnd, uint msg, nint wParam, nint lParam)
{
OnWndProcCalled(hWnd, msg, wParam, lParam);
return CallWindowProc(_hWndProcHook, hWnd, msg, wParam, lParam);
}
public WindowsInputProvider(ILogger logger, IInputService inputService) public WindowsInputProvider(ILogger logger, IInputService inputService)
{ {
_logger = logger; _logger = logger;
_inputService = inputService; _inputService = inputService;
_sponge = new SpongeWindow();
_sponge.WndProcCalled += SpongeOnWndProcCalled;
_taskManagerTimer = new Timer(500); _taskManagerTimer = new Timer(500);
_taskManagerTimer.Elapsed += TaskManagerTimerOnElapsed; _taskManagerTimer.Elapsed += TaskManagerTimerOnElapsed;
_taskManagerTimer.Start(); _taskManagerTimer.Start();
RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, _sponge.Handle.Handle); _window = PlatformManager.CreateWindow();
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _sponge.Handle.Handle);
_hWndProcHook = GetWindowLongPtr(_window.Handle.Handle, GWL_WNDPROC);
_fnWndProcHook = CustomWndProc;
nint newLong = Marshal.GetFunctionPointerForDelegate(_fnWndProcHook);
SetWindowLongPtr(_window.Handle.Handle, GWL_WNDPROC, newLong);
RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, _window.Handle.Handle);
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _window.Handle.Handle);
} }
public static Guid Id { get; } = new("6737b204-ffb1-4cd9-8776-9fb851db303a"); public static Guid Id { get; } = new("6737b204-ffb1-4cd9-8776-9fb851db303a");
@ -55,19 +72,18 @@ public class WindowsInputProvider : InputProvider
{ {
if (disposing) if (disposing)
{ {
_sponge.Dispose();
_taskManagerTimer.Dispose(); _taskManagerTimer.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
private void SpongeOnWndProcCalled(object? sender, SpongeWindowEventArgs message) private void OnWndProcCalled(nint hWnd, uint msg, nint wParam, nint lParam)
{ {
if (message.Msg != WM_INPUT) if (msg != WM_INPUT)
return; return;
RawInputData data = RawInputData.FromHandle(message.LParam); RawInputData data = RawInputData.FromHandle(lParam);
switch (data) switch (data)
{ {
case RawInputMouseData mouse: case RawInputMouseData mouse:
@ -221,6 +237,15 @@ public class WindowsInputProvider : InputProvider
#region Native #region Native
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CallWindowProc(nint lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Unicode)]
private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Unicode)]
private static extern IntPtr SetWindowLongPtr(nint hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll")] [DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(ref Win32Point pt); private static extern bool GetCursorPos(ref Win32Point pt);

View File

@ -9,7 +9,6 @@ using Artemis.UI.Screens.Settings;
using Artemis.UI.Services.Updating; using Artemis.UI.Services.Updating;
using Artemis.UI.Shared.Services.MainWindow; using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading; using Avalonia.Threading;
using DryIoc.ImTools;
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Notifications;
using ReactiveUI; using ReactiveUI;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Avalonia.Win32; using Avalonia.Controls.Platform;
using Avalonia.Platform;
using SharpVk; using SharpVk;
using SharpVk.Khronos; using SharpVk.Khronos;
@ -10,7 +11,7 @@ internal sealed class Win32VkContext : VkContext
{ {
public Win32VkContext() public Win32VkContext()
{ {
Window = new WindowImpl(); Window = PlatformManager.CreateWindow();
Instance = Instance.Create(null, new[] {"VK_KHR_surface", "VK_KHR_win32_surface"}); Instance = Instance.Create(null, new[] {"VK_KHR_surface", "VK_KHR_win32_surface"});
PhysicalDevice = Instance.EnumeratePhysicalDevices().First(); PhysicalDevice = Instance.EnumeratePhysicalDevices().First();
Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, Window.Handle.Handle); Surface = Instance.CreateWin32Surface(Kernel32.CurrentModuleHandle, Window.Handle.Handle);
@ -43,7 +44,7 @@ internal sealed class Win32VkContext : VkContext
}; };
} }
public WindowImpl Window { get; } public IWindowImpl Window { get; }
public override void Dispose() public override void Dispose()
{ {

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputPath>bin/</OutputPath> <OutputPath>bin/</OutputPath>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
@ -15,29 +15,28 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="0.10.12.2" /> <PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" /> <PackageReference Include="Avalonia.Controls.Skia" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.16" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--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.18" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview6" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.18" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview6" />
<PackageReference Include="DryIoc.dll" Version="5.3.1" /> <PackageReference Include="DryIoc.dll" Version="5.3.4" />
<PackageReference Include="DynamicData" Version="7.9.14" /> <PackageReference Include="DynamicData" Version="7.13.1" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.0-preview6" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" /> <PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="0.10.13" /> <PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.0-b1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.100" /> <PackageReference Include="Octopus.Octodiff" Version="2.0.261" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" /> <PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.29" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.17" /> <PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.29" />
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="Splat.DryIoc" Version="14.6.1" /> <PackageReference Include="Splat.DryIoc" Version="14.6.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,5 @@
<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"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdebugger_005Ctabs/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdebugger_005Ctabs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdevice_005Ctabs/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdevice_005Ctabs/@EntryIndexedValue">True</s:Boolean>

View File

@ -106,7 +106,7 @@ public class SimpleContextDragBehavior : Behavior<Control>
PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (!properties.IsLeftButtonPressed || FocusManager.Instance?.Current is TextBox) if (!properties.IsLeftButtonPressed || FocusManager.Instance?.Current is TextBox)
return; return;
if (e.Source is not IControl control || AssociatedObject?.DataContext != control.DataContext) if (e.Source is not Control control || AssociatedObject?.DataContext != control.DataContext)
return; return;
_dragStartPoint = e.GetPosition(null); _dragStartPoint = e.GetPosition(null);

View File

@ -2,7 +2,6 @@
using System.Collections; using System.Collections;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -14,7 +13,7 @@ namespace Artemis.UI.Behaviors;
/// <summary> /// <summary>
/// </summary> /// </summary>
public class TreeItemDragBehavior : Behavior<IControl> public class TreeItemDragBehavior : Behavior<Control>
{ {
/// <summary> /// <summary>
/// </summary> /// </summary>
@ -31,7 +30,7 @@ public class TreeItemDragBehavior : Behavior<IControl>
public static readonly StyledProperty<double> VerticalDragThresholdProperty = public static readonly StyledProperty<double> VerticalDragThresholdProperty =
AvaloniaProperty.Register<TreeItemDragBehavior, double>(nameof(VerticalDragThreshold), 3); AvaloniaProperty.Register<TreeItemDragBehavior, double>(nameof(VerticalDragThreshold), 3);
private IControl? _draggedContainer; private Control? _draggedContainer;
private int _draggedIndex; private int _draggedIndex;
private bool _dragStarted; private bool _dragStarted;
private bool _enableDrag; private bool _enableDrag;
@ -69,12 +68,12 @@ public class TreeItemDragBehavior : Behavior<IControl>
{ {
base.OnAttached(); base.OnAttached();
if (AssociatedObject is { }) if (AssociatedObject is not null)
{ {
AssociatedObject.AddHandler(InputElement.PointerReleasedEvent, Released, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerReleasedEvent, ReleasedHandler, RoutingStrategies.Tunnel);
AssociatedObject.AddHandler(InputElement.PointerPressedEvent, Pressed, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerPressedEvent, PressedHandler, RoutingStrategies.Tunnel);
AssociatedObject.AddHandler(InputElement.PointerMovedEvent, Moved, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerMovedEvent, MovedHandler, RoutingStrategies.Tunnel);
AssociatedObject.AddHandler(InputElement.PointerCaptureLostEvent, CaptureLost, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerCaptureLostEvent, CaptureLostHandler, RoutingStrategies.Tunnel);
} }
} }
@ -84,16 +83,16 @@ public class TreeItemDragBehavior : Behavior<IControl>
{ {
base.OnDetaching(); base.OnDetaching();
if (AssociatedObject is { }) if (AssociatedObject is not null)
{ {
AssociatedObject.RemoveHandler(InputElement.PointerReleasedEvent, Released); AssociatedObject.RemoveHandler(InputElement.PointerReleasedEvent, ReleasedHandler);
AssociatedObject.RemoveHandler(InputElement.PointerPressedEvent, Pressed); AssociatedObject.RemoveHandler(InputElement.PointerPressedEvent, PressedHandler);
AssociatedObject.RemoveHandler(InputElement.PointerMovedEvent, Moved); AssociatedObject.RemoveHandler(InputElement.PointerMovedEvent, MovedHandler);
AssociatedObject.RemoveHandler(InputElement.PointerCaptureLostEvent, CaptureLost); AssociatedObject.RemoveHandler(InputElement.PointerCaptureLostEvent, CaptureLostHandler);
} }
} }
private void Pressed(object? sender, PointerPressedEventArgs e) private void PressedHandler(object? sender, PointerPressedEventArgs e)
{ {
PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (properties.IsLeftButtonPressed if (properties.IsLeftButtonPressed
@ -101,20 +100,20 @@ public class TreeItemDragBehavior : Behavior<IControl>
{ {
_enableDrag = true; _enableDrag = true;
_dragStarted = false; _dragStarted = false;
_start = e.GetPosition(AssociatedObject.Parent); _start = e.GetPosition(AssociatedObject.Parent as Visual);
_draggedIndex = -1; _draggedIndex = -1;
_targetIndex = -1; _targetIndex = -1;
_itemsControl = itemsControl; _itemsControl = itemsControl;
_draggedContainer = AssociatedObject; _draggedContainer = AssociatedObject;
if (_draggedContainer is { }) if (_draggedContainer is not null)
SetDraggingPseudoClasses(_draggedContainer, true); SetDraggingPseudoClasses(_draggedContainer, true);
AddTransforms(_itemsControl); AddTransforms(_itemsControl);
} }
} }
private void Released(object? sender, PointerReleasedEventArgs e) private void ReleasedHandler(object? sender, PointerReleasedEventArgs e)
{ {
if (_dragStarted) if (_dragStarted)
{ {
@ -125,7 +124,7 @@ public class TreeItemDragBehavior : Behavior<IControl>
} }
} }
private void CaptureLost(object? sender, PointerCaptureLostEventArgs e) private void CaptureLostHandler(object? sender, PointerCaptureLostEventArgs e)
{ {
Released(); Released();
} }
@ -137,19 +136,23 @@ public class TreeItemDragBehavior : Behavior<IControl>
RemoveTransforms(_itemsControl); RemoveTransforms(_itemsControl);
if (_itemsControl is { }) if (_itemsControl is not null)
foreach (ItemContainerInfo? container in _itemsControl.ItemContainerGenerator.Containers) {
SetDraggingPseudoClasses(container.ContainerControl, true); foreach (Control realizedContainer in _itemsControl.GetRealizedContainers())
SetDraggingPseudoClasses(realizedContainer, true);
}
if (_dragStarted) if (_dragStarted)
if (_draggedIndex >= 0 && _targetIndex >= 0 && _draggedIndex != _targetIndex) if (_draggedIndex >= 0 && _targetIndex >= 0 && _draggedIndex != _targetIndex)
MoveDraggedItem(_itemsControl, _draggedIndex, _targetIndex); MoveDraggedItem(_itemsControl, _draggedIndex, _targetIndex);
if (_itemsControl is { }) if (_itemsControl is not null)
foreach (ItemContainerInfo? container in _itemsControl.ItemContainerGenerator.Containers) {
SetDraggingPseudoClasses(container.ContainerControl, false); foreach (Control realizedContainer in _itemsControl.GetRealizedContainers())
SetDraggingPseudoClasses(realizedContainer, false);
}
if (_draggedContainer is { }) if (_draggedContainer is not null)
SetDraggingPseudoClasses(_draggedContainer, false); SetDraggingPseudoClasses(_draggedContainer, false);
_draggedIndex = -1; _draggedIndex = -1;
@ -166,16 +169,8 @@ public class TreeItemDragBehavior : Behavior<IControl>
if (itemsControl?.Items is null) if (itemsControl?.Items is null)
return; return;
int i = 0; foreach (Control container in itemsControl.GetRealizedContainers())
foreach (object? _ in itemsControl.Items)
{
IControl? container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
if (container is not null)
SetTranslateTransform(container, 0, 0); SetTranslateTransform(container, 0, 0);
i++;
}
} }
private void RemoveTransforms(ItemsControl? itemsControl) private void RemoveTransforms(ItemsControl? itemsControl)
@ -183,16 +178,8 @@ public class TreeItemDragBehavior : Behavior<IControl>
if (itemsControl?.Items is null) if (itemsControl?.Items is null)
return; return;
int i = 0; foreach (Control container in itemsControl.GetRealizedContainers())
foreach (object? _ in itemsControl.Items)
{
IControl? container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
if (container is not null)
SetTranslateTransform(container, 0, 0); SetTranslateTransform(container, 0, 0);
i++;
}
} }
private void MoveDraggedItem(ItemsControl? itemsControl, int draggedIndex, int targetIndex) private void MoveDraggedItem(ItemsControl? itemsControl, int draggedIndex, int targetIndex)
@ -208,7 +195,7 @@ public class TreeItemDragBehavior : Behavior<IControl>
selectingItemsControl.SelectedIndex = targetIndex; selectingItemsControl.SelectedIndex = targetIndex;
} }
private void Moved(object? sender, PointerEventArgs e) private void MovedHandler(object? sender, PointerEventArgs e)
{ {
PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (properties.IsLeftButtonPressed) if (properties.IsLeftButtonPressed)
@ -249,7 +236,7 @@ public class TreeItemDragBehavior : Behavior<IControl>
else else
SetTranslateTransform(_draggedContainer, 0, delta); SetTranslateTransform(_draggedContainer, 0, delta);
_draggedIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(_draggedContainer); _draggedIndex = _itemsControl.IndexFromContainer(_draggedContainer);
_targetIndex = -1; _targetIndex = -1;
Rect draggedBounds = _draggedContainer.Bounds; Rect draggedBounds = _draggedContainer.Bounds;
@ -264,16 +251,10 @@ public class TreeItemDragBehavior : Behavior<IControl>
? draggedBounds.X + delta + draggedBounds.Width ? draggedBounds.X + delta + draggedBounds.Width
: draggedBounds.Y + delta + draggedBounds.Height; : draggedBounds.Y + delta + draggedBounds.Height;
int i = 0; foreach (Control targetContainer in _itemsControl.GetRealizedContainers())
foreach (object? _ in _itemsControl.Items)
{ {
IControl? targetContainer = _itemsControl.ItemContainerGenerator.ContainerFromIndex(i); if (targetContainer.RenderTransform is null || ReferenceEquals(targetContainer, _draggedContainer))
if (targetContainer?.RenderTransform is null || ReferenceEquals(targetContainer, _draggedContainer))
{
i++;
continue; continue;
}
// If the target container has children, there are two options // If the target container has children, there are two options
// Move into the top of the container // Move into the top of the container
@ -286,7 +267,7 @@ public class TreeItemDragBehavior : Behavior<IControl>
? targetBounds.X + targetBounds.Width / 2 ? targetBounds.X + targetBounds.Width / 2
: targetBounds.Y + targetBounds.Height / 2; : targetBounds.Y + targetBounds.Height / 2;
int targetIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(targetContainer); int targetIndex = _itemsControl.IndexFromContainer(targetContainer);
if (targetStart > draggedStart && draggedDeltaEnd >= targetMid) if (targetStart > draggedStart && draggedDeltaEnd >= targetMid)
{ {
@ -315,22 +296,24 @@ public class TreeItemDragBehavior : Behavior<IControl>
else else
SetTranslateTransform(targetContainer, 0, 0); SetTranslateTransform(targetContainer, 0, 0);
} }
i++;
} }
} }
} }
private void SetDraggingPseudoClasses(IControl control, bool isDragging) private void SetDraggingPseudoClasses(Control? control, bool isDragging)
{ {
if (control == null)
return;
if (isDragging) if (isDragging)
((IPseudoClasses) control.Classes).Add(":dragging"); ((IPseudoClasses) control.Classes).Add(":dragging");
else else
((IPseudoClasses) control.Classes).Remove(":dragging"); ((IPseudoClasses) control.Classes).Remove(":dragging");
} }
private void SetTranslateTransform(IControl control, double x, double y) private void SetTranslateTransform(Control? control, double x, double y)
{ {
if (control == null)
return;
TransformOperations.Builder transformBuilder = new(1); TransformOperations.Builder transformBuilder = new(1);
transformBuilder.AppendTranslate(x, y); transformBuilder.AppendTranslate(x, y);
control.RenderTransform = transformBuilder.Build(); control.RenderTransform = transformBuilder.Build();

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
@ -137,47 +138,47 @@ public class TimelineHeader : Control
private void RenderLabel(DrawingContext drawingContext, string text, double x) private void RenderLabel(DrawingContext drawingContext, string text, double x)
{ {
Typeface typeFace = new(FontFamily); Typeface typeFace = new(FontFamily);
FormattedText formattedText = new(text, typeFace, 9, TextAlignment.Left, TextWrapping.NoWrap, Bounds.Size); FormattedText formattedText = new(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, 9, Foreground);
if (x == 0 && OffsetFirstValue) if (x == 0 && OffsetFirstValue)
drawingContext.DrawText(Foreground, new Point(2, 5), formattedText); drawingContext.DrawText(formattedText, new Point(2, 5));
else else
drawingContext.DrawText(Foreground, new Point(x - formattedText.Bounds.Width / 2, 5), formattedText); drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 5));
} }
private void UpdateTimeScale() private void UpdateTimeScale()
{ {
object[] subds; double[] subds;
if (PixelsPerSecond > 350) if (PixelsPerSecond > 350)
subds = new object[] {12d, 12d, 60d}; subds = new[] {12d, 12d, 60d};
else if (PixelsPerSecond > 250) else if (PixelsPerSecond > 250)
subds = new object[] {6d, 12d, 60d}; subds = new[] {6d, 12d, 60d};
else if (PixelsPerSecond > 200) else if (PixelsPerSecond > 200)
subds = new object[] {6d, 6d, 30d}; subds = new[] {6d, 6d, 30d};
else if (PixelsPerSecond > 150) else if (PixelsPerSecond > 150)
subds = new object[] {4d, 4d, 20d}; subds = new[] {4d, 4d, 20d};
else if (PixelsPerSecond > 140) else if (PixelsPerSecond > 140)
subds = new object[] {4d, 4d, 20d}; subds = new[] {4d, 4d, 20d};
else if (PixelsPerSecond > 90) else if (PixelsPerSecond > 90)
subds = new object[] {2d, 4d, 20d}; subds = new[] {2d, 4d, 20d};
else if (PixelsPerSecond > 60) else if (PixelsPerSecond > 60)
subds = new object[] {2d, 4d, 8d}; subds = new[] {2d, 4d, 8d};
else if (PixelsPerSecond > 40) else if (PixelsPerSecond > 40)
subds = new object[] {1d, 2d, 10d}; subds = new[] {1d, 2d, 10d};
else if (PixelsPerSecond > 30) else if (PixelsPerSecond > 30)
subds = new object[] {1d, 2d, 10d}; subds = new[] {1d, 2d, 10d};
else if (PixelsPerSecond > 10) else if (PixelsPerSecond > 10)
subds = new object[] {1d / 2d, 1d / 2d, 1d / 2d}; subds = new[] {1d / 2d, 1d / 2d, 1d / 2d};
else if (PixelsPerSecond > 4) else if (PixelsPerSecond > 4)
subds = new object[] {1d / 5d, 1d / 5d, 1d / 5d}; subds = new[] {1d / 5d, 1d / 5d, 1d / 5d};
else if (PixelsPerSecond > 3) else if (PixelsPerSecond > 3)
subds = new object[] {1d / 10d, 1d / 10d, 1d / 5d}; subds = new[] {1d / 10d, 1d / 10d, 1d / 5d};
else if (PixelsPerSecond > 1) else if (PixelsPerSecond > 1)
subds = new object[] {1d / 20d, 1d / 20d, 1d / 10d}; subds = new[] {1d / 20d, 1d / 20d, 1d / 10d};
else if (PixelsPerSecond >= 1) else if (PixelsPerSecond >= 1)
subds = new object[] {1d / 30d, 1d / 30d, 1d / 15d}; subds = new[] {1d / 30d, 1d / 30d, 1d / 15d};
else else
// 1s per pixel // 1s per pixel
subds = new object[] {1d / 60d, 1d / 60d, 1d / 15d}; subds = new[] {1d / 60d, 1d / 60d, 1d / 15d};
_subd1 = (double) subds[0]; // big ticks / labels _subd1 = (double) subds[0]; // big ticks / labels
_subd2 = (double) subds[1]; // medium ticks _subd2 = (double) subds[1]; // medium ticks

View File

@ -3,15 +3,11 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class BoolPropertyInputView : ReactiveUserControl<BoolPropertyInputViewModel> public partial class BoolPropertyInputView : ReactiveUserControl<BoolPropertyInputViewModel>
{ {
public BoolPropertyInputView() public BoolPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -3,15 +3,11 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class BrushPropertyInputView : ReactiveUserControl<BrushPropertyInputViewModel> public partial class BrushPropertyInputView : ReactiveUserControl<BrushPropertyInputViewModel>
{ {
public BrushPropertyInputView() public BrushPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
@ -11,7 +12,6 @@ using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using Avalonia.Controls.Mixins;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel> public partial class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel>
{ {
public ColorGradientPropertyInputView() public ColorGradientPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void GradientPickerButton_OnFlyoutOpened(GradientPickerButton sender, EventArgs args) private void GradientPickerButton_OnFlyoutOpened(GradientPickerButton sender, EventArgs args)
{ {

View File

@ -4,15 +4,11 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class EnumPropertyInputView : ReactiveUserControl<PropertyInputViewModel> public partial class EnumPropertyInputView : ReactiveUserControl<PropertyInputViewModel>
{ {
public EnumPropertyInputView() public EnumPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class FloatPropertyInputView : ReactiveUserControl<FloatPropertyInputViewModel> public partial class FloatPropertyInputView : ReactiveUserControl<FloatPropertyInputViewModel>
{ {
public FloatPropertyInputView() public FloatPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class FloatRangePropertyInputView : ReactiveUserControl<FloatRangePropertyInputViewModel> public partial class FloatRangePropertyInputView : ReactiveUserControl<FloatRangePropertyInputViewModel>
{ {
public FloatRangePropertyInputView() public FloatRangePropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class IntPropertyInputView : ReactiveUserControl<IntPropertyInputViewModel> public partial class IntPropertyInputView : ReactiveUserControl<IntPropertyInputViewModel>
{ {
public IntPropertyInputView() public IntPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class IntRangePropertyInputView : ReactiveUserControl<IntRangePropertyInputViewModel> public partial class IntRangePropertyInputView : ReactiveUserControl<IntRangePropertyInputViewModel>
{ {
public IntRangePropertyInputView() public IntRangePropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -5,6 +5,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView" x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView"
x:DataType="propertyInput:SKColorPropertyInputViewModel"> x:DataType="propertyInput:SKColorPropertyInputViewModel">
@ -13,13 +14,15 @@
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" /> <shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources> </UserControl.Resources>
<Grid Height="24" ColumnDefinitions="*"> <Grid Height="24" ColumnDefinitions="*">
<TextBox Classes="condensed" <TextBox Classes="condensed" Padding="2 2 30 2">
Text="{CompiledBinding InputValue, Converter={StaticResource SKColorToStringConverter}}" <Interaction.Behaviors>
Padding="2 2 30 2"> <behaviors:LostFocusTextBoxBindingBehavior Text="{CompiledBinding InputValue, Converter={StaticResource SKColorToStringConverter}}" />
</Interaction.Behaviors>
</TextBox> </TextBox>
<controls:ColorPickerButton Classes="contained-color-picker-button" <controls:ColorPickerButton Classes="contained-color-picker-button"
Color="{CompiledBinding InputValue, Converter={StaticResource SKColorToColorConverter}}" Color="{CompiledBinding InputValue, Converter={StaticResource SKColorToColorConverter}}"
ShowAcceptDismissButtons="False" ShowAcceptDismissButtons="False"
FlyoutPlacement="Right"
FlyoutOpened="ColorPickerButton_OnFlyoutOpened" FlyoutOpened="ColorPickerButton_OnFlyoutOpened"
FlyoutClosed="ColorPickerButton_OnFlyoutClosed" /> FlyoutClosed="ColorPickerButton_OnFlyoutClosed" />
</Grid> </Grid>

View File

@ -5,17 +5,13 @@ using FluentAvalonia.UI.Controls;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class SKColorPropertyInputView : ReactiveUserControl<SKColorPropertyInputViewModel> public partial class SKColorPropertyInputView : ReactiveUserControl<SKColorPropertyInputViewModel>
{ {
public SKColorPropertyInputView() public SKColorPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ColorPickerButton_OnFlyoutOpened(ColorPickerButton sender, EventArgs args) private void ColorPickerButton_OnFlyoutOpened(ColorPickerButton sender, EventArgs args)
{ {

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class SKPointPropertyInputView : ReactiveUserControl<SKPointPropertyInputViewModel> public partial class SKPointPropertyInputView : ReactiveUserControl<SKPointPropertyInputViewModel>
{ {
public SKPointPropertyInputView() public SKPointPropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -5,17 +5,13 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class SKSizePropertyInputView : ReactiveUserControl<SKSizePropertyInputViewModel> public partial class SKSizePropertyInputView : ReactiveUserControl<SKSizePropertyInputViewModel>
{ {
public SKSizePropertyInputView() public SKSizePropertyInputView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args) private void DraggableNumberBox_OnDragStarted(DraggableNumberBox sender, EventArgs args)
{ {

View File

@ -4,7 +4,7 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
public class StringPropertyInputView : ReactiveUserControl<FloatPropertyInputViewModel> public partial class StringPropertyInputView : ReactiveUserControl<FloatPropertyInputViewModel>
{ {
public StringPropertyInputView() public StringPropertyInputView()
{ {
@ -12,10 +12,6 @@ public class StringPropertyInputView : ReactiveUserControl<FloatPropertyInputVie
AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true); AddHandler(KeyUpEvent, OnRoutedKeyUp, handledEventsToo: true);
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnRoutedKeyUp(object? sender, KeyEventArgs e) private void OnRoutedKeyUp(object? sender, KeyEventArgs e)
{ {

View File

@ -1,7 +1,6 @@
using System.Reflection; using System.Reflection;
using Artemis.UI.DryIoc.Factories; using Artemis.UI.DryIoc.Factories;
using Artemis.UI.DryIoc.InstanceProviders; using Artemis.UI.DryIoc.InstanceProviders;
using Artemis.UI.Screens;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Services.Updating; using Artemis.UI.Services.Updating;
@ -9,7 +8,6 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Shared.PlatformSupport;
using DryIoc; using DryIoc;
namespace Artemis.UI.DryIoc; namespace Artemis.UI.DryIoc;

View File

@ -1,16 +1,26 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui" <windowing:AppWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.MainWindow" x:Class="Artemis.UI.MainWindow"
Icon="/Assets/Images/Logo/application.ico" Icon="/Assets/Images/Logo/application.ico"
Title="Artemis 2.0" Title="Artemis 2.0"
MinWidth="600" MinWidth="600"
MinHeight="400"> MinHeight="400">
<windowing:AppWindow.Styles>
<Styles>
<Style Selector="Border#TitleBarContainer">
<Setter Property="Height" Value="40"></Setter>
</Style>
<Style Selector="windowing|AppWindow:windows Border#TitleBarContainer">
<Setter Property="Margin" Value="0 0 138 0"></Setter>
</Style>
</Styles>
</windowing:AppWindow.Styles>
<Panel Name="RootPanel"> <Panel Name="RootPanel">
<Border Name="DragHandle" Background="Transparent" Height="40" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
<DockPanel> <DockPanel>
<ContentControl Name="SidebarContentControl" Content="{Binding SidebarViewModel}" DockPanel.Dock="Left" Width="240"> <ContentControl Name="SidebarContentControl" Content="{Binding SidebarViewModel}" DockPanel.Dock="Left" Width="240">
<ContentControl.Transitions> <ContentControl.Transitions>
@ -19,11 +29,11 @@
</Transitions> </Transitions>
</ContentControl.Transitions> </ContentControl.Transitions>
</ContentControl> </ContentControl>
<Border Name="TitleBar" DockPanel.Dock="Top"> <Border Name="TitleBarContainer" DockPanel.Dock="Top">
<ContentControl Content="{Binding TitleBarViewModel}"/> <ContentControl Content="{Binding TitleBarViewModel}"/>
</Border> </Border>
<ContentControl Content="{Binding}" /> <ContentControl Content="{Binding}" />
</DockPanel> </DockPanel>
<StackPanel Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right"/> <StackPanel Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Panel> </Panel>
</controls:CoreWindow> </windowing:AppWindow>

View File

@ -7,16 +7,13 @@ using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.ReactiveUI;
using FluentAvalonia.Core.ApplicationModel;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI; namespace Artemis.UI;
public class MainWindow : ReactiveCoreWindow<RootViewModel> public partial class MainWindow : ReactiveAppWindow<RootViewModel>
{ {
private readonly Panel _rootPanel;
private readonly ContentControl _sidebarContentControl;
private bool _activated; private bool _activated;
public MainWindow() public MainWindow()
@ -25,12 +22,10 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
Activated += OnActivated; Activated += OnActivated;
Deactivated += OnDeactivated; Deactivated += OnDeactivated;
ApplyWindowSize();
InitializeComponent(); InitializeComponent();
ApplyWindowSize();
_rootPanel = this.Get<Panel>("RootPanel"); RootPanel.LayoutUpdated += OnLayoutUpdated;
_sidebarContentControl = this.Get<ContentControl>("SidebarContentControl");
_rootPanel.LayoutUpdated += OnLayoutUpdated;
#if DEBUG #if DEBUG
this.AttachDevTools(); this.AttachDevTools();
@ -58,21 +53,15 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
RootViewModel.WindowSizeSetting.Value.ApplyFromWindow(this); RootViewModel.WindowSizeSetting.Value.ApplyFromWindow(this);
} }
// TODO: Replace with a media query once https://github.com/AvaloniaUI/Avalonia/pull/7938 is implemented
private void OnLayoutUpdated(object? sender, EventArgs e) private void OnLayoutUpdated(object? sender, EventArgs e)
{ {
_sidebarContentControl.Width = _rootPanel.Bounds.Width >= 1800 ? 300 : 240; SidebarContentControl.Width = RootPanel.Bounds.Width >= 1800 ? 300 : 240;
} }
private void OnOpened(object? sender, EventArgs e) private void OnOpened(object? sender, EventArgs e)
{ {
Opened -= OnOpened; Opened -= OnOpened;
ICoreApplicationView coreAppTitleBar = this; TitleBar.ExtendsContentIntoTitleBar = true;
if (coreAppTitleBar.TitleBar != null)
{
coreAppTitleBar.TitleBar.ExtendViewIntoTitleBar = true;
SetTitleBar(this.Get<Border>("DragHandle"));
}
} }
private void OnActivated(object? sender, EventArgs e) private void OnActivated(object? sender, EventArgs e)
@ -85,8 +74,4 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
ViewModel?.Unfocused(); ViewModel?.Unfocused();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -1,4 +1,4 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui" <windowing:AppWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,6 +6,7 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:debugger="clr-namespace:Artemis.UI.Screens.Debugger" xmlns:debugger="clr-namespace:Artemis.UI.Screens.Debugger"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Debugger.DebugView" x:Class="Artemis.UI.Screens.Debugger.DebugView"
x:DataType="debugger:DebugViewModel" x:DataType="debugger:DebugViewModel"
@ -15,7 +16,6 @@
Height="800"> Height="800">
<Window.Styles> <Window.Styles>
<StyleInclude Source="avares://AvaloniaEdit/AvaloniaEdit.xaml" />
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon"> <Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
<Setter Property="Margin" Value="-7 0 0 0" /> <Setter Property="Margin" Value="-7 0 0 0" />
</Style> </Style>
@ -38,4 +38,4 @@
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right" /> <StackPanel Grid.Column="0" Grid.ColumnSpan="2" Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
</Grid> </Grid>
</controls:CoreWindow> </windowing:AppWindow>

View File

@ -10,7 +10,7 @@ using ReactiveUI;
namespace Artemis.UI.Screens.Debugger; namespace Artemis.UI.Screens.Debugger;
public class DebugView : ReactiveCoreWindow<DebugViewModel> public partial class DebugView : ReactiveAppWindow<DebugViewModel>
{ {
public DebugView() public DebugView()
{ {
@ -29,10 +29,6 @@ public class DebugView : ReactiveCoreWindow<DebugViewModel>
}); });
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e) private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e)
{ {

View File

@ -3,15 +3,11 @@ using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.DataModel; namespace Artemis.UI.Screens.Debugger.DataModel;
public class DataModelDebugView : ReactiveUserControl<DataModelDebugViewModel> public partial class DataModelDebugView : ReactiveUserControl<DataModelDebugViewModel>
{ {
public DataModelDebugView() public DataModelDebugView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
} }

View File

@ -8,19 +8,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView" x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView"
x:DataType="logs:LogsDebugViewModel"> x:DataType="logs:LogsDebugViewModel">
<aedit:TextEditor Name="log" <ScrollViewer Name="LogsScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
Document="{ CompiledBinding Document }" <SelectableTextBlock Inlines="{CompiledBinding Lines}" FontFamily="Consolas" SizeChanged="Control_OnSizeChanged"></SelectableTextBlock>
IsReadOnly="True" </ScrollViewer>
FontFamily="Consolas"
FontSize="12"
HorizontalScrollBarVisibility="Auto"
Padding="0 15 15 15"
TextChanged="OnTextChanged" >
<aedit:TextEditor.Styles>
<Style Selector="aedit|TextArea">
<Setter Property="SelectionBrush" Value="#44ffffff" />
</Style>
</aedit:TextEditor.Styles>
</aedit:TextEditor>
</UserControl> </UserControl>

View File

@ -1,20 +1,16 @@
using System; using System;
using System.Diagnostics;
using System.Reflection;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using AvaloniaEdit; using ReactiveUI;
using Serilog;
namespace Artemis.UI.Screens.Debugger.Logs; namespace Artemis.UI.Screens.Debugger.Logs;
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel> public partial class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
{ {
private int _lineCount; private int _lineCount;
private TextEditor? _textEditor;
public LogsDebugView() public LogsDebugView()
{ {
@ -22,47 +18,46 @@ public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
InitializeComponent(); InitializeComponent();
} }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_textEditor = this.FindControl<TextEditor>("log");
}
protected override void OnInitialized() protected override void OnInitialized()
{ {
base.OnInitialized(); base.OnInitialized();
Dispatcher.UIThread.Post(() => _textEditor?.ScrollToEnd(), DispatcherPriority.ApplicationIdle); Dispatcher.UIThread.Post(() => LogsScrollViewer.ScrollToEnd(), DispatcherPriority.ApplicationIdle);
} }
private void OnTextChanged(object? sender, EventArgs e) // private void OnTextChanged(object? sender, EventArgs e)
// {
// if (LogTextEditor.ExtentHeight == 0)
// return;
//
// int linesAdded = LogTextEditor.LineCount - _lineCount;
// double lineHeight = LogTextEditor.ExtentHeight / LogTextEditor.LineCount;
// double outOfScreenTextHeight = LogTextEditor.ExtentHeight - LogTextEditor.VerticalOffset - LogTextEditor.ViewportHeight;
// double outOfScreenLines = outOfScreenTextHeight / lineHeight;
//
// //we need this help distance because of rounding.
// //if we scroll slightly above the end, we still want it
// //to scroll down to the new lines.
// const double GRACE_DISTANCE = 1d;
//
// //if we were at the bottom of the log and
// //if the last log event was 5 lines long
// //we will be 5 lines out sync.
// //if this is the case, scroll down.
//
// //if we are more than that out of sync,
// //the user scrolled up and we should not
// //mess with anything.
// if (_lineCount == 0 || linesAdded + GRACE_DISTANCE > outOfScreenLines)
// {
// Dispatcher.UIThread.Post(() => LogTextEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle);
// _lineCount = LogTextEditor.LineCount;
// }
// }
private void Control_OnSizeChanged(object? sender, SizeChangedEventArgs e)
{ {
if (_textEditor is null) if (!(LogsScrollViewer.Extent.Height - LogsScrollViewer.Offset.Y - LogsScrollViewer.Bounds.Bottom <= 60))
return; return;
if (_textEditor.ExtentHeight == 0) Dispatcher.UIThread.Post(() => LogsScrollViewer.ScrollToEnd(), DispatcherPriority.Normal);
return;
int linesAdded = _textEditor.LineCount - _lineCount;
double lineHeight = _textEditor.ExtentHeight / _textEditor.LineCount;
double outOfScreenTextHeight = _textEditor.ExtentHeight - _textEditor.VerticalOffset - _textEditor.ViewportHeight;
double outOfScreenLines = outOfScreenTextHeight / lineHeight;
//we need this help distance because of rounding.
//if we scroll slightly above the end, we still want it
//to scroll down to the new lines.
const double GRACE_DISTANCE = 1d;
//if we were at the bottom of the log and
//if the last log event was 5 lines long
//we will be 5 lines out sync.
//if this is the case, scroll down.
//if we are more than that out of sync,
//the user scrolled up and we should not
//mess with anything.
if (_lineCount == 0 || linesAdded + GRACE_DISTANCE > outOfScreenLines)
{
Dispatcher.UIThread.Post(() => _textEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle);
_lineCount = _textEditor.LineCount;
}
} }
} }

View File

@ -1,12 +1,15 @@
using Artemis.Core; using System;
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Avalonia.Threading; using Avalonia.Threading;
using AvaloniaEdit.Document;
using ReactiveUI; using ReactiveUI;
using Serilog.Events; using Serilog.Events;
using Serilog.Formatting.Display; using Serilog.Formatting.Display;
using System.IO; using System.IO;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls.Documents;
using Avalonia.Media;
namespace Artemis.UI.Screens.Debugger.Logs; namespace Artemis.UI.Screens.Debugger.Logs;
@ -14,14 +17,14 @@ public class LogsDebugViewModel : ActivatableViewModelBase
{ {
private readonly MessageTemplateTextFormatter _formatter; private readonly MessageTemplateTextFormatter _formatter;
public TextDocument Document { get; } public InlineCollection Lines { get; } = new InlineCollection();
private const int MAX_ENTRIES = 1000; private const int MAX_ENTRIES = 1000;
public LogsDebugViewModel() public LogsDebugViewModel()
{ {
DisplayName = "Logs"; DisplayName = "Logs";
Document = new TextDocument();
_formatter = new MessageTemplateTextFormatter( _formatter = new MessageTemplateTextFormatter(
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"
); );
@ -33,19 +36,13 @@ public class LogsDebugViewModel : ActivatableViewModelBase
{ {
LogStore.EventAdded += OnLogEventAdded; LogStore.EventAdded += OnLogEventAdded;
Disposable.Create(() => Disposable.Create(() => { LogStore.EventAdded -= OnLogEventAdded; }).DisposeWith(disp);
{
LogStore.EventAdded -= OnLogEventAdded;
}).DisposeWith(disp);
}); });
} }
private void OnLogEventAdded(object? sender, LogEventEventArgs e) private void OnLogEventAdded(object? sender, LogEventEventArgs e)
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() => { AddLogEvent(e.LogEvent); });
{
AddLogEvent(e.LogEvent);
});
} }
private void AddLogEvent(LogEvent? logEvent) private void AddLogEvent(LogEvent? logEvent)
@ -56,22 +53,27 @@ public class LogsDebugViewModel : ActivatableViewModelBase
using StringWriter writer = new(); using StringWriter writer = new();
_formatter.Format(logEvent, writer); _formatter.Format(logEvent, writer);
string line = writer.ToString(); string line = writer.ToString();
Document.Insert(Document.TextLength, '\n' + line.TrimEnd('\r', '\n'));
while (Document.LineCount > MAX_ENTRIES)
RemoveOldestLine(); Lines.Add(new Run(line.TrimEnd('\r', '\n') + '\n')
{
Foreground = logEvent.Level switch
{
LogEventLevel.Verbose => new SolidColorBrush(Colors.White),
LogEventLevel.Debug => new SolidColorBrush(Color.FromRgb(216, 216, 216)),
LogEventLevel.Information => new SolidColorBrush(Color.FromRgb(93, 201, 255)),
LogEventLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 177, 53)),
LogEventLevel.Error => new SolidColorBrush(Color.FromRgb(255, 63, 63)),
LogEventLevel.Fatal => new SolidColorBrush(Colors.Red),
_ => throw new ArgumentOutOfRangeException()
}
});
LimitLines();
} }
private void RemoveOldestLine() private void LimitLines()
{ {
int firstNewLine = Document.IndexOf('\n', 0, Document.TextLength); if (Lines.Count > MAX_ENTRIES)
if (firstNewLine == -1) Lines.RemoveRange(0, Lines.Count - MAX_ENTRIES);
{
//this should never happen.
//just in case let's return
//instead of throwing
return;
}
Document.Remove(0, firstNewLine + 1);
} }
} }

Some files were not shown because too many files have changed in this diff Show More