From a0faeb21fd78d67a0ff202aa7923bac6160774de Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 24 Mar 2021 21:53:23 +0100 Subject: [PATCH 1/7] Data bindings - Added copy/pasting of data binding --- .../Profile/DataBindings/DataBinding.cs | 57 +++++++++++-------- .../DataModelConditionGroupViewModel.cs | 21 ++----- .../DataBindingConditionView.xaml | 37 ++++++------ .../DataBindings/DataBindingView.xaml | 36 ++++++++++-- .../DataBindings/DataBindingViewModel.cs | 42 +++++++++++--- .../DirectDataBindingModeView.xaml | 8 +-- .../LayerProperties/LayerPropertiesView.xaml | 3 +- .../LayerPropertiesViewModel.cs | 7 ++- 8 files changed, 136 insertions(+), 75 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 4bfbacc48..6b560ee52 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -63,7 +63,10 @@ namespace Artemis.Core /// public Easings.Functions EasingFunction { get; set; } - internal DataBindingEntity Entity { get; } + /// + /// Gets the data binding entity this data binding uses for persistent storage + /// + public DataBindingEntity Entity { get; } /// /// Gets the current value of the data binding @@ -100,6 +103,25 @@ namespace Artemis.Core return Registration?.Getter.Method.ReturnType; } + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _disposed = true; + + if (Registration != null) + Registration.DataBinding = null; + DataBindingMode?.Dispose(); + } + } + private void ResetEasing(TProperty value) { _previousValue = GetInterpolatedValue(); @@ -192,27 +214,6 @@ namespace Artemis.Core _reapplyValue = true; } - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _disposed = true; - - if (Registration != null) - Registration.DataBinding = null; - DataBindingMode?.Dispose(); - } - } - /// public void Dispose() { @@ -220,8 +221,6 @@ namespace Artemis.Core GC.SuppressFinalize(this); } - #endregion - #region Mode management /// @@ -245,6 +244,16 @@ namespace Artemis.Core ApplyDataBindingMode(); } + /// + /// Replaces the current data binding mode with one based on the provided data binding mode entity + /// + /// The data binding mode entity to base the new data binding mode upon + public void ApplyDataBindingEntity(IDataBindingModeEntity dataBindingModeEntity) + { + Entity.DataBindingMode = dataBindingModeEntity; + ApplyDataBindingMode(); + } + private void ApplyDataBindingMode() { DataBindingMode?.Dispose(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs index 437fe3252..de5822a2c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; @@ -16,7 +16,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory; private readonly IProfileEditorService _profileEditorService; private bool _isEventGroup; - private bool _isInitialized; private bool _isRootGroup; public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup, @@ -30,12 +29,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions _dataModelConditionsVmFactory = dataModelConditionsVmFactory; Items.CollectionChanged += (_, _) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator)); - - Execute.PostToUIThread(async () => - { - await Task.Delay(50); - IsInitialized = true; - }); } public ConditionGroupType GroupType { get; } @@ -63,12 +56,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions } } - public bool IsInitialized - { - get => _isInitialized; - set => SetAndNotify(ref _isInitialized, value); - } - public bool DisplayBooleanOperator => Items.Count > 1; public bool DisplayEvaluationResult => GroupType == ConditionGroupType.General && !IsEventGroup; public string SelectedBooleanOperator => DataModelConditionGroup.BooleanOperator.Humanize(); @@ -132,7 +119,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions { NotifyOfPropertyChange(nameof(SelectedBooleanOperator)); // Remove VMs of effects no longer applied on the layer - Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList()); + List toRemove = Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList(); + if (toRemove.Any()) + Items.RemoveRange(toRemove); foreach (DataModelConditionPart childModel in Model.Children) { @@ -169,8 +158,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel); if (IsEventGroup) + { if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And) SelectBooleanOperator("And"); + } OnUpdated(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml index f3e1833c6..42e982f8d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml @@ -6,22 +6,25 @@ xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - - - - + + + + + + + - - + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml index 327b7ede9..b65593a90 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml @@ -17,7 +17,7 @@ - + @@ -33,6 +33,7 @@ + - Data binding result + Result - Other data bindings not updating? + Other bindings not updating? @@ -134,12 +135,39 @@ + + + + + + + + + - diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index b25710928..593c7144d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Timers; using Artemis.Core; using Artemis.Core.Services; +using Artemis.Storage.Entities.Profile.DataBindings; +using Artemis.UI.Exceptions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline; using Artemis.UI.Shared; @@ -186,16 +188,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings return; if (Registration.DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None) - { RemoveDataBinding(); - CreateDataBindingModeModeViewModel(); - return; + else + { + if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None) + EnableDataBinding(); + + Registration.DataBinding!.ChangeDataBindingMode(SelectedDataBindingMode); } - if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None) - EnableDataBinding(); - - Registration.DataBinding.ChangeDataBindingMode(SelectedDataBindingMode); CreateDataBindingModeModeViewModel(); _profileEditorService.UpdateSelectedProfileElement(); } @@ -256,6 +257,33 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings _profileEditorService.UpdateSelectedProfileElement(); } + public void CopyDataBinding() + { + if (Registration.DataBinding != null) + JsonClipboard.SetObject(Registration.DataBinding.Entity); + } + + public void PasteDataBinding() + { + if (Registration.DataBinding == null) + Registration.LayerProperty.EnableDataBinding(Registration); + if (Registration.DataBinding == null) + throw new ArtemisUIException("Failed to create a data binding in order to paste"); + + DataBindingEntity dataBindingEntity = JsonClipboard.GetData(); + if (dataBindingEntity == null) + return; + + Registration.DataBinding.EasingTime = dataBindingEntity.EasingTime; + Registration.DataBinding.EasingFunction = (Easings.Functions) dataBindingEntity.EasingFunction; + Registration.DataBinding.ApplyDataBindingEntity(dataBindingEntity.DataBindingMode); + CreateDataBindingModeModeViewModel(); + Update(); + + + _profileEditorService.UpdateSelectedProfileElement(); + } + private void OnFrameRendered(object sender, FrameRenderedEventArgs e) { UpdateTestResult(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml index f92bcd319..d33ce960e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml @@ -8,7 +8,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + @@ -32,11 +32,7 @@ Padding="6 4" Height="22" Command="{s:Action AddModifier}"> - - - ADD MODIFIER - - + ADD MODIFIER + IsEnabled="{Binding SelectedProfileElement, Converter={StaticResource NullToBooleanConverter}}" + Visibility="{Binding TimelineVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"> - - + public sealed class SKTexture : PixelTexture, IDisposable { + private readonly bool _isScaledDown; private readonly SKPixmap _pixelData; private readonly IntPtr _pixelDataPtr; #region Constructors - internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, 4, new AverageByteSampler()) + internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler()) { ImageInfo = new SKImageInfo(width, height); - Surface = graphicsContext == null - ? SKSurface.Create(ImageInfo) + Surface = graphicsContext == null + ? SKSurface.Create(ImageInfo) : SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo); RenderScale = scale; - + _isScaledDown = Math.Abs(scale - 1) > 0.001; _pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize); _pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes); } #endregion + private void ReleaseUnmanagedResources() + { + Marshal.FreeHGlobal(_pixelDataPtr); + } + + /// + ~SKTexture() + { + ReleaseUnmanagedResources(); + } + + /// + public void Dispose() + { + Surface.Dispose(); + _pixelData.Dispose(); + + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + #region Constants + + private const int STACK_ALLOC_LIMIT = 1024; + private const int DATA_PER_PIXEL = 4; + + #endregion + #region Methods /// @@ -53,6 +83,61 @@ namespace Artemis.Core return new(pixel[2], pixel[1], pixel[0]); } + /// + public override Color this[in Rectangle rectangle] + { + get + { + if (Data.Length == 0) return Color.Transparent; + + SKRectI skRectI = CreatedFlooredRectI( + Size.Width * rectangle.Location.X.Clamp(0, 1), + Size.Height * rectangle.Location.Y.Clamp(0, 1), + Size.Width * rectangle.Size.Width.Clamp(0, 1), + Size.Height * rectangle.Size.Height.Clamp(0, 1) + ); + + if (skRectI.Width == 0 || skRectI.Height == 0) return Color.Transparent; + if (skRectI.Width == 1 && skRectI.Height == 1) return GetColor(GetPixelData(skRectI.Left, skRectI.Top)); + + int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL; + if (bufferSize <= STACK_ALLOC_LIMIT) + { + Span buffer = stackalloc byte[bufferSize]; + GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); + + Span pixelData = stackalloc byte[DATA_PER_PIXEL]; + Sampler.SampleColor(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); + + return GetColor(pixelData); + } + else + { + byte[] rent = ArrayPool.Shared.Rent(bufferSize); + + Span buffer = new Span(rent).Slice(0, bufferSize); + GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer); + + Span pixelData = stackalloc byte[DATA_PER_PIXEL]; + Sampler.SampleColor(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData); + + ArrayPool.Shared.Return(rent); + + return GetColor(pixelData); + } + } + } + + private static SKRectI CreatedFlooredRectI(float x, float y, float width, float height) + { + return new( + width <= 0.0 ? checked((int) Math.Floor(x)) : checked((int) Math.Ceiling(x)), + height <= 0.0 ? checked((int) Math.Floor(y)) : checked((int) Math.Ceiling(y)), + width >= 0.0 ? checked((int) Math.Floor(x + width)) : checked((int) Math.Ceiling(x + width)), + height >= 0.0 ? checked((int) Math.Floor(y + height)) : checked((int) Math.Ceiling(y + height)) + ); + } + #endregion #region Properties & Fields @@ -84,30 +169,5 @@ namespace Artemis.Core public bool IsInvalid { get; private set; } #endregion - - #region IDisposable - - private void ReleaseUnmanagedResources() - { - Marshal.FreeHGlobal(_pixelDataPtr); - } - - /// - public void Dispose() - { - Surface.Dispose(); - _pixelData.Dispose(); - - ReleaseUnmanagedResources(); - GC.SuppressFinalize(this); - } - - /// - ~SKTexture() - { - ReleaseUnmanagedResources(); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 17961a9e2..1a0f26a4a 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -144,16 +144,17 @@ namespace Artemis.Core.Services SKCanvas canvas = texture.Surface.Canvas; canvas.Save(); - canvas.Scale(texture.RenderScale); + if (Math.Abs(texture.RenderScale - 1) > 0.001) + canvas.Scale(texture.RenderScale); canvas.Clear(new SKColor(0, 0, 0)); - + // While non-activated modules may be updated above if they expand the main data model, they may never render if (!ModuleRenderingDisabled) { foreach (Module module in modules.Where(m => m.IsActivated)) module.InternalRender(args.DeltaTime, canvas, texture.ImageInfo); } - + OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); canvas.RestoreToCount(-1); canvas.Flush(); diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 40b502208..4f812c886 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -258,9 +258,17 @@ namespace Artemis.Core.Services else _logger.Debug("Creating SKTexture with software-based graphics context"); + float evenWidth = Surface.Boundary.Size.Width; + if (evenWidth % 2 != 0) + evenWidth++; + float evenHeight = Surface.Boundary.Size.Height; + if (evenHeight % 2 != 0) + evenHeight++; + float renderScale = (float) _renderScaleSetting.Value; - int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt()); - int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt()); + int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt()); + int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); + _texture?.Dispose(); _texture = new SKTexture(graphicsContext, width, height, renderScale); _textureBrush.Texture = _texture; diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 06cd3d765..9bb5456e4 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -30,7 +30,7 @@ namespace Artemis.Core public void Render(double deltaTime, SKCanvas canvas) { AnimationProfile.Update(deltaTime); - AnimationProfile.Render(canvas, SKPoint.Empty); + AnimationProfile.Render(canvas, SKPointI.Empty); } private Profile CreateIntroProfile() diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 720bf0ad3..79d71359b 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -11,6 +11,7 @@ using Artemis.UI.Shared.Services.Models; using Ninject; using Ninject.Parameters; using Serilog; +using SkiaSharp; using SkiaSharp.Views.WPF; using Stylet; @@ -350,7 +351,10 @@ namespace Artemis.UI.Shared.Services public List GetLedsInRectangle(Rect rect) { - return _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(led => led.AbsoluteRectangle.IntersectsWith(rect.ToSKRect())).ToList(); + return _rgbService.EnabledDevices + .SelectMany(d => d.Leds) + .Where(led => led.AbsoluteRectangle.IntersectsWith(SKRectI.Round(rect.ToSKRect()))) + .ToList(); } #region Copy/paste diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index c8caa051e..2f8978ef4 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -59,9 +59,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.General LogLevels = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel))); ColorSchemes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme))); - RenderScales = new List> {new("10%", 0.1)}; - for (int i = 25; i <= 100; i += 25) - RenderScales.Add(new Tuple(i + "%", i / 100.0)); + RenderScales = new List> + { + new("25%", 0.25), + new("50%", 0.5), + new("100%", 1), + }; TargetFrameRates = new List>(); for (int i = 10; i <= 30; i += 5) From 5611ad420a09da8ef35e76b7e4ab347f04e12de3 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Mar 2021 00:18:10 +0100 Subject: [PATCH 4/7] Device visualizer - Added LedClicked event Device properties - Added the ability to select LEDs by clicking on them in the preview --- .../Extensions/RgbDeviceExtensions.cs | 8 +-- .../Models/Surface/ArtemisDevice.cs | 1 + .../Controls/DeviceVisualizer.cs | 63 +++++++++++++++---- .../Events/LedClickedEventArgs.cs | 27 ++++++++ .../Settings/Device/DeviceDialogView.xaml | 2 +- .../Settings/Device/DeviceDialogViewModel.cs | 6 ++ 6 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs diff --git a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs index 5c1e11353..f2537a907 100644 --- a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs +++ b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs @@ -25,10 +25,10 @@ namespace Artemis.Core public static SKRect ToSKRect(this Rectangle rectangle) { return SKRect.Create( - (float) rectangle.Location.X, - (float) rectangle.Location.Y, - (float) rectangle.Size.Width, - (float) rectangle.Size.Height + rectangle.Location.X, + rectangle.Location.Y, + rectangle.Size.Width, + rectangle.Size.Height ); } } diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index cee4d8dfd..d28885e65 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -337,6 +337,7 @@ namespace Artemis.Core Layout = layout; Layout.ApplyDevice(this); + CalculateRenderProperties(); OnDeviceUpdated(); } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index ffb154dac..bc4b3648f 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -6,9 +6,11 @@ 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 Artemis.Core; +using SkiaSharp; using Stylet; namespace Artemis.UI.Shared @@ -54,6 +56,7 @@ namespace Artemis.UI.Shared _timer = new Timer(40); _timer.Elapsed += TimerOnTick; + MouseLeftButtonUp += OnMouseLeftButtonUp; Loaded += OnLoaded; Unloaded += OnUnloaded; } @@ -85,18 +88,6 @@ namespace Artemis.UI.Shared set => SetValue(HighlightedLedsProperty, value); } - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) _timer.Stop(); - } - /// protected override void OnRender(DrawingContext drawingContext) { @@ -149,6 +140,7 @@ namespace Artemis.UI.Shared return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height); } + private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight) { double scale; @@ -191,6 +183,21 @@ namespace Artemis.UI.Shared } } + private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (Device == null) + return; + + Point position = e.GetPosition(this); + double x = (position.X / RenderSize.Width); + double y = (position.Y / RenderSize.Height); + + Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height); + DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition)); + if (deviceVisualizerLed != null) + OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led)); + } + private void OnLoaded(object? sender, RoutedEventArgs e) { _timer.Start(); @@ -310,11 +317,43 @@ namespace Artemis.UI.Shared drawingContext.Close(); } + #region Events + + public event EventHandler? LedClicked; + + /// + /// Invokes the event + /// + /// + protected virtual void OnLedClicked(LedClickedEventArgs e) + { + LedClicked?.Invoke(this, e); + } + + #endregion + + #region IDisposable + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) _timer.Stop(); + } + + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs b/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs new file mode 100644 index 000000000..3455b9800 --- /dev/null +++ b/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using Artemis.Core; + +namespace Artemis.UI.Shared +{ + /// + /// Provides data on LED click events raised by the device visualizer + /// + public class LedClickedEventArgs : EventArgs + { + internal LedClickedEventArgs(ArtemisDevice device, ArtemisLed led) + { + Device = device; + Led = led; + } + + /// + /// The device that was clicked + /// + public ArtemisDevice Device { get; set; } + + /// + /// The LED that was clicked + /// + public ArtemisLed Led { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml index 9e8dc0c23..8973e4109 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -110,7 +110,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" ShowColors="True" - Margin="0 0 100 0" /> + LedClicked="{s:Action OnLedClicked}"/> diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index 760f3fb96..52bae4f15 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -8,6 +8,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Shared; +using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using MaterialDesignThemes.Wpf; using Ookii.Dialogs.Wpf; @@ -197,6 +198,11 @@ namespace Artemis.UI.Screens.Settings.Device NotifyOfPropertyChange(nameof(CanExportLayout)); } + public void OnLedClicked(object sender, LedClickedEventArgs e) + { + SelectedLed = e.Led; + } + #endregion } } \ No newline at end of file From 5545bdb78385f22249066d8403326a7d94741a42 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 26 Mar 2021 19:39:48 +0100 Subject: [PATCH 5/7] Plugins - Fixed GetFeature always returning null Plugins - Added AlwaysEnabled property to [PluginFeature] attribute --- src/Artemis.Core/Plugins/Plugin.cs | 7 +++- .../Plugins/PluginFeatureAttribute.cs | 7 +++- src/Artemis.Core/Plugins/PluginFeatureInfo.cs | 10 ++++- .../Services/PluginManagementService.cs | 6 +-- .../Tabs/Plugins/PluginFeatureView.xaml | 8 +++- .../Tabs/Plugins/PluginFeatureViewModel.cs | 14 +++++++ src/Artemis.sln.DotSettings | 39 +++++++++++++------ 7 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 3ab1a99ef..0b4379a09 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -107,7 +107,7 @@ namespace Artemis.Core /// If found, the instance of the feature public T? GetFeature() where T : PluginFeature { - return _features.FirstOrDefault(i => i.Instance is T) as T; + return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T; } /// @@ -164,6 +164,11 @@ namespace Artemis.Core } } + internal bool HasEnabledFeatures() + { + return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled); + } + #region IDisposable /// diff --git a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs index 0bb78fb75..40863ce25 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs @@ -23,5 +23,10 @@ namespace Artemis.Core /// available icons /// public string? Icon { get; set; } + + /// + /// Marks the feature to always be enabled as long as the plugin is enabled + /// + public bool AlwaysEnabled { get; set; } } -} +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs index b8515197f..47f34cbaa 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -28,7 +28,8 @@ namespace Artemis.Core Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title); Description = attribute?.Description; Icon = attribute?.Icon; - + AlwaysEnabled = attribute?.AlwaysEnabled ?? false; + if (Icon != null) return; if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType)) Icon = "TableAdd"; @@ -55,6 +56,7 @@ namespace Artemis.Core Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title); Description = attribute?.Description; Icon = attribute?.Icon; + AlwaysEnabled = attribute?.AlwaysEnabled ?? false; Instance = instance; if (Icon != null) return; @@ -111,6 +113,12 @@ namespace Artemis.Core set => SetAndNotify(ref _icon, value); } + /// + /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled + /// + [JsonProperty] + public bool AlwaysEnabled { get; } + /// /// Gets the feature this info is associated with /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index cadb77caf..7840660ac 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -207,7 +207,7 @@ namespace Artemis.Core.Services // ReSharper disable InconsistentlySynchronizedField - It's read-only, idc _logger.Debug("Loaded {count} plugin(s)", _plugins.Count); - bool adminRequired = _plugins.Any(p => p.Info.RequiresAdmin && p.Entity.IsEnabled && p.Entity.Features.Any(f => f.IsEnabled)); + bool adminRequired = _plugins.Any(p => p.Info.RequiresAdmin && p.Entity.IsEnabled && p.HasEnabledFeatures()); if (!isElevated && adminRequired) { _logger.Information("Restarting because one or more plugins requires elevation"); @@ -340,7 +340,7 @@ namespace Artemis.Core.Services if (plugin.Assembly == null) throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded"); - if (plugin.Info.RequiresAdmin && plugin.Entity.Features.Any(f => f.IsEnabled) && !_isElevated) + if (plugin.Info.RequiresAdmin && plugin.HasEnabledFeatures() && !_isElevated) { if (!saveState) throw new ArtemisCoreException("Cannot enable a plugin that requires elevation without saving it's state."); @@ -387,7 +387,7 @@ namespace Artemis.Core.Services } // Activate features after they are all loaded - foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && f.Instance.Entity.IsEnabled)) + foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.Instance.Entity.IsEnabled || f.AlwaysEnabled))) { try { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index b97f80323..2060c520c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -50,13 +50,17 @@ VerticalAlignment="Center" Margin="8" Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}" - Orientation="Horizontal"> + Orientation="Horizontal" + ToolTip="This feature cannot be disabled without disabling the whole plugin" + ToolTipService.IsEnabled="{Binding FeatureInfo.AlwaysEnabled}"> - + Feature enabled diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index fda530a9c..a6a80b341 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -16,6 +16,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private readonly IPluginManagementService _pluginManagementService; private bool _enabling; private readonly IMessageService _messageService; + private bool _canToggleEnabled; public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield, @@ -50,6 +51,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins set => Task.Run(() => UpdateEnabled(value)); } + public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled; + public void ShowLogsFolder() { try @@ -76,6 +79,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins _pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped; _pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped; + FeatureInfo.Plugin.Enabled += PluginOnToggled; + FeatureInfo.Plugin.Disabled += PluginOnToggled; + base.OnInitialActivate(); } @@ -85,6 +91,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped; _pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped; + FeatureInfo.Plugin.Enabled -= PluginOnToggled; + FeatureInfo.Plugin.Disabled -= PluginOnToggled; + base.OnClose(); } @@ -147,6 +156,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins NotifyOfPropertyChange(nameof(LoadException)); } + private void PluginOnToggled(object sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(CanToggleEnabled)); + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings index 3a843dade..bffc7dc38 100644 --- a/src/Artemis.sln.DotSettings +++ b/src/Artemis.sln.DotSettings @@ -1,6 +1,9 @@  + Inherit + True True ERROR + ERROR Built-in: Full Cleanup True NEVER @@ -128,17 +131,6 @@ <Name /> </Entry.SortBy> </Entry> - <Entry Priority="100" DisplayName="Public Enums"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Enum" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> <Entry DisplayName="Static Fields and Constants"> <Entry.Match> <Or> @@ -207,16 +199,41 @@ <ImplementsInterface Immediate="True" /> </Entry.SortBy> </Entry> + <Entry Priority="100" DisplayName="Public Enums"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Region Name="Events" /> + <Region Name="Event handlers" /> + <Region Name="IDisposable"> + <Region.GroupBy> + <ImplementsInterface /> + </Region.GroupBy> + </Region> </TypePattern> </Patterns> ERROR ERROR ERROR + True + Replace + True + True True + True False True + False False True + RGB SK UI True From f4b42e2cf261c3a6f4686ea39f9708a8332113a6 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 27 Mar 2021 17:05:38 +0100 Subject: [PATCH 6/7] Device layout dialog - Don't prefill the logical layout --- .../Screens/Settings/Device/DeviceLayoutDialogView.xaml | 2 +- .../Screens/Settings/Device/DeviceLayoutDialogViewModel.cs | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml index 90f9ae552..19a8e170d 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml @@ -151,8 +151,8 @@ - { - AutocompleteSource = new RegionInfoAutocompleteSource(); - SelectedRegion = AutocompleteSource.Regions.FirstOrDefault(r => r.TwoLetterISORegionName == Device.LogicalLayout || - r.TwoLetterISORegionName == "US" && Device.LogicalLayout == "NA"); - }); + Task.Run(() => AutocompleteSource = new RegionInfoAutocompleteSource()); } public ArtemisDevice Device { get; } From a74c81ba9df27694150f11e7fc665cc45fe83392 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 28 Mar 2021 13:43:02 +0200 Subject: [PATCH 7/7] Color gradients - Tweaked default rainbow Events - Hide some properties that shouldn't be visible Events - Added time since last trigger property --- .../Models/Profile/Colors/ColorGradient.cs | 35 +++++++++++------ .../Profile/DataModel/DataModelEvent.cs | 20 +++++++--- .../Profile/DataModel/IDataModelEvent.cs | 5 +++ .../DataModelValueChangedEvent.cs | 1 + .../ConditionalDataBindingModeView.xaml | 4 +- .../ConditionalDataBindingModeViewModel.cs | 5 +++ .../DataBindingConditionView.xaml | 38 ++++++++++++++++++- .../DataBindingConditionViewModel.cs | 11 ++++++ .../DataBindings/DataBindingViewModel.cs | 1 + 9 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index 9f18fa243..06cdab520 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -10,6 +10,19 @@ namespace Artemis.Core /// public class ColorGradient : CorePropertyChanged { + private static readonly SKColor[] FastLedRainbow = + { + new(0xFFFF0000), // Red + new(0xFFFF9900), // Orange + new(0xFFFFFF00), // Yellow + new(0xFF00FF00), // Green + new(0xFF00FF7E), // Aqua + new(0xFF0078FF), // Blue + new(0xFF9E22FF), // Purple + new(0xFFFF34AE), // Pink + new(0xFFFF0000) // and back to Red + }; + /// /// Creates a new instance of the class /// @@ -31,9 +44,9 @@ namespace Artemis.Core public SKColor[] GetColorsArray(int timesToRepeat = 0) { if (timesToRepeat == 0) - return Stops.OrderBy(c => c.Position).Select(c => c.Color).ToArray(); + return Stops.Select(c => c.Color).ToArray(); - List colors = Stops.OrderBy(c => c.Position).Select(c => c.Color).ToList(); + List colors = Stops.Select(c => c.Color).ToList(); List result = new(); for (int i = 0; i <= timesToRepeat; i++) @@ -53,10 +66,10 @@ namespace Artemis.Core public float[] GetPositionsArray(int timesToRepeat = 0) { if (timesToRepeat == 0) - return Stops.OrderBy(c => c.Position).Select(c => c.Position).ToArray(); + return Stops.Select(c => c.Position).ToArray(); // Create stops and a list of divided stops - List stops = Stops.OrderBy(c => c.Position).Select(c => c.Position / (timesToRepeat + 1)).ToList(); + List stops = Stops.Select(c => c.Position / (timesToRepeat + 1)).ToList(); List result = new(); // For each repeat cycle, add the base stops to the end result @@ -74,6 +87,7 @@ namespace Artemis.Core /// public void OnColorValuesUpdated() { + Stops.Sort((a, b) => a.Position.CompareTo(b.Position)); OnPropertyChanged(nameof(Stops)); } @@ -86,7 +100,7 @@ namespace Artemis.Core if (!Stops.Any()) return SKColor.Empty; - ColorGradientStop[] stops = Stops.OrderBy(x => x.Position).ToArray(); + ColorGradientStop[] stops = Stops.ToArray(); if (position <= 0) return stops[0].Color; if (position >= 1) return stops[^1].Color; ColorGradientStop left = stops[0]; @@ -119,15 +133,14 @@ namespace Artemis.Core /// public static ColorGradient GetUnicornBarf() { - const int amount = 8; ColorGradient gradient = new(); - - for (int i = 0; i <= amount; i++) + for (int index = 0; index < FastLedRainbow.Length; index++) { - float percent = i / (float)amount; - gradient.Stops.Add(new ColorGradientStop(SKColor.FromHsv(360f * percent, 100, 100), percent)); + SKColor skColor = FastLedRainbow[index]; + float position = 1f / (FastLedRainbow.Length - 1f) * index; + gradient.Stops.Add(new ColorGradientStop(skColor, position)); } - + return gradient; } } diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs index aeca78a60..cd56a38b2 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs @@ -29,9 +29,13 @@ namespace Artemis.Core } /// - [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")] + [DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")] public DateTime LastTrigger { get; private set; } + /// + [DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")] + public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger; + /// /// Gets the event arguments of the last time the event was triggered /// @@ -85,6 +89,7 @@ namespace Artemis.Core public Type ArgumentsType => typeof(T); /// + [DataModelIgnore] public string TriggerPastParticiple => "triggered"; /// @@ -147,14 +152,18 @@ namespace Artemis.Core } /// - [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")] + [DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")] public DateTime LastTrigger { get; private set; } + /// + [DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")] + public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger; + /// /// Gets the event arguments of the last time the event was triggered /// [DataModelProperty(Description = "The arguments of the last time this event triggered")] - public DataModelEventArgs? LastEventArguments { get; private set; } + public DataModelEventArgs? LastTriggerArguments { get; private set; } /// [DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")] @@ -174,7 +183,7 @@ namespace Artemis.Core { DataModelEventArgs eventArgs = new() {TriggerTime = DateTime.Now}; - LastEventArguments = eventArgs; + LastTriggerArguments = eventArgs; LastTrigger = DateTime.Now; TriggerCount++; @@ -201,6 +210,7 @@ namespace Artemis.Core public Type ArgumentsType => typeof(DataModelEventArgs); /// + [DataModelIgnore] public string TriggerPastParticiple => "triggered"; /// @@ -217,7 +227,7 @@ namespace Artemis.Core /// [DataModelIgnore] - public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments; + public DataModelEventArgs? LastEventArgumentsUntyped => LastTriggerArguments; /// [DataModelIgnore] diff --git a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs index 954c2b7a6..4357dc3c6 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs @@ -13,6 +13,11 @@ namespace Artemis.Core /// DateTime LastTrigger { get; } + /// + /// Gets the time that has passed since the last trigger + /// + TimeSpan TimeSinceLastTrigger { get; } + /// /// Gets the amount of times the event was triggered /// diff --git a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs index 3a2ee7de8..fcf52f32b 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs @@ -14,6 +14,7 @@ namespace Artemis.Core public T? LastValue { get; private set; } public T? CurrentValue { get; private set; } public DateTime LastTrigger { get; private set; } + public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger; public int TriggerCount { get; private set; } public Type ArgumentsType { get; } = typeof(DataModelValueChangedEventArgs); public string TriggerPastParticiple => "changed"; diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml index 493b65c76..7fded19b2 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml @@ -42,12 +42,12 @@ Margin="-10 0 -10 -5"> - + - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs index b45c7728b..f7cfd0071 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs @@ -44,6 +44,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio _profileEditorService.UpdateSelectedProfileElement(); } + public void RemoveCondition(DataBindingCondition dataBindingCondition) + { + ConditionalDataBinding.RemoveCondition(dataBindingCondition); + } + protected override void OnInitialActivate() { Initialize(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml index 42e982f8d..6ae8e1eeb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml @@ -4,9 +4,45 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs index 81d9dfe6a..4361be98d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs @@ -61,6 +61,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio ActiveItem?.Evaluate(); } + public void AddCondition() + { + ((ConditionalDataBindingModeViewModel) Parent).AddCondition(); + } + + public void RemoveCondition() + { + ((ConditionalDataBindingModeViewModel)Parent).RemoveCondition(DataBindingCondition); + + } + #region IDisposable /// diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 593c7144d..2127e82c8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -251,6 +251,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings if (Registration.DataBinding == null) return; + ActiveItem = null; Registration.LayerProperty.DisableDataBinding(Registration.DataBinding); Update();