mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Meta - Replace WPF projects with Avalonia projects
This commit is contained in:
parent
fcf0376a9a
commit
d7f3ee1190
@ -4,6 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
1828
src/Artemis.UI.Linux/packages.lock.json
Normal file
1828
src/Artemis.UI.Linux/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
1828
src/Artemis.UI.MacOS/packages.lock.json
Normal file
1828
src/Artemis.UI.MacOS/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,76 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<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>
|
||||
<Compile Update="Controls\HotkeyBox.axaml.cs">
|
||||
<DependentUpon>HotkeyBox.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Services\Window\ExceptionDialogView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj">
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</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>
|
||||
@ -1,41 +0,0 @@
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Navigation;
|
||||
using Artemis.Core;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a behavior that opens the URI of the hyperlink in the browser when requested
|
||||
/// </summary>
|
||||
public class OpenInBrowser : Behavior<Hyperlink>
|
||||
{
|
||||
private Hyperlink? _hyperLink;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
_hyperLink = AssociatedObject;
|
||||
if (_hyperLink == null)
|
||||
return;
|
||||
|
||||
_hyperLink.RequestNavigate += HyperLinkOnRequestNavigate;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
if (_hyperLink == null) return;
|
||||
_hyperLink.RequestNavigate -= HyperLinkOnRequestNavigate;
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void HyperLinkOnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a behavior that puts the cursor at the end of a text box when it receives focus
|
||||
/// </summary>
|
||||
public class PutCursorAtEndTextBox : Behavior<UIElement>
|
||||
{
|
||||
private TextBox? _textBox;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
_textBox = AssociatedObject as TextBox;
|
||||
|
||||
if (_textBox == null) return;
|
||||
_textBox.GotFocus += TextBoxGotFocus;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
if (_textBox == null) return;
|
||||
_textBox.GotFocus -= TextBoxGotFocus;
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
|
||||
{
|
||||
if (_textBox == null) return;
|
||||
_textBox.CaretIndex = _textBox.Text.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// A behavior that makes a scroll viewer bubble up its events if it is at its scroll limit
|
||||
/// </summary>
|
||||
public class ScrollParentWhenAtMax : Behavior<FrameworkElement>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.PreviewMouseWheel += PreviewMouseWheel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel;
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
ScrollViewer? scrollViewer = GetVisualChild<ScrollViewer>(AssociatedObject);
|
||||
if (scrollViewer == null)
|
||||
return;
|
||||
double scrollPos = scrollViewer.ContentVerticalOffset;
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0 || scrollPos == 0 && e.Delta > 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
MouseWheelEventArgs e2 = new(e.MouseDevice, e.Timestamp, e.Delta);
|
||||
e2.RoutedEvent = UIElement.MouseWheelEvent;
|
||||
AssociatedObject.RaiseEvent(e2);
|
||||
}
|
||||
}
|
||||
|
||||
private static T? GetVisualChild<T>(DependencyObject parent) where T : Visual
|
||||
{
|
||||
T? child = default;
|
||||
|
||||
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (int i = 0; i < numVisuals; i++)
|
||||
{
|
||||
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
|
||||
child = v as T;
|
||||
if (child == null) child = GetVisualChild<T>(v);
|
||||
if (child != null) break;
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
using Artemis.UI.Shared.Controls;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Ninject;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the main entry point for the shared UI library
|
||||
/// <para>The Artemis UI calls this so there's no need to deal with this in a plugin</para>
|
||||
/// </summary>
|
||||
public static class Bootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether or not the shared UI library has been initialized
|
||||
/// </summary>
|
||||
public static bool Initialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the shared UI library
|
||||
/// </summary>
|
||||
/// <param name="kernel"></param>
|
||||
public static void Initialize(IKernel kernel)
|
||||
{
|
||||
if (Initialized)
|
||||
return;
|
||||
|
||||
GradientPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
||||
ColorPicker.ColorPickerService = kernel.Get<IColorPickerService>();
|
||||
DataModelPicker.DataModelUIService = kernel.Get<IDataModelUIService>();
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.ArtemisIcon"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<ContentControl Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Icon}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Focusable="False">
|
||||
<ContentControl.Resources>
|
||||
<DataTemplate DataType="{x:Type system:Uri}">
|
||||
<Rectangle Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Foreground}">
|
||||
<Rectangle.OpacityMask>
|
||||
<VisualBrush Stretch="Uniform">
|
||||
<VisualBrush.Visual>
|
||||
<Image Source="{Binding Converter={svgc:SvgImageConverter}, Mode=OneWay}"/>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Rectangle.OpacityMask>
|
||||
</Rectangle>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type materialDesign:PackIconKind}">
|
||||
<materialDesign:PackIcon Kind="{Binding}" Width="Auto" Height="Auto"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</UserControl>
|
||||
@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ArtemisIcon.xaml
|
||||
/// </summary>
|
||||
public partial class ArtemisIcon : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
|
||||
/// to an SVG
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(object), typeof(ArtemisIcon),
|
||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="PackIconKind" />
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PackIconProperty = DependencyProperty.Register(nameof(PackIcon), typeof(PackIconKind?), typeof(ArtemisIcon),
|
||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Uri" /> pointing to the SVG
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SvgSourceProperty = DependencyProperty.Register(nameof(SvgSource), typeof(Uri), typeof(ArtemisIcon),
|
||||
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
|
||||
|
||||
private bool _inCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisIcon"/> class
|
||||
/// </summary>
|
||||
public ArtemisIcon()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
|
||||
/// to an SVG
|
||||
/// </summary>
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="PackIconKind" />
|
||||
/// </summary>
|
||||
public PackIconKind? PackIcon
|
||||
{
|
||||
get => (PackIconKind?) GetValue(PackIconProperty);
|
||||
set => SetValue(PackIconProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Uri" /> pointing to the SVG
|
||||
/// </summary>
|
||||
public Uri SvgSource
|
||||
{
|
||||
get => (Uri) GetValue(SvgSourceProperty);
|
||||
set => SetValue(SvgSourceProperty, value);
|
||||
}
|
||||
|
||||
private static void IconPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
ArtemisIcon artemisIcon = (ArtemisIcon) d;
|
||||
if (artemisIcon._inCallback)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
artemisIcon._inCallback = true;
|
||||
if (artemisIcon.PackIcon != null)
|
||||
{
|
||||
artemisIcon.Icon = artemisIcon.PackIcon;
|
||||
}
|
||||
else if (artemisIcon.SvgSource != null)
|
||||
{
|
||||
artemisIcon.Icon = artemisIcon.SvgSource;
|
||||
}
|
||||
else if (artemisIcon.Icon is string iconString)
|
||||
{
|
||||
if (Uri.TryCreate(iconString, UriKind.Absolute, out Uri? uriResult))
|
||||
artemisIcon.Icon = uriResult;
|
||||
else if (Enum.TryParse(typeof(PackIconKind), iconString, true, out object? result))
|
||||
artemisIcon.Icon = result;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
artemisIcon._inCallback = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,159 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
x:Class="Artemis.UI.Shared.ColorPicker"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="101.848" d:DesignWidth="242.956">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/Resources/ArtemisShared.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.ColorPicker.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<shared:ColorToStringConverter x:Key="ColorToStringConverter" />
|
||||
<shared:ColorToSolidColorConverter x:Key="ColorToSolidColorConverter" />
|
||||
<ControlTemplate x:Key="MaterialDesignOpacitySlider" TargetType="{x:Type Slider}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="1" Height="8" CornerRadius="4" Background="{StaticResource Checkerboard}">
|
||||
<Border Height="8" CornerRadius="4">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="Transparent" />
|
||||
<GradientStop Offset="1.0"
|
||||
Color="{Binding Color, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorToSolidColorConverter}}" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</Border>
|
||||
<Track x:Name="PART_Track"
|
||||
Grid.Row="1"
|
||||
OpacityMask="{x:Null}">
|
||||
<Track.DecreaseRepeatButton>
|
||||
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
|
||||
</Track.DecreaseRepeatButton>
|
||||
<Track.IncreaseRepeatButton>
|
||||
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource MaterialDesignRepeatButton}" />
|
||||
</Track.IncreaseRepeatButton>
|
||||
<Track.Thumb>
|
||||
<Thumb x:Name="Thumb" Width="20" Height="20" VerticalAlignment="Center" Focusable="False" OverridesDefaultStyle="True"
|
||||
Template="{DynamicResource MaterialDesignColorSliderThumb}">
|
||||
<Thumb.Foreground>
|
||||
<SolidColorBrush
|
||||
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay, Converter={StaticResource ColorToSolidColorConverter}}" />
|
||||
</Thumb.Foreground>
|
||||
</Thumb>
|
||||
</Track.Thumb>
|
||||
</Track>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<TextBox x:Name="ColorCodeTextBox"
|
||||
materialDesign:TextFieldAssist.TextBoxViewMargin="0 2 20 0"
|
||||
materialDesign:HintAssist.IsFloating="False"
|
||||
Style="{Binding TextBoxStyle, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Text="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource ColorToStringConverter}}"
|
||||
MinWidth="95"
|
||||
MaxLength="9"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
FontFamily="Consolas"
|
||||
CharacterCasing="Upper">
|
||||
<materialDesign:TextFieldAssist.CharacterCounterStyle>
|
||||
<Style TargetType="TextBlock" />
|
||||
</materialDesign:TextFieldAssist.CharacterCounterStyle>
|
||||
</TextBox>
|
||||
|
||||
<Border Width="15"
|
||||
Height="15"
|
||||
CornerRadius="15"
|
||||
Margin="0 0 2 0"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{StaticResource Checkerboard}">
|
||||
<Ellipse Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
|
||||
<Ellipse.Fill>
|
||||
<SolidColorBrush Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=OneWay}" />
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Border>
|
||||
|
||||
<Popup AllowsTransparency="True"
|
||||
Placement="Bottom"
|
||||
CustomPopupPlacementCallback="{x:Static materialDesign:CustomPopupPlacementCallbackHelper.LargePopupCallback}"
|
||||
PlacementTarget="{Binding ElementName=ColorCodeTextBox}"
|
||||
StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
PopupAnimation="Fade"
|
||||
IsOpen="{Binding PopupOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
||||
<materialDesign:Card Width="200" Height="260" Margin="30" materialDesign:ShadowAssist.ShadowDepth="Depth4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<materialDesign:ColorPicker Grid.Row="0"
|
||||
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
MouseUp="ColorGradient_OnMouseUp"
|
||||
PreviewMouseDown="Slider_OnMouseDown"
|
||||
PreviewMouseUp="Slider_OnMouseUp" />
|
||||
<Slider Grid.Row="1" Margin="8 2 8 8"
|
||||
IsMoveToPointEnabled="True"
|
||||
Orientation="Horizontal"
|
||||
Style="{DynamicResource MaterialDesignColorSlider}"
|
||||
Template="{StaticResource MaterialDesignOpacitySlider}"
|
||||
Value="{Binding ColorOpacity, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
PreviewMouseDown="Slider_OnMouseDown"
|
||||
PreviewMouseUp="Slider_OnMouseUp"
|
||||
Maximum="255" />
|
||||
|
||||
<ItemsControl Grid.Row="2" x:Name="RecentColorsContainer" Margin="10 0" HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border CornerRadius="4" Background="{StaticResource Checkerboard}" Width="16" Height="16" Margin="2">
|
||||
<Rectangle RadiusX="4" RadiusY="4" Cursor="Hand" MouseLeftButtonDown="SelectRecentColor" ToolTip="{Binding}">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{Binding}" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
<CheckBox Grid.Row="3"
|
||||
x:Name="PreviewCheckBox"
|
||||
Margin="10 5 0 10"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource MaterialDesignCheckBox}"
|
||||
Click="PreviewCheckBoxClick">
|
||||
Preview on devices
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -1,303 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Artemis.UI.Shared.Services;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPicker.xaml
|
||||
/// </summary>
|
||||
public partial class ColorPicker : INotifyPropertyChanged
|
||||
{
|
||||
private static IColorPickerService? _colorPickerService;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(nameof(Color), typeof(Color), typeof(ColorPicker),
|
||||
new FrameworkPropertyMetadata(default(Color), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating that the popup containing the color picker is open
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PopupOpenProperty = DependencyProperty.Register(nameof(PopupOpen), typeof(bool), typeof(ColorPicker),
|
||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PopupOpenPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty StaysOpenProperty = DependencyProperty.Register(nameof(StaysOpen), typeof(bool), typeof(ColorPicker),
|
||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, StaysOpenPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used on the text box
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextBoxStyleProperty = DependencyProperty.Register(nameof(TextBoxStyle), typeof(Style), typeof(ColorPicker),
|
||||
new FrameworkPropertyMetadata(Application.Current.Resources["MaterialDesignTextBox"]));
|
||||
|
||||
internal static readonly DependencyProperty ColorOpacityProperty = DependencyProperty.Register(nameof(ColorOpacity), typeof(byte), typeof(ColorPicker),
|
||||
new FrameworkPropertyMetadata((byte) 255, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorOpacityPropertyChangedCallback));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the selected color has changed
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent ColorChangedEvent =
|
||||
EventManager.RegisterRoutedEvent(
|
||||
nameof(Color),
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedPropertyChangedEventHandler<Color>),
|
||||
typeof(ColorPicker));
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the popup opens or closes
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent PopupOpenChangedEvent =
|
||||
EventManager.RegisterRoutedEvent(
|
||||
nameof(PopupOpen),
|
||||
RoutingStrategy.Bubble,
|
||||
typeof(RoutedPropertyChangedEventHandler<bool>),
|
||||
typeof(ColorPicker));
|
||||
|
||||
private bool _inCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ColorPicker" /> class
|
||||
/// </summary>
|
||||
public ColorPicker()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get => (Color) GetValue(ColorProperty);
|
||||
set => SetValue(ColorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating that the popup containing the color picker is open
|
||||
/// </summary>
|
||||
public bool PopupOpen
|
||||
{
|
||||
get => (bool) GetValue(PopupOpenProperty);
|
||||
set => SetValue(PopupOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
|
||||
/// </summary>
|
||||
public bool StaysOpen
|
||||
{
|
||||
get => (bool) GetValue(StaysOpenProperty);
|
||||
set => SetValue(StaysOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used on the text box
|
||||
/// </summary>
|
||||
public Style TextBoxStyle
|
||||
{
|
||||
get => (Style) GetValue(TextBoxStyleProperty);
|
||||
set => SetValue(TextBoxStyleProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
|
||||
/// </summary>
|
||||
internal static IColorPickerService ColorPickerService
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_colorPickerService != null)
|
||||
throw new AccessViolationException("This is not for you to touch");
|
||||
_colorPickerService = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal byte ColorOpacity
|
||||
{
|
||||
get => (byte) GetValue(ColorOpacityProperty);
|
||||
set => SetValue(ColorOpacityProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when dragging the color picker has started
|
||||
/// </summary>
|
||||
public event EventHandler? DragStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when dragging the color picker has ended
|
||||
/// </summary>
|
||||
public event EventHandler? DragEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PropertyChanged" /> event
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DragStarted" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDragStarted()
|
||||
{
|
||||
DragStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DragEnded" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDragEnded()
|
||||
{
|
||||
DragEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static void ColorPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
ColorPicker colorPicker = (ColorPicker) d;
|
||||
if (colorPicker._inCallback)
|
||||
return;
|
||||
|
||||
colorPicker._inCallback = true;
|
||||
|
||||
colorPicker.SetCurrentValue(ColorOpacityProperty, ((Color) e.NewValue).A);
|
||||
colorPicker.OnPropertyChanged(nameof(Color));
|
||||
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
|
||||
|
||||
colorPicker._inCallback = false;
|
||||
}
|
||||
|
||||
private static void PopupOpenPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
ColorPicker colorPicker = (ColorPicker) d;
|
||||
if (colorPicker._inCallback)
|
||||
return;
|
||||
|
||||
colorPicker._inCallback = true;
|
||||
colorPicker.OnPropertyChanged(nameof(PopupOpen));
|
||||
|
||||
if ((bool) e.NewValue)
|
||||
colorPicker.PopupOpened();
|
||||
else
|
||||
colorPicker.PopupClosed();
|
||||
|
||||
colorPicker._inCallback = false;
|
||||
}
|
||||
|
||||
private static void StaysOpenPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
ColorPicker colorPicker = (ColorPicker) d;
|
||||
if (colorPicker._inCallback)
|
||||
return;
|
||||
|
||||
colorPicker._inCallback = true;
|
||||
colorPicker.OnPropertyChanged(nameof(PopupOpen));
|
||||
colorPicker._inCallback = false;
|
||||
}
|
||||
|
||||
private static void ColorOpacityPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
ColorPicker colorPicker = (ColorPicker) d;
|
||||
if (colorPicker._inCallback)
|
||||
return;
|
||||
|
||||
colorPicker._inCallback = true;
|
||||
|
||||
Color color = colorPicker.Color;
|
||||
if (e.NewValue is byte opacity)
|
||||
color = Color.FromArgb(opacity, color.R, color.G, color.B);
|
||||
colorPicker.SetCurrentValue(ColorProperty, color);
|
||||
colorPicker.OnPropertyChanged(nameof(ColorOpacity));
|
||||
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
|
||||
|
||||
colorPicker._inCallback = false;
|
||||
}
|
||||
|
||||
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
PopupOpen = !PopupOpen;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ColorGradient_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Slider_OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
OnDragStarted();
|
||||
|
||||
if (_colorPickerService.PreviewSetting.Value)
|
||||
_colorPickerService.StartColorDisplay();
|
||||
}
|
||||
|
||||
private void Slider_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
OnDragEnded();
|
||||
_colorPickerService.StopColorDisplay();
|
||||
}
|
||||
|
||||
private void PreviewCheckBoxClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
_colorPickerService.PreviewSetting.Value = PreviewCheckBox.IsChecked ?? false;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
|
||||
_colorPickerService.PreviewSetting.SettingChanged += PreviewSettingOnSettingChanged;
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
_colorPickerService.PreviewSetting.SettingChanged -= PreviewSettingOnSettingChanged;
|
||||
}
|
||||
|
||||
private void PreviewSettingOnSettingChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null) return;
|
||||
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
|
||||
}
|
||||
|
||||
private void PopupClosed()
|
||||
{
|
||||
_colorPickerService?.QueueRecentColor(Color);
|
||||
}
|
||||
|
||||
private void PopupOpened()
|
||||
{
|
||||
if (_colorPickerService != null)
|
||||
RecentColorsContainer.ItemsSource = new ObservableCollection<Color>(_colorPickerService.RecentColors);
|
||||
}
|
||||
|
||||
private void SelectRecentColor(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Color = (Color) ((Rectangle) sender).DataContext;
|
||||
PopupOpen = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.Controls.DataModelPicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Shared.Controls"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/ResourceDictionaries/DataModelConditions.xaml" />
|
||||
<ResourceDictionary>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<shared:BindingProxy x:Key="DataContextProxy" Data="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
||||
<DataTemplate x:Key="DataModelDataTemplate">
|
||||
<Control x:Name="TemplateControl" Focusable="False" Template="{StaticResource DataModelSelectionTemplate}" />
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Data.ShowDataModelValues, Source={StaticResource DataContextProxy}}" Value="True">
|
||||
<Setter TargetName="TemplateControl" Property="Template" Value="{StaticResource DataModelSelectionTemplateWithValues}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Button Background="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
BorderBrush="{Binding ButtonBrush, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Style="{StaticResource DataModelConditionButton}"
|
||||
IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
HorizontalAlignment="Left"
|
||||
Click="PropertyButton_OnClick"
|
||||
x:Name="DataModelButton">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu.Resources>
|
||||
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MaterialDesignMenuItem}">
|
||||
<Setter Property="ItemsSource" Value="{Binding Children}" />
|
||||
<Setter Property="Command" Value="{Binding Data.SelectPropertyCommand, Source={StaticResource DataContextProxy}}" />
|
||||
<Setter Property="CommandParameter" Value="{Binding}" />
|
||||
<Setter Property="CommandTarget" Value="{Binding}" />
|
||||
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes}" />
|
||||
<Setter Property="IsSubmenuOpen" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="HeaderTemplate" Value="{StaticResource DataModelDataTemplate}" />
|
||||
</Style>
|
||||
<shared:IntToVisibilityConverter x:Key="IntToVisibilityConverter" />
|
||||
</ContextMenu.Resources>
|
||||
<ContextMenu.ItemsSource>
|
||||
<CompositeCollection>
|
||||
<CollectionContainer Collection="{Binding Data.ExtraDataModelViewModels, Source={StaticResource DataContextProxy}}" />
|
||||
<Separator Visibility="{Binding Data.ExtraDataModelViewModels.Count, Source={StaticResource DataContextProxy}, Converter={StaticResource IntToVisibilityConverter}, Mode=OneWay}" />
|
||||
<CollectionContainer Collection="{Binding Data.DataModelViewModel.Children, Source={StaticResource DataContextProxy}}" />
|
||||
</CompositeCollection>
|
||||
</ContextMenu.ItemsSource>
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Grid>
|
||||
<Grid x:Name="ValueDisplay">
|
||||
<TextBlock x:Name="ValueDisplayTextBlock"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
||||
<TextBlock FontStyle="Italic"
|
||||
Visibility="{Binding DataModelPath, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
||||
<Run Text="« " /><Run Text="{Binding Placeholder, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" /><Run Text=" »" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock x:Name="ValuePlaceholder" FontStyle="Italic">
|
||||
« Invalid »
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@ -1,343 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
|
||||
namespace Artemis.UI.Shared.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for DataModelPicker.xaml
|
||||
/// </summary>
|
||||
public partial class DataModelPicker : INotifyPropertyChanged
|
||||
{
|
||||
private static IDataModelUIService _dataModelUIService;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets data model path
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataModelPathProperty = DependencyProperty.Register(
|
||||
nameof(DataModelPath), typeof(DataModelPath), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DataModelPathPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(
|
||||
nameof(Placeholder), typeof(string), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata("Click to select")
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowDataModelValuesProperty = DependencyProperty.Register(nameof(ShowDataModelValues), typeof(bool), typeof(DataModelPicker));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register(
|
||||
nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(ShowFullPathPropertyCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ButtonBrushProperty = DependencyProperty.Register(nameof(ButtonBrush), typeof(Brush), typeof(DataModelPicker));
|
||||
|
||||
/// <summary>
|
||||
/// A list of extra modules to show data models of
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ModulesProperty = DependencyProperty.Register(
|
||||
nameof(Modules), typeof(ObservableCollection<Module>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new ObservableCollection<Module>(), FrameworkPropertyMetadataOptions.None, ModulesPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// The data model view model to show, if not provided one will be retrieved by the control
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataModelViewModelProperty = DependencyProperty.Register(
|
||||
nameof(DataModelViewModel), typeof(DataModelPropertiesViewModel), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, DataModelViewModelPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// A list of data model view models to show
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ExtraDataModelViewModelsProperty = DependencyProperty.Register(
|
||||
nameof(ExtraDataModelViewModels), typeof(ObservableCollection<DataModelPropertiesViewModel>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new ObservableCollection<DataModelPropertiesViewModel>(), FrameworkPropertyMetadataOptions.None, ExtraDataModelViewModelsPropertyChangedCallback)
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// A list of data model view models to show
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FilterTypesProperty = DependencyProperty.Register(
|
||||
nameof(FilterTypes), typeof(ObservableCollection<Type>), typeof(DataModelPicker),
|
||||
new FrameworkPropertyMetadata(new ObservableCollection<Type>())
|
||||
);
|
||||
|
||||
public DataModelPicker()
|
||||
{
|
||||
SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand);
|
||||
Unloaded += OnUnloaded;
|
||||
InitializeComponent();
|
||||
GetDataModel();
|
||||
UpdateValueDisplay();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public DataModelPath? DataModelPath
|
||||
{
|
||||
get => (DataModelPath) GetValue(DataModelPathProperty);
|
||||
set => SetValue(DataModelPathProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string) GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public bool ShowDataModelValues
|
||||
{
|
||||
get => (bool) GetValue(ShowDataModelValuesProperty);
|
||||
set => SetValue(ShowDataModelValuesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public bool ShowFullPath
|
||||
{
|
||||
get => (bool) GetValue(ShowFullPathProperty);
|
||||
set => SetValue(ShowFullPathProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brush to use when drawing the button
|
||||
/// </summary>
|
||||
public Brush ButtonBrush
|
||||
{
|
||||
get => (Brush) GetValue(ButtonBrushProperty);
|
||||
set => SetValue(ButtonBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of extra modules to show data models of
|
||||
/// </summary>
|
||||
public ObservableCollection<Module>? Modules
|
||||
{
|
||||
get => (ObservableCollection<Module>) GetValue(ModulesProperty);
|
||||
set => SetValue(ModulesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data model view model to show
|
||||
/// </summary>
|
||||
public DataModelPropertiesViewModel? DataModelViewModel
|
||||
{
|
||||
get => (DataModelPropertiesViewModel) GetValue(DataModelViewModelProperty);
|
||||
set => SetValue(DataModelViewModelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of data model view models to show
|
||||
/// </summary>
|
||||
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
|
||||
{
|
||||
get => (ObservableCollection<DataModelPropertiesViewModel>) GetValue(ExtraDataModelViewModelsProperty);
|
||||
set => SetValue(ExtraDataModelViewModelsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the types of properties this view model will allow to be selected
|
||||
/// </summary>
|
||||
public ObservableCollection<Type>? FilterTypes
|
||||
{
|
||||
get => (ObservableCollection<Type>) GetValue(FilterTypesProperty);
|
||||
set => SetValue(FilterTypesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command used by view
|
||||
/// </summary>
|
||||
public DelegateCommand SelectPropertyCommand { get; }
|
||||
|
||||
internal static IDataModelUIService DataModelUIService
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_dataModelUIService != null)
|
||||
throw new AccessViolationException("This is not for you to touch");
|
||||
_dataModelUIService = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new path has been selected
|
||||
/// </summary>
|
||||
public event EventHandler<DataModelSelectedEventArgs>? DataModelPathSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DataModelPathSelected" /> event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
|
||||
{
|
||||
DataModelPathSelected?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void GetDataModel()
|
||||
{
|
||||
ChangeDataModel(_dataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List<Module>(), true));
|
||||
}
|
||||
|
||||
private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
|
||||
{
|
||||
if (DataModelViewModel != null)
|
||||
{
|
||||
DataModelViewModel.Dispose();
|
||||
DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
|
||||
}
|
||||
|
||||
DataModelViewModel = dataModel;
|
||||
if (DataModelViewModel != null)
|
||||
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
|
||||
}
|
||||
|
||||
private void UpdateValueDisplay()
|
||||
{
|
||||
ValueDisplay.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Visible : Visibility.Collapsed;
|
||||
ValuePlaceholder.Visibility = DataModelPath == null || DataModelPath.IsValid ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
string? formattedPath = null;
|
||||
if (DataModelPath != null && DataModelPath.IsValid)
|
||||
formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
|
||||
|
||||
DataModelButton.ToolTip = formattedPath;
|
||||
ValueDisplayTextBlock.Text = ShowFullPath
|
||||
? formattedPath
|
||||
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
|
||||
}
|
||||
|
||||
private void DataModelOnUpdateRequested(object? sender, EventArgs e)
|
||||
{
|
||||
DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
||||
if (ExtraDataModelViewModels == null) return;
|
||||
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
|
||||
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
|
||||
}
|
||||
|
||||
private void ExecuteSelectPropertyCommand(object? context)
|
||||
{
|
||||
if (context is not DataModelVisualizationViewModel selected)
|
||||
return;
|
||||
|
||||
if (selected.DataModelPath == null)
|
||||
return;
|
||||
if (selected.DataModelPath.Equals(DataModelPath))
|
||||
return;
|
||||
|
||||
DataModelPath = new DataModelPath(selected.DataModelPath);
|
||||
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
|
||||
}
|
||||
|
||||
private void PropertyButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// DataContext is not set when using left button, I don't know why but there it is
|
||||
if (sender is Button button && button.ContextMenu != null)
|
||||
{
|
||||
button.ContextMenu.DataContext = button.DataContext;
|
||||
button.ContextMenu.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DataModelPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
if (e.OldValue is DataModelPath oldPath)
|
||||
{
|
||||
oldPath.PathInvalidated -= dataModelPicker.PathValidationChanged;
|
||||
oldPath.PathValidated -= dataModelPicker.PathValidationChanged;
|
||||
oldPath.Dispose();
|
||||
}
|
||||
|
||||
dataModelPicker.UpdateValueDisplay();
|
||||
if (e.NewValue is DataModelPath newPath)
|
||||
{
|
||||
newPath.PathInvalidated += dataModelPicker.PathValidationChanged;
|
||||
newPath.PathValidated += dataModelPicker.PathValidationChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowFullPathPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
dataModelPicker.UpdateValueDisplay();
|
||||
}
|
||||
|
||||
private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
dataModelPicker.GetDataModel();
|
||||
}
|
||||
|
||||
private static void DataModelViewModelPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
|
||||
if (e.OldValue is DataModelPropertiesViewModel vm)
|
||||
vm.Dispose();
|
||||
}
|
||||
|
||||
private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not DataModelPicker dataModelPicker)
|
||||
return;
|
||||
}
|
||||
|
||||
private void PathValidationChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.Invoke(UpdateValueDisplay, DispatcherPriority.DataBind);
|
||||
}
|
||||
|
||||
private void OnUnloaded(object o, RoutedEventArgs routedEventArgs)
|
||||
{
|
||||
if (DataModelPath != null)
|
||||
{
|
||||
DataModelPath.PathInvalidated -= PathValidationChanged;
|
||||
DataModelPath.PathValidated -= PathValidationChanged;
|
||||
}
|
||||
|
||||
DataModelViewModel?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
}
|
||||
}
|
||||
@ -1,94 +1,93 @@
|
||||
using System;
|
||||
using System.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
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.GradientPicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<shared:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
|
||||
<VisualBrush x:Key="Checkerboard" TileMode="Tile" Stretch="Uniform" ViewportUnits="Absolute" Viewport="0,0,10,10">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="10" Height="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Gray" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" Fill="White" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" Fill="White" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Gray" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Height="15"
|
||||
BorderBrush="{DynamicResource NormalBorderBrush}"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{StaticResource Checkerboard}">
|
||||
<Rectangle Stroke="{DynamicResource NormalBorderBrush}" Cursor="Hand" MouseUp="UIElement_OnMouseUp">
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush x:Name="GradientPreview" EndPoint="1,0" StartPoint="0,0" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
@ -1,171 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Properties;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for GradientPicker.xaml
|
||||
/// </summary>
|
||||
public partial class GradientPicker : INotifyPropertyChanged
|
||||
{
|
||||
private static IColorPickerService? _colorPickerService;
|
||||
private bool _inCallback;
|
||||
private ColorGradientToGradientStopsConverter _gradientConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="GradientPicker" /> class
|
||||
/// </summary>
|
||||
public GradientPicker()
|
||||
{
|
||||
_gradientConverter = new ColorGradientToGradientStopsConverter();
|
||||
InitializeComponent();
|
||||
Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color gradient
|
||||
/// </summary>
|
||||
public ColorGradient ColorGradient
|
||||
{
|
||||
get => (ColorGradient) GetValue(ColorGradientProperty);
|
||||
set => SetValue(ColorGradientProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dialog host in which to show the gradient dialog
|
||||
/// </summary>
|
||||
public string DialogHost
|
||||
{
|
||||
get => (string) GetValue(DialogHostProperty);
|
||||
set => SetValue(DialogHostProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
|
||||
/// </summary>
|
||||
internal static IColorPickerService ColorPickerService
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_colorPickerService != null)
|
||||
throw new AccessViolationException("This is not for you to touch");
|
||||
_colorPickerService = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the dialog has opened
|
||||
/// </summary>
|
||||
public event EventHandler? DialogOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the dialog has closed
|
||||
/// </summary>
|
||||
public event EventHandler? DialogClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="PropertyChanged" /> event
|
||||
/// </summary>
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DialogOpened" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDialogOpened()
|
||||
{
|
||||
DialogOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="DialogClosed" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnDialogClosed()
|
||||
{
|
||||
DialogClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static void ColorGradientPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
GradientPicker gradientPicker = (GradientPicker) d;
|
||||
if (gradientPicker._inCallback)
|
||||
return;
|
||||
|
||||
gradientPicker._inCallback = true;
|
||||
|
||||
if (e.OldValue is ColorGradient oldGradient)
|
||||
oldGradient.CollectionChanged -= gradientPicker.GradientChanged;
|
||||
if (e.NewValue is ColorGradient newGradient)
|
||||
newGradient.CollectionChanged += gradientPicker.GradientChanged;
|
||||
gradientPicker.UpdateGradientStops();
|
||||
gradientPicker.OnPropertyChanged(nameof(ColorGradient));
|
||||
|
||||
gradientPicker._inCallback = false;
|
||||
}
|
||||
|
||||
private void GradientChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Dispatcher.Invoke(UpdateGradientStops);
|
||||
}
|
||||
|
||||
private void UpdateGradientStops()
|
||||
{
|
||||
GradientPreview.GradientStops = (GradientStopCollection) _gradientConverter.Convert(ColorGradient, null!, null!, null!);
|
||||
}
|
||||
|
||||
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_colorPickerService == null)
|
||||
return;
|
||||
|
||||
Execute.OnUIThread(async () =>
|
||||
{
|
||||
OnDialogOpened();
|
||||
await _colorPickerService.ShowGradientPicker(ColorGradient, DialogHost);
|
||||
OnDialogClosed();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ColorGradient.CollectionChanged -= GradientChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#region Static WPF fields
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color gradient
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorGradientProperty = DependencyProperty.Register(nameof(ColorGradient), typeof(ColorGradient), typeof(GradientPicker),
|
||||
new FrameworkPropertyMetadata(default(ColorGradient), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorGradientPropertyChangedCallback));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dialog host in which to show the gradient dialog
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DialogHostProperty = DependencyProperty.Register(nameof(DialogHost), typeof(string), typeof(GradientPicker),
|
||||
new FrameworkPropertyMetadata(default(string)));
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the color gradient has changed
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent ColorGradientChangedEvent =
|
||||
EventManager.RegisterRoutedEvent(nameof(ColorGradient), RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<ColorGradient>), typeof(GradientPicker));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a toggle button that can be locked using a property
|
||||
/// </summary>
|
||||
public class LockableToggleButton : ToggleButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the toggle button is locked
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsLockedProperty =
|
||||
DependencyProperty.Register("IsLocked", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the toggle button is locked
|
||||
/// </summary>
|
||||
public bool IsLocked
|
||||
{
|
||||
get => (bool) GetValue(IsLockedProperty);
|
||||
set => SetValue(IsLockedProperty, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnToggle()
|
||||
{
|
||||
if (!IsLocked) base.OnToggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Shared.ProfileConfigurationIcon"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<shared:StreamToBitmapImageConverter x:Key="StreamToBitmapImageConverter" />
|
||||
<shared:StreamToSvgImageConverter x:Key="StreamToSvgImageConverter" />
|
||||
</UserControl.Resources>
|
||||
<ContentControl>
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="ContentControl">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.MaterialIcon}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<materialDesign:PackIcon Kind="{Binding ConfigurationIcon.IconName, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.BitmapImage}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Image
|
||||
Source="{Binding ConfigurationIcon.FileIcon, Converter={StaticResource StreamToBitmapImageConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConfigurationIcon.IconType, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
Value="{x:Static core:ProfileConfigurationIconType.SvgImage}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Image
|
||||
Source="{Binding ConfigurationIcon.FileIcon, Converter={StaticResource StreamToSvgImageConverter}, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Width="Auto"
|
||||
Height="Auto" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
</UserControl>
|
||||
@ -1,35 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ProfileConfigurationIcon.xaml
|
||||
/// </summary>
|
||||
public partial class ProfileConfigurationIcon : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ConfigurationIconProperty =
|
||||
DependencyProperty.Register(nameof(ConfigurationIcon), typeof(Core.ProfileConfigurationIcon), typeof(ProfileConfigurationIcon));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileConfigurationIcon" /> class
|
||||
/// </summary>
|
||||
public ProfileConfigurationIcon()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public Core.ProfileConfigurationIcon ConfigurationIcon
|
||||
{
|
||||
get => (Core.ProfileConfigurationIcon) GetValue(ConfigurationIconProperty);
|
||||
set => SetValue(ConfigurationIconProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,46 +1,41 @@
|
||||
using System;
|
||||
using System.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="int"/> to <see cref="Visibility"/>
|
||||
/// </summary>
|
||||
public class IntToVisibilityConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Parameters direction;
|
||||
if (parameter == null)
|
||||
direction = Parameters.Normal;
|
||||
else
|
||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
||||
|
||||
return direction == Parameters.Normal
|
||||
? value is > 1 ? Visibility.Visible : Visibility.Collapsed
|
||||
: value is > 1 ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private enum Parameters
|
||||
{
|
||||
Normal,
|
||||
Inverted
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see langword="null"/> to <see cref="Visibility"/>
|
||||
/// </summary>
|
||||
public class NullToVisibilityConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Parameters direction;
|
||||
if (parameter == null)
|
||||
direction = Parameters.Normal;
|
||||
else
|
||||
direction = (Parameters) Enum.Parse(typeof(Parameters), (string) parameter);
|
||||
|
||||
if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue))
|
||||
value = null;
|
||||
|
||||
if (direction == Parameters.Normal)
|
||||
return value == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
return value == null ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private enum Parameters
|
||||
{
|
||||
Normal,
|
||||
Inverted
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +1,28 @@
|
||||
using System;
|
||||
using System.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();
|
||||
}
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
get => _display;
|
||||
set => RaiseAndSetIfChanged(ref _display, value);
|
||||
}
|
||||
|
||||
public bool ShowNull
|
||||
{
|
||||
get => _showNull;
|
||||
set => SetAndNotify(ref _showNull, 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user