mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Meta - Replace WPF projects with Avalonia projects
This commit is contained in:
1 parent
fcf0376a9a
commit
d7f3ee1190
1110 files changed
+10835
-52203
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-1
@@ -4,6 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
@@ -18,7 +19,7 @@
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File diff suppressed because it is too large.
Load diff
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-1
@@ -4,6 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
@@ -18,7 +19,7 @@
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File renamed without changes.
File renamed without changes.
File diff suppressed because it is too large.
Load diff
File renamed without changes.
File renamed without changes.
@@ -1,76 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
||||
<AssemblyTitle>Artemis.UI.Shared</AssemblyTitle>
|
||||
<Company>Artemis.UI.Shared</Company>
|
||||
<Product>Artemis.UI.Shared</Product>
|
||||
<Copyright>Copyright © Robert Beekman - 2020</Copyright>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<UseWPF>true</UseWPF>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<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>
|
||||
<DocumentationFile>bin\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Humanizer.Core" Version="2.11.10" />
|
||||
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.1.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31" />
|
||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
||||
<None Remove="Artemis.UI.Avalonia.Shared.csproj.DotSettings" />
|
||||
<None Remove="Artemis.UI.Shared.csproj.DotSettings" />
|
||||
<None Remove="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
|
||||
<PackageReference Include="DynamicData" Version="7.5.4" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0-prerelease7" />
|
||||
<PackageReference Include="SharpVectors.Reloaded" Version="1.7.5" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.178" />
|
||||
<PackageReference Include="SkiaSharp.Views.WPF" Version="2.80.3" />
|
||||
<PackageReference Include="Stylet" Version="1.3.6" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="obj\x64\Debug\ColorPicker.g.cs" />
|
||||
<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" />
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
<ItemGroup>
|
||||
<Compile Update="Controls\HotkeyBox.axaml.cs">
|
||||
<DependentUpon>HotkeyBox.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Services\Window\ExceptionDialogView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,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">
|
||||
<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/=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/=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/=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>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -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;
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,94 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
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 System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Events;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.Visuals.Media.Imaging;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
||||
/// </summary>
|
||||
public class DeviceVisualizer : FrameworkElement
|
||||
public class DeviceVisualizer : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 const double UpdateFrameRate = 25.0;
|
||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||
private readonly DispatcherTimer _timer;
|
||||
private BitmapImage? _deviceImage;
|
||||
private ArtemisDevice? _oldDevice;
|
||||
private List<DeviceVisualizerLed> _highlightedLeds;
|
||||
private List<DeviceVisualizerLed> _dimmedLeds;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeviceVisualizer" /> class
|
||||
/// </summary>
|
||||
private Rect _deviceBounds;
|
||||
private RenderTargetBitmap? _deviceImage;
|
||||
private List<DeviceVisualizerLed>? _dimmedLeds;
|
||||
private List<DeviceVisualizerLed>? _highlightedLeds;
|
||||
private ArtemisDevice? _oldDevice;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceVisualizer()
|
||||
{
|
||||
_backingStore = new DrawingGroup();
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(1000.0 / UpdateFrameRate)};
|
||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
// Run an update timer at 25 fps
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
|
||||
|
||||
MouseLeftButtonUp += OnMouseLeftButtonUp;
|
||||
Loaded += OnLoaded;
|
||||
Unloaded += OnUnloaded;
|
||||
PointerReleased += OnPointerReleased;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device to visualize
|
||||
/// </summary>
|
||||
public ArtemisDevice? Device
|
||||
/// <inheritdoc />
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
get => (ArtemisDevice) GetValue(DeviceProperty);
|
||||
set => SetValue(DeviceProperty, value);
|
||||
}
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether or not to show per-LED colors
|
||||
/// </summary>
|
||||
public bool ShowColors
|
||||
{
|
||||
get => (bool) GetValue(ShowColorsProperty);
|
||||
set => SetValue(ShowColorsProperty, value);
|
||||
}
|
||||
// Determine the scale required to fit the desired size of the control
|
||||
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of LEDs to highlight
|
||||
/// </summary>
|
||||
public ObservableCollection<ArtemisLed>? HighlightedLeds
|
||||
{
|
||||
get => (ObservableCollection<ArtemisLed>) GetValue(HighlightedLedsProperty);
|
||||
set => SetValue(HighlightedLedsProperty, value);
|
||||
DrawingContext.PushedState? boundsPush = null;
|
||||
try
|
||||
{
|
||||
// Scale the visualization in the desired bounding box
|
||||
if (Bounds.Width > 0 && Bounds.Height > 0)
|
||||
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale));
|
||||
|
||||
// Apply device rotation
|
||||
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
|
||||
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
|
||||
|
||||
// Apply device scale
|
||||
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
|
||||
|
||||
// Render device and LED images
|
||||
if (_deviceImage != null)
|
||||
{
|
||||
drawingContext.DrawImage(
|
||||
_deviceImage,
|
||||
new Rect(_deviceImage.Size),
|
||||
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height),
|
||||
RenderOptions.GetBitmapInterpolationMode(this)
|
||||
);
|
||||
}
|
||||
|
||||
if (!ShowColors)
|
||||
return;
|
||||
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.RenderGeometry(drawingContext, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
boundsPush?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -96,54 +95,6 @@ namespace Artemis.UI.Shared
|
||||
/// </summary>
|
||||
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>
|
||||
/// Invokes the <see cref="LedClicked" /> event
|
||||
/// </summary>
|
||||
@@ -153,72 +104,37 @@ namespace Artemis.UI.Shared
|
||||
LedClicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
private void Update()
|
||||
{
|
||||
if (disposing)
|
||||
_timer.Stop();
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
|
||||
{
|
||||
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()
|
||||
private Rect MeasureDevice()
|
||||
{
|
||||
if (Device == null)
|
||||
return Size.Empty;
|
||||
return Rect.Empty;
|
||||
|
||||
Rect rotationRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
||||
rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
|
||||
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
||||
Geometry geometry = new RectangleGeometry(deviceRect);
|
||||
geometry.Transform = new RotateTransform(Device.Rotation);
|
||||
|
||||
return rotationRect.Size;
|
||||
return geometry.Bounds;
|
||||
}
|
||||
|
||||
private void OnUnloaded(object? sender, RoutedEventArgs e)
|
||||
private void TimerOnTick(object? sender, EventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Tick -= TimerOnTick;
|
||||
|
||||
if (HighlightedLeds != null)
|
||||
HighlightedLeds.CollectionChanged -= HighlightedLedsChanged;
|
||||
if (_oldDevice != null)
|
||||
{
|
||||
if (Device != null)
|
||||
{
|
||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
Device.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
_oldDevice = null;
|
||||
}
|
||||
if (ShowColors && IsVisible && Opacity > 0)
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
Point position = e.GetPosition(this);
|
||||
double x = position.X / RenderSize.Width;
|
||||
double y = position.Y / RenderSize.Height;
|
||||
double x = position.X / Bounds.Width;
|
||||
double y = position.Y / Bounds.Height;
|
||||
|
||||
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
||||
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
|
||||
@@ -226,154 +142,173 @@ namespace Artemis.UI.Shared
|
||||
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
|
||||
}
|
||||
|
||||
private void OnLoaded(object? sender, RoutedEventArgs e)
|
||||
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
_timer.Start();
|
||||
_timer.Tick += TimerOnTick;
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void TimerOnTick(object? sender, EventArgs e)
|
||||
private void DeviceUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
if (ShowColors && Visibility == Visibility.Visible)
|
||||
Render();
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
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 (_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();
|
||||
if (!before)
|
||||
((DeviceVisualizer) sender).SetupForDevice();
|
||||
}
|
||||
|
||||
private void UpdateTransform()
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||
/// </summary>
|
||||
public ArtemisDevice? Device
|
||||
{
|
||||
InvalidateVisual();
|
||||
InvalidateMeasure();
|
||||
get => GetValue(DeviceProperty);
|
||||
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;
|
||||
_deviceVisualizerLeds.Clear();
|
||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
if (_oldDevice != null)
|
||||
if (Device != null)
|
||||
{
|
||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
Device.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Start();
|
||||
_timer.Tick += TimerOnTick;
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Tick -= TimerOnTick;
|
||||
base.OnDetachedFromLogicalTree(e);
|
||||
}
|
||||
|
||||
private void SetupForDevice()
|
||||
{
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = null;
|
||||
_deviceVisualizerLeds.Clear();
|
||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
if (_oldDevice != null)
|
||||
{
|
||||
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
_oldDevice.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
_oldDevice = Device;
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
_deviceBounds = MeasureDevice();
|
||||
|
||||
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
||||
Device.DeviceUpdated += DeviceUpdated;
|
||||
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
|
||||
foreach (ArtemisLed artemisLed in Device.Leds)
|
||||
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
||||
|
||||
if (!ShowColors)
|
||||
// Load the device main image on a background thread
|
||||
ArtemisDevice? device = Device;
|
||||
Task.Run(() =>
|
||||
{
|
||||
InvalidateMeasure();
|
||||
return;
|
||||
}
|
||||
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
|
||||
return;
|
||||
|
||||
// Create the opacity drawing group
|
||||
DrawingGroup opacityDrawingGroup = new();
|
||||
DrawingContext drawingContext = opacityDrawingGroup.Open();
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.RenderOpacityMask(drawingContext);
|
||||
drawingContext.Close();
|
||||
try
|
||||
{
|
||||
// Create a bitmap that'll be used to render the device and LED images just once
|
||||
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.Size.Width * 4, (int) device.RgbDevice.Size.Height * 4));
|
||||
|
||||
// Render the store as a bitmap
|
||||
DrawingImage drawingImage = new(opacityDrawingGroup);
|
||||
Image image = new() {Source = drawingImage};
|
||||
RenderTargetBitmap bitmap = new(
|
||||
Math.Max(1, (int) (opacityDrawingGroup.Bounds.Width * 2.5)),
|
||||
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();
|
||||
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
|
||||
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
|
||||
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.DrawBitmap(context);
|
||||
|
||||
// Set the bitmap as the opacity mask for the colors backing store
|
||||
ImageBrush bitmapBrush = new(bitmap);
|
||||
bitmapBrush.Freeze();
|
||||
_backingStore.OpacityMask = bitmapBrush;
|
||||
_backingStore.Children.Clear();
|
||||
_deviceImage = renderTargetBitmap;
|
||||
|
||||
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)
|
||||
{
|
||||
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>();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Artemis.Core;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Visuals.Media.Imaging;
|
||||
using RGB.NET.Core;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using Point = System.Windows.Point;
|
||||
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
|
||||
using Color = Avalonia.Media.Color;
|
||||
using Point = Avalonia.Point;
|
||||
using SolidColorBrush = Avalonia.Media.SolidColorBrush;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.Controls
|
||||
{
|
||||
internal class DeviceVisualizerLed
|
||||
{
|
||||
private const byte Dimmed = 100;
|
||||
private const byte NonDimmed = 255;
|
||||
private GeometryDrawing? _geometryDrawing;
|
||||
private DrawingGroup? _lastBackingStore;
|
||||
private Color _renderColor;
|
||||
private SolidColorBrush? _renderColorBrush;
|
||||
private readonly SolidColorBrush _penBrush;
|
||||
private readonly SolidColorBrush _fillBrush;
|
||||
private readonly Pen _pen;
|
||||
|
||||
public DeviceVisualizerLed(ArtemisLed led)
|
||||
{
|
||||
@@ -30,83 +29,63 @@ namespace Artemis.UI.Shared
|
||||
Led.RgbLed.Size.Height
|
||||
);
|
||||
|
||||
if (Led.Layout?.Image != null && File.Exists(Led.Layout.Image.LocalPath))
|
||||
LedImage = new BitmapImage(Led.Layout.Image);
|
||||
_fillBrush = new SolidColorBrush();
|
||||
_penBrush = new SolidColorBrush();
|
||||
_pen = new Pen(_penBrush) {LineJoin = PenLineJoin.Round};
|
||||
|
||||
CreateLedGeometry();
|
||||
}
|
||||
|
||||
|
||||
public ArtemisLed Led { get; }
|
||||
public Rect LedRect { get; set; }
|
||||
public BitmapImage? LedImage { get; 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;
|
||||
|
||||
_renderColorBrush ??= new SolidColorBrush();
|
||||
_geometryDrawing ??= new GeometryDrawing(_renderColorBrush, null, new RectangleGeometry(LedRect));
|
||||
try
|
||||
{
|
||||
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
|
||||
drawingContext.DrawBitmap(
|
||||
bitmap.PlatformImpl,
|
||||
1,
|
||||
new Rect(bitmap.Size),
|
||||
new Rect(Led.RgbLed.Location.X * 4, Led.RgbLed.Location.Y * 4, Led.RgbLed.Size.Width * 4, Led.RgbLed.Size.Height * 4),
|
||||
BitmapInterpolationMode.HighQuality
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
||||
{
|
||||
byte r = Led.RgbLed.Color.GetR();
|
||||
byte g = Led.RgbLed.Color.GetG();
|
||||
byte b = Led.RgbLed.Color.GetB();
|
||||
|
||||
_renderColor.A = isDimmed ? Dimmed : NonDimmed;
|
||||
_renderColor.R = r;
|
||||
_renderColor.G = g;
|
||||
_renderColor.B = b;
|
||||
_renderColorBrush.Color = _renderColor;
|
||||
|
||||
if (_lastBackingStore != backingStore)
|
||||
if (dimmed)
|
||||
{
|
||||
backingStore.Children.Add(_geometryDrawing);
|
||||
_lastBackingStore = backingStore;
|
||||
_fillBrush.Color = new Color(50, r, g, b);
|
||||
_penBrush.Color = new Color(100, r, g, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fillBrush.Color = new Color(100, r, g, b);
|
||||
_penBrush.Color = new Color(255, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
drawingContext.DrawGeometry(fillBrush, new Pen(penBrush, 1) {LineJoin = PenLineJoin.Round}, DisplayGeometry.GetOutlinedPathGeometry());
|
||||
// Restore the drawing context
|
||||
drawingContext.Pop();
|
||||
drawingContext.DrawGeometry(_fillBrush, _pen, DisplayGeometry);
|
||||
}
|
||||
|
||||
public bool HitTest(Point position)
|
||||
{
|
||||
if (DisplayGeometry == null)
|
||||
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);
|
||||
return DisplayGeometry != null && DisplayGeometry.FillContains(position);
|
||||
}
|
||||
|
||||
private void CreateLedGeometry()
|
||||
@@ -139,17 +118,17 @@ namespace Artemis.UI.Shared
|
||||
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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)
|
||||
@@ -158,36 +137,17 @@ namespace Artemis.UI.Shared
|
||||
{
|
||||
double width = Led.RgbLed.Size.Width - deflateAmount;
|
||||
double height = Led.RgbLed.Size.Height - deflateAmount;
|
||||
// DisplayGeometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
||||
DisplayGeometry = Geometry.Combine(
|
||||
Geometry.Empty,
|
||||
Geometry.Parse(Led.RgbLed.ShapeData),
|
||||
GeometryCombineMode.Union,
|
||||
new TransformGroup
|
||||
|
||||
Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
||||
geometry.Transform = new TransformGroup
|
||||
{
|
||||
Children = new Transforms
|
||||
{
|
||||
Children = new TransformCollection
|
||||
{
|
||||
new ScaleTransform(width, height),
|
||||
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
|
||||
}
|
||||
new ScaleTransform(width, height),
|
||||
new TranslateTransform(Led.RgbLed.Location.X + deflateAmount / 2, Led.RgbLed.Location.Y + deflateAmount / 2)
|
||||
}
|
||||
);
|
||||
|
||||
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)}
|
||||
});
|
||||
}
|
||||
};
|
||||
DisplayGeometry = geometry;
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -1,46 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
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 />
|
||||
/// <summary>
|
||||
/// 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
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
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));
|
||||
ColorGradient? colorGradient = value as ColorGradient;
|
||||
GradientStops collection = new();
|
||||
if (colorGradient == null)
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
GradientStopCollection? collection = value as GradientStopCollection;
|
||||
ColorGradient colorGradients = new();
|
||||
if (collection == null)
|
||||
return colorGradients;
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
GradientStops? collection = value as GradientStops;
|
||||
ColorGradient colorGradients = new();
|
||||
if (collection == null)
|
||||
return colorGradients;
|
||||
}
|
||||
|
||||
foreach (GradientStop c in collection.OrderBy(s => s.Offset))
|
||||
colorGradients.Add(new ColorGradientStop(new SKColor(c.Color.R, c.Color.G, c.Color.B, c.Color.A), (float) c.Offset));
|
||||
return colorGradients;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,38 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Media;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <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>
|
||||
[ValueConversion(typeof(Color), typeof(SKColor))]
|
||||
public class SKColorToColorConverter : IValueConverter
|
||||
public class ColorToSKColorConverter : IValueConverter
|
||||
{
|
||||
/// <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;
|
||||
return Color.FromArgb(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
if (value is Color avaloniaColor)
|
||||
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||
if (value is Color2 fluentAvaloniaColor)
|
||||
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||
|
||||
return SKColor.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Color color = (Color) value;
|
||||
return new SKColor(color.R, color.G, color.B, color.A);
|
||||
Color result = new(0, 0, 0, 0);
|
||||
if (value is SKColor skColor)
|
||||
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
|
||||
if (targetType == typeof(Color2))
|
||||
return (Color2) result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,31 +1,28 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using Avalonia.Data.Converters;
|
||||
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 />
|
||||
/// <summary>
|
||||
/// Converts <see cref="SKColor" />into <see cref="T:System.String" />.
|
||||
/// </summary>
|
||||
[ValueConversion(typeof(Color), typeof(string))]
|
||||
public class SKColorToStringConverter : IValueConverter
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.ToString();
|
||||
}
|
||||
return value?.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value as string))
|
||||
return SKColor.Empty;
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value as string))
|
||||
return SKColor.Empty;
|
||||
|
||||
return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
|
||||
}
|
||||
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.Globalization;
|
||||
using System.Windows.Data;
|
||||
using Artemis.Core;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
|
||||
@@ -11,7 +11,7 @@ namespace Artemis.UI.Shared
|
||||
public class TypeToStringConverter : IValueConverter
|
||||
{
|
||||
/// <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);
|
||||
if (value is Type type)
|
||||
@@ -21,7 +21,7 @@ namespace Artemis.UI.Shared
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -1,8 +1,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Artemis.Core.Modules;
|
||||
using Stylet;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
|
||||
{
|
||||
[AllowNull]
|
||||
private T _displayValue = default!;
|
||||
[AllowNull] private T _displayValue = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets value that the view model must display
|
||||
@@ -22,7 +21,8 @@ namespace Artemis.UI.Shared
|
||||
get => _displayValue;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _displayValue, value)) return;
|
||||
if (Equals(value, _displayValue)) return;
|
||||
RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
OnDisplayValueUpdated();
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace Artemis.UI.Shared
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class DataModelDisplayViewModel : PropertyChangedBase
|
||||
public abstract class DataModelDisplayViewModel : ViewModelBase
|
||||
{
|
||||
private DataModelPropertyAttribute? _propertyDescription;
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Artemis.UI.Shared
|
||||
public DataModelPropertyAttribute? PropertyDescription
|
||||
{
|
||||
get => _propertyDescription;
|
||||
internal set => SetAndNotify(ref _propertyDescription, value);
|
||||
internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Modules;
|
||||
using Stylet;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="DataModel" /> input view model
|
||||
@@ -37,7 +33,7 @@ namespace Artemis.UI.Shared
|
||||
public T InputValue
|
||||
{
|
||||
get => _inputValue;
|
||||
set => SetAndNotify(ref _inputValue, value);
|
||||
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,9 +50,6 @@ namespace Artemis.UI.Shared
|
||||
return;
|
||||
_closed = true;
|
||||
|
||||
foreach (BindingExpressionBase sourceUpdatingBinding in BindingOperations.GetSourceUpdatingBindings(View))
|
||||
sourceUpdatingBinding.UpdateSource();
|
||||
|
||||
OnSubmit();
|
||||
UpdateCallback(InputValue, true);
|
||||
}
|
||||
@@ -76,7 +69,7 @@ namespace Artemis.UI.Shared
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class DataModelInputViewModel : PropertyChangedBase, IViewAware
|
||||
public abstract class DataModelInputViewModel : ReactiveObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
@@ -117,24 +110,5 @@ namespace Artemis.UI.Shared
|
||||
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 Artemis.Core;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a layer brush registered through
|
||||
|
||||
@@ -3,8 +3,10 @@ using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes an event data model property
|
||||
@@ -23,7 +25,7 @@ namespace Artemis.UI.Shared
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => SetAndNotify(ref _displayValueType, value);
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
File renamed without changes.
-82
@@ -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.Collections;
|
||||
using System.Windows.Documents;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
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>
|
||||
/// Represents a view model that visualizes a list data model property
|
||||
@@ -16,14 +17,14 @@ namespace Artemis.UI.Shared
|
||||
private string _countDisplay;
|
||||
private Type? _displayValueType;
|
||||
private IEnumerable? _list;
|
||||
private BindableCollection<DataModelVisualizationViewModel> _listChildren;
|
||||
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
|
||||
private int _listCount;
|
||||
|
||||
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
_countDisplay = "0 items";
|
||||
_listChildren = new BindableCollection<DataModelVisualizationViewModel>();
|
||||
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +33,7 @@ namespace Artemis.UI.Shared
|
||||
public IEnumerable? List
|
||||
{
|
||||
get => _list;
|
||||
private set => SetAndNotify(ref _list, value);
|
||||
private set => this.RaiseAndSetIfChanged(ref _list, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +42,7 @@ namespace Artemis.UI.Shared
|
||||
public int ListCount
|
||||
{
|
||||
get => _listCount;
|
||||
private set => SetAndNotify(ref _listCount, value);
|
||||
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,7 +51,7 @@ namespace Artemis.UI.Shared
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => SetAndNotify(ref _displayValueType, value);
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,16 +60,16 @@ namespace Artemis.UI.Shared
|
||||
public string CountDisplay
|
||||
{
|
||||
get => _countDisplay;
|
||||
set => SetAndNotify(ref _countDisplay, value);
|
||||
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of child view models that visualize the elements in the list
|
||||
/// </summary>
|
||||
public BindableCollection<DataModelVisualizationViewModel> ListChildren
|
||||
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
|
||||
{
|
||||
get => _listChildren;
|
||||
private set => SetAndNotify(ref _listChildren, value);
|
||||
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -97,22 +98,15 @@ namespace Artemis.UI.Shared
|
||||
ListChildren.Add(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = ListChildren[index];
|
||||
}
|
||||
|
||||
if (child is DataModelListPropertiesViewModel dataModelListClassViewModel)
|
||||
{
|
||||
dataModelListClassViewModel.DisplayValue = item;
|
||||
dataModelListClassViewModel.Index = index;
|
||||
}
|
||||
else if (child is DataModelListPropertyViewModel dataModelListPropertyViewModel)
|
||||
if (child is DataModelListItemViewModel dataModelListPropertyViewModel)
|
||||
{
|
||||
dataModelListPropertyViewModel.DisplayValue = item;
|
||||
dataModelListPropertyViewModel.Index = index;
|
||||
dataModelListPropertyViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
|
||||
child.Update(dataModelUIService, configuration);
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -134,16 +128,10 @@ namespace Artemis.UI.Shared
|
||||
{
|
||||
// If a display VM was found, prefer to use that in any case
|
||||
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.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
get => _displayValueType;
|
||||
private set => SetAndNotify(ref _displayValueType, value);
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +35,7 @@ namespace Artemis.UI.Shared
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
private set => SetAndNotify(ref _displayValue, value);
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
get => _displayValue;
|
||||
internal set => SetAndNotify(ref _displayValue, value);
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,7 +37,7 @@ namespace Artemis.UI.Shared
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
protected set => SetAndNotify(ref _displayValueType, value);
|
||||
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +46,7 @@ namespace Artemis.UI.Shared
|
||||
public DataModelDisplayViewModel? DisplayViewModel
|
||||
{
|
||||
get => _displayViewModel;
|
||||
internal set => SetAndNotify(ref _displayViewModel, value);
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
|
||||
|
||||
+361
-333
@@ -6,368 +6,396 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
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>
|
||||
/// Represents a base class for a view model that visualizes a part of the data model
|
||||
/// </summary>
|
||||
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a base class for a view model that visualizes a part of the data model
|
||||
/// </summary>
|
||||
public abstract class DataModelVisualizationViewModel : PropertyChangedBase, IDisposable
|
||||
private const int MaxDepth = 4;
|
||||
private ObservableCollection<DataModelVisualizationViewModel> _children;
|
||||
private DataModel? _dataModel;
|
||||
private bool _isMatchingFilteredTypes;
|
||||
private bool _isVisualizationExpanded;
|
||||
private DataModelVisualizationViewModel? _parent;
|
||||
private bool _populatedStaticChildren;
|
||||
private DataModelPropertyAttribute? _propertyDescription;
|
||||
|
||||
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||
{
|
||||
private const int MaxDepth = 4;
|
||||
private BindableCollection<DataModelVisualizationViewModel> _children;
|
||||
private DataModel? _dataModel;
|
||||
private bool _isMatchingFilteredTypes;
|
||||
private bool _isVisualizationExpanded;
|
||||
private DataModelVisualizationViewModel? _parent;
|
||||
private DataModelPropertyAttribute? _propertyDescription;
|
||||
private bool _populatedStaticChildren;
|
||||
_dataModel = dataModel;
|
||||
_children = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||
_parent = parent;
|
||||
DataModelPath = dataModelPath;
|
||||
IsMatchingFilteredTypes = true;
|
||||
|
||||
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;
|
||||
_children = new BindableCollection<DataModelVisualizationViewModel>();
|
||||
_parent = parent;
|
||||
DataModelPath = dataModelPath;
|
||||
IsMatchingFilteredTypes = true;
|
||||
|
||||
if (parent == null)
|
||||
IsRootViewModel = true;
|
||||
else
|
||||
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
|
||||
if (!this.RaiseAndSetIfChanged(ref _isVisualizationExpanded, value)) return;
|
||||
RequestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 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>
|
||||
/// 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 => 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>
|
||||
/// 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();
|
||||
}
|
||||
|
||||
private string AppendToPath(string toAppend)
|
||||
// If the VM has children, its own type is not relevant
|
||||
if (Children.Any())
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
return toAppend;
|
||||
foreach (DataModelVisualizationViewModel child in Children)
|
||||
child?.ApplyTypeFilter(looseMatch, filteredTypes);
|
||||
|
||||
StringBuilder builder = new();
|
||||
builder.Append(Path);
|
||||
builder.Append(".");
|
||||
builder.Append(toAppend);
|
||||
return builder.ToString();
|
||||
IsMatchingFilteredTypes = true;
|
||||
return;
|
||||
}
|
||||
|
||||
private void RequestUpdate()
|
||||
// If null is passed, clear the type filter
|
||||
if (filteredTypes == null || filteredTypes.Length == 0)
|
||||
{
|
||||
Parent?.RequestUpdate();
|
||||
OnUpdateRequested();
|
||||
IsMatchingFilteredTypes = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// 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()
|
||||
// If the type couldn't be retrieved either way, assume false
|
||||
Type? type = DataModelPath?.GetPropertyType();
|
||||
if (type == null)
|
||||
{
|
||||
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>
|
||||
/// 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)
|
||||
/// <summary>
|
||||
/// Expands this view model and any children to expose the provided <paramref name="dataModelPath" />.
|
||||
/// </summary>
|
||||
/// <param name="dataModelPath">The data model path to expose.</param>
|
||||
public void ExpandToPath(DataModelPath dataModelPath)
|
||||
{
|
||||
if (dataModelPath.Target != DataModel)
|
||||
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
|
||||
|
||||
IsVisualizationExpanded = true;
|
||||
DataModelPathSegment current = dataModelPath.Segments.Skip(1).First();
|
||||
Children.FirstOrDefault(c => c.Path == current.Path)?.ExpandToPath(current.Next);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the view model that hosts the given path.
|
||||
/// </summary>
|
||||
/// <param name="dataModelPath">The path to find</param>
|
||||
/// <returns>The matching view model, may be null if the path doesn't exist or isn't expanded</returns>
|
||||
public DataModelVisualizationViewModel? GetViewModelForPath(DataModelPath dataModelPath)
|
||||
{
|
||||
if (dataModelPath.Target != DataModel)
|
||||
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
|
||||
|
||||
if (DataModelPath?.Path == dataModelPath.Path)
|
||||
return this;
|
||||
|
||||
return Children.Select(c => c.GetViewModelForPath(dataModelPath)).FirstOrDefault(match => match != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="UpdateRequested" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnUpdateRequested()
|
||||
{
|
||||
UpdateRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">
|
||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
||||
/// <see langword="false" /> to release only unmanaged resources.
|
||||
/// </param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
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();
|
||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||
dataModelVisualizationViewModel.Dispose(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
// 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;
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+21
-17
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.DataModelVisualization;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||
{
|
||||
@@ -8,33 +12,33 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||
/// </summary>
|
||||
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
|
||||
{
|
||||
private bool _showNull;
|
||||
private bool _showToString;
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
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;
|
||||
private set => SetAndNotify(ref _showToString, value);
|
||||
}
|
||||
|
||||
public bool ShowNull
|
||||
{
|
||||
get => _showNull;
|
||||
set => SetAndNotify(ref _showNull, value);
|
||||
get => _display;
|
||||
set => RaiseAndSetIfChanged(ref _display, value);
|
||||
}
|
||||
|
||||
protected override void OnDisplayValueUpdated()
|
||||
{
|
||||
if (DisplayValue is Enum enumDisplayValue)
|
||||
DisplayValue = EnumUtilities.HumanizeValue(enumDisplayValue);
|
||||
|
||||
ShowToString = DisplayValue != null;
|
||||
ShowNull = DisplayValue == null;
|
||||
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
|
||||
else if (DisplayValue is not string)
|
||||
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
|
||||
else
|
||||
Display = DisplayValue?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1110 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user