Profile editor - Visualization fixes

Profile editor - Timeline fixes
This commit is contained in:
Robert committed 2022-02-06 00:00:48 +01:00
1 parent 0b42e7f510
commit c36110e79d
22 files changed
+331 -230

No files matched your search

@@ -43,6 +43,13 @@ public class SelectionRectangle : Control
public static readonly StyledProperty<IControl?> InputElementProperty =
AvaloniaProperty.Register<SelectionRectangle, IControl?>(nameof(InputElement), notifying: OnInputElementChanged);
/// <summary>
/// Defines the <see cref="ZoomRatio" /> property.
/// </summary>
public static readonly StyledProperty<double> ZoomRatioProperty =
AvaloniaProperty.Register<SelectionRectangle, double>(nameof(ZoomRatio), 1);
/// <summary>
/// Defines the read-only <see cref="IsSelecting" /> property.
/// </summary>
@@ -108,6 +115,15 @@ public class SelectionRectangle : Control
set => SetValue(InputElementProperty, value);
}
/// <summary>
/// Gets or sets the zoom ratio to counteract when drawing
/// </summary>
public double ZoomRatio
{
get => GetValue(ZoomRatioProperty);
set => SetValue(ZoomRatioProperty, value);
}
/// <summary>
/// Gets a boolean indicating whether the selection rectangle is currently performing a selection.
/// </summary>
@@ -149,6 +165,7 @@ public class SelectionRectangle : Control
{
((SelectionRectangle) sender).SubscribeToInputElement();
}
private void ParentOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
@@ -227,7 +244,7 @@ public class SelectionRectangle : Control
public override void Render(DrawingContext drawingContext)
{
if (_displayRect != null)
drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderThickness), _displayRect.Value, BorderRadius, BorderRadius);
drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderThickness / ZoomRatio), _displayRect.Value, BorderRadius / ZoomRatio, BorderRadius / ZoomRatio);
}
/// <inheritdoc />
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
@@ -32,16 +33,37 @@ public class ToggleLayerPropertyKeyframes<T> : IProfileEditorCommand
/// <inheritdoc />
public void Execute()
{
_keyframe ??= new LayerPropertyKeyframe<T>(_layerProperty.CurrentValue, _time, Easings.Functions.Linear, _layerProperty);
_layerProperty.KeyframesEnabled = _enable;
_layerProperty.AddKeyframe(_keyframe);
if (_enable)
{
_layerProperty.KeyframesEnabled = true;
// If there weren't any keyframes yet, add one with the current value at the current time
if (!_layerProperty.Keyframes.Any())
{
// If executed before, reuse the previous keyframe
_keyframe ??= new LayerPropertyKeyframe<T>(_layerProperty.CurrentValue, _time, Easings.Functions.Linear, _layerProperty);
_layerProperty.AddKeyframe(_keyframe);
}
}
else
{
_layerProperty.KeyframesEnabled = false;
}
}
/// <inheritdoc />
public void Undo()
{
_layerProperty.RemoveKeyframe(_keyframe!);
_layerProperty.KeyframesEnabled = !_enable;
if (_enable)
{
if (_keyframe != null)
_layerProperty.RemoveKeyframe(_keyframe);
_layerProperty.KeyframesEnabled = false;
}
else
{
_layerProperty.KeyframesEnabled = true;
}
}
#endregion
@@ -33,6 +33,12 @@
<controls:HyperlinkButton Classes="icon-button icon-button-small">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<TextBlock Margin="0 5 0 0">Button.window-button</TextBlock>
<Button Classes="title-bar-button">
<avalonia:MaterialIcon Kind="WindowMinimize" />
</Button>
</StackPanel>
</Border>
</Design.PreviewWith>
@@ -69,4 +75,15 @@
<Style Selector="controls|HyperlinkButton.icon-button">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="Button.title-bar-button">
<Setter Property="Width" Value="46"></Setter>
<Setter Property="Height" Value="32"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
<Setter Property="CornerRadius" Value="0"></Setter>
<Setter Property="Margin" Value="0"></Setter>
</Style>
<Style Selector="Button.title-bar-button:pointerover">
<Setter Property="Background" Value="Red"></Setter>
</Style>
</Styles>
@@ -5,7 +5,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugView">
<Grid RowDefinitions="Auto,*">
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Grid.Row="0">
<TextBlock Classes="h3">Performance</TextBlock>
<TextBlock TextWrapping="Wrap">
@@ -21,7 +21,19 @@
</Grid>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="10 0">
<TextBlock Text="FPS: " />
<TextBlock FontWeight="Bold" Text="{Binding CurrentFps}" />
<TextBlock Text=" at " />
<TextBlock Text="{Binding RenderWidth}" />
<TextBlock Text="x" />
<TextBlock Text="{Binding RenderHeight}" />
<TextBlock Text=" - Renderer: "></TextBlock>
<TextBlock Text="{Binding Renderer}"></TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
<ItemsControl Items="{Binding Items}" Margin="0 0 10 0" />
</ScrollViewer>
</Grid>
@@ -8,16 +8,23 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.Debugger.Performance
{
public class PerformanceDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{
private readonly ICoreService _coreService;
private readonly IPluginManagementService _pluginManagementService;
private double _currentFps;
private string? _renderer;
private int _renderHeight;
private int _renderWidth;
public PerformanceDebugViewModel(IScreen hostScreen, IPluginManagementService pluginManagementService)
public PerformanceDebugViewModel(IScreen hostScreen, ICoreService coreService, IPluginManagementService pluginManagementService)
{
HostScreen = hostScreen;
_coreService = coreService;
_pluginManagementService = pluginManagementService;
Timer updateTimer = new(500);
@@ -38,6 +45,7 @@ namespace Artemis.UI.Screens.Debugger.Performance
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
HandleActivation();
PopulateItems();
updateTimer.Start();
@@ -45,21 +53,57 @@ namespace Artemis.UI.Screens.Debugger.Performance
{
updateTimer.Stop();
Items.Clear();
HandleDeactivation();
}).DisposeWith(disposables);
});
}
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
public string UrlPathSegment => "performance";
public IScreen HostScreen { get; }
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
public double CurrentFps
{
get => _currentFps;
set => this.RaiseAndSetIfChanged(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => this.RaiseAndSetIfChanged(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => this.RaiseAndSetIfChanged(ref _renderHeight, value);
}
public string? Renderer
{
get => _renderer;
set => this.RaiseAndSetIfChanged(ref _renderer, value);
}
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
}
private void PopulateItems()
{
foreach (PerformanceDebugPluginViewModel performanceDebugPluginViewModel in _pluginManagementService.GetAllPlugins()
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
.OrderBy(p => p.Info.Name)
.Select(p => new PerformanceDebugPluginViewModel(p)))
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
.OrderBy(p => p.Info.Name)
.Select(p => new PerformanceDebugPluginViewModel(p)))
Items.Add(performanceDebugPluginViewModel);
}
@@ -69,10 +113,19 @@ namespace Artemis.UI.Screens.Debugger.Performance
PopulateItems();
}
private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
private void UpdateTimerOnElapsed(object? sender, ElapsedEventArgs e)
{
foreach (PerformanceDebugPluginViewModel viewModel in Items)
viewModel.Update();
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
CurrentFps = _coreService.FrameRate;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
}
}
}
@@ -24,6 +24,21 @@
<TextBlock Text="{Binding Renderer}"></TextBlock>
</StackPanel>
<Image Source="{Binding CurrentFrame}" />
<Border Classes="card">
<ZoomBorder Name="ZoomBorder"
Stretch="None"
ClipToBounds="True"
Focusable="True"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Image Name="Visualization" Source="{Binding CurrentFrame}">
<Image.Transitions>
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut"/>
</Transitions>
</Image.Transitions>
</Image>
</ZoomBorder>
</Border>
</StackPanel>
</UserControl>
@@ -102,7 +102,6 @@ namespace Artemis.UI.Screens.Debugger.Render
}
}
public string UrlPathSegment => "render";
public IScreen HostScreen { get; }
}
@@ -9,7 +9,9 @@
<controls:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"
SelectionFinished="SelectionRectangle_OnSelectionFinished">
BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<controls:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background>
@@ -1,4 +1,5 @@
using Artemis.UI.Shared.Events;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Skia;
@@ -19,6 +20,6 @@ public class SelectionAddToolView : ReactiveUserControl<SelectionAddToolViewMode
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
{
ViewModel?.AddLedsInRectangle(e.Rectangle.ToSKRect());
ViewModel?.AddLedsInRectangle(e.Rectangle.ToSKRect(), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
}
}
@@ -57,12 +57,26 @@ public class SelectionAddToolViewModel : ToolViewModel
base.Dispose(disposing);
}
public void AddLedsInRectangle(SKRect rect)
public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse)
{
if (_layer == null)
return;
List<ArtemisLed> leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds));
if (inverse)
{
List<ArtemisLed> toRemove = _layer.Leds.Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
List<ArtemisLed> toAdd = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).Except(toRemove).ToList();
List<ArtemisLed> leds = _layer.Leds.Except(toRemove).ToList();
leds.AddRange(toAdd);
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds));
}
else
{
List<ArtemisLed> leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
if (expand)
leds.AddRange(_layer.Leds);
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds.Distinct().ToList()));
}
}
}
@@ -11,7 +11,9 @@
<controls:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"
SelectionFinished="SelectionRectangle_OnSelectionFinished">
BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<controls:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background>
@@ -10,38 +10,53 @@
<UserControl.Styles>
<Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition>
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Path.layer-visualizer-selected">
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorLight1}" />
</Style>
<Style Selector="Path.layer-visualizer-unbound">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
<Setter Property="Opacity" Value="0.50" />
<Setter Property="Opacity" Value="0.50" />
<Setter Property="Transitions">
<Setter.Value>
<Transitions>
<DoubleTransition Property="StrokeThickness" Duration="0:0:0.2" Easing="CubicEaseOut"></DoubleTransition>
</Transitions>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Path.layer-visualizer-unbound-selected">
<Setter Property="Opacity" Value="0.75" />
</Style>
</UserControl.Styles>
<Canvas>
<Path Classes="layer-visualizer-unbound"
<Path Name="LayerVisualizerUnbound"
Classes="layer-visualizer-unbound"
Classes.layer-visualizer-unbound-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="2"
Margin="0 0 2 2"
StrokeThickness="4"
StrokeJoin="Round">
</Path>
<Border ClipToBounds="True"
Width="{CompiledBinding LayerBounds.Width}"
Height="{CompiledBinding LayerBounds.Height}">
<Path Classes="layer-visualizer"
<Border Name="LayerVisualizerBounds"
ClipToBounds="True"
Width="{CompiledBinding LayerBounds.Width, Mode=OneWay}"
Height="{CompiledBinding LayerBounds.Height, Mode=OneWay}">
<Path Name="LayerVisualizer"
Classes="layer-visualizer"
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="2"
Margin="0 0 2 2"
StrokeThickness="4"
StrokeJoin="Round">
</Path>
</Border>
</Canvas>
</UserControl>
@@ -1,3 +1,10 @@
using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Controls.Shapes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
@@ -5,14 +12,50 @@ namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers
{
public partial class LayerShapeVisualizerView : ReactiveUserControl<LayerShapeVisualizerViewModel>
{
private ZoomBorder? _zoomBorder;
private readonly Path _layerVisualizerUnbound;
private readonly Path _layerVisualizer;
public LayerShapeVisualizerView()
{
InitializeComponent();
_layerVisualizer = this.Get<Path>("LayerVisualizer");
_layerVisualizerUnbound = this.Get<Path>("LayerVisualizerUnbound");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_zoomBorder = (ZoomBorder?) this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
if (_zoomBorder != null)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_zoomBorder != null)
_zoomBorder.PropertyChanged -= ZoomBorderOnPropertyChanged;
base.OnDetachedFromLogicalTree(e);
}
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ZoomBorder.ZoomXProperty || _zoomBorder == null)
return;
_layerVisualizer.StrokeThickness = Math.Max(1, 4 / _zoomBorder.ZoomX);
_layerVisualizerUnbound.StrokeThickness = _layerVisualizer.StrokeThickness;
}
#endregion
}
}
}
@@ -15,14 +15,14 @@
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" />
</Style>
</UserControl.Styles>
<Path Classes="layer-visualizer"
<Path Name="LayerVisualizer"
Classes="layer-visualizer"
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
StrokeThickness="2"
Margin="0 0 2 2"
StrokeThickness="4"
StrokeDashArray="6,2"
StrokeJoin="Round">
<Path.Data>
<RectangleGeometry Rect="{CompiledBinding LayerBounds}"></RectangleGeometry>
</Path.Data>
</Path>
</UserControl>
</UserControl>
@@ -1,18 +1,58 @@
using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Controls.Shapes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.VisualTree;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers
{
public partial class LayerVisualizerView : ReactiveUserControl<LayerVisualizerViewModel>
{
private ZoomBorder? _zoomBorder;
private readonly Path _layerVisualizer;
public LayerVisualizerView()
{
InitializeComponent();
_layerVisualizer = this.Get<Path>("LayerVisualizer");
}
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_zoomBorder = (ZoomBorder?) this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
if (_zoomBorder != null)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_zoomBorder != null)
_zoomBorder.PropertyChanged -= ZoomBorderOnPropertyChanged;
base.OnDetachedFromLogicalTree(e);
}
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ZoomBorder.ZoomXProperty || _zoomBorder == null)
return;
_layerVisualizer.StrokeThickness = Math.Max(1, 4 / _zoomBorder.ZoomX);
}
#endregion
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
}
@@ -5,7 +5,7 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Root.DefaultTitleBarView">
<Button Classes="icon-button icon-button-small" Command="{Binding ShowDebugger}" HorizontalAlignment="Right">
<Button Classes="title-bar-button" Command="{Binding ShowDebugger}" HorizontalAlignment="Right">
<avalonia:MaterialIcon Kind="Bug"></avalonia:MaterialIcon>
</Button>
</UserControl>