Profiles - Removed the technical concept of extra timelines for events

Profiles - Properly separated out update and render logic
Profile editor - Implemented layer visualization
This commit is contained in:
Robert committed 2022-02-04 00:18:00 +01:00
1 parent ff4ec16690
commit 89beb92935
18 files changed
+431 -292

No files matched your search

@@ -79,10 +79,8 @@ internal class ProfileEditorService : IProfileEditorService
else
{
renderElement.Enable();
renderElement.Timeline.Override(
time,
(renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
);
bool stickToMainSegment = (renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat;
renderElement.OverrideTimelineAndApply(time, stickToMainSegment);
foreach (ProfileElement child in renderElement.Children)
TickProfileElement(child, time);
@@ -56,6 +56,9 @@
<Compile Update="Screens\ProfileEditor\Panels\Properties\PropertiesView.axaml.cs">
<DependentUpon>PropertiesView.axaml</DependentUpon>
</Compile>
<Compile Update="Screens\ProfileEditor\Panels\VisualEditor\Visualizers\LayerShapeVisualizerView.axaml.cs">
<DependentUpon>LayerShapeVisualizerView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="DefaultTypes\DataModel\Display\" />
@@ -65,6 +65,7 @@ namespace Artemis.UI.Ninject.Factories
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
}
@@ -48,6 +48,12 @@
<!-- The middle layer contains visualizers -->
<ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False">
<ItemsControl.Styles>
<Style Selector="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Disposables;
using Artemis.Core;
@@ -8,6 +9,8 @@ using Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor;
@@ -16,14 +19,22 @@ public class VisualEditorViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorVmFactory _vmFactory;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private readonly SourceList<IVisualizerViewModel> _visualizers;
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
{
_vmFactory = vmFactory;
_visualizers = new SourceList<IVisualizerViewModel>();
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices);
Visualizers = new ObservableCollection<IVisualizerViewModel>();
Tools = new ObservableCollection<IToolViewModel>();
_visualizers.Connect()
.Sort(SortExpressionComparer<IVisualizerViewModel>.Ascending(vm => vm.Order))
.Bind(out ReadOnlyObservableCollection<IVisualizerViewModel> visualizers)
.Subscribe();
Visualizers = visualizers;
this.WhenActivated(d =>
{
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
@@ -34,21 +45,24 @@ public class VisualEditorViewModel : ActivatableViewModelBase
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public ObservableCollection<ArtemisDevice> Devices { get; }
public ObservableCollection<IVisualizerViewModel> Visualizers { get; set; }
public ObservableCollection<IToolViewModel> Tools { get; set; }
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }
public ObservableCollection<IToolViewModel> Tools { get; }
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)
{
Visualizers.Clear();
if (profileConfiguration?.Profile == null)
return;
foreach (Layer layer in profileConfiguration.Profile.GetAllLayers())
CreateVisualizer(layer);
_visualizers.Edit(list =>
{
list.Clear();
if (profileConfiguration?.Profile == null)
return;
foreach (Layer layer in profileConfiguration.Profile.GetAllLayers())
CreateVisualizer(list, layer);
});
}
private void CreateVisualizer(Layer layer)
private void CreateVisualizer(ICollection<IVisualizerViewModel> visualizerViewModels, Layer layer)
{
Visualizers.Add(_vmFactory.LayerVisualizerViewModel(layer));
visualizerViewModels.Add(_vmFactory.LayerShapeVisualizerViewModel(layer));
visualizerViewModels.Add(_vmFactory.LayerVisualizerViewModel(layer));
}
}
@@ -2,4 +2,7 @@
public interface IVisualizerViewModel
{
int X { get; }
int Y { get; }
int Order { get; }
}
@@ -0,0 +1,48 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:visualizers="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerShapeVisualizerView"
x:DataType="visualizers:LayerShapeVisualizerViewModel"
ClipToBounds="False"
ZIndex="2">
<UserControl.Styles>
<Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
</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" />
</Style>
<Style Selector="Path.layer-visualizer-unbound-selected">
<Setter Property="Opacity" Value="0.75" />
</Style>
</UserControl.Styles>
<Canvas>
<Path Classes="layer-visualizer-unbound"
Classes.layer-visualizer-unbound-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="2"
Margin="0 0 2 2"
StrokeJoin="Round">
</Path>
<Border ClipToBounds="True"
Width="{CompiledBinding LayerBounds.Width}"
Height="{CompiledBinding LayerBounds.Height}">
<Path Classes="layer-visualizer"
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="2"
Margin="0 0 2 2"
StrokeJoin="Round">
</Path>
</Border>
</Canvas>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers
{
public partial class LayerShapeVisualizerView : ReactiveUserControl<LayerShapeVisualizerViewModel>
{
public LayerShapeVisualizerView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,78 @@
using System;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using Avalonia.Media;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{
private ObservableAsPropertyHelper<bool>? _selected;
private Geometry? _shapeGeometry;
public LayerShapeVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
{
Layer = layer;
this.WhenActivated(d =>
{
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x).Subscribe(_ => Update()).DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.AnchorPoint.CurrentValueSet += x, x => Layer.Transform.AnchorPoint.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
_selected = profileEditorService.ProfileElement.Select(p => p == Layer).ToProperty(this, vm => vm.Selected).DisposeWith(d);
profileEditorService.Time.Subscribe(_ => UpdateTransform()).DisposeWith(d);
Update();
UpdateTransform();
});
}
public Layer Layer { get; }
public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds => Layer.Bounds.ToRect();
public Geometry? ShapeGeometry
{
get => _shapeGeometry;
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
}
private void Update()
{
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
ShapeGeometry = new RectangleGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
else
ShapeGeometry = new EllipseGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
this.RaisePropertyChanged(nameof(X));
this.RaisePropertyChanged(nameof(Y));
this.RaisePropertyChanged(nameof(LayerBounds));
}
private void UpdateTransform()
{
if (ShapeGeometry != null)
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(true, true, true, true).ToMatrix());
}
public int X => Layer.Bounds.Left;
public int Y => Layer.Bounds.Top;
public int Order => 2;
}
@@ -5,19 +5,24 @@
xmlns:visualizers="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerVisualizerView"
x:DataType="visualizers:LayerVisualizerViewModel">
x:DataType="visualizers:LayerVisualizerViewModel"
ZIndex="1">
<UserControl.Styles>
<Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="Path.layer-visualizer-selected">
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorLight1}" />
<Setter Property="Stroke" Value="{StaticResource SystemAccentColorDark2}" />
</Style>
</UserControl.Styles>
<Path Classes="layer-visualizer"
Classes.layer-visualizer-selected="{CompiledBinding Selected}"
Data="{CompiledBinding ShapeGeometry, Mode=OneWay}"
StrokeThickness="2"
Margin="0 0 2 2">
Margin="0 0 2 2"
StrokeDashArray="6,2"
StrokeJoin="Round">
<Path.Data>
<RectangleGeometry Rect="{CompiledBinding LayerBounds}"></RectangleGeometry>
</Path.Data>
</Path>
</UserControl>
</UserControl>
@@ -2,66 +2,44 @@
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using Avalonia.Media;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{
private Geometry? _shapeGeometry;
private ObservableAsPropertyHelper<bool>? _selected;
public LayerVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
{
Layer = layer;
this.WhenActivated(d =>
{
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x).Subscribe(_ => UpdateShape()).DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Position.CurrentValueSet += x, x => Layer.Transform.Position.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
Observable.FromEventPattern(x => Layer.RenderPropertiesUpdated += x, x => Layer.RenderPropertiesUpdated -= x)
.Subscribe(_ => Update())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Rotation.CurrentValueSet += x, x => Layer.Transform.Rotation.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
_selected = profileEditorService.ProfileElement
.Select(p => p == Layer)
.ToProperty(this, vm => vm.Selected)
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.Scale.CurrentValueSet += x, x => Layer.Transform.Scale.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
Observable.FromEventPattern<LayerPropertyEventArgs>(x => Layer.Transform.AnchorPoint.CurrentValueSet += x, x => Layer.Transform.AnchorPoint.CurrentValueSet -= x)
.Subscribe(_ => UpdateTransform())
.DisposeWith(d);
_selected = profileEditorService.ProfileElement.Select(p => p == Layer).ToProperty(this, vm => vm.Selected).DisposeWith(d);
profileEditorService.Time.Subscribe(_ => UpdateTransform()).DisposeWith(d);
UpdateShape();
});
}
public Layer Layer { get; }
public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds => new(0, 0, Layer.Bounds.Width, Layer.Bounds.Height);
public Geometry? ShapeGeometry
private void Update()
{
get => _shapeGeometry;
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
this.RaisePropertyChanged(nameof(X));
this.RaisePropertyChanged(nameof(Y));
this.RaisePropertyChanged(nameof(LayerBounds));
}
private void UpdateShape()
{
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
ShapeGeometry = new RectangleGeometry(Layer.Bounds.ToRect());
else
ShapeGeometry = new EllipseGeometry(Layer.Bounds.ToRect());
}
private void UpdateTransform()
{
if (ShapeGeometry != null)
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(false, true, true, true).ToMatrix());
}
public int X => Layer.Bounds.Left;
public int Y => Layer.Bounds.Top;
public int Order => 1;
}