mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Meta - Replace WPF projects with Avalonia projects
This commit is contained in:
parent
fcf0376a9a
commit
d7f3ee1190
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
@ -18,7 +19,7 @@
|
|||||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
1828
src/Artemis.UI.Linux/packages.lock.json
Normal file
1828
src/Artemis.UI.Linux/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
@ -18,7 +19,7 @@
|
|||||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
1828
src/Artemis.UI.MacOS/packages.lock.json
Normal file
1828
src/Artemis.UI.MacOS/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,76 +1,50 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-windows</TargetFramework>
|
<OutputType>Library</OutputType>
|
||||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<Nullable>enable</Nullable>
|
||||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
<ApplicationIcon />
|
||||||
<AssemblyTitle>Artemis.UI.Shared</AssemblyTitle>
|
<StartupObject />
|
||||||
<Company>Artemis.UI.Shared</Company>
|
|
||||||
<Product>Artemis.UI.Shared</Product>
|
|
||||||
<Copyright>Copyright © Robert Beekman - 2020</Copyright>
|
|
||||||
<OutputPath>bin\</OutputPath>
|
<OutputPath>bin\</OutputPath>
|
||||||
<UseWPF>true</UseWPF>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<DocumentationFile>bin\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
|
||||||
<DocumentationFile>bin\Artemis.UI.Shared.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<NrtRevisionFormat>2.0-{chash:6}</NrtRevisionFormat>
|
|
||||||
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
|
|
||||||
<NrtResolveInformationalAttribute>true</NrtResolveInformationalAttribute>
|
|
||||||
<NrtResolveCopyright>true</NrtResolveCopyright>
|
|
||||||
<NrtTagMatch>v[0-9]*</NrtTagMatch>
|
|
||||||
<NrtRemoveTagV>true</NrtRemoveTagV>
|
|
||||||
<NrtRequiredVcs>git</NrtRequiredVcs>
|
|
||||||
<NrtShowRevision>true</NrtShowRevision>
|
|
||||||
<Version>2.0.0</Version>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
|
||||||
<DocumentationFile>bin\Artemis.UI.Shared.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.11.10" />
|
<None Remove="Artemis.UI.Avalonia.Shared.csproj.DotSettings" />
|
||||||
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
|
<None Remove="Artemis.UI.Shared.csproj.DotSettings" />
|
||||||
<PackageReference Include="MaterialDesignThemes" Version="4.1.0" />
|
<None Remove="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml" />
|
||||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />
|
</ItemGroup>
|
||||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
<ItemGroup>
|
||||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||||
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
||||||
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
|
||||||
|
<PackageReference Include="DynamicData" Version="7.5.4" />
|
||||||
|
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
||||||
|
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||||
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
|
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
||||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" />
|
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" />
|
||||||
<PackageReference Include="SharpVectors.Reloaded" Version="1.7.5" />
|
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
|
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
|
||||||
<PackageReference Include="SkiaSharp.Views.WPF" Version="2.80.3" />
|
</ItemGroup>
|
||||||
<PackageReference Include="Stylet" Version="1.3.6" />
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
<Page Include="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml">
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
</Page>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="obj\x64\Debug\ColorPicker.g.cs" />
|
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<Compile Remove="obj\x64\Debug\ColorPicker.g.i.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\DraggableDouble.g.i.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\DraggableFloat.g.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\DraggableFloat.g.i.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\DraggableInt.g.i.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\GeneratedInternalTypeHelper.g.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\GeneratedInternalTypeHelper.g.i.cs" />
|
|
||||||
<Compile Remove="obj\x64\Debug\UserControl1.g.i.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\Fonts\RobotoMono-Regular.ttf" />
|
<Compile Update="Controls\HotkeyBox.axaml.cs">
|
||||||
|
<DependentUpon>HotkeyBox.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Services\Window\ExceptionDialogView.axaml.cs">
|
||||||
|
<DependentUpon>%(Filename)</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
</Project>
|
||||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj">
|
|
||||||
<Private>false</Private>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
|
|||||||
@ -1,20 +1,6 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=behaviors/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=converters/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=datamodelvisualization/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=datamodelvisualization_005Cshared/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=dependencyproperties/@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/=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/=extensions/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ninject/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ninject_005Cfactories/@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/=propertyinput/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdatamodelvisualization/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdialog/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cmessage/@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>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utilities/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindows/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@ -1,41 +0,0 @@
|
|||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Navigation;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Microsoft.Xaml.Behaviors;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a behavior that opens the URI of the hyperlink in the browser when requested
|
|
||||||
/// </summary>
|
|
||||||
public class OpenInBrowser : Behavior<Hyperlink>
|
|
||||||
{
|
|
||||||
private Hyperlink? _hyperLink;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttached()
|
|
||||||
{
|
|
||||||
base.OnAttached();
|
|
||||||
|
|
||||||
_hyperLink = AssociatedObject;
|
|
||||||
if (_hyperLink == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_hyperLink.RequestNavigate += HyperLinkOnRequestNavigate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnDetaching()
|
|
||||||
{
|
|
||||||
if (_hyperLink == null) return;
|
|
||||||
_hyperLink.RequestNavigate -= HyperLinkOnRequestNavigate;
|
|
||||||
|
|
||||||
base.OnDetaching();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HyperLinkOnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
|
||||||
{
|
|
||||||
Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using Microsoft.Xaml.Behaviors;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a behavior that puts the cursor at the end of a text box when it receives focus
|
|
||||||
/// </summary>
|
|
||||||
public class PutCursorAtEndTextBox : Behavior<UIElement>
|
|
||||||
{
|
|
||||||
private TextBox? _textBox;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttached()
|
|
||||||
{
|
|
||||||
base.OnAttached();
|
|
||||||
|
|
||||||
_textBox = AssociatedObject as TextBox;
|
|
||||||
|
|
||||||
if (_textBox == null) return;
|
|
||||||
_textBox.GotFocus += TextBoxGotFocus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnDetaching()
|
|
||||||
{
|
|
||||||
if (_textBox == null) return;
|
|
||||||
_textBox.GotFocus -= TextBoxGotFocus;
|
|
||||||
|
|
||||||
base.OnDetaching();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
|
|
||||||
{
|
|
||||||
if (_textBox == null) return;
|
|
||||||
_textBox.CaretIndex = _textBox.Text.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using Microsoft.Xaml.Behaviors;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A behavior that makes a scroll viewer bubble up its events if it is at its scroll limit
|
|
||||||
/// </summary>
|
|
||||||
public class ScrollParentWhenAtMax : Behavior<FrameworkElement>
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttached()
|
|
||||||
{
|
|
||||||
base.OnAttached();
|
|
||||||
AssociatedObject.PreviewMouseWheel += PreviewMouseWheel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnDetaching()
|
|
||||||
{
|
|
||||||
AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel;
|
|
||||||
base.OnDetaching();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
|
||||||
{
|
|
||||||
ScrollViewer? scrollViewer = GetVisualChild<ScrollViewer>(AssociatedObject);
|
|
||||||
if (scrollViewer == null)
|
|
||||||
return;
|
|
||||||
double scrollPos = scrollViewer.ContentVerticalOffset;
|
|
||||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|
||||||
if (scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0 || scrollPos == 0 && e.Delta > 0)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
MouseWheelEventArgs e2 = new(e.MouseDevice, e.Timestamp, e.Delta);
|
|
||||||
e2.RoutedEvent = UIElement.MouseWheelEvent;
|
|
||||||
AssociatedObject.RaiseEvent(e2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T? GetVisualChild<T>(DependencyObject parent) where T : Visual
|
|
||||||
{
|
|
||||||
T? child = default;
|
|
||||||
|
|
||||||
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
|
|
||||||
for (int i = 0; i < numVisuals; i++)
|
|
||||||
{
|
|
||||||
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
|
|
||||||
child = v as T;
|
|
||||||
if (child == null) child = GetVisualChild<T>(v);
|
|
||||||
if (child != null) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
using Artemis.UI.Shared.Controls;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Ninject;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the main entry point for the shared UI library
|
|
||||||
/// <para>The Artemis UI calls this so there's no need to deal with this in a plugin</para>
|
|
||||||
/// </summary>
|
|
||||||
public static class Bootstrapper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean indicating whether or not the shared UI library has been initialized
|
|
||||||
/// </summary>
|
|
||||||
public static bool Initialized { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the shared UI library
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kernel"></param>
|
|
||||||
public static void Initialize(IKernel kernel)
|
|
||||||
{
|
|
||||||
if (Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
GradientPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
|
||||||
ColorPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
|
||||||
DataModelPicker.DataModelUIService = kernel.Get<IDataModelUIService>();
|
|
||||||
|
|
||||||
Initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Shared.ArtemisIcon"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
|
||||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<ContentControl Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Icon}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Focusable="False">
|
|
||||||
<ContentControl.Resources>
|
|
||||||
<DataTemplate DataType="{x:Type system:Uri}">
|
|
||||||
<Rectangle Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Foreground}">
|
|
||||||
<Rectangle.OpacityMask>
|
|
||||||
<VisualBrush Stretch="Uniform">
|
|
||||||
<VisualBrush.Visual>
|
|
||||||
<Image Source="{Binding Converter={svgc:SvgImageConverter}, Mode=OneWay}"/>
|
|
||||||
</VisualBrush.Visual>
|
|
||||||
</VisualBrush>
|
|
||||||
</Rectangle.OpacityMask>
|
|
||||||
</Rectangle>
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate DataType="{x:Type materialDesign:PackIconKind}">
|
|
||||||
<materialDesign:PackIcon Kind="{Binding}" Width="Auto" Height="Auto"/>
|
|
||||||
</DataTemplate>
|
|
||||||
</ContentControl.Resources>
|
|
||||||
</ContentControl>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using MaterialDesignThemes.Wpf;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for ArtemisIcon.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class ArtemisIcon : UserControl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
|
|
||||||
/// to an SVG
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(object), typeof(ArtemisIcon),
|
|
||||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="PackIconKind" />
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty PackIconProperty = DependencyProperty.Register(nameof(PackIcon), typeof(PackIconKind?), typeof(ArtemisIcon),
|
|
||||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="Uri" /> pointing to the SVG
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty SvgSourceProperty = DependencyProperty.Register(nameof(SvgSource), typeof(Uri), typeof(ArtemisIcon),
|
|
||||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
|
||||||
|
|
||||||
private bool _inCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ArtemisIcon"/> class
|
|
||||||
/// </summary>
|
|
||||||
public ArtemisIcon()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
|
|
||||||
/// to an SVG
|
|
||||||
/// </summary>
|
|
||||||
public object? Icon
|
|
||||||
{
|
|
||||||
get => GetValue(IconProperty);
|
|
||||||
set => SetValue(IconProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="PackIconKind" />
|
|
||||||
/// </summary>
|
|
||||||
public PackIconKind? PackIcon
|
|
||||||
{
|
|
||||||
get => (PackIconKind?) GetValue(PackIconProperty);
|
|
||||||
set => SetValue(PackIconProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="Uri" /> pointing to the SVG
|
|
||||||
/// </summary>
|
|
||||||
public Uri SvgSource
|
|
||||||
{
|
|
||||||
get => (Uri) GetValue(SvgSourceProperty);
|
|
||||||
set => SetValue(SvgSourceProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void IconPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ArtemisIcon artemisIcon = (ArtemisIcon) d;
|
|
||||||
if (artemisIcon._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
artemisIcon._inCallback = true;
|
|
||||||
if (artemisIcon.PackIcon != null)
|
|
||||||
{
|
|
||||||
artemisIcon.Icon = artemisIcon.PackIcon;
|
|
||||||
}
|
|
||||||
else if (artemisIcon.SvgSource != null)
|
|
||||||
{
|
|
||||||
artemisIcon.Icon = artemisIcon.SvgSource;
|
|
||||||
}
|
|
||||||
else if (artemisIcon.Icon is string iconString)
|
|
||||||
{
|
|
||||||
if (Uri.TryCreate(iconString, UriKind.Absolute, out Uri? uriResult))
|
|
||||||
artemisIcon.Icon = uriResult;
|
|
||||||
else if (Enum.TryParse(typeof(PackIconKind), iconString, true, out object? result))
|
|
||||||
artemisIcon.Icon = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
artemisIcon._inCallback = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
<UserControl
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
|
||||||
x:Class="Artemis.UI.Shared.ColorPicker"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="101.848" d:DesignWidth="242.956">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/Resources/ArtemisShared.xaml" />
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ColorPicker.xaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
<shared:ColorToStringConverter x:Key="ColorToStringConverter" />
|
|
||||||
<shared:ColorToSolidColorConverter x:Key="ColorToSolidColorConverter" />
|
|
||||||
<ControlTemplate x:Key="MaterialDesignOpacitySlider" TargetType="{x:Type Slider}">
|
|
||||||
<Border
|
|
||||||
x:Name="border"
|
|
||||||
Background="{TemplateBinding Background}"
|
|
||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
|
||||||
BorderThickness="{TemplateBinding BorderThickness}"
|
|
||||||
SnapsToDevicePixels="True">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Border Grid.Row="1" Height="8" CornerRadius="4" Background="{StaticResource Checkerboard}">
|
|
||||||
<Border Height="8" CornerRadius="4">
|
|
||||||
<Border.Background>
|
|
||||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
|
||||||
<GradientStop Offset="0" Color="Transparent" />
|
|
||||||
<GradientStop Offset="1.0"
|
|
||||||
Color="{Binding Color, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorToSolidColorConverter}}" />
|
|
||||||
</LinearGradientBrush>
|
|
||||||
</Border.Background>
|
|
||||||
</Border>
|
|
||||||
</Border>
|
|
||||||
<Track x:Name="PART_Track"
|
|
||||||
Grid.Row="1"
|
|
||||||
OpacityMask="{x:Null}">
|
|
||||||
<Track.DecreaseRepeatButton>
|
|
||||||
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
|
|
||||||
</Track.DecreaseRepeatButton>
|
|
||||||
<Track.IncreaseRepeatButton>
|
|
||||||
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
|
|
||||||
</Track.IncreaseRepeatButton>
|
|
||||||
<Track.Thumb>
|
|
||||||
<Thumb x:Name="Thumb" Width="20" Height="20" VerticalAlignment="Center" Focusable="False" OverridesDefaultStyle="True"
|
|
||||||
Template="{DynamicResource MaterialDesignColorSliderThumb}">
|
|
||||||
<Thumb.Foreground>
|
|
||||||
<SolidColorBrush
|
|
||||||
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay, Converter={StaticResource ColorToSolidColorConverter}}" />
|
|
||||||
</Thumb.Foreground>
|
|
||||||
</Thumb>
|
|
||||||
</Track.Thumb>
|
|
||||||
</Track>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid HorizontalAlignment="Stretch">
|
|
||||||
<TextBox x:Name="ColorCodeTextBox"
|
|
||||||
materialDesign:TextFieldAssist.TextBoxViewMargin="0 2 20 0"
|
|
||||||
materialDesign:HintAssist.IsFloating="False"
|
|
||||||
Style="{Binding TextBoxStyle, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Text="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorToStringConverter}}"
|
|
||||||
MinWidth="95"
|
|
||||||
MaxLength="9"
|
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
FontFamily="Consolas"
|
|
||||||
CharacterCasing="Upper">
|
|
||||||
<materialDesign:TextFieldAssist.CharacterCounterStyle>
|
|
||||||
<Style TargetType="TextBlock" />
|
|
||||||
</materialDesign:TextFieldAssist.CharacterCounterStyle>
|
|
||||||
</TextBox>
|
|
||||||
|
|
||||||
<Border Width="15"
|
|
||||||
Height="15"
|
|
||||||
CornerRadius="15"
|
|
||||||
Margin="0 0 2 0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Background="{StaticResource Checkerboard}">
|
|
||||||
<Ellipse Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
|
|
||||||
<Ellipse.Fill>
|
|
||||||
<SolidColorBrush Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay}" />
|
|
||||||
</Ellipse.Fill>
|
|
||||||
</Ellipse>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Popup AllowsTransparency="True"
|
|
||||||
Placement="Bottom"
|
|
||||||
CustomPopupPlacementCallback="{x:Static materialDesign:CustomPopupPlacementCallbackHelper.LargePopupCallback}"
|
|
||||||
PlacementTarget="{Binding ElementName=ColorCodeTextBox}"
|
|
||||||
StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
PopupAnimation="Fade"
|
|
||||||
IsOpen="{Binding PopupOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
|
||||||
<materialDesign:Card Width="200" Height="260" Margin="30" materialDesign:ShadowAssist.ShadowDepth="Depth4">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<materialDesign:ColorPicker Grid.Row="0"
|
|
||||||
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
MouseUp="ColorGradient_OnMouseUp"
|
|
||||||
PreviewMouseDown="Slider_OnMouseDown"
|
|
||||||
PreviewMouseUp="Slider_OnMouseUp" />
|
|
||||||
<Slider Grid.Row="1" Margin="8 2 8 8"
|
|
||||||
IsMoveToPointEnabled="True"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Style="{DynamicResource MaterialDesignColorSlider}"
|
|
||||||
Template="{StaticResource MaterialDesignOpacitySlider}"
|
|
||||||
Value="{Binding ColorOpacity, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
PreviewMouseDown="Slider_OnMouseDown"
|
|
||||||
PreviewMouseUp="Slider_OnMouseUp"
|
|
||||||
Maximum="255" />
|
|
||||||
|
|
||||||
<ItemsControl Grid.Row="2" x:Name="RecentColorsContainer" Margin="10 0" HorizontalAlignment="Left">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Border CornerRadius="4" Background="{StaticResource Checkerboard}" Width="16" Height="16" Margin="2">
|
|
||||||
<Rectangle RadiusX="4" RadiusY="4" Cursor="Hand" MouseLeftButtonDown="SelectRecentColor" ToolTip="{Binding}">
|
|
||||||
<Rectangle.Fill>
|
|
||||||
<SolidColorBrush Color="{Binding}" />
|
|
||||||
</Rectangle.Fill>
|
|
||||||
</Rectangle>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<WrapPanel Orientation="Horizontal" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
</ItemsControl>
|
|
||||||
|
|
||||||
<CheckBox Grid.Row="3"
|
|
||||||
x:Name="PreviewCheckBox"
|
|
||||||
Margin="10 5 0 10"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Style="{StaticResource MaterialDesignCheckBox}"
|
|
||||||
Click="PreviewCheckBoxClick">
|
|
||||||
Preview on devices
|
|
||||||
</CheckBox>
|
|
||||||
</Grid>
|
|
||||||
</materialDesign:Card>
|
|
||||||
</Popup>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,303 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Shapes;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for ColorPicker.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class ColorPicker : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private static IColorPickerService? _colorPickerService;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the color
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(nameof(Color), typeof(Color), typeof(ColorPicker),
|
|
||||||
new FrameworkPropertyMetadata(default(Color), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating that the popup containing the color picker is open
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty PopupOpenProperty = DependencyProperty.Register(nameof(PopupOpen), typeof(bool), typeof(ColorPicker),
|
|
||||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PopupOpenPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty StaysOpenProperty = DependencyProperty.Register(nameof(StaysOpen), typeof(bool), typeof(ColorPicker),
|
|
||||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, StaysOpenPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the style used on the text box
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty TextBoxStyleProperty = DependencyProperty.Register(nameof(TextBoxStyle), typeof(Style), typeof(ColorPicker),
|
|
||||||
new FrameworkPropertyMetadata(Application.Current.Resources["MaterialDesignTextBox"]));
|
|
||||||
|
|
||||||
internal static readonly DependencyProperty ColorOpacityProperty = DependencyProperty.Register(nameof(ColorOpacity), typeof(byte), typeof(ColorPicker),
|
|
||||||
new FrameworkPropertyMetadata((byte) 255, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorOpacityPropertyChangedCallback));
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the selected color has changed
|
|
||||||
/// </summary>
|
|
||||||
public static readonly RoutedEvent ColorChangedEvent =
|
|
||||||
EventManager.RegisterRoutedEvent(
|
|
||||||
nameof(Color),
|
|
||||||
RoutingStrategy.Bubble,
|
|
||||||
typeof(RoutedPropertyChangedEventHandler<Color>),
|
|
||||||
typeof(ColorPicker));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the popup opens or closes
|
|
||||||
/// </summary>
|
|
||||||
public static readonly RoutedEvent PopupOpenChangedEvent =
|
|
||||||
EventManager.RegisterRoutedEvent(
|
|
||||||
nameof(PopupOpen),
|
|
||||||
RoutingStrategy.Bubble,
|
|
||||||
typeof(RoutedPropertyChangedEventHandler<bool>),
|
|
||||||
typeof(ColorPicker));
|
|
||||||
|
|
||||||
private bool _inCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ColorPicker" /> class
|
|
||||||
/// </summary>
|
|
||||||
public ColorPicker()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
Loaded += OnLoaded;
|
|
||||||
Unloaded += OnUnloaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the color
|
|
||||||
/// </summary>
|
|
||||||
public Color Color
|
|
||||||
{
|
|
||||||
get => (Color) GetValue(ColorProperty);
|
|
||||||
set => SetValue(ColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating that the popup containing the color picker is open
|
|
||||||
/// </summary>
|
|
||||||
public bool PopupOpen
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(PopupOpenProperty);
|
|
||||||
set => SetValue(PopupOpenProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
|
|
||||||
/// </summary>
|
|
||||||
public bool StaysOpen
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(StaysOpenProperty);
|
|
||||||
set => SetValue(StaysOpenProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the style used on the text box
|
|
||||||
/// </summary>
|
|
||||||
public Style TextBoxStyle
|
|
||||||
{
|
|
||||||
get => (Style) GetValue(TextBoxStyleProperty);
|
|
||||||
set => SetValue(TextBoxStyleProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
|
|
||||||
/// </summary>
|
|
||||||
internal static IColorPickerService ColorPickerService
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_colorPickerService != null)
|
|
||||||
throw new AccessViolationException("This is not for you to touch");
|
|
||||||
_colorPickerService = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal byte ColorOpacity
|
|
||||||
{
|
|
||||||
get => (byte) GetValue(ColorOpacityProperty);
|
|
||||||
set => SetValue(ColorOpacityProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when dragging the color picker has started
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DragStarted;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when dragging the color picker has ended
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DragEnded;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="PropertyChanged" /> event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName"></param>
|
|
||||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DragStarted" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDragStarted()
|
|
||||||
{
|
|
||||||
DragStarted?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DragEnded" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDragEnded()
|
|
||||||
{
|
|
||||||
DragEnded?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ColorPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ColorPicker colorPicker = (ColorPicker) d;
|
|
||||||
if (colorPicker._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
colorPicker._inCallback = true;
|
|
||||||
|
|
||||||
colorPicker.SetCurrentValue(ColorOpacityProperty, ((Color) e.NewValue).A);
|
|
||||||
colorPicker.OnPropertyChanged(nameof(Color));
|
|
||||||
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
|
|
||||||
|
|
||||||
colorPicker._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PopupOpenPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ColorPicker colorPicker = (ColorPicker) d;
|
|
||||||
if (colorPicker._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
colorPicker._inCallback = true;
|
|
||||||
colorPicker.OnPropertyChanged(nameof(PopupOpen));
|
|
||||||
|
|
||||||
if ((bool) e.NewValue)
|
|
||||||
colorPicker.PopupOpened();
|
|
||||||
else
|
|
||||||
colorPicker.PopupClosed();
|
|
||||||
|
|
||||||
colorPicker._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void StaysOpenPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ColorPicker colorPicker = (ColorPicker) d;
|
|
||||||
if (colorPicker._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
colorPicker._inCallback = true;
|
|
||||||
colorPicker.OnPropertyChanged(nameof(PopupOpen));
|
|
||||||
colorPicker._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ColorOpacityPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
ColorPicker colorPicker = (ColorPicker) d;
|
|
||||||
if (colorPicker._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
colorPicker._inCallback = true;
|
|
||||||
|
|
||||||
Color color = colorPicker.Color;
|
|
||||||
if (e.NewValue is byte opacity)
|
|
||||||
color = Color.FromArgb(opacity, color.R, color.G, color.B);
|
|
||||||
colorPicker.SetCurrentValue(ColorProperty, color);
|
|
||||||
colorPicker.OnPropertyChanged(nameof(ColorOpacity));
|
|
||||||
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
|
|
||||||
|
|
||||||
colorPicker._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
PopupOpen = !PopupOpen;
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ColorGradient_OnMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Slider_OnMouseDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
OnDragStarted();
|
|
||||||
|
|
||||||
if (_colorPickerService.PreviewSetting.Value)
|
|
||||||
_colorPickerService.StartColorDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Slider_OnMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
OnDragEnded();
|
|
||||||
_colorPickerService.StopColorDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreviewCheckBoxClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
_colorPickerService.PreviewSetting.Value = PreviewCheckBox.IsChecked ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
|
|
||||||
_colorPickerService.PreviewSetting.SettingChanged += PreviewSettingOnSettingChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
_colorPickerService.PreviewSetting.SettingChanged -= PreviewSettingOnSettingChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PreviewSettingOnSettingChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null) return;
|
|
||||||
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopupClosed()
|
|
||||||
{
|
|
||||||
_colorPickerService?.QueueRecentColor(Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopupOpened()
|
|
||||||
{
|
|
||||||
if (_colorPickerService != null)
|
|
||||||
RecentColorsContainer.ItemsSource = new ObservableCollection<Color>(_colorPickerService.RecentColors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SelectRecentColor(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
Color = (Color) ((Rectangle) sender).DataContext;
|
|
||||||
PopupOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Shared.Controls.DataModelPicker"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Shared.Controls"
|
|
||||||
xmlns:s="https://github.com/canton7/Stylet"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
|
||||||
<ResourceDictionary>
|
|
||||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
|
||||||
<shared:BindingProxy x:Key="DataContextProxy" Data="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
|
||||||
<DataTemplate x:Key="DataModelDataTemplate">
|
|
||||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
|
|
||||||
<DataTemplate.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding Data.ShowDataModelValues, Source={StaticResource DataContextProxy}}" Value="True">
|
|
||||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
|
|
||||||
</DataTrigger>
|
|
||||||
</DataTemplate.Triggers>
|
|
||||||
</DataTemplate>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Button Background="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
BorderBrush="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Style="{StaticResource DataModelConditionButton}"
|
|
||||||
IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Click="PropertyButton_OnClick"
|
|
||||||
x:Name="DataModelButton">
|
|
||||||
<Button.ContextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<ContextMenu.Resources>
|
|
||||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
|
||||||
<Setter Property="ItemsSource" Value="{Binding Children}" />
|
|
||||||
<Setter Property="Command" Value="{Binding Data.SelectPropertyCommand, Source={StaticResource DataContextProxy}}" />
|
|
||||||
<Setter Property="CommandParameter" Value="{Binding}" />
|
|
||||||
<Setter Property="CommandTarget" Value="{Binding}" />
|
|
||||||
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
|
|
||||||
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
|
|
||||||
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
|
|
||||||
</Style>
|
|
||||||
<shared:IntToVisibilityConverter x:Key="IntToVisibilityConverter" />
|
|
||||||
</ContextMenu.Resources>
|
|
||||||
<ContextMenu.ItemsSource>
|
|
||||||
<CompositeCollection>
|
|
||||||
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
|
||||||
<Separator Visibility="{Binding Data.ExtraDataModelViewModels.Count, Source={StaticResource DataContextProxy}, Converter={StaticResource IntToVisibilityConverter}, Mode=OneWay}" />
|
|
||||||
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
|
||||||
</CompositeCollection>
|
|
||||||
</ContextMenu.ItemsSource>
|
|
||||||
</ContextMenu>
|
|
||||||
</Button.ContextMenu>
|
|
||||||
<Grid>
|
|
||||||
<Grid x:Name="ValueDisplay">
|
|
||||||
<TextBlock x:Name="ValueDisplayTextBlock"
|
|
||||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
|
||||||
<TextBlock FontStyle="Italic"
|
|
||||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
|
||||||
<Run Text="« " /><Run Text="{Binding Placeholder, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" /><Run Text=" »" />
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
<TextBlock x:Name="ValuePlaceholder" FontStyle="Italic">
|
|
||||||
« Invalid »
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
</Button>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,343 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Controls
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for DataModelPicker.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class DataModelPicker : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private static IDataModelUIService _dataModelUIService;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets data model path
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty DataModelPathProperty = DependencyProperty.Register(
|
|
||||||
nameof(DataModelPath), typeof(DataModelPath), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DataModelPathPropertyChangedCallback)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
|
|
||||||
nameof(Placeholder), typeof(string), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata("Click to select")
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ShowDataModelValuesProperty = DependencyProperty.Register(nameof(ShowDataModelValues), typeof(bool), typeof(DataModelPicker));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register(
|
|
||||||
nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(ShowFullPathPropertyCallback)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ButtonBrushProperty = DependencyProperty.Register(nameof(ButtonBrush), typeof(Brush), typeof(DataModelPicker));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of extra modules to show data models of
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ModulesProperty = DependencyProperty.Register(
|
|
||||||
nameof(Modules), typeof(ObservableCollection<Module>), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(new ObservableCollection<Module>(), FrameworkPropertyMetadataOptions.None, ModulesPropertyChangedCallback)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The data model view model to show, if not provided one will be retrieved by the control
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty DataModelViewModelProperty = DependencyProperty.Register(
|
|
||||||
nameof(DataModelViewModel), typeof(DataModelPropertiesViewModel), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, DataModelViewModelPropertyChangedCallback)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of data model view models to show
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ExtraDataModelViewModelsProperty = DependencyProperty.Register(
|
|
||||||
nameof(ExtraDataModelViewModels), typeof(ObservableCollection<DataModelPropertiesViewModel>), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(new ObservableCollection<DataModelPropertiesViewModel>(), FrameworkPropertyMetadataOptions.None, ExtraDataModelViewModelsPropertyChangedCallback)
|
|
||||||
);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of data model view models to show
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty FilterTypesProperty = DependencyProperty.Register(
|
|
||||||
nameof(FilterTypes), typeof(ObservableCollection<Type>), typeof(DataModelPicker),
|
|
||||||
new FrameworkPropertyMetadata(new ObservableCollection<Type>())
|
|
||||||
);
|
|
||||||
|
|
||||||
public DataModelPicker()
|
|
||||||
{
|
|
||||||
SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand);
|
|
||||||
Unloaded += OnUnloaded;
|
|
||||||
InitializeComponent();
|
|
||||||
GetDataModel();
|
|
||||||
UpdateValueDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPath? DataModelPath
|
|
||||||
{
|
|
||||||
get => (DataModelPath) GetValue(DataModelPathProperty);
|
|
||||||
set => SetValue(DataModelPathProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public string Placeholder
|
|
||||||
{
|
|
||||||
get => (string) GetValue(PlaceholderProperty);
|
|
||||||
set => SetValue(PlaceholderProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowDataModelValues
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(ShowDataModelValuesProperty);
|
|
||||||
set => SetValue(ShowDataModelValuesProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowFullPath
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(ShowFullPathProperty);
|
|
||||||
set => SetValue(ShowFullPathProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the brush to use when drawing the button
|
|
||||||
/// </summary>
|
|
||||||
public Brush ButtonBrush
|
|
||||||
{
|
|
||||||
get => (Brush) GetValue(ButtonBrushProperty);
|
|
||||||
set => SetValue(ButtonBrushProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of extra modules to show data models of
|
|
||||||
/// </summary>
|
|
||||||
public ObservableCollection<Module>? Modules
|
|
||||||
{
|
|
||||||
get => (ObservableCollection<Module>) GetValue(ModulesProperty);
|
|
||||||
set => SetValue(ModulesProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the data model view model to show
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPropertiesViewModel? DataModelViewModel
|
|
||||||
{
|
|
||||||
get => (DataModelPropertiesViewModel) GetValue(DataModelViewModelProperty);
|
|
||||||
set => SetValue(DataModelViewModelProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a list of data model view models to show
|
|
||||||
/// </summary>
|
|
||||||
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
|
|
||||||
{
|
|
||||||
get => (ObservableCollection<DataModelPropertiesViewModel>) GetValue(ExtraDataModelViewModelsProperty);
|
|
||||||
set => SetValue(ExtraDataModelViewModelsProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the types of properties this view model will allow to be selected
|
|
||||||
/// </summary>
|
|
||||||
public ObservableCollection<Type>? FilterTypes
|
|
||||||
{
|
|
||||||
get => (ObservableCollection<Type>) GetValue(FilterTypesProperty);
|
|
||||||
set => SetValue(FilterTypesProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Command used by view
|
|
||||||
/// </summary>
|
|
||||||
public DelegateCommand SelectPropertyCommand { get; }
|
|
||||||
|
|
||||||
internal static IDataModelUIService DataModelUIService
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_dataModelUIService != null)
|
|
||||||
throw new AccessViolationException("This is not for you to touch");
|
|
||||||
_dataModelUIService = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a new path has been selected
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<DataModelSelectedEventArgs>? DataModelPathSelected;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DataModelPathSelected" /> event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="e"></param>
|
|
||||||
protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
|
|
||||||
{
|
|
||||||
DataModelPathSelected?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetDataModel()
|
|
||||||
{
|
|
||||||
ChangeDataModel(_dataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List<Module>(), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
|
|
||||||
{
|
|
||||||
if (DataModelViewModel != null)
|
|
||||||
{
|
|
||||||
DataModelViewModel.Dispose();
|
|
||||||
DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataModelViewModel = dataModel;
|
|
||||||
if (DataModelViewModel != null)
|
|
||||||
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateValueDisplay()
|
|
||||||
{
|
|
||||||
ValueDisplay.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
ValuePlaceholder.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Collapsed : Visibility.Visible;
|
|
||||||
|
|
||||||
string? formattedPath = null;
|
|
||||||
if (DataModelPath != null && DataModelPath.IsValid)
|
|
||||||
formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
|
|
||||||
|
|
||||||
DataModelButton.ToolTip = formattedPath;
|
|
||||||
ValueDisplayTextBlock.Text = ShowFullPath
|
|
||||||
? formattedPath
|
|
||||||
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DataModelOnUpdateRequested(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
|
||||||
if (ExtraDataModelViewModels == null) return;
|
|
||||||
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
|
|
||||||
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteSelectPropertyCommand(object? context)
|
|
||||||
{
|
|
||||||
if (context is not DataModelVisualizationViewModel selected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (selected.DataModelPath == null)
|
|
||||||
return;
|
|
||||||
if (selected.DataModelPath.Equals(DataModelPath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
DataModelPath = new DataModelPath(selected.DataModelPath);
|
|
||||||
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PropertyButton_OnClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
// DataContext is not set when using left button, I don't know why but there it is
|
|
||||||
if (sender is Button button && button.ContextMenu != null)
|
|
||||||
{
|
|
||||||
button.ContextMenu.DataContext = button.DataContext;
|
|
||||||
button.ContextMenu.IsOpen = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DataModelPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is not DataModelPicker dataModelPicker)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (e.OldValue is DataModelPath oldPath)
|
|
||||||
{
|
|
||||||
oldPath.PathInvalidated -= dataModelPicker.PathValidationChanged;
|
|
||||||
oldPath.PathValidated -= dataModelPicker.PathValidationChanged;
|
|
||||||
oldPath.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
dataModelPicker.UpdateValueDisplay();
|
|
||||||
if (e.NewValue is DataModelPath newPath)
|
|
||||||
{
|
|
||||||
newPath.PathInvalidated += dataModelPicker.PathValidationChanged;
|
|
||||||
newPath.PathValidated += dataModelPicker.PathValidationChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ShowFullPathPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is not DataModelPicker dataModelPicker)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dataModelPicker.UpdateValueDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is not DataModelPicker dataModelPicker)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dataModelPicker.GetDataModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DataModelViewModelPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is not DataModelPicker dataModelPicker)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (e.OldValue is DataModelPropertiesViewModel vm)
|
|
||||||
vm.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (d is not DataModelPicker dataModelPicker)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PathValidationChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Dispatcher.Invoke(UpdateValueDisplay, DispatcherPriority.DataBind);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnloaded(object o, RoutedEventArgs routedEventArgs)
|
|
||||||
{
|
|
||||||
if (DataModelPath != null)
|
|
||||||
{
|
|
||||||
DataModelPath.PathInvalidated -= PathValidationChanged;
|
|
||||||
DataModelPath.PathValidated -= PathValidationChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataModelViewModel?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,94 +1,93 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Timers;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Events;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Rendering;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.Visuals.Media.Imaging;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Controls
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeviceVisualizer : FrameworkElement
|
public class DeviceVisualizer : Control
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const double UpdateFrameRate = 25.0;
|
||||||
/// The device to visualize
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty DeviceProperty = DependencyProperty.Register(nameof(Device), typeof(ArtemisDevice), typeof(DeviceVisualizer),
|
|
||||||
new FrameworkPropertyMetadata(default(ArtemisDevice), FrameworkPropertyMetadataOptions.AffectsRender, DevicePropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not to show per-LED colors
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ShowColorsProperty = DependencyProperty.Register(nameof(ShowColors), typeof(bool), typeof(DeviceVisualizer),
|
|
||||||
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender, ShowColorsPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of LEDs to highlight
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(ObservableCollection<ArtemisLed>), typeof(DeviceVisualizer),
|
|
||||||
new FrameworkPropertyMetadata(default(ObservableCollection<ArtemisLed>), HighlightedLedsPropertyChanged));
|
|
||||||
|
|
||||||
private readonly DrawingGroup _backingStore;
|
|
||||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||||
private readonly DispatcherTimer _timer;
|
private readonly DispatcherTimer _timer;
|
||||||
private BitmapImage? _deviceImage;
|
|
||||||
private ArtemisDevice? _oldDevice;
|
|
||||||
private List<DeviceVisualizerLed> _highlightedLeds;
|
|
||||||
private List<DeviceVisualizerLed> _dimmedLeds;
|
|
||||||
|
|
||||||
/// <summary>
|
private Rect _deviceBounds;
|
||||||
/// Creates a new instance of the <see cref="DeviceVisualizer" /> class
|
private RenderTargetBitmap? _deviceImage;
|
||||||
/// </summary>
|
private List<DeviceVisualizerLed>? _dimmedLeds;
|
||||||
|
private List<DeviceVisualizerLed>? _highlightedLeds;
|
||||||
|
private ArtemisDevice? _oldDevice;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public DeviceVisualizer()
|
public DeviceVisualizer()
|
||||||
{
|
{
|
||||||
_backingStore = new DrawingGroup();
|
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(1000.0 / UpdateFrameRate)};
|
||||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
|
||||||
|
|
||||||
// Run an update timer at 25 fps
|
PointerReleased += OnPointerReleased;
|
||||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
|
|
||||||
|
|
||||||
MouseLeftButtonUp += OnMouseLeftButtonUp;
|
|
||||||
Loaded += OnLoaded;
|
|
||||||
Unloaded += OnUnloaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets or sets the device to visualize
|
public override void Render(DrawingContext drawingContext)
|
||||||
/// </summary>
|
|
||||||
public ArtemisDevice? Device
|
|
||||||
{
|
{
|
||||||
get => (ArtemisDevice) GetValue(DeviceProperty);
|
if (Device == null)
|
||||||
set => SetValue(DeviceProperty, value);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
// Determine the scale required to fit the desired size of the control
|
||||||
/// Gets or sets whether or not to show per-LED colors
|
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
|
||||||
/// </summary>
|
|
||||||
public bool ShowColors
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(ShowColorsProperty);
|
|
||||||
set => SetValue(ShowColorsProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
DrawingContext.PushedState? boundsPush = null;
|
||||||
/// Gets or sets a list of LEDs to highlight
|
try
|
||||||
/// </summary>
|
{
|
||||||
public ObservableCollection<ArtemisLed>? HighlightedLeds
|
// Scale the visualization in the desired bounding box
|
||||||
{
|
if (Bounds.Width > 0 && Bounds.Height > 0)
|
||||||
get => (ObservableCollection<ArtemisLed>) GetValue(HighlightedLedsProperty);
|
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale));
|
||||||
set => SetValue(HighlightedLedsProperty, value);
|
|
||||||
|
// Apply device rotation
|
||||||
|
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
|
||||||
|
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
|
||||||
|
|
||||||
|
// Apply device scale
|
||||||
|
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
|
||||||
|
|
||||||
|
// Render device and LED images
|
||||||
|
if (_deviceImage != null)
|
||||||
|
{
|
||||||
|
drawingContext.DrawImage(
|
||||||
|
_deviceImage,
|
||||||
|
new Rect(_deviceImage.Size),
|
||||||
|
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height),
|
||||||
|
RenderOptions.GetBitmapInterpolationMode(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShowColors)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||||
|
deviceVisualizerLed.RenderGeometry(drawingContext, false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
boundsPush?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -96,54 +95,6 @@ namespace Artemis.UI.Shared
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnRender(DrawingContext drawingContext)
|
|
||||||
{
|
|
||||||
if (Device == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Determine the scale required to fit the desired size of the control
|
|
||||||
Size measureSize = MeasureDevice();
|
|
||||||
double scale = Math.Min(RenderSize.Width / measureSize.Width, RenderSize.Height / measureSize.Height);
|
|
||||||
|
|
||||||
// Scale the visualization in the desired bounding box
|
|
||||||
if (RenderSize.Width > 0 && RenderSize.Height > 0)
|
|
||||||
drawingContext.PushTransform(new ScaleTransform(scale, scale));
|
|
||||||
|
|
||||||
// Determine the offset required to rotate within bounds
|
|
||||||
Rect rotationRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
|
||||||
rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
|
|
||||||
|
|
||||||
// Apply device rotation
|
|
||||||
drawingContext.PushTransform(new TranslateTransform(0 - rotationRect.Left, 0 - rotationRect.Top));
|
|
||||||
drawingContext.PushTransform(new RotateTransform(Device.Rotation));
|
|
||||||
|
|
||||||
// Apply device scale
|
|
||||||
drawingContext.PushTransform(new ScaleTransform(Device.Scale, Device.Scale));
|
|
||||||
|
|
||||||
// Render device and LED images
|
|
||||||
if (_deviceImage != null)
|
|
||||||
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
|
|
||||||
|
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
||||||
deviceVisualizerLed.RenderImage(drawingContext);
|
|
||||||
|
|
||||||
drawingContext.DrawDrawing(_backingStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override Size MeasureOverride(Size availableSize)
|
|
||||||
{
|
|
||||||
if (Device == null)
|
|
||||||
return Size.Empty;
|
|
||||||
|
|
||||||
Size deviceSize = MeasureDevice();
|
|
||||||
if (deviceSize.Width <= 0 || deviceSize.Height <= 0)
|
|
||||||
return Size.Empty;
|
|
||||||
|
|
||||||
return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes the <see cref="LedClicked" /> event
|
/// Invokes the <see cref="LedClicked" /> event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -153,72 +104,37 @@ namespace Artemis.UI.Shared
|
|||||||
LedClicked?.Invoke(this, e);
|
LedClicked?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void Update()
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">
|
|
||||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
|
||||||
/// <see langword="false" /> to release only unmanaged resources.
|
|
||||||
/// </param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
{
|
||||||
if (disposing)
|
InvalidateVisual();
|
||||||
_timer.Stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
|
private Rect MeasureDevice()
|
||||||
{
|
|
||||||
double scale;
|
|
||||||
if (double.IsPositiveInfinity(maxWidth) && !double.IsPositiveInfinity(maxHeight))
|
|
||||||
scale = maxHeight / src.Height;
|
|
||||||
else if (!double.IsPositiveInfinity(maxWidth) && double.IsPositiveInfinity(maxHeight))
|
|
||||||
scale = maxWidth / src.Width;
|
|
||||||
else if (double.IsPositiveInfinity(maxWidth) && double.IsPositiveInfinity(maxHeight))
|
|
||||||
return src;
|
|
||||||
else
|
|
||||||
scale = Math.Min(maxWidth / src.Width, maxHeight / src.Height);
|
|
||||||
|
|
||||||
return new Size(src.Width * scale, src.Height * scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Size MeasureDevice()
|
|
||||||
{
|
{
|
||||||
if (Device == null)
|
if (Device == null)
|
||||||
return Size.Empty;
|
return Rect.Empty;
|
||||||
|
|
||||||
Rect rotationRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
||||||
rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
|
Geometry geometry = new RectangleGeometry(deviceRect);
|
||||||
|
geometry.Transform = new RotateTransform(Device.Rotation);
|
||||||
|
|
||||||
return rotationRect.Size;
|
return geometry.Bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnloaded(object? sender, RoutedEventArgs e)
|
private void TimerOnTick(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_timer.Stop();
|
if (ShowColors && IsVisible && Opacity > 0)
|
||||||
_timer.Tick -= TimerOnTick;
|
Update();
|
||||||
|
|
||||||
if (HighlightedLeds != null)
|
|
||||||
HighlightedLeds.CollectionChanged -= HighlightedLedsChanged;
|
|
||||||
if (_oldDevice != null)
|
|
||||||
{
|
|
||||||
if (Device != null)
|
|
||||||
{
|
|
||||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
|
||||||
Device.DeviceUpdated -= DeviceUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
_oldDevice = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Device == null)
|
if (Device == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Point position = e.GetPosition(this);
|
Point position = e.GetPosition(this);
|
||||||
double x = position.X / RenderSize.Width;
|
double x = position.X / Bounds.Width;
|
||||||
double y = position.Y / RenderSize.Height;
|
double y = position.Y / Bounds.Height;
|
||||||
|
|
||||||
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
||||||
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
|
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
|
||||||
@ -226,154 +142,173 @@ namespace Artemis.UI.Shared
|
|||||||
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
|
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLoaded(object? sender, RoutedEventArgs e)
|
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
_timer.Start();
|
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||||
_timer.Tick += TimerOnTick;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimerOnTick(object? sender, EventArgs e)
|
private void DeviceUpdated(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (ShowColors && Visibility == Visibility.Visible)
|
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||||
Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render()
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
|
||||||
|
AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device), notifying: DeviceUpdated);
|
||||||
|
|
||||||
|
private static void DeviceUpdated(IAvaloniaObject sender, bool before)
|
||||||
{
|
{
|
||||||
DrawingContext drawingContext = _backingStore.Append();
|
if (!before)
|
||||||
|
((DeviceVisualizer) sender).SetupForDevice();
|
||||||
if (_highlightedLeds.Any())
|
|
||||||
{
|
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _highlightedLeds)
|
|
||||||
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
|
|
||||||
|
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _dimmedLeds)
|
|
||||||
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
||||||
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawingContext.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTransform()
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||||
|
/// </summary>
|
||||||
|
public ArtemisDevice? Device
|
||||||
{
|
{
|
||||||
InvalidateVisual();
|
get => GetValue(DeviceProperty);
|
||||||
InvalidateMeasure();
|
set => SetValue(DeviceProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupForDevice()
|
/// <summary>
|
||||||
|
/// Gets or sets boolean indicating whether or not to show per-LED colors
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<bool> ShowColorsProperty =
|
||||||
|
AvaloniaProperty.Register<DeviceVisualizer, bool>(nameof(ShowColors));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether or not to show per-LED colors
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowColors
|
||||||
{
|
{
|
||||||
|
get => GetValue(ShowColorsProperty);
|
||||||
|
set => SetValue(ShowColorsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of LEDs to highlight
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
|
||||||
|
AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a list of LEDs to highlight
|
||||||
|
/// </summary>
|
||||||
|
public ObservableCollection<ArtemisLed>? HighlightedLeds
|
||||||
|
{
|
||||||
|
get => GetValue(HighlightedLedsProperty);
|
||||||
|
set => SetValue(HighlightedLedsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lifetime management
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
_deviceImage?.Dispose();
|
||||||
_deviceImage = null;
|
_deviceImage = null;
|
||||||
_deviceVisualizerLeds.Clear();
|
|
||||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
|
||||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
|
||||||
|
|
||||||
if (Device == null)
|
if (Device != null)
|
||||||
return;
|
|
||||||
|
|
||||||
if (_oldDevice != null)
|
|
||||||
{
|
{
|
||||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||||
Device.DeviceUpdated -= DeviceUpdated;
|
Device.DeviceUpdated -= DeviceUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
_timer.Start();
|
||||||
|
_timer.Tick += TimerOnTick;
|
||||||
|
base.OnAttachedToLogicalTree(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
_timer.Tick -= TimerOnTick;
|
||||||
|
base.OnDetachedFromLogicalTree(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupForDevice()
|
||||||
|
{
|
||||||
|
_deviceImage?.Dispose();
|
||||||
|
_deviceImage = null;
|
||||||
|
_deviceVisualizerLeds.Clear();
|
||||||
|
_highlightedLeds = new List<DeviceVisualizerLed>();
|
||||||
|
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||||
|
|
||||||
|
if (_oldDevice != null)
|
||||||
|
{
|
||||||
|
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||||
|
_oldDevice.DeviceUpdated -= DeviceUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
_oldDevice = Device;
|
_oldDevice = Device;
|
||||||
|
if (Device == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_deviceBounds = MeasureDevice();
|
||||||
|
|
||||||
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
||||||
Device.DeviceUpdated += DeviceUpdated;
|
Device.DeviceUpdated += DeviceUpdated;
|
||||||
UpdateTransform();
|
|
||||||
|
|
||||||
// Load the device main image
|
|
||||||
if (Device.Layout?.Image != null && File.Exists(Device.Layout.Image.LocalPath))
|
|
||||||
_deviceImage = new BitmapImage(Device.Layout.Image);
|
|
||||||
|
|
||||||
// Create all the LEDs
|
// Create all the LEDs
|
||||||
foreach (ArtemisLed artemisLed in Device.Leds)
|
foreach (ArtemisLed artemisLed in Device.Leds)
|
||||||
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
||||||
|
|
||||||
if (!ShowColors)
|
// Load the device main image on a background thread
|
||||||
|
ArtemisDevice? device = Device;
|
||||||
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
InvalidateMeasure();
|
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Create the opacity drawing group
|
try
|
||||||
DrawingGroup opacityDrawingGroup = new();
|
{
|
||||||
DrawingContext drawingContext = opacityDrawingGroup.Open();
|
// Create a bitmap that'll be used to render the device and LED images just once
|
||||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.Size.Width * 4, (int) device.RgbDevice.Size.Height * 4));
|
||||||
deviceVisualizerLed.RenderOpacityMask(drawingContext);
|
|
||||||
drawingContext.Close();
|
|
||||||
|
|
||||||
// Render the store as a bitmap
|
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
|
||||||
DrawingImage drawingImage = new(opacityDrawingGroup);
|
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
|
||||||
Image image = new() {Source = drawingImage};
|
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
|
||||||
RenderTargetBitmap bitmap = new(
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||||
Math.Max(1, (int) (opacityDrawingGroup.Bounds.Width * 2.5)),
|
deviceVisualizerLed.DrawBitmap(context);
|
||||||
Math.Max(1, (int) (opacityDrawingGroup.Bounds.Height * 2.5)),
|
|
||||||
96,
|
|
||||||
96,
|
|
||||||
PixelFormats.Pbgra32
|
|
||||||
);
|
|
||||||
image.Arrange(new Rect(0, 0, bitmap.Width, bitmap.Height));
|
|
||||||
bitmap.Render(image);
|
|
||||||
bitmap.Freeze();
|
|
||||||
|
|
||||||
// Set the bitmap as the opacity mask for the colors backing store
|
_deviceImage = renderTargetBitmap;
|
||||||
ImageBrush bitmapBrush = new(bitmap);
|
|
||||||
bitmapBrush.Freeze();
|
|
||||||
_backingStore.OpacityMask = bitmapBrush;
|
|
||||||
_backingStore.Children.Clear();
|
|
||||||
|
|
||||||
InvalidateMeasure();
|
Dispatcher.UIThread.Post(InvalidateMeasure);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeviceUpdated(object? sender, EventArgs e)
|
/// <inheritdoc />
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(SetupForDevice);
|
if (_deviceBounds.Width <= 0 || _deviceBounds.Height <= 0)
|
||||||
|
return new Size(0, 0);
|
||||||
|
|
||||||
|
double availableWidth = double.IsInfinity(availableSize.Width) ? _deviceBounds.Width : availableSize.Width;
|
||||||
|
double availableHeight = double.IsInfinity(availableSize.Height) ? _deviceBounds.Height : availableSize.Height;
|
||||||
|
double bestRatio = Math.Min(availableWidth / _deviceBounds.Width, availableHeight / _deviceBounds.Height);
|
||||||
|
|
||||||
|
return new Size(_deviceBounds.Width * bestRatio, _deviceBounds.Height * bestRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
#endregion
|
||||||
{
|
|
||||||
Dispatcher.Invoke(SetupForDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
|
|
||||||
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ShowColorsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
|
|
||||||
deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HighlightedLedsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
|
|
||||||
if (e.OldValue is ObservableCollection<ArtemisLed> oldCollection)
|
|
||||||
oldCollection.CollectionChanged -= deviceVisualizer.HighlightedLedsChanged;
|
|
||||||
if (e.NewValue is ObservableCollection<ArtemisLed> newCollection)
|
|
||||||
newCollection.CollectionChanged += deviceVisualizer.HighlightedLedsChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HighlightedLedsChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
|
|
||||||
{
|
|
||||||
if (HighlightedLeds != null)
|
|
||||||
{
|
|
||||||
_highlightedLeds = _deviceVisualizerLeds.Where(l => HighlightedLeds.Contains(l.Led)).ToList();
|
|
||||||
_dimmedLeds = _deviceVisualizerLeds.Where(l => !HighlightedLeds.Contains(l.Led)).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
|
||||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,24 +1,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Visuals.Media.Imaging;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Color = System.Windows.Media.Color;
|
using Color = Avalonia.Media.Color;
|
||||||
using Point = System.Windows.Point;
|
using Point = Avalonia.Point;
|
||||||
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
|
using SolidColorBrush = Avalonia.Media.SolidColorBrush;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Controls
|
||||||
{
|
{
|
||||||
internal class DeviceVisualizerLed
|
internal class DeviceVisualizerLed
|
||||||
{
|
{
|
||||||
private const byte Dimmed = 100;
|
private readonly SolidColorBrush _penBrush;
|
||||||
private const byte NonDimmed = 255;
|
private readonly SolidColorBrush _fillBrush;
|
||||||
private GeometryDrawing? _geometryDrawing;
|
private readonly Pen _pen;
|
||||||
private DrawingGroup? _lastBackingStore;
|
|
||||||
private Color _renderColor;
|
|
||||||
private SolidColorBrush? _renderColorBrush;
|
|
||||||
|
|
||||||
public DeviceVisualizerLed(ArtemisLed led)
|
public DeviceVisualizerLed(ArtemisLed led)
|
||||||
{
|
{
|
||||||
@ -30,83 +29,63 @@ namespace Artemis.UI.Shared
|
|||||||
Led.RgbLed.Size.Height
|
Led.RgbLed.Size.Height
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Led.Layout?.Image != null && File.Exists(Led.Layout.Image.LocalPath))
|
_fillBrush = new SolidColorBrush();
|
||||||
LedImage = new BitmapImage(Led.Layout.Image);
|
_penBrush = new SolidColorBrush();
|
||||||
|
_pen = new Pen(_penBrush) {LineJoin = PenLineJoin.Round};
|
||||||
|
|
||||||
CreateLedGeometry();
|
CreateLedGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ArtemisLed Led { get; }
|
public ArtemisLed Led { get; }
|
||||||
public Rect LedRect { get; set; }
|
public Rect LedRect { get; set; }
|
||||||
public BitmapImage? LedImage { get; set; }
|
|
||||||
public Geometry? DisplayGeometry { get; private set; }
|
public Geometry? DisplayGeometry { get; private set; }
|
||||||
|
|
||||||
public void RenderColor(DrawingGroup backingStore, DrawingContext drawingContext, bool isDimmed)
|
public void DrawBitmap(IDrawingContextImpl drawingContext)
|
||||||
{
|
{
|
||||||
if (DisplayGeometry == null || backingStore == null)
|
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_renderColorBrush ??= new SolidColorBrush();
|
try
|
||||||
_geometryDrawing ??= new GeometryDrawing(_renderColorBrush, null, new RectangleGeometry(LedRect));
|
{
|
||||||
|
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
|
||||||
|
drawingContext.DrawBitmap(
|
||||||
|
bitmap.PlatformImpl,
|
||||||
|
1,
|
||||||
|
new Rect(bitmap.Size),
|
||||||
|
new Rect(Led.RgbLed.Location.X * 4, Led.RgbLed.Location.Y * 4, Led.RgbLed.Size.Width * 4, Led.RgbLed.Size.Height * 4),
|
||||||
|
BitmapInterpolationMode.HighQuality
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
||||||
|
{
|
||||||
byte r = Led.RgbLed.Color.GetR();
|
byte r = Led.RgbLed.Color.GetR();
|
||||||
byte g = Led.RgbLed.Color.GetG();
|
byte g = Led.RgbLed.Color.GetG();
|
||||||
byte b = Led.RgbLed.Color.GetB();
|
byte b = Led.RgbLed.Color.GetB();
|
||||||
|
|
||||||
_renderColor.A = isDimmed ? Dimmed : NonDimmed;
|
if (dimmed)
|
||||||
_renderColor.R = r;
|
|
||||||
_renderColor.G = g;
|
|
||||||
_renderColor.B = b;
|
|
||||||
_renderColorBrush.Color = _renderColor;
|
|
||||||
|
|
||||||
if (_lastBackingStore != backingStore)
|
|
||||||
{
|
{
|
||||||
backingStore.Children.Add(_geometryDrawing);
|
_fillBrush.Color = new Color(50, r, g, b);
|
||||||
_lastBackingStore = backingStore;
|
_penBrush.Color = new Color(100, r, g, b);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fillBrush.Color = new Color(100, r, g, b);
|
||||||
|
_penBrush.Color = new Color(255, r, g, b);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderImage(DrawingContext drawingContext)
|
|
||||||
{
|
|
||||||
if (LedImage == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
drawingContext.DrawImage(LedImage, LedRect);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderOpacityMask(DrawingContext drawingContext)
|
|
||||||
{
|
|
||||||
if (DisplayGeometry == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SolidColorBrush fillBrush = new(Color.FromArgb(100, 255, 255, 255));
|
|
||||||
fillBrush.Freeze();
|
|
||||||
SolidColorBrush penBrush = new(Color.FromArgb(255, 255, 255, 255));
|
|
||||||
penBrush.Freeze();
|
|
||||||
|
|
||||||
// Create transparent pixels covering the entire LedRect so the image size matched the LedRect size
|
|
||||||
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Transparent), new Pen(new SolidColorBrush(Colors.Transparent), 1), LedRect);
|
|
||||||
// Translate to the top-left of the LedRect
|
|
||||||
drawingContext.PushTransform(new TranslateTransform(LedRect.X, LedRect.Y));
|
|
||||||
// Render the LED geometry
|
// Render the LED geometry
|
||||||
drawingContext.DrawGeometry(fillBrush, new Pen(penBrush, 1) {LineJoin = PenLineJoin.Round}, DisplayGeometry.GetOutlinedPathGeometry());
|
drawingContext.DrawGeometry(_fillBrush, _pen, DisplayGeometry);
|
||||||
// Restore the drawing context
|
|
||||||
drawingContext.Pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HitTest(Point position)
|
public bool HitTest(Point position)
|
||||||
{
|
{
|
||||||
if (DisplayGeometry == null)
|
return DisplayGeometry != null && DisplayGeometry.FillContains(position);
|
||||||
return false;
|
|
||||||
|
|
||||||
PathGeometry translatedGeometry = Geometry.Combine(
|
|
||||||
Geometry.Empty,
|
|
||||||
DisplayGeometry,
|
|
||||||
GeometryCombineMode.Union,
|
|
||||||
new TranslateTransform(Led.RgbLed.Location.X, Led.RgbLed.Location.Y)
|
|
||||||
);
|
|
||||||
return translatedGeometry.FillContains(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLedGeometry()
|
private void CreateLedGeometry()
|
||||||
@ -139,17 +118,17 @@ namespace Artemis.UI.Shared
|
|||||||
|
|
||||||
private void CreateRectangleGeometry()
|
private void CreateRectangleGeometry()
|
||||||
{
|
{
|
||||||
DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateCircleGeometry()
|
private void CreateCircleGeometry()
|
||||||
{
|
{
|
||||||
DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
DisplayGeometry = new EllipseGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateKeyCapGeometry()
|
private void CreateKeyCapGeometry()
|
||||||
{
|
{
|
||||||
DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2), 1.6, 1.6);
|
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 1, Led.RgbLed.Location.Y + 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateCustomGeometry(double deflateAmount)
|
private void CreateCustomGeometry(double deflateAmount)
|
||||||
@ -158,36 +137,17 @@ namespace Artemis.UI.Shared
|
|||||||
{
|
{
|
||||||
double width = Led.RgbLed.Size.Width - deflateAmount;
|
double width = Led.RgbLed.Size.Width - deflateAmount;
|
||||||
double height = Led.RgbLed.Size.Height - deflateAmount;
|
double height = Led.RgbLed.Size.Height - deflateAmount;
|
||||||
// DisplayGeometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
|
||||||
DisplayGeometry = Geometry.Combine(
|
Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
||||||
Geometry.Empty,
|
geometry.Transform = new TransformGroup
|
||||||
Geometry.Parse(Led.RgbLed.ShapeData),
|
{
|
||||||
GeometryCombineMode.Union,
|
Children = new Transforms
|
||||||
new TransformGroup
|
|
||||||
{
|
{
|
||||||
Children = new TransformCollection
|
new ScaleTransform(width, height),
|
||||||
{
|
new TranslateTransform(Led.RgbLed.Location.X + deflateAmount / 2, Led.RgbLed.Location.Y + deflateAmount / 2)
|
||||||
new ScaleTransform(width, height),
|
|
||||||
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
|
DisplayGeometry = geometry;
|
||||||
if (DisplayGeometry.Bounds.Width > width)
|
|
||||||
{
|
|
||||||
DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup
|
|
||||||
{
|
|
||||||
Children = new TransformCollection {new ScaleTransform(width / DisplayGeometry.Bounds.Width, 1)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DisplayGeometry.Bounds.Height > height)
|
|
||||||
{
|
|
||||||
DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup
|
|
||||||
{
|
|
||||||
Children = new TransformCollection {new ScaleTransform(1, height / DisplayGeometry.Bounds.Height)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Shared.DraggableFloat"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<UserControl.Style>
|
|
||||||
<Style TargetType="UserControl" >
|
|
||||||
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource MaterialDesignValidationErrorTemplate}" />
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsEnabled}" Value="False">
|
|
||||||
<Setter Property="Opacity" Value="0.5" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Style>
|
|
||||||
<UserControl.Resources>
|
|
||||||
<Style TargetType="Rectangle" x:Key="RectStyle">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=IsEnabled}" Value="False">
|
|
||||||
<Setter Property="StrokeDashArray" Value="2 2" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid>
|
|
||||||
<!-- Drag handle -->
|
|
||||||
<Border x:Name="DragHandle" BorderThickness="0,0,0,1" Height="19">
|
|
||||||
<Border.BorderBrush>
|
|
||||||
<VisualBrush>
|
|
||||||
<VisualBrush.Visual>
|
|
||||||
<Rectangle x:Name="BorderVisual"
|
|
||||||
Style="{StaticResource RectStyle}"
|
|
||||||
Stroke="{DynamicResource MaterialDesignTextBoxBorder}"
|
|
||||||
StrokeThickness="1"
|
|
||||||
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
|
|
||||||
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}">
|
|
||||||
</Rectangle>
|
|
||||||
</VisualBrush.Visual>
|
|
||||||
</VisualBrush>
|
|
||||||
</Border.BorderBrush>
|
|
||||||
<TextBlock Style="{x:Null}"
|
|
||||||
Width="60"
|
|
||||||
Height="17"
|
|
||||||
Padding="2 0"
|
|
||||||
Margin="0 3 0 0"
|
|
||||||
Text="{Binding InputValue, StringFormat=N3, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Cursor="/Resources/Cursors/aero_drag_ew.cur"
|
|
||||||
MouseDown="InputMouseDown"
|
|
||||||
MouseUp="InputMouseUp"
|
|
||||||
MouseMove="InputMouseMove"
|
|
||||||
RequestBringIntoView="Input_OnRequestBringIntoView" />
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Input -->
|
|
||||||
<TextBox x:Name="DraggableFloatInputTextBox"
|
|
||||||
Width="60"
|
|
||||||
Height="21"
|
|
||||||
Padding="0 0 -2 0"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Text="{Binding InputValue, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
LostFocus="InputLostFocus"
|
|
||||||
KeyDown="InputKeyDown"
|
|
||||||
Visibility="Collapsed"
|
|
||||||
RequestBringIntoView="Input_OnRequestBringIntoView"
|
|
||||||
PreviewTextInput="Input_PreviewTextInput"
|
|
||||||
DataObject.Pasting="Input_OnPasting"/>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,303 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for DraggableFloat.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class DraggableFloat : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current value
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(float), typeof(DraggableFloat),
|
|
||||||
new FrameworkPropertyMetadata(default(float), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, FloatPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the step size when dragging
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty StepSizeProperty = DependencyProperty.Register(nameof(StepSize), typeof(float), typeof(DraggableFloat));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the minimum value
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(object), typeof(DraggableFloat));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the maximum value
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(object), typeof(DraggableFloat));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the value has changed
|
|
||||||
/// </summary>
|
|
||||||
public static readonly RoutedEvent ValueChangedEvent =
|
|
||||||
EventManager.RegisterRoutedEvent(
|
|
||||||
nameof(Value),
|
|
||||||
RoutingStrategy.Bubble,
|
|
||||||
typeof(RoutedPropertyChangedEventHandler<float>),
|
|
||||||
typeof(DraggableFloat));
|
|
||||||
|
|
||||||
private readonly Regex _inputRegex = new("^[.][-|0-9]+$|^-?[0-9]*[.]{0,1}[0-9]*$");
|
|
||||||
|
|
||||||
private bool _calledDragStarted;
|
|
||||||
|
|
||||||
private bool _inCallback;
|
|
||||||
private Point _mouseDragStartPoint;
|
|
||||||
private float _startValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="DraggableFloat" /> class
|
|
||||||
/// </summary>
|
|
||||||
public DraggableFloat()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current value
|
|
||||||
/// </summary>
|
|
||||||
public float Value
|
|
||||||
{
|
|
||||||
get => (float) GetValue(ValueProperty);
|
|
||||||
set => SetValue(ValueProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current value as a string
|
|
||||||
/// </summary>
|
|
||||||
public string InputValue
|
|
||||||
{
|
|
||||||
get => Value.ToString("N3", CultureInfo.InvariantCulture);
|
|
||||||
set => UpdateValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the step size when dragging
|
|
||||||
/// </summary>
|
|
||||||
public float StepSize
|
|
||||||
{
|
|
||||||
get => (float) GetValue(StepSizeProperty);
|
|
||||||
set => SetValue(StepSizeProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the minimum value
|
|
||||||
/// </summary>
|
|
||||||
public object? Min
|
|
||||||
{
|
|
||||||
get => (object?) GetValue(MinProperty);
|
|
||||||
set => SetValue(MinProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the maximum value
|
|
||||||
/// </summary>
|
|
||||||
public object? Max
|
|
||||||
{
|
|
||||||
get => (object?) GetValue(MaxProperty);
|
|
||||||
set => SetValue(MaxProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateValue(string value)
|
|
||||||
{
|
|
||||||
if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float parsedResult))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Value = parsedResult;
|
|
||||||
OnPropertyChanged(nameof(InputValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void DisplayInput()
|
|
||||||
{
|
|
||||||
DragHandle.Visibility = Visibility.Collapsed;
|
|
||||||
DraggableFloatInputTextBox.Visibility = Visibility.Visible;
|
|
||||||
DraggableFloatInputTextBox.Focus();
|
|
||||||
DraggableFloatInputTextBox.SelectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisplayDragHandle()
|
|
||||||
{
|
|
||||||
DraggableFloatInputTextBox.Visibility = Visibility.Collapsed;
|
|
||||||
DragHandle.Visibility = Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rounds the provided decimal to the nearest value of x with a given threshold
|
|
||||||
/// Source: https://stackoverflow.com/a/25922075/5015269
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">The value to round</param>
|
|
||||||
/// <param name="nearestOf">The value to round down towards</param>
|
|
||||||
private static decimal RoundToNearestOf(decimal input, decimal nearestOf)
|
|
||||||
{
|
|
||||||
return Math.Floor(input / nearestOf + 0.5m) * nearestOf;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
|
|
||||||
private static void FloatPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
DraggableFloat draggableFloat = (DraggableFloat) d;
|
|
||||||
if (draggableFloat._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
draggableFloat._inCallback = true;
|
|
||||||
draggableFloat.OnPropertyChanged(nameof(Value));
|
|
||||||
draggableFloat.OnPropertyChanged(nameof(InputValue));
|
|
||||||
draggableFloat._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputMouseDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
|
|
||||||
_startValue = Value;
|
|
||||||
((IInputElement) sender).CaptureMouse();
|
|
||||||
_mouseDragStartPoint = e.GetPosition((IInputElement) sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
|
|
||||||
Point position = e.GetPosition((IInputElement) sender);
|
|
||||||
if (position == _mouseDragStartPoint)
|
|
||||||
{
|
|
||||||
DisplayInput();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OnDragEnded();
|
|
||||||
_calledDragStarted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
((IInputElement) sender).ReleaseMouseCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputMouseMove(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.LeftButton != MouseButtonState.Pressed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
|
|
||||||
if (!_calledDragStarted)
|
|
||||||
{
|
|
||||||
OnDragStarted();
|
|
||||||
_calledDragStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use decimals for everything to avoid floating point errors
|
|
||||||
decimal startValue = new(_startValue);
|
|
||||||
decimal startX = new(_mouseDragStartPoint.X);
|
|
||||||
decimal x = new(e.GetPosition((IInputElement) sender).X);
|
|
||||||
decimal stepSize = new(StepSize);
|
|
||||||
if (stepSize == 0)
|
|
||||||
stepSize = 0.1m;
|
|
||||||
|
|
||||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
|
||||||
stepSize = stepSize * 10;
|
|
||||||
|
|
||||||
float value = (float) RoundToNearestOf(startValue + stepSize * (x - startX), stepSize);
|
|
||||||
|
|
||||||
if (Min != null && float.TryParse(Min.ToString(), out float minFloat))
|
|
||||||
value = Math.Max(value, minFloat);
|
|
||||||
if (Max != null && float.TryParse(Max.ToString(), out float maxFloat))
|
|
||||||
value = Math.Min(value, maxFloat);
|
|
||||||
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputLostFocus(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
DisplayDragHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InputKeyDown(object sender, KeyEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Key == Key.Enter)
|
|
||||||
{
|
|
||||||
DisplayDragHandle();
|
|
||||||
}
|
|
||||||
else if (e.Key == Key.Escape)
|
|
||||||
{
|
|
||||||
DraggableFloatInputTextBox.Text = _startValue.ToString(CultureInfo.InvariantCulture);
|
|
||||||
DisplayDragHandle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Input_OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Input_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
|
||||||
{
|
|
||||||
e.Handled = !_inputRegex.IsMatch(e.Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Input_OnPasting(object sender, DataObjectPastingEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.DataObject.GetDataPresent(typeof(string)))
|
|
||||||
{
|
|
||||||
if (e.DataObject.GetData(typeof(string)) is string text && !_inputRegex.IsMatch(text))
|
|
||||||
e.CancelCommand();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
e.CancelCommand();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when dragging has started
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DragStarted;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when dragging has ended
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DragEnded;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="PropertyChanged" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DragStarted" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDragStarted()
|
|
||||||
{
|
|
||||||
DragStarted?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DragEnded" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDragEnded()
|
|
||||||
{
|
|
||||||
DragEnded?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Shared.GradientPicker"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<shared:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
|
|
||||||
<VisualBrush x:Key="Checkerboard" TileMode="Tile" Stretch="Uniform" ViewportUnits="Absolute" Viewport="0,0,10,10">
|
|
||||||
<VisualBrush.Visual>
|
|
||||||
<Grid Width="10" Height="10">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Gray" />
|
|
||||||
<Rectangle Grid.Row="0" Grid.Column="1" Fill="White" />
|
|
||||||
<Rectangle Grid.Row="1" Grid.Column="0" Fill="White" />
|
|
||||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Gray" />
|
|
||||||
</Grid>
|
|
||||||
</VisualBrush.Visual>
|
|
||||||
</VisualBrush>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<Border Height="15"
|
|
||||||
BorderBrush="{DynamicResource NormalBorderBrush}"
|
|
||||||
BorderThickness="1"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Background="{StaticResource Checkerboard}">
|
|
||||||
<Rectangle Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
|
|
||||||
<Rectangle.Fill>
|
|
||||||
<LinearGradientBrush x:Name="GradientPreview" EndPoint="1,0" StartPoint="0,0" />
|
|
||||||
</Rectangle.Fill>
|
|
||||||
</Rectangle>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
</UserControl>
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Specialized;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared.Properties;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for GradientPicker.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class GradientPicker : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private static IColorPickerService? _colorPickerService;
|
|
||||||
private bool _inCallback;
|
|
||||||
private ColorGradientToGradientStopsConverter _gradientConverter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="GradientPicker" /> class
|
|
||||||
/// </summary>
|
|
||||||
public GradientPicker()
|
|
||||||
{
|
|
||||||
_gradientConverter = new ColorGradientToGradientStopsConverter();
|
|
||||||
InitializeComponent();
|
|
||||||
Unloaded += OnUnloaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the color gradient
|
|
||||||
/// </summary>
|
|
||||||
public ColorGradient ColorGradient
|
|
||||||
{
|
|
||||||
get => (ColorGradient) GetValue(ColorGradientProperty);
|
|
||||||
set => SetValue(ColorGradientProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the dialog host in which to show the gradient dialog
|
|
||||||
/// </summary>
|
|
||||||
public string DialogHost
|
|
||||||
{
|
|
||||||
get => (string) GetValue(DialogHostProperty);
|
|
||||||
set => SetValue(DialogHostProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
|
|
||||||
/// </summary>
|
|
||||||
internal static IColorPickerService ColorPickerService
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_colorPickerService != null)
|
|
||||||
throw new AccessViolationException("This is not for you to touch");
|
|
||||||
_colorPickerService = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the dialog has opened
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DialogOpened;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the dialog has closed
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? DialogClosed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="PropertyChanged" /> event
|
|
||||||
/// </summary>
|
|
||||||
[NotifyPropertyChangedInvocator]
|
|
||||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DialogOpened" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDialogOpened()
|
|
||||||
{
|
|
||||||
DialogOpened?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="DialogClosed" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnDialogClosed()
|
|
||||||
{
|
|
||||||
DialogClosed?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ColorGradientPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
GradientPicker gradientPicker = (GradientPicker) d;
|
|
||||||
if (gradientPicker._inCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
gradientPicker._inCallback = true;
|
|
||||||
|
|
||||||
if (e.OldValue is ColorGradient oldGradient)
|
|
||||||
oldGradient.CollectionChanged -= gradientPicker.GradientChanged;
|
|
||||||
if (e.NewValue is ColorGradient newGradient)
|
|
||||||
newGradient.CollectionChanged += gradientPicker.GradientChanged;
|
|
||||||
gradientPicker.UpdateGradientStops();
|
|
||||||
gradientPicker.OnPropertyChanged(nameof(ColorGradient));
|
|
||||||
|
|
||||||
gradientPicker._inCallback = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GradientChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
Dispatcher.Invoke(UpdateGradientStops);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateGradientStops()
|
|
||||||
{
|
|
||||||
GradientPreview.GradientStops = (GradientStopCollection) _gradientConverter.Convert(ColorGradient, null!, null!, null!);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (_colorPickerService == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Execute.OnUIThread(async () =>
|
|
||||||
{
|
|
||||||
OnDialogOpened();
|
|
||||||
await _colorPickerService.ShowGradientPicker(ColorGradient, DialogHost);
|
|
||||||
OnDialogClosed();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
ColorGradient.CollectionChanged -= GradientChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
#region Static WPF fields
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the color gradient
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ColorGradientProperty = DependencyProperty.Register(nameof(ColorGradient), typeof(ColorGradient), typeof(GradientPicker),
|
|
||||||
new FrameworkPropertyMetadata(default(ColorGradient), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorGradientPropertyChangedCallback));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the dialog host in which to show the gradient dialog
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty DialogHostProperty = DependencyProperty.Register(nameof(DialogHost), typeof(string), typeof(GradientPicker),
|
|
||||||
new FrameworkPropertyMetadata(default(string)));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the color gradient has changed
|
|
||||||
/// </summary>
|
|
||||||
public static readonly RoutedEvent ColorGradientChangedEvent =
|
|
||||||
EventManager.RegisterRoutedEvent(nameof(ColorGradient), RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<ColorGradient>), typeof(GradientPicker));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls.Primitives;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a toggle button that can be locked using a property
|
|
||||||
/// </summary>
|
|
||||||
public class LockableToggleButton : ToggleButton
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the toggle button is locked
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty IsLockedProperty =
|
|
||||||
DependencyProperty.Register("IsLocked", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the toggle button is locked
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLocked
|
|
||||||
{
|
|
||||||
get => (bool) GetValue(IsLockedProperty);
|
|
||||||
set => SetValue(IsLockedProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnToggle()
|
|
||||||
{
|
|
||||||
if (!IsLocked) base.OnToggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
<UserControl x:Class="Artemis.UI.Shared.ProfileConfigurationIcon"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
|
||||||
<UserControl.Resources>
|
|
||||||
<shared:StreamToBitmapImageConverter x:Key="StreamToBitmapImageConverter" />
|
|
||||||
<shared:StreamToSvgImageConverter x:Key="StreamToSvgImageConverter" />
|
|
||||||
</UserControl.Resources>
|
|
||||||
<ContentControl>
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Value="{x:Static core:ProfileConfigurationIconType.MaterialIcon}">
|
|
||||||
<Setter Property="ContentTemplate">
|
|
||||||
<Setter.Value>
|
|
||||||
<DataTemplate>
|
|
||||||
<materialDesign:PackIcon Kind="{Binding ConfigurationIcon.IconName, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Width="Auto"
|
|
||||||
Height="Auto" />
|
|
||||||
</DataTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Value="{x:Static core:ProfileConfigurationIconType.BitmapImage}">
|
|
||||||
<Setter Property="ContentTemplate">
|
|
||||||
<Setter.Value>
|
|
||||||
<DataTemplate>
|
|
||||||
<Image
|
|
||||||
Source="{Binding ConfigurationIcon.FileIcon, Converter={StaticResource StreamToBitmapImageConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
RenderOptions.BitmapScalingMode="HighQuality"
|
|
||||||
Width="Auto"
|
|
||||||
Height="Auto" />
|
|
||||||
</DataTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
Value="{x:Static core:ProfileConfigurationIconType.SvgImage}">
|
|
||||||
<Setter Property="ContentTemplate">
|
|
||||||
<Setter.Value>
|
|
||||||
<DataTemplate>
|
|
||||||
<Image
|
|
||||||
Source="{Binding ConfigurationIcon.FileIcon, Converter={StaticResource StreamToSvgImageConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
|
||||||
RenderOptions.BitmapScalingMode="HighQuality"
|
|
||||||
Width="Auto"
|
|
||||||
Height="Auto" />
|
|
||||||
</DataTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
</UserControl>
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for ProfileConfigurationIcon.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class ProfileConfigurationIcon : UserControl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
|
||||||
/// </summary>
|
|
||||||
public static readonly DependencyProperty ConfigurationIconProperty =
|
|
||||||
DependencyProperty.Register(nameof(ConfigurationIcon), typeof(Core.ProfileConfigurationIcon), typeof(ProfileConfigurationIcon));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ProfileConfigurationIcon" /> class
|
|
||||||
/// </summary>
|
|
||||||
public ProfileConfigurationIcon()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
|
||||||
/// </summary>
|
|
||||||
public Core.ProfileConfigurationIcon ConfigurationIcon
|
|
||||||
{
|
|
||||||
get => (Core.ProfileConfigurationIcon) GetValue(ConfigurationIconProperty);
|
|
||||||
set => SetValue(ConfigurationIconProperty, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +1,41 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Converters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts <see cref="T:Artemis.Core.ColorGradient" /> into a <see cref="T:Avalonia.Media.GradientStops" />.
|
||||||
|
/// </summary>
|
||||||
|
public class ColorGradientToGradientStopsConverter : IValueConverter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <summary>
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
|
|
||||||
/// <see cref="T:System.Windows.Media.GradientStopCollection" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(ColorGradient), typeof(GradientStopCollection))]
|
|
||||||
public class ColorGradientToGradientStopsConverter : IValueConverter
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
ColorGradient? colorGradient = value as ColorGradient;
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
GradientStops collection = new();
|
||||||
{
|
if (colorGradient == null)
|
||||||
ColorGradient? colorGradient = value as ColorGradient;
|
|
||||||
GradientStopCollection collection = new();
|
|
||||||
if (colorGradient == null)
|
|
||||||
return collection;
|
|
||||||
|
|
||||||
foreach (ColorGradientStop c in colorGradient.OrderBy(s => s.Position))
|
|
||||||
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
foreach (ColorGradientStop c in colorGradient.OrderBy(s => s.Position))
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
||||||
{
|
return collection;
|
||||||
GradientStopCollection? collection = value as GradientStopCollection;
|
}
|
||||||
ColorGradient colorGradients = new();
|
|
||||||
if (collection == null)
|
|
||||||
return colorGradients;
|
|
||||||
|
|
||||||
foreach (GradientStop c in collection.OrderBy(s => s.Offset))
|
/// <inheritdoc />
|
||||||
colorGradients.Add(new ColorGradientStop(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset));
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
GradientStops? collection = value as GradientStops;
|
||||||
|
ColorGradient colorGradients = new();
|
||||||
|
if (collection == null)
|
||||||
return colorGradients;
|
return colorGradients;
|
||||||
}
|
|
||||||
|
foreach (GradientStop c in collection.OrderBy(s => s.Offset))
|
||||||
|
colorGradients.Add(new ColorGradientStop(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset));
|
||||||
|
return colorGradients;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,30 +1,38 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows.Data;
|
using Avalonia.Data.Converters;
|
||||||
using System.Windows.Media;
|
using Avalonia.Media;
|
||||||
|
using FluentAvalonia.UI.Media;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Converters
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts <see cref="T:SkiaSharp.SKColor" /> into a <see cref="T:System.Windows.Media.Color" />.
|
/// Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ValueConversion(typeof(Color), typeof(SKColor))]
|
public class ColorToSKColorConverter : IValueConverter
|
||||||
public class SKColorToColorConverter : 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)
|
||||||
{
|
{
|
||||||
SKColor skColor = (SKColor) value;
|
if (value is Color avaloniaColor)
|
||||||
return Color.FromArgb(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||||
|
if (value is Color2 fluentAvaloniaColor)
|
||||||
|
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||||
|
|
||||||
|
return SKColor.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
Color color = (Color) value;
|
Color result = new(0, 0, 0, 0);
|
||||||
return new SKColor(color.R, color.G, color.B, color.A);
|
if (value is SKColor skColor)
|
||||||
|
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||||
|
|
||||||
|
if (targetType == typeof(Color2))
|
||||||
|
return (Color2) result;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// Converts <see cref="T:System.Windows.Media.Color" /> into a <see cref="T:System.Windows.Media.Color" /> with full
|
|
||||||
/// opacity.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(Color), typeof(string))]
|
|
||||||
public class ColorToSolidColorConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
Color color = (Color) value;
|
|
||||||
return Color.FromRgb(color.R, color.G, color.B);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// Converts <see cref="T:System.Windows.Media.Color" /> into <see cref="T:System.String" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(Color), typeof(string))]
|
|
||||||
public class ColorToStringConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return value?.ToString()?.ToUpper();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value as string))
|
|
||||||
return default(Color);
|
|
||||||
|
|
||||||
object? color = ColorConverter.ConvertFromString((string) value!);
|
|
||||||
if (color is Color c)
|
|
||||||
return c;
|
|
||||||
|
|
||||||
return default(Color);
|
|
||||||
}
|
|
||||||
catch (FormatException)
|
|
||||||
{
|
|
||||||
return default(Color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Converts <see cref="int"/> to <see cref="Visibility"/>
|
|
||||||
/// </summary>
|
|
||||||
public class IntToVisibilityConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
Parameters direction;
|
|
||||||
if (parameter == null)
|
|
||||||
direction = Parameters.Normal;
|
|
||||||
else
|
|
||||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
|
||||||
|
|
||||||
return direction == Parameters.Normal
|
|
||||||
? value is > 1 ? Visibility.Visible : Visibility.Collapsed
|
|
||||||
: value is > 1 ? Visibility.Collapsed : Visibility.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Parameters
|
|
||||||
{
|
|
||||||
Normal,
|
|
||||||
Inverted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Converts <see langword="null"/> to <see cref="Visibility"/>
|
|
||||||
/// </summary>
|
|
||||||
public class NullToVisibilityConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
Parameters direction;
|
|
||||||
if (parameter == null)
|
|
||||||
direction = Parameters.Normal;
|
|
||||||
else
|
|
||||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
|
||||||
|
|
||||||
if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue))
|
|
||||||
value = null;
|
|
||||||
|
|
||||||
if (direction == Parameters.Normal)
|
|
||||||
return value == null ? Visibility.Collapsed : Visibility.Visible;
|
|
||||||
return value == null ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Parameters
|
|
||||||
{
|
|
||||||
Normal,
|
|
||||||
Inverted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows.Data;
|
using Avalonia.Data.Converters;
|
||||||
using System.Windows.Media;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Converters;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Converts <see cref="SKColor" />into <see cref="T:System.String" />.
|
||||||
|
/// </summary>
|
||||||
|
public class SKColorToStringConverter : IValueConverter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <summary>
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
/// Converts <see cref="SKColor" />into <see cref="T:System.String" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(Color), typeof(string))]
|
|
||||||
public class SKColorToStringConverter : IValueConverter
|
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
return value?.ToString();
|
||||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
}
|
||||||
{
|
|
||||||
return value?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 (string.IsNullOrWhiteSpace(value as string))
|
if (string.IsNullOrWhiteSpace(value as string))
|
||||||
return SKColor.Empty;
|
return SKColor.Empty;
|
||||||
|
|
||||||
return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
|
return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,42 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// Converts bitmap file in the form of a <see cref="T:Stream" /> into <see cref="T:BitmapImage" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(Stream), typeof(BitmapImage))]
|
|
||||||
public class StreamToBitmapImageConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is not Stream stream)
|
|
||||||
return Binding.DoNothing;
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
BitmapImage selectedBitmap = new();
|
|
||||||
selectedBitmap.BeginInit();
|
|
||||||
selectedBitmap.StreamSource = stream;
|
|
||||||
selectedBitmap.CacheOption = BitmapCacheOption.OnLoad;
|
|
||||||
selectedBitmap.EndInit();
|
|
||||||
selectedBitmap.Freeze();
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
return selectedBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return Binding.DoNothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using SharpVectors.Converters;
|
|
||||||
using SharpVectors.Renderers.Wpf;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
|
||||||
/// Converts SVG file in the form of a <see cref="T:Stream" /> into <see cref="T:BitmapImage" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(Stream), typeof(BitmapImage))]
|
|
||||||
public class StreamToSvgImageConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
if (value is not Stream stream)
|
|
||||||
return Binding.DoNothing;
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
StreamSvgConverter converter = new(new WpfDrawingSettings());
|
|
||||||
using MemoryStream imageStream = new();
|
|
||||||
converter.Convert(stream, imageStream);
|
|
||||||
|
|
||||||
BitmapImage selectedBitmap = new();
|
|
||||||
selectedBitmap.BeginInit();
|
|
||||||
selectedBitmap.StreamSource = imageStream;
|
|
||||||
selectedBitmap.CacheOption = BitmapCacheOption.OnLoad;
|
|
||||||
selectedBitmap.EndInit();
|
|
||||||
selectedBitmap.Freeze();
|
|
||||||
|
|
||||||
stream.Position = 0;
|
|
||||||
return selectedBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return Binding.DoNothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using Artemis.Core;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Converts <see cref="T:System.String" /> into <see cref="Numeric" />.
|
|
||||||
/// </summary>
|
|
||||||
[ValueConversion(typeof(string), typeof(Numeric))]
|
|
||||||
public class StringToNumericConverter : IValueConverter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return value?.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
Numeric.TryParse(value as string, out Numeric result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows.Data;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.Converters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
|
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
|
||||||
@ -11,7 +11,7 @@ namespace Artemis.UI.Shared
|
|||||||
public class TypeToStringConverter : IValueConverter
|
public class TypeToStringConverter : 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)
|
||||||
{
|
{
|
||||||
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
|
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
|
||||||
if (value is Type type)
|
if (value is Type type)
|
||||||
@ -21,7 +21,7 @@ namespace Artemis.UI.Shared
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Stylet;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a <see cref="DataModel" /> display view model
|
/// Represents a <see cref="DataModel" /> display view model
|
||||||
@ -10,8 +10,7 @@ namespace Artemis.UI.Shared
|
|||||||
/// <typeparam name="T">The type of the data model</typeparam>
|
/// <typeparam name="T">The type of the data model</typeparam>
|
||||||
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
|
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
|
||||||
{
|
{
|
||||||
[AllowNull]
|
[AllowNull] private T _displayValue = default!;
|
||||||
private T _displayValue = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets value that the view model must display
|
/// Gets or sets value that the view model must display
|
||||||
@ -22,7 +21,8 @@ namespace Artemis.UI.Shared
|
|||||||
get => _displayValue;
|
get => _displayValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (!SetAndNotify(ref _displayValue, value)) return;
|
if (Equals(value, _displayValue)) return;
|
||||||
|
RaiseAndSetIfChanged(ref _displayValue, value);
|
||||||
OnDisplayValueUpdated();
|
OnDisplayValueUpdated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ namespace Artemis.UI.Shared
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DataModelDisplayViewModel : PropertyChangedBase
|
public abstract class DataModelDisplayViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private DataModelPropertyAttribute? _propertyDescription;
|
private DataModelPropertyAttribute? _propertyDescription;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ namespace Artemis.UI.Shared
|
|||||||
public DataModelPropertyAttribute? PropertyDescription
|
public DataModelPropertyAttribute? PropertyDescription
|
||||||
{
|
{
|
||||||
get => _propertyDescription;
|
get => _propertyDescription;
|
||||||
internal set => SetAndNotify(ref _propertyDescription, value);
|
internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Stylet;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a <see cref="DataModel" /> input view model
|
/// Represents a <see cref="DataModel" /> input view model
|
||||||
@ -37,7 +33,7 @@ namespace Artemis.UI.Shared
|
|||||||
public T InputValue
|
public T InputValue
|
||||||
{
|
{
|
||||||
get => _inputValue;
|
get => _inputValue;
|
||||||
set => SetAndNotify(ref _inputValue, value);
|
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,9 +50,6 @@ namespace Artemis.UI.Shared
|
|||||||
return;
|
return;
|
||||||
_closed = true;
|
_closed = true;
|
||||||
|
|
||||||
foreach (BindingExpressionBase sourceUpdatingBinding in BindingOperations.GetSourceUpdatingBindings(View))
|
|
||||||
sourceUpdatingBinding.UpdateSource();
|
|
||||||
|
|
||||||
OnSubmit();
|
OnSubmit();
|
||||||
UpdateCallback(InputValue, true);
|
UpdateCallback(InputValue, true);
|
||||||
}
|
}
|
||||||
@ -76,7 +69,7 @@ namespace Artemis.UI.Shared
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class DataModelInputViewModel : PropertyChangedBase, IViewAware
|
public abstract class DataModelInputViewModel : ReactiveObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||||
@ -117,24 +110,5 @@ namespace Artemis.UI.Shared
|
|||||||
protected virtual void OnCancel()
|
protected virtual void OnCancel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void AttachView(UIElement view)
|
|
||||||
{
|
|
||||||
if (View != null)
|
|
||||||
throw new InvalidOperationException(string.Format("Tried to attach View {0} to ViewModel {1}, but it already has a view attached", view.GetType().Name, GetType().Name));
|
|
||||||
|
|
||||||
View = view;
|
|
||||||
|
|
||||||
// After the animation finishes attempt to focus the input field
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(50);
|
|
||||||
await Execute.OnUIThreadAsync(() => View.MoveFocus(new TraversalRequest(FocusNavigationDirection.First)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public UIElement? View { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,8 +2,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a layer brush registered through
|
/// Represents a layer brush registered through
|
||||||
|
|||||||
@ -3,8 +3,10 @@ using System.Linq;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a view model that visualizes an event data model property
|
/// Represents a view model that visualizes an event data model property
|
||||||
@ -23,7 +25,7 @@ namespace Artemis.UI.Shared
|
|||||||
public Type? DisplayValueType
|
public Type? DisplayValueType
|
||||||
{
|
{
|
||||||
get => _displayValueType;
|
get => _displayValueType;
|
||||||
set => SetAndNotify(ref _displayValueType, value);
|
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a view model that wraps a regular <see cref="DataModelPropertiesViewModel" /> but contained in
|
|
||||||
/// a <see cref="DataModelListViewModel" />
|
|
||||||
/// </summary>
|
|
||||||
public class DataModelListPropertiesViewModel : DataModelPropertiesViewModel
|
|
||||||
{
|
|
||||||
private object? _displayValue;
|
|
||||||
private int _index;
|
|
||||||
private Type? _listType;
|
|
||||||
|
|
||||||
internal DataModelListPropertiesViewModel(Type listType, string? name) : base(null, null, null)
|
|
||||||
{
|
|
||||||
ListType = listType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the element within the list
|
|
||||||
/// </summary>
|
|
||||||
public int Index
|
|
||||||
{
|
|
||||||
get => _index;
|
|
||||||
set => SetAndNotify(ref _index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of elements contained in the list
|
|
||||||
/// </summary>
|
|
||||||
public Type? ListType
|
|
||||||
{
|
|
||||||
get => _listType;
|
|
||||||
set => SetAndNotify(ref _listType, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of the property that is being visualized
|
|
||||||
/// </summary>
|
|
||||||
public new object? DisplayValue
|
|
||||||
{
|
|
||||||
get => _displayValue;
|
|
||||||
set => SetAndNotify(ref _displayValue, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the view model that handles displaying the property
|
|
||||||
/// </summary>
|
|
||||||
public DataModelVisualizationViewModel? DisplayViewModel => Children.FirstOrDefault();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string? DisplayPath => null;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
|
||||||
{
|
|
||||||
PopulateProperties(dataModelUIService, configuration);
|
|
||||||
if (DisplayViewModel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsVisualizationExpanded && !DisplayViewModel.IsVisualizationExpanded)
|
|
||||||
DisplayViewModel.IsVisualizationExpanded = IsVisualizationExpanded;
|
|
||||||
DisplayViewModel.Update(dataModelUIService, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override object? GetCurrentValue()
|
|
||||||
{
|
|
||||||
return DisplayValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"[List item {Index}] {DisplayPath ?? Path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a view model that visualizes a single data model property contained in a
|
|
||||||
/// <see cref="DataModelListViewModel" />
|
|
||||||
/// </summary>
|
|
||||||
public class DataModelListPropertyViewModel : DataModelPropertyViewModel
|
|
||||||
{
|
|
||||||
private int _index;
|
|
||||||
private Type? _listType;
|
|
||||||
|
|
||||||
internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
|
|
||||||
{
|
|
||||||
ListType = listType;
|
|
||||||
DisplayViewModel = displayViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal DataModelListPropertyViewModel(Type listType, string? name) : base(null, null, null)
|
|
||||||
{
|
|
||||||
ListType = listType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the element within the list
|
|
||||||
/// </summary>
|
|
||||||
public int Index
|
|
||||||
{
|
|
||||||
get => _index;
|
|
||||||
internal set => SetAndNotify(ref _index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of elements contained in the list
|
|
||||||
/// </summary>
|
|
||||||
public Type? ListType
|
|
||||||
{
|
|
||||||
get => _listType;
|
|
||||||
private set => SetAndNotify(ref _listType, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override object? GetCurrentValue()
|
|
||||||
{
|
|
||||||
return DisplayValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
|
||||||
{
|
|
||||||
// Display value gets updated by parent, don't do anything if it is null
|
|
||||||
if (DisplayValue == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (DisplayViewModel == null)
|
|
||||||
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
|
|
||||||
|
|
||||||
ListType = DisplayValue.GetType();
|
|
||||||
DisplayViewModel?.UpdateValue(DisplayValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"[List item {Index}] {DisplayPath ?? Path} - {DisplayValue}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Windows.Documents;
|
using System.Collections.ObjectModel;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Stylet;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a view model that visualizes a list data model property
|
/// Represents a view model that visualizes a list data model property
|
||||||
@ -16,14 +17,14 @@ namespace Artemis.UI.Shared
|
|||||||
private string _countDisplay;
|
private string _countDisplay;
|
||||||
private Type? _displayValueType;
|
private Type? _displayValueType;
|
||||||
private IEnumerable? _list;
|
private IEnumerable? _list;
|
||||||
private BindableCollection<DataModelVisualizationViewModel> _listChildren;
|
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
|
||||||
private int _listCount;
|
private int _listCount;
|
||||||
|
|
||||||
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
|
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
|
||||||
: base(dataModel, parent, dataModelPath)
|
: base(dataModel, parent, dataModelPath)
|
||||||
{
|
{
|
||||||
_countDisplay = "0 items";
|
_countDisplay = "0 items";
|
||||||
_listChildren = new BindableCollection<DataModelVisualizationViewModel>();
|
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -32,7 +33,7 @@ namespace Artemis.UI.Shared
|
|||||||
public IEnumerable? List
|
public IEnumerable? List
|
||||||
{
|
{
|
||||||
get => _list;
|
get => _list;
|
||||||
private set => SetAndNotify(ref _list, value);
|
private set => this.RaiseAndSetIfChanged(ref _list, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -41,7 +42,7 @@ namespace Artemis.UI.Shared
|
|||||||
public int ListCount
|
public int ListCount
|
||||||
{
|
{
|
||||||
get => _listCount;
|
get => _listCount;
|
||||||
private set => SetAndNotify(ref _listCount, value);
|
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -50,7 +51,7 @@ namespace Artemis.UI.Shared
|
|||||||
public Type? DisplayValueType
|
public Type? DisplayValueType
|
||||||
{
|
{
|
||||||
get => _displayValueType;
|
get => _displayValueType;
|
||||||
set => SetAndNotify(ref _displayValueType, value);
|
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -59,16 +60,16 @@ namespace Artemis.UI.Shared
|
|||||||
public string CountDisplay
|
public string CountDisplay
|
||||||
{
|
{
|
||||||
get => _countDisplay;
|
get => _countDisplay;
|
||||||
set => SetAndNotify(ref _countDisplay, value);
|
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of child view models that visualize the elements in the list
|
/// Gets a list of child view models that visualize the elements in the list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BindableCollection<DataModelVisualizationViewModel> ListChildren
|
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
|
||||||
{
|
{
|
||||||
get => _listChildren;
|
get => _listChildren;
|
||||||
private set => SetAndNotify(ref _listChildren, value);
|
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -97,22 +98,15 @@ namespace Artemis.UI.Shared
|
|||||||
ListChildren.Add(child);
|
ListChildren.Add(child);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
child = ListChildren[index];
|
child = ListChildren[index];
|
||||||
}
|
|
||||||
|
|
||||||
if (child is DataModelListPropertiesViewModel dataModelListClassViewModel)
|
if (child is DataModelListItemViewModel dataModelListPropertyViewModel)
|
||||||
{
|
|
||||||
dataModelListClassViewModel.DisplayValue = item;
|
|
||||||
dataModelListClassViewModel.Index = index;
|
|
||||||
}
|
|
||||||
else if (child is DataModelListPropertyViewModel dataModelListPropertyViewModel)
|
|
||||||
{
|
{
|
||||||
dataModelListPropertyViewModel.DisplayValue = item;
|
dataModelListPropertyViewModel.DisplayValue = item;
|
||||||
dataModelListPropertyViewModel.Index = index;
|
dataModelListPropertyViewModel.Index = index;
|
||||||
|
dataModelListPropertyViewModel.Update(dataModelUIService, configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
child.Update(dataModelUIService, configuration);
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,16 +128,10 @@ namespace Artemis.UI.Shared
|
|||||||
{
|
{
|
||||||
// If a display VM was found, prefer to use that in any case
|
// If a display VM was found, prefer to use that in any case
|
||||||
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
|
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
|
||||||
if (typeViewModel != null)
|
|
||||||
return new DataModelListPropertyViewModel(listType, typeViewModel, name);
|
|
||||||
// For primitives, create a property view model, it may be null that is fine
|
|
||||||
if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string))
|
|
||||||
return new DataModelListPropertyViewModel(listType, name);
|
|
||||||
// For other value types create a child view model
|
|
||||||
if (listType.IsClass || listType.IsStruct())
|
|
||||||
return new DataModelListPropertiesViewModel(listType, name);
|
|
||||||
|
|
||||||
return null;
|
return typeViewModel != null
|
||||||
|
? new DataModelListItemViewModel(listType, typeViewModel, name)
|
||||||
|
: new DataModelListItemViewModel(listType, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,8 +2,10 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
|
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
|
||||||
@ -24,7 +26,7 @@ namespace Artemis.UI.Shared
|
|||||||
public Type? DisplayValueType
|
public Type? DisplayValueType
|
||||||
{
|
{
|
||||||
get => _displayValueType;
|
get => _displayValueType;
|
||||||
private set => SetAndNotify(ref _displayValueType, value);
|
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -33,7 +35,7 @@ namespace Artemis.UI.Shared
|
|||||||
public object? DisplayValue
|
public object? DisplayValue
|
||||||
{
|
{
|
||||||
get => _displayValue;
|
get => _displayValue;
|
||||||
private set => SetAndNotify(ref _displayValue, value);
|
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -2,8 +2,10 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a view model that visualizes a single data model property contained in a
|
/// Represents a view model that visualizes a single data model property contained in a
|
||||||
@ -26,7 +28,7 @@ namespace Artemis.UI.Shared
|
|||||||
public object? DisplayValue
|
public object? DisplayValue
|
||||||
{
|
{
|
||||||
get => _displayValue;
|
get => _displayValue;
|
||||||
internal set => SetAndNotify(ref _displayValue, value);
|
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -35,7 +37,7 @@ namespace Artemis.UI.Shared
|
|||||||
public Type? DisplayValueType
|
public Type? DisplayValueType
|
||||||
{
|
{
|
||||||
get => _displayValueType;
|
get => _displayValueType;
|
||||||
protected set => SetAndNotify(ref _displayValueType, value);
|
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,7 +46,7 @@ namespace Artemis.UI.Shared
|
|||||||
public DataModelDisplayViewModel? DisplayViewModel
|
public DataModelDisplayViewModel? DisplayViewModel
|
||||||
{
|
{
|
||||||
get => _displayViewModel;
|
get => _displayViewModel;
|
||||||
internal set => SetAndNotify(ref _displayViewModel, value);
|
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
|
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
|
||||||
|
|||||||
@ -6,368 +6,396 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
using Stylet;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a base class for a view model that visualizes a part of the data model
|
||||||
|
/// </summary>
|
||||||
|
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const int MaxDepth = 4;
|
||||||
/// Represents a base class for a view model that visualizes a part of the data model
|
private ObservableCollection<DataModelVisualizationViewModel> _children;
|
||||||
/// </summary>
|
private DataModel? _dataModel;
|
||||||
public abstract class DataModelVisualizationViewModel : PropertyChangedBase, IDisposable
|
private bool _isMatchingFilteredTypes;
|
||||||
|
private bool _isVisualizationExpanded;
|
||||||
|
private DataModelVisualizationViewModel? _parent;
|
||||||
|
private bool _populatedStaticChildren;
|
||||||
|
private DataModelPropertyAttribute? _propertyDescription;
|
||||||
|
|
||||||
|
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||||
{
|
{
|
||||||
private const int MaxDepth = 4;
|
_dataModel = dataModel;
|
||||||
private BindableCollection<DataModelVisualizationViewModel> _children;
|
_children = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||||
private DataModel? _dataModel;
|
_parent = parent;
|
||||||
private bool _isMatchingFilteredTypes;
|
DataModelPath = dataModelPath;
|
||||||
private bool _isVisualizationExpanded;
|
IsMatchingFilteredTypes = true;
|
||||||
private DataModelVisualizationViewModel? _parent;
|
|
||||||
private DataModelPropertyAttribute? _propertyDescription;
|
|
||||||
private bool _populatedStaticChildren;
|
|
||||||
|
|
||||||
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
if (parent == null)
|
||||||
|
IsRootViewModel = true;
|
||||||
|
else
|
||||||
|
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether this view model is at the root of the data model
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRootViewModel { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data model path to the property this view model is visualizing
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPath? DataModelPath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a string representation of the path backing this model
|
||||||
|
/// </summary>
|
||||||
|
public string? Path => DataModelPath?.Path;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property depth of the view model
|
||||||
|
/// </summary>
|
||||||
|
public int Depth { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data model backing this view model
|
||||||
|
/// </summary>
|
||||||
|
public DataModel? DataModel
|
||||||
|
{
|
||||||
|
get => _dataModel;
|
||||||
|
protected set => this.RaiseAndSetIfChanged(ref _dataModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property description of the property this view model is visualizing
|
||||||
|
/// </summary>
|
||||||
|
public DataModelPropertyAttribute? PropertyDescription
|
||||||
|
{
|
||||||
|
get => _propertyDescription;
|
||||||
|
protected set => this.RaiseAndSetIfChanged(ref _propertyDescription, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent of this view model
|
||||||
|
/// </summary>
|
||||||
|
public DataModelVisualizationViewModel? Parent
|
||||||
|
{
|
||||||
|
get => _parent;
|
||||||
|
protected set => this.RaiseAndSetIfChanged(ref _parent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an observable collection containing the children of this view model
|
||||||
|
/// </summary>
|
||||||
|
public ObservableCollection<DataModelVisualizationViewModel> Children
|
||||||
|
{
|
||||||
|
get => _children;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _children, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether the property being visualized matches the types last provided to
|
||||||
|
/// <see cref="ApplyTypeFilter" />
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMatchingFilteredTypes
|
||||||
|
{
|
||||||
|
get => _isMatchingFilteredTypes;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _isMatchingFilteredTypes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the visualization is expanded, exposing the <see cref="Children" />
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisualizationExpanded
|
||||||
|
{
|
||||||
|
get => _isVisualizationExpanded;
|
||||||
|
set
|
||||||
{
|
{
|
||||||
_dataModel = dataModel;
|
if (!this.RaiseAndSetIfChanged(ref _isVisualizationExpanded, value)) return;
|
||||||
_children = new BindableCollection<DataModelVisualizationViewModel>();
|
RequestUpdate();
|
||||||
_parent = parent;
|
|
||||||
DataModelPath = dataModelPath;
|
|
||||||
IsMatchingFilteredTypes = true;
|
|
||||||
|
|
||||||
if (parent == null)
|
|
||||||
IsRootViewModel = true;
|
|
||||||
else
|
|
||||||
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a boolean indicating whether this view model is at the root of the data model
|
/// Gets a user-friendly representation of the <see cref="DataModelPath" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRootViewModel { get; protected set; }
|
public virtual string? DisplayPath => DataModelPath != null
|
||||||
|
? string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier))
|
||||||
|
: null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data model path to the property this view model is visualizing
|
/// Updates the datamodel and if in an parent, any children
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataModelPath? DataModelPath { get; }
|
/// <param name="dataModelUIService">The data model UI service used during update</param>
|
||||||
|
/// <param name="configuration">The configuration to apply while updating</param>
|
||||||
/// <summary>
|
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
|
||||||
/// Gets a string representation of the path backing this model
|
|
||||||
/// </summary>
|
|
||||||
public string? Path => DataModelPath?.Path;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the property depth of the view model
|
|
||||||
/// </summary>
|
|
||||||
public int Depth { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data model backing this view model
|
|
||||||
/// </summary>
|
|
||||||
public DataModel? DataModel
|
|
||||||
{
|
|
||||||
get => _dataModel;
|
|
||||||
protected set => SetAndNotify(ref _dataModel, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the property description of the property this view model is visualizing
|
|
||||||
/// </summary>
|
|
||||||
public DataModelPropertyAttribute? PropertyDescription
|
|
||||||
{
|
|
||||||
get => _propertyDescription;
|
|
||||||
protected set => SetAndNotify(ref _propertyDescription, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the parent of this view model
|
|
||||||
/// </summary>
|
|
||||||
public DataModelVisualizationViewModel? Parent
|
|
||||||
{
|
|
||||||
get => _parent;
|
|
||||||
protected set => SetAndNotify(ref _parent, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a bindable collection containing the children of this view model
|
|
||||||
/// </summary>
|
|
||||||
public BindableCollection<DataModelVisualizationViewModel> Children
|
|
||||||
{
|
|
||||||
get => _children;
|
|
||||||
set => SetAndNotify(ref _children, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean indicating whether the property being visualized matches the types last provided to
|
|
||||||
/// <see cref="ApplyTypeFilter" />
|
|
||||||
/// </summary>
|
|
||||||
public bool IsMatchingFilteredTypes
|
|
||||||
{
|
|
||||||
get => _isMatchingFilteredTypes;
|
|
||||||
private set => SetAndNotify(ref _isMatchingFilteredTypes, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the visualization is expanded, exposing the <see cref="Children" />
|
|
||||||
/// </summary>
|
|
||||||
public bool IsVisualizationExpanded
|
|
||||||
{
|
|
||||||
get => _isVisualizationExpanded;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (!SetAndNotify(ref _isVisualizationExpanded, value)) return;
|
|
||||||
RequestUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a user-friendly representation of the <see cref="DataModelPath" />
|
|
||||||
/// </summary>
|
|
||||||
public virtual string? DisplayPath => DataModelPath != null
|
|
||||||
? string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier))
|
|
||||||
: null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the datamodel and if in an parent, any children
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dataModelUIService">The data model UI service used during update</param>
|
|
||||||
/// <param name="configuration">The configuration to apply while updating</param>
|
|
||||||
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current value of the property being visualized
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The current value of the property being visualized</returns>
|
|
||||||
public virtual object? GetCurrentValue()
|
|
||||||
{
|
|
||||||
if (IsRootViewModel)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return DataModelPath?.GetValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the provided types match the type of the property being visualized and sets the result in
|
|
||||||
/// <see cref="IsMatchingFilteredTypes" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
|
|
||||||
/// <param name="filteredTypes">The types to filter</param>
|
|
||||||
public void ApplyTypeFilter(bool looseMatch, params Type[]? filteredTypes)
|
|
||||||
{
|
|
||||||
if (filteredTypes != null)
|
|
||||||
{
|
|
||||||
if (filteredTypes.All(t => t == null))
|
|
||||||
filteredTypes = null;
|
|
||||||
else
|
|
||||||
filteredTypes = filteredTypes.Where(t => t != null).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the VM has children, its own type is not relevant
|
|
||||||
if (Children.Any())
|
|
||||||
{
|
|
||||||
foreach (DataModelVisualizationViewModel child in Children)
|
|
||||||
child?.ApplyTypeFilter(looseMatch, filteredTypes);
|
|
||||||
|
|
||||||
IsMatchingFilteredTypes = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If null is passed, clear the type filter
|
|
||||||
if (filteredTypes == null || filteredTypes.Length == 0)
|
|
||||||
{
|
|
||||||
IsMatchingFilteredTypes = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the type couldn't be retrieved either way, assume false
|
|
||||||
Type? type = DataModelPath?.GetPropertyType();
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
IsMatchingFilteredTypes = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (looseMatch)
|
|
||||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
|
|
||||||
t == typeof(Enum) && type.IsEnum ||
|
|
||||||
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
|
|
||||||
type.IsGenericType && t == type.GetGenericTypeDefinition());
|
|
||||||
else
|
|
||||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual int GetChildDepth()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? dataModelUpdateConfiguration)
|
|
||||||
{
|
|
||||||
if (IsRootViewModel && DataModel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Type? modelType = IsRootViewModel ? DataModel?.GetType() : DataModelPath?.GetPropertyType();
|
|
||||||
if (modelType == null)
|
|
||||||
throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type");
|
|
||||||
|
|
||||||
// Add missing static children only once, they're static after all
|
|
||||||
if (!_populatedStaticChildren)
|
|
||||||
{
|
|
||||||
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
|
|
||||||
{
|
|
||||||
string childPath = AppendToPath(propertyInfo.Name);
|
|
||||||
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
|
|
||||||
continue;
|
|
||||||
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
|
|
||||||
continue;
|
|
||||||
MethodInfo? getMethod = propertyInfo.GetGetMethod();
|
|
||||||
if (getMethod == null || getMethod.GetParameters().Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
|
|
||||||
if (child != null)
|
|
||||||
Children.Add(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
_populatedStaticChildren = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove static children that should be hidden
|
|
||||||
if (DataModel != null)
|
|
||||||
{
|
|
||||||
ReadOnlyCollection<PropertyInfo> hiddenProperties = DataModel.GetHiddenProperties();
|
|
||||||
foreach (PropertyInfo hiddenProperty in hiddenProperties)
|
|
||||||
{
|
|
||||||
string childPath = AppendToPath(hiddenProperty.Name);
|
|
||||||
DataModelVisualizationViewModel? toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath);
|
|
||||||
if (toRemove != null)
|
|
||||||
Children.Remove(toRemove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add missing dynamic children
|
|
||||||
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
|
|
||||||
if (value is DataModel dataModel)
|
|
||||||
{
|
|
||||||
foreach (string key in dataModel.DynamicChildren.Keys.ToList())
|
|
||||||
{
|
|
||||||
string childPath = AppendToPath(key);
|
|
||||||
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
|
|
||||||
if (child != null)
|
|
||||||
Children.Add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove dynamic children that have been removed from the data model
|
|
||||||
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();
|
|
||||||
if (toRemoveDynamic.Any())
|
|
||||||
Children.RemoveRange(toRemoveDynamic);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataModelVisualizationViewModel? CreateChild(IDataModelUIService dataModelUIService, string path, int depth)
|
|
||||||
{
|
|
||||||
if (DataModel == null)
|
|
||||||
throw new ArtemisSharedUIException("Cannot create a data model visualization child VM for a parent without a data model");
|
|
||||||
if (depth > MaxDepth)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
DataModelPath dataModelPath = new(DataModel, path);
|
|
||||||
if (!dataModelPath.IsValid)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
PropertyInfo? propertyInfo = dataModelPath.GetPropertyInfo();
|
|
||||||
Type? propertyType = dataModelPath.GetPropertyType();
|
|
||||||
|
|
||||||
// Skip properties decorated with DataModelIgnore
|
|
||||||
if (propertyInfo != null && Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute)))
|
|
||||||
return null;
|
|
||||||
// Skip properties that are in the ignored properties list of the respective profile module/data model expansion
|
|
||||||
if (DataModel.GetHiddenProperties().Any(p => p.Equals(propertyInfo)))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (propertyType == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// If a display VM was found, prefer to use that in any case
|
|
||||||
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription);
|
|
||||||
if (typeViewModel != null)
|
|
||||||
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
|
|
||||||
// For primitives, create a property view model, it may be null that is fine
|
|
||||||
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
|
||||||
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
|
||||||
if (propertyType.IsGenericEnumerable())
|
|
||||||
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
|
||||||
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
|
|
||||||
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
|
||||||
// For other value types create a child view model
|
|
||||||
if (propertyType.IsClass || propertyType.IsStruct())
|
|
||||||
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current value of the property being visualized
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current value of the property being visualized</returns>
|
||||||
|
public virtual object? GetCurrentValue()
|
||||||
|
{
|
||||||
|
if (IsRootViewModel)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
return DataModelPath?.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the provided types match the type of the property being visualized and sets the result in
|
||||||
|
/// <see cref="IsMatchingFilteredTypes" />
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
|
||||||
|
/// <param name="filteredTypes">The types to filter</param>
|
||||||
|
public void ApplyTypeFilter(bool looseMatch, params Type[]? filteredTypes)
|
||||||
|
{
|
||||||
|
if (filteredTypes != null)
|
||||||
|
{
|
||||||
|
if (filteredTypes.All(t => t == null))
|
||||||
|
filteredTypes = null;
|
||||||
|
else
|
||||||
|
filteredTypes = filteredTypes.Where(t => t != null).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string AppendToPath(string toAppend)
|
// If the VM has children, its own type is not relevant
|
||||||
|
if (Children.Any())
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Path))
|
foreach (DataModelVisualizationViewModel child in Children)
|
||||||
return toAppend;
|
child?.ApplyTypeFilter(looseMatch, filteredTypes);
|
||||||
|
|
||||||
StringBuilder builder = new();
|
IsMatchingFilteredTypes = true;
|
||||||
builder.Append(Path);
|
return;
|
||||||
builder.Append(".");
|
|
||||||
builder.Append(toAppend);
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RequestUpdate()
|
// If null is passed, clear the type filter
|
||||||
|
if (filteredTypes == null || filteredTypes.Length == 0)
|
||||||
{
|
{
|
||||||
Parent?.RequestUpdate();
|
IsMatchingFilteredTypes = true;
|
||||||
OnUpdateRequested();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Events
|
// If the type couldn't be retrieved either way, assume false
|
||||||
|
Type? type = DataModelPath?.GetPropertyType();
|
||||||
/// <summary>
|
if (type == null)
|
||||||
/// Occurs when an update to the property this view model visualizes is requested
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? UpdateRequested;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the <see cref="UpdateRequested" /> event
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnUpdateRequested()
|
|
||||||
{
|
{
|
||||||
UpdateRequested?.Invoke(this, EventArgs.Empty);
|
IsMatchingFilteredTypes = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
if (looseMatch)
|
||||||
|
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
|
||||||
|
t == typeof(Enum) && type.IsEnum ||
|
||||||
|
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
|
||||||
|
type.IsGenericType && t == type.GetGenericTypeDefinition());
|
||||||
|
else
|
||||||
|
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
|
||||||
|
}
|
||||||
|
|
||||||
#region IDisposable
|
/// <summary>
|
||||||
|
/// Occurs when an update to the property this view model visualizes is requested
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? UpdateRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
/// Expands this view model and any children to expose the provided <paramref name="dataModelPath" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="disposing">
|
/// <param name="dataModelPath">The data model path to expose.</param>
|
||||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
public void ExpandToPath(DataModelPath dataModelPath)
|
||||||
/// <see langword="false" /> to release only unmanaged resources.
|
{
|
||||||
/// </param>
|
if (dataModelPath.Target != DataModel)
|
||||||
protected virtual void Dispose(bool disposing)
|
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
|
||||||
|
|
||||||
|
IsVisualizationExpanded = true;
|
||||||
|
DataModelPathSegment current = dataModelPath.Segments.Skip(1).First();
|
||||||
|
Children.FirstOrDefault(c => c.Path == current.Path)?.ExpandToPath(current.Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the view model that hosts the given path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataModelPath">The path to find</param>
|
||||||
|
/// <returns>The matching view model, may be null if the path doesn't exist or isn't expanded</returns>
|
||||||
|
public DataModelVisualizationViewModel? GetViewModelForPath(DataModelPath dataModelPath)
|
||||||
|
{
|
||||||
|
if (dataModelPath.Target != DataModel)
|
||||||
|
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
|
||||||
|
|
||||||
|
if (DataModelPath?.Path == dataModelPath.Path)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return Children.Select(c => c.GetViewModelForPath(dataModelPath)).FirstOrDefault(match => match != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the <see cref="UpdateRequested" /> event
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnUpdateRequested()
|
||||||
|
{
|
||||||
|
UpdateRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">
|
||||||
|
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||||
|
/// <see langword="false" /> to release only unmanaged resources.
|
||||||
|
/// </param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
DataModelPath?.Dispose();
|
||||||
|
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||||
|
dataModelVisualizationViewModel.Dispose(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual int GetChildDepth()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? dataModelUpdateConfiguration)
|
||||||
|
{
|
||||||
|
if (IsRootViewModel && DataModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Type? modelType = IsRootViewModel ? DataModel?.GetType() : DataModelPath?.GetPropertyType();
|
||||||
|
if (modelType == null)
|
||||||
|
throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type");
|
||||||
|
|
||||||
|
// Add missing static children only once, they're static after all
|
||||||
|
if (!_populatedStaticChildren)
|
||||||
|
{
|
||||||
|
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
|
||||||
{
|
{
|
||||||
DataModelPath?.Dispose();
|
string childPath = AppendToPath(propertyInfo.Name);
|
||||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
if (Children.Any(c => c?.Path != null && c.Path.Equals(childPath)))
|
||||||
dataModelVisualizationViewModel.Dispose(true);
|
continue;
|
||||||
|
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
|
||||||
|
continue;
|
||||||
|
MethodInfo? getMethod = propertyInfo.GetGetMethod();
|
||||||
|
if (getMethod == null || getMethod.GetParameters().Any())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
|
||||||
|
if (child != null)
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
_populatedStaticChildren = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove static children that should be hidden
|
||||||
|
if (DataModel != null)
|
||||||
|
{
|
||||||
|
ReadOnlyCollection<PropertyInfo> hiddenProperties = DataModel.GetHiddenProperties();
|
||||||
|
foreach (PropertyInfo hiddenProperty in hiddenProperties)
|
||||||
|
{
|
||||||
|
string childPath = AppendToPath(hiddenProperty.Name);
|
||||||
|
DataModelVisualizationViewModel? toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath);
|
||||||
|
if (toRemove != null)
|
||||||
|
Children.Remove(toRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
// Add missing dynamic children
|
||||||
public void Dispose()
|
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
|
||||||
{
|
if (value is DataModel dataModel)
|
||||||
Dispose(true);
|
foreach (string key in dataModel.DynamicChildren.Keys.ToList())
|
||||||
GC.SuppressFinalize(this);
|
{
|
||||||
}
|
string childPath = AppendToPath(key);
|
||||||
|
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
|
||||||
|
continue;
|
||||||
|
|
||||||
#endregion
|
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
|
||||||
|
if (child != null)
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dynamic children that have been removed from the data model
|
||||||
|
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();
|
||||||
|
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in toRemoveDynamic)
|
||||||
|
Children.Remove(dataModelVisualizationViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataModelVisualizationViewModel? CreateChild(IDataModelUIService dataModelUIService, string path, int depth)
|
||||||
|
{
|
||||||
|
if (DataModel == null)
|
||||||
|
throw new ArtemisSharedUIException("Cannot create a data model visualization child VM for a parent without a data model");
|
||||||
|
if (depth > MaxDepth)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DataModelPath dataModelPath = new(DataModel, path);
|
||||||
|
if (!dataModelPath.IsValid)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
PropertyInfo? propertyInfo = dataModelPath.GetPropertyInfo();
|
||||||
|
Type? propertyType = dataModelPath.GetPropertyType();
|
||||||
|
|
||||||
|
// Skip properties decorated with DataModelIgnore
|
||||||
|
if (propertyInfo != null && Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute)))
|
||||||
|
return null;
|
||||||
|
// Skip properties that are in the ignored properties list of the respective profile module/data model expansion
|
||||||
|
if (DataModel.GetHiddenProperties().Any(p => p.Equals(propertyInfo)))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (propertyType == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// If a display VM was found, prefer to use that in any case
|
||||||
|
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription);
|
||||||
|
if (typeViewModel != null)
|
||||||
|
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
|
||||||
|
// For primitives, create a property view model, it may be null that is fine
|
||||||
|
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||||
|
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
|
if (propertyType.IsGenericEnumerable())
|
||||||
|
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
|
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
|
||||||
|
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
|
// For other value types create a child view model
|
||||||
|
if (propertyType.IsClass || propertyType.IsStruct())
|
||||||
|
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AppendToPath(string toAppend)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Path))
|
||||||
|
return toAppend;
|
||||||
|
|
||||||
|
StringBuilder builder = new();
|
||||||
|
builder.Append(Path);
|
||||||
|
builder.Append(".");
|
||||||
|
builder.Append(toAppend);
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestUpdate()
|
||||||
|
{
|
||||||
|
Parent?.RequestUpdate();
|
||||||
|
OnUpdateRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExpandToPath(DataModelPathSegment? segment)
|
||||||
|
{
|
||||||
|
if (segment == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsVisualizationExpanded = true;
|
||||||
|
Children.FirstOrDefault(c => c.Path == segment.Path)?.ExpandToPath(segment.Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.DataModelVisualization;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||||
{
|
{
|
||||||
@ -8,33 +12,33 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
|
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
|
||||||
{
|
{
|
||||||
private bool _showNull;
|
private readonly JsonSerializerSettings _serializerSettings;
|
||||||
private bool _showToString;
|
private string _display;
|
||||||
|
|
||||||
internal DefaultDataModelDisplayViewModel()
|
public DefaultDataModelDisplayViewModel()
|
||||||
{
|
{
|
||||||
ShowNull = true;
|
_serializerSettings = new JsonSerializerSettings()
|
||||||
|
{
|
||||||
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||||
|
PreserveReferencesHandling = PreserveReferencesHandling.None
|
||||||
|
};
|
||||||
|
_display = "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowToString
|
public string Display
|
||||||
{
|
{
|
||||||
get => _showToString;
|
get => _display;
|
||||||
private set => SetAndNotify(ref _showToString, value);
|
set => RaiseAndSetIfChanged(ref _display, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowNull
|
|
||||||
{
|
|
||||||
get => _showNull;
|
|
||||||
set => SetAndNotify(ref _showNull, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisplayValueUpdated()
|
protected override void OnDisplayValueUpdated()
|
||||||
{
|
{
|
||||||
if (DisplayValue is Enum enumDisplayValue)
|
if (DisplayValue is Enum enumDisplayValue)
|
||||||
DisplayValue = EnumUtilities.HumanizeValue(enumDisplayValue);
|
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
|
||||||
|
else if (DisplayValue is not string)
|
||||||
ShowToString = DisplayValue != null;
|
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
|
||||||
ShowNull = DisplayValue == null;
|
else
|
||||||
|
Display = DisplayValue?.ToString() ?? "null";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user