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

Profile editor - Only save profile on drag-edit when mouse is released

Profile editor - Format all numbers with 3 decimals 
Profile editor - Fixed drag-edit rounding errors with very small steps
Logging - Output logs to debug
Settings - Added logging level setting
Nuget - Updated Serilog and the used sinks
This commit is contained in:
Robert 2020-02-19 21:33:51 +01:00
parent 05cc032271
commit 73f7bdbf1e
26 changed files with 323 additions and 161 deletions

View File

@ -90,8 +90,11 @@
<Reference Include="Serilog.Enrichers.Demystify, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Enrichers.Demystify.1.0.0-dev-00019\lib\net45\Serilog.Enrichers.Demystify.dll</HintPath>
</Reference>
<Reference Include="Serilog.Sinks.Debug, Version=1.0.1.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Sinks.Debug.1.0.1\lib\net46\Serilog.Sinks.Debug.dll</HintPath>
</Reference>
<Reference Include="Serilog.Sinks.File, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\packages\Serilog.Sinks.File.4.0.0\lib\net45\Serilog.Sinks.File.dll</HintPath>
<HintPath>..\packages\Serilog.Sinks.File.4.1.0\lib\net45\Serilog.Sinks.File.dll</HintPath>
</Reference>
<Reference Include="SkiaSharp, Version=1.68.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\SkiaSharp.1.68.2-preview.29\lib\net45\SkiaSharp.dll</HintPath>

View File

@ -1,24 +1,30 @@
using Ninject.Activation;
using Serilog;
using Serilog.Core;
using Serilog.Events;
namespace Artemis.Core.Ninject
{
internal class LoggerProvider : Provider<ILogger>
{
private static readonly ILogger _logger = new LoggerConfiguration()
internal static readonly LoggingLevelSwitch LoggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
private static readonly ILogger Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithDemystifiedStackTraces()
.WriteTo.File("Logs/Artemis log-.txt",
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}")
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}")
.WriteTo.Debug()
.MinimumLevel.ControlledBy(LoggingLevelSwitch)
.CreateLogger();
protected override ILogger CreateInstance(IContext context)
{
var requestingType = context.Request.ParentContext?.Plan?.Type;
if (requestingType != null)
return _logger.ForContext(requestingType);
return _logger;
return Logger.ForContext(requestingType);
return Logger;
}
}
}

View File

@ -4,12 +4,15 @@ using System.Threading.Tasks;
using Artemis.Core.Events;
using Artemis.Core.Exceptions;
using Artemis.Core.JsonConverters;
using Artemis.Core.Ninject;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Core.Services.Storage.Interfaces;
using Newtonsoft.Json;
using RGB.NET.Core;
using Serilog;
using Serilog.Events;
using SkiaSharp;
namespace Artemis.Core.Services
@ -25,16 +28,21 @@ namespace Artemis.Core.Services
private readonly IRgbService _rgbService;
private readonly ISurfaceService _surfaceService;
private List<Module> _modules;
private PluginSetting<LogEventLevel> _loggingLevel;
internal CoreService(ILogger logger, IPluginService pluginService, IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService)
internal CoreService(ILogger logger, ISettingsService settingsService, IPluginService pluginService, IRgbService rgbService,
ISurfaceService surfaceService, IProfileService profileService)
{
_logger = logger;
_pluginService = pluginService;
_rgbService = rgbService;
_surfaceService = surfaceService;
_profileService = profileService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
_rgbService.Surface.Updating += SurfaceOnUpdating;
_rgbService.Surface.Updated += SurfaceOnUpdated;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
_modules = _pluginService.GetPluginsOfType<Module>();
_pluginService.PluginEnabled += (sender, args) => _modules = _pluginService.GetPluginsOfType<Module>();
@ -79,6 +87,7 @@ namespace Artemis.Core.Services
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
_logger.Information("Initializing Artemis Core version {version}", typeof(CoreService).Assembly.GetName().Version);
ApplyLoggingLevel();
// Initialize the services
await Task.Run(() => _pluginService.CopyBuiltInPlugins());
@ -95,6 +104,12 @@ namespace Artemis.Core.Services
OnInitialized();
}
private void ApplyLoggingLevel()
{
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
private void SurfaceOnUpdating(UpdatingEventArgs args)
{
try

View File

@ -10,6 +10,7 @@ using Artemis.Core.Services.Storage.Interfaces;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services.Storage
{
@ -19,12 +20,14 @@ namespace Artemis.Core.Services.Storage
public class ProfileService : IProfileService
{
private readonly ILayerService _layerService;
private readonly ILogger _logger;
private readonly IPluginService _pluginService;
private readonly IProfileRepository _profileRepository;
private readonly ISurfaceService _surfaceService;
internal ProfileService(IPluginService pluginService, ISurfaceService surfaceService, ILayerService layerService, IProfileRepository profileRepository)
internal ProfileService(ILogger logger, IPluginService pluginService, ISurfaceService surfaceService, ILayerService layerService, IProfileRepository profileRepository)
{
_logger = logger;
_pluginService = pluginService;
_surfaceService = surfaceService;
_layerService = layerService;
@ -121,7 +124,10 @@ namespace Artemis.Core.Services.Storage
public void UndoUpdateProfile(Profile profile, ProfileModule module)
{
if (!profile.UndoStack.Any())
{
_logger.Debug("Undo profile update - Failed, undo stack empty");
return;
}
ActivateProfile(module, null);
var top = profile.UndoStack.Pop();
@ -130,12 +136,17 @@ namespace Artemis.Core.Services.Storage
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top);
profile.ApplyToProfile();
ActivateProfile(module, profile);
_logger.Debug("Undo profile update - Success");
}
public void RedoUpdateProfile(Profile profile, ProfileModule module)
{
if (!profile.RedoStack.Any())
{
_logger.Debug("Redo profile update - Failed, redo empty");
return;
}
ActivateProfile(module, null);
var top = profile.RedoStack.Pop();
@ -144,6 +155,8 @@ namespace Artemis.Core.Services.Storage
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top);
profile.ApplyToProfile();
ActivateProfile(module, profile);
_logger.Debug("Redo profile update - Success");
}
private void InstantiateProfileLayerBrushes(Profile profile)

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AppDomainToolkit" version="1.0.4.3" targetFramework="net461" />
<package id="Ben.Demystifier" version="0.1.4" targetFramework="net472" />
@ -14,11 +13,13 @@
<package id="PropertyChanged.Fody" version="3.1.3" targetFramework="net472" />
<package id="Serilog" version="2.8.0" targetFramework="net472" />
<package id="Serilog.Enrichers.Demystify" version="1.0.0-dev-00019" targetFramework="net472" />
<package id="Serilog.Sinks.File" version="4.0.0" targetFramework="net472" />
<package id="Serilog.Sinks.Debug" version="1.0.1" targetFramework="net472" />
<package id="Serilog.Sinks.File" version="4.1.0" targetFramework="net472" />
<package id="SkiaSharp" version="1.68.2-preview.29" targetFramework="net472" />
<package id="Stylet" version="1.3.1" targetFramework="net472" />
<package id="System.Buffers" version="4.5.0" targetFramework="net472" />
<package id="System.Collections.Immutable" version="1.6.0-preview8.19405.3" targetFramework="net472" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net472" />
<package id="System.Diagnostics.DiagnosticSource" version="4.5.1" targetFramework="net472" />
<package id="System.Memory" version="4.5.3" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />

View File

@ -91,20 +91,12 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UserControl1.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="ColorPicker.xaml.cs">
<DependentUpon>ColorPicker.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\ColorToSKColorConverter.cs" />
<Compile Include="Converters\ColorToSolidColorConverter.cs" />
<Compile Include="Converters\ColorToStringConverter.cs" />
<Compile Include="UserControl1.xaml.cs">
<DependentUpon>UserControl1.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">

View File

@ -22,12 +22,13 @@
Height="17"
Padding="1 0"
Margin="0 4 0 0"
Text="{Binding Value, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Text="{Binding Value, StringFormat=N3, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Cursor="/Resources/aero_drag_ew.cur"
Foreground="{DynamicResource SecondaryAccentBrush}"
MouseDown="InputMouseDown"
MouseUp="InputMouseUp"
MouseMove="InputMouseMove" />
MouseMove="InputMouseMove"
RequestBringIntoView="Input_OnRequestBringIntoView"/>
</Border>
<!-- Input -->
@ -36,7 +37,7 @@
Height="20"
materialDesign:ValidationAssist.UsePopup="True"
HorizontalAlignment="Left"
Text="{Binding Value, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Text="{Binding Value, StringFormat=N3, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
LostFocus="InputLostFocus"
KeyDown="InputKeyDown"
RequestBringIntoView="Input_OnRequestBringIntoView" />

View File

@ -24,9 +24,13 @@ namespace Artemis.UI.Shared
typeof(RoutedPropertyChangedEventHandler<float>),
typeof(DraggableFloat));
public event EventHandler DragStarted;
public event EventHandler DragEnded;
private bool _inCallback;
private Point _mouseDragStartPoint;
private float _startValue;
private bool _calledDragStarted;
public DraggableFloat()
{
@ -75,22 +79,33 @@ namespace Artemis.UI.Shared
var 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)
{
// Use decimals for everything to avoid floating point errors
var startValue = new decimal(_startValue);
var startX = new decimal(_mouseDragStartPoint.X);
var x = new decimal(e.GetPosition((IInputElement) sender).X);
var stepSize = new decimal(StepSize);
if (e.LeftButton != MouseButtonState.Pressed)
return;
Value = (float) (Math.Round(startValue + stepSize * (x - startX) / stepSize) * stepSize);
if (!_calledDragStarted)
{
OnDragStarted();
_calledDragStarted = true;
}
// Use decimals for everything to avoid floating point errors
var startValue = new decimal(_startValue);
var startX = new decimal(_mouseDragStartPoint.X);
var x = new decimal(e.GetPosition((IInputElement) sender).X);
var stepSize = new decimal(StepSize);
Value = (float) UltimateRoundingFunction(startValue + stepSize * (x - startX), stepSize, 0.5m);
}
private void InputLostFocus(object sender, RoutedEventArgs e)
@ -127,5 +142,20 @@ namespace Artemis.UI.Shared
{
e.Handled = true;
}
private static decimal UltimateRoundingFunction(decimal amountToRound, decimal nearstOf, decimal fairness)
{
return Math.Floor(amountToRound / nearstOf + fairness) * nearstOf;
}
protected virtual void OnDragStarted()
{
DragStarted?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnDragEnded()
{
DragEnded?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -1,10 +0,0 @@
<UserControl x:Class="Artemis.UI.Shared.UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid />
</UserControl>

View File

@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace Artemis.UI.Shared
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
}

View File

@ -227,6 +227,7 @@
<Compile Include="Services\Interfaces\IProfileEditorService.cs" />
<Compile Include="Services\LayerEditorService.cs" />
<Compile Include="Services\ProfileEditorService.cs" />
<Compile Include="Utilities\HitTestUtilities.cs" />
<Compile Include="Utilities\ThemeWatcher.cs" />
<Compile Include="Behaviors\TreeViewSelectionBehavior.cs" />
<Compile Include="Utilities\TriggerTracing.cs" />

View File

@ -3,9 +3,7 @@
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.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
@ -19,8 +17,7 @@
ItemsSource="{Binding Path=EnumValues}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=BrushInputValue}"
materialDesign:ComboBoxAssist.ClassicMode="True" />
SelectedValue="{Binding Path=BrushInputValue}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -5,7 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:EnumPropertyInputViewModel}">
@ -20,8 +19,7 @@
ItemsSource="{Binding Path=EnumValues}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=EnumInputValue}"
materialDesign:ComboBoxAssist.ClassicMode="True" />
SelectedValue="{Binding Path=EnumInputValue}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -11,7 +11,10 @@
d:DataContext="{d:DesignInstance local:FloatPropertyInputViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat Value="{Binding FloatInputValue}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
<shared:DraggableFloat Value="{Binding FloatInputValue}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
@ -12,7 +11,10 @@
d:DataContext="{d:DesignInstance local:IntPropertyInputViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat Value="{Binding IntInputValue}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
<shared:DraggableFloat Value="{Binding IntInputValue}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -13,10 +13,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
ProfileEditorService = profileEditorService;
}
protected IProfileEditorService ProfileEditorService { get; set; }
protected IProfileEditorService ProfileEditorService { get; }
public abstract List<Type> CompatibleTypes { get; }
public bool Initialized { get; private set; }
public bool InputDragging { get; private set; }
public LayerPropertyViewModel LayerPropertyViewModel { get; private set; }
protected object InputValue
@ -56,7 +57,25 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
// Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress
LayerPropertyViewModel.LayerProperty.KeyframeEngine?.Update(0);
if (!InputDragging)
ProfileEditorService.UpdateSelectedProfileElement();
else
ProfileEditorService.UpdateProfilePreview();
}
#region Event handlers
public void InputDragStarted(object sender, EventArgs e)
{
InputDragging = true;
}
public void InputDragEnded(object sender, EventArgs e)
{
InputDragging = false;
ProfileEditorService.UpdateSelectedProfileElement();
}
#endregion
}
}

View File

@ -4,8 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
mc:Ignorable="d"

View File

@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
@ -14,11 +13,15 @@
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat ToolTip="X-coordinate (horizontal)"
Value="{Binding X}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Y-coordinate (vertical)"
Value="{Binding Y}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
@ -12,9 +11,17 @@
d:DataContext="{d:DesignInstance local:SKSizePropertyInputViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat ToolTip="Height" Value="{Binding Height}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
<shared:DraggableFloat ToolTip="Height"
Value="{Binding Height}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Width" Value="{Binding Width}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" />
<shared:DraggableFloat ToolTip="Width"
Value="{Binding Width}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0 0 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
</StackPanel>
</UserControl>

View File

@ -9,11 +9,11 @@
d:DesignHeight="25"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PropertyTimelineViewModel}">
<Canvas Background="{DynamicResource MaterialDesignToolBarBackground}"
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}"
MouseDown="{s:Action TimelineCanvasMouseDown}"
MouseUp="{s:Action TimelineCanvasMouseUp}"
MouseMove="{s:Action TimelineCanvasMouseMove}">
<Canvas.Triggers>
<Grid.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseLeftButtonDown">
<BeginStoryboard>
<Storyboard Storyboard.TargetName="MultiSelectionPath" Storyboard.TargetProperty="Opacity">
@ -28,7 +28,7 @@
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Grid.Triggers>
<ItemsControl ItemsSource="{Binding PropertyTrackViewModels}"
Width="{Binding Width}"
@ -51,5 +51,5 @@
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
</Path.Fill>
</Path>
</Canvas>
</Grid>
</UserControl>

View File

@ -7,6 +7,7 @@ using System.Windows.Media;
using System.Windows.Shapes;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Utilities;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
@ -95,26 +96,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
UpdateEndTime();
}
#region Keyframe movement
public void MoveSelectedKeyframes(TimeSpan offset)
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
{
// TODO: Not ideal as this stacks them all if they get to 0, oh well
if (keyframeViewModel.Keyframe.Position + offset > TimeSpan.Zero)
{
keyframeViewModel.Keyframe.Position += offset;
keyframeViewModel.Update(LayerPropertiesViewModel.PixelsPerSecond);
}
}
_profileEditorService.UpdateProfilePreview();
}
#endregion
private void CreateViewModels(LayerPropertyViewModel property)
{
PropertyTrackViewModels.Add(_propertyTrackVmFactory.Create(this, property));
@ -122,6 +103,30 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
CreateViewModels(child);
}
#region Keyframe movement
public void MoveSelectedKeyframes(TimeSpan cursorTime)
{
// Ensure the selection rectangle doesn't show, the view isn't aware of different types of dragging
SelectionRectangle.Rect = new Rect();
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
keyframeViewModel.ApplyMovement(cursorTime);
_profileEditorService.UpdateProfilePreview();
}
public void ReleaseSelectedKeyframes()
{
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected))
keyframeViewModel.ReleaseMovement();
}
#endregion
#region Keyframe selection
private Point _mouseDragStartPoint;
@ -130,6 +135,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
// ReSharper disable once UnusedMember.Global - Called from view
public void TimelineCanvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released)
return;
((IInputElement) sender).CaptureMouse();
SelectionRectangle.Rect = new Rect();
@ -148,18 +156,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
var selectedRect = new Rect(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
// Find all keyframes in the rectangle
var selectedKeyframes = new List<PropertyTrackKeyframeViewModel>();
var hitTestParams = new GeometryHitTestParameters(SelectionRectangle);
var resultCallback = new HitTestResultCallback(result => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(element =>
{
if (element is Ellipse ellipse)
selectedKeyframes.Add((PropertyTrackKeyframeViewModel) ellipse.DataContext);
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest((Visual) sender, filterCallback, resultCallback, hitTestParams);
var selectedKeyframes = HitTestUtilities.GetHitViewModels<PropertyTrackKeyframeViewModel>((Visual) sender, SelectionRectangle);
var keyframeViewModels = PropertyTrackViewModels.SelectMany(t => t.KeyframeViewModels.OrderBy(k => k.Keyframe.Position)).ToList();
foreach (var keyframeViewModel in keyframeViewModels)
keyframeViewModel.IsSelected = selectedKeyframes.Contains(keyframeViewModel);

View File

@ -49,7 +49,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
if (e.LeftButton == MouseButtonState.Released)
return;
((IInputElement)sender).CaptureMouse();
((IInputElement) sender).CaptureMouse();
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected)
PropertyTrackViewModel.PropertyTimelineViewModel.SelectKeyframe(this, true, false);
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
@ -63,42 +63,45 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public void KeyframeMouseUp(object sender, MouseButtonEventArgs e)
{
_profileEditorService.UpdateSelectedProfileElement();
PropertyTrackViewModel.PropertyTimelineViewModel.ReleaseSelectedKeyframes();
((IInputElement)sender).ReleaseMouseCapture();
e.Handled = true;
((IInputElement) sender).ReleaseMouseCapture();
}
public void KeyframeMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
// Get the parent grid, need that for our position
var x = Math.Max(0, e.GetPosition(ParentView).X);
var newTime = TimeSpan.FromSeconds(x / _pixelsPerSecond);
// Round the time to something that fits the current zoom level
if (_pixelsPerSecond < 200)
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
else if (_pixelsPerSecond < 500)
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
else
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
// If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width)
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
var tolerance = 1000f / _pixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - newTime.TotalMilliseconds) < tolerance)
newTime = _profileEditorService.CurrentTime;
}
PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(newTime - Keyframe.Position);
}
PropertyTrackViewModel.PropertyTimelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView)));
e.Handled = true;
}
private TimeSpan GetCursorTime(Point position)
{
// Get the parent grid, need that for our position
var x = Math.Max(0, position.X);
var time = TimeSpan.FromSeconds(x / _pixelsPerSecond);
// Round the time to something that fits the current zoom level
if (_pixelsPerSecond < 200)
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0);
else if (_pixelsPerSecond < 500)
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0);
else
time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds));
// If shift is held, snap to the current time
// Take a tolerance of 5 pixels (half a keyframe width)
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
var tolerance = 1000f / _pixelsPerSecond * 5;
if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance)
time = _profileEditorService.CurrentTime;
}
return time;
}
#endregion
#region Context menu actions
@ -137,5 +140,34 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
}
#endregion
#region Movement
private bool _movementReleased = true;
private TimeSpan _startOffset;
public void ApplyMovement(TimeSpan cursorTime)
{
if (_movementReleased)
{
_movementReleased = false;
_startOffset = cursorTime - Keyframe.Position;
}
else
{
Keyframe.Position = cursorTime - _startOffset;
if (Keyframe.Position < TimeSpan.Zero)
Keyframe.Position = TimeSpan.Zero;
Update(_pixelsPerSecond);
}
}
public void ReleaseMovement()
{
_movementReleased = true;
}
#endregion
}
}

View File

@ -318,8 +318,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
// Scale down the resulting position and make it relative
var scaled = _layerEditorService.GetScaledPoint(layer, position, true);
// Update the position property
layer.PositionProperty.SetCurrentValue(scaled, ProfileEditorService.CurrentTime);
// Round and update the position property
layer.PositionProperty.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}
@ -338,13 +338,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false);
// Update the anchor point, this causes the shape to move
layer.AnchorPointProperty.SetCurrentValue(RoundPoint(scaled, 5), ProfileEditorService.CurrentTime);
layer.AnchorPointProperty.SetCurrentValue(RoundPoint(scaled, 3), ProfileEditorService.CurrentTime);
// TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor
var path = _layerEditorService.GetLayerPath(layer, true, true, true);
// Calculate the (scaled) difference between the old and now position
var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false);
// Apply the difference so that the shape effectively stays in place
layer.PositionProperty.SetCurrentValue(RoundPoint(layer.PositionProperty.CurrentValue + difference, 5), ProfileEditorService.CurrentTime);
layer.PositionProperty.SetCurrentValue(RoundPoint(layer.PositionProperty.CurrentValue + difference, 3), ProfileEditorService.CurrentTime);
ProfileEditorService.UpdateProfilePreview();
}

View File

@ -36,7 +36,7 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Start up with Windows</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Start up with Windows</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" />
@ -54,7 +54,7 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Start up with Windows minimized</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Start up with Windows minimized</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" ToolTip="Default ToggleButton Style" />
@ -72,8 +72,8 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Debugger</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Debugger</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Use the debugger to see the raw image Artemis is rendering on the surface.
</TextBlock>
</StackPanel>
@ -95,18 +95,17 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Logs</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where logs are stored.
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Application files</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where application files like plugins and settings are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowLogsFolder}" Width="150">
SHOW LOGS
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowDataFolder}" Width="150">
SHOW APP FILES
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
@ -119,17 +118,39 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Application files</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where application files like plugins and settings are stored.
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Logs</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where logs are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowDataFolder}" Width="150">
SHOW APP FILES
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowLogsFolder}" Width="150">
SHOW LOGS
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Log level</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the logging level, a verbose logging level will result in more log files.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Width="80" SelectedValue="{Binding SelectedLogLevel}" ItemsSource="{Binding LogLevels}" SelectedValuePath="Value" DisplayMemberPath="Description" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
@ -147,8 +168,8 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Render scale</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Render scale</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
</TextBlock>
</StackPanel>
@ -168,8 +189,8 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">Target framerate</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Target framerate</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations.
</TextBlock>
</StackPanel>
@ -189,8 +210,8 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}">LED sample size</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">LED sample size</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the amount of samples that is taken to determine each LEDs color. This means a LED can be semi off if it is not completely covered by a color.
</TextBlock>
</StackPanel>

View File

@ -12,8 +12,10 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Settings.Debug;
using Artemis.UI.Screens.Settings.Tabs.Devices;
using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Artemis.UI.Shared.Utilities;
using MaterialDesignThemes.Wpf;
using Ninject;
using Serilog.Events;
using Stylet;
namespace Artemis.UI.Screens.Settings
@ -26,6 +28,7 @@ namespace Artemis.UI.Screens.Settings
private readonly ISettingsService _settingsService;
private readonly ISurfaceService _surfaceService;
private readonly IWindowManager _windowManager;
private object _test;
public SettingsViewModel(IKernel kernel,
ISurfaceService surfaceService,
@ -47,6 +50,8 @@ namespace Artemis.UI.Screens.Settings
DeviceSettingsViewModels = new BindableCollection<DeviceSettingsViewModel>();
Plugins = new BindableCollection<PluginSettingsViewModel>();
LogLevels = EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel));
RenderScales = new List<Tuple<string, double>> {new Tuple<string, double>("10%", 0.1)};
for (var i = 25; i <= 100; i += 25)
RenderScales.Add(new Tuple<string, double>(i + "%", i / 100.0));
@ -61,6 +66,8 @@ namespace Artemis.UI.Screens.Settings
public List<Tuple<string, int>> TargetFrameRates { get; set; }
public List<Tuple<string, double>> RenderScales { get; set; }
public IEnumerable<ValueDescription> LogLevels { get; private set; }
public List<int> SampleSizes { get; set; }
public BindableCollection<DeviceSettingsViewModel> DeviceSettingsViewModels { get; set; }
public BindableCollection<PluginSettingsViewModel> Plugins { get; set; }
@ -77,6 +84,16 @@ namespace Artemis.UI.Screens.Settings
set => TargetFrameRate = value.Item2;
}
public LogEventLevel SelectedLogLevel
{
get => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information).Value;
set
{
_settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information).Value = value;
_settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information).Save();
}
}
public double RenderScale
{
get => _settingsService.GetSetting("Core.RenderScale", 1.0).Value;
@ -87,7 +104,6 @@ namespace Artemis.UI.Screens.Settings
}
}
public int TargetFrameRate
{
get => _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace Artemis.UI.Utilities
{
public static class HitTestUtilities
{
/// <summary>
/// Runs a hit test on children of the container within the rectangle matching all elements that have a data context of <see cref="T"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="container"></param>
/// <param name="rectangleGeometry"></param>
/// <returns></returns>
public static List<T> GetHitViewModels<T>(Visual container, RectangleGeometry rectangleGeometry)
{
var result = new List<T>();
var hitTestParams = new GeometryHitTestParameters(rectangleGeometry);
var resultCallback = new HitTestResultCallback(r => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(e =>
{
if (e is FrameworkElement fe && fe.DataContext.GetType() == typeof(T) && !result.Contains((T) fe.DataContext))
result.Add((T) fe.DataContext);
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest(container, filterCallback, resultCallback, hitTestParams);
return result;
}
}
}