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

Profile editor - Added F5 previewing

Core - Performance fixes
This commit is contained in:
Robert 2022-04-14 21:12:29 +02:00
parent 5b183d3010
commit 8c7bbc3f0f
11 changed files with 180 additions and 82 deletions

View File

@ -149,9 +149,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
if (TriggerMode == EventTriggerMode.Toggle) if (TriggerMode == EventTriggerMode.Toggle)
{ {
if (!IsMet && _wasMet) if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToEnd();
else if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart(); ProfileElement.Timeline.JumpToStart();
} }
else else
@ -164,12 +162,13 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
if (OverlapMode == EventOverlapMode.Restart) if (OverlapMode == EventOverlapMode.Restart)
ProfileElement.Timeline.JumpToStart(); ProfileElement.Timeline.JumpToStart();
else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer) else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer && layer.Parent is not Layer)
layer.CreateCopyAsChild(); layer.CreateRenderCopy(10);
} }
} }
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle); // Stick to mean segment in toggle mode for as long as the condition is met
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle && IsMet);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -225,10 +224,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventDefaultNode.NodeId); INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventDefaultNode.NodeId);
if (existingEventNode != null) if (existingEventNode != null)
_eventNode = (EventDefaultNode) existingEventNode; _eventNode = (EventDefaultNode) existingEventNode;
UpdateEventNode(); UpdateEventNode();
Script.LoadConnections(); Script.LoadConnections();
} }
#endregion #endregion

View File

@ -190,8 +190,11 @@ namespace Artemis.Core
try try
{ {
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height); SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint); {
if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
}
// No point rendering if the alpha was set to zero by one of the effects // No point rendering if the alpha was set to zero by one of the effects
if (layerPaint.Color.Alpha == 0) if (layerPaint.Color.Alpha == 0)
@ -204,8 +207,11 @@ namespace Artemis.Core
for (int index = Children.Count - 1; index > -1; index--) for (int index = Children.Count - 1; index > -1; index--)
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top)); Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint); {
if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
}
} }
finally finally
{ {

View File

@ -16,6 +16,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class Layer : RenderProfileElement public sealed class Layer : RenderProfileElement
{ {
private readonly List<Layer> _renderCopies;
private LayerGeneralProperties _general; private LayerGeneralProperties _general;
private BaseLayerBrush? _layerBrush; private BaseLayerBrush? _layerBrush;
private LayerShape? _layerShape; private LayerShape? _layerShape;
@ -37,6 +38,8 @@ namespace Artemis.Core
Name = name; Name = name;
Suspended = false; Suspended = false;
// TODO: move to top
_renderCopies = new List<Layer>();
_general = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
@ -61,6 +64,8 @@ namespace Artemis.Core
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
// TODO: move to top
_renderCopies = new List<Layer>();
_general = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
@ -76,15 +81,15 @@ namespace Artemis.Core
/// Creates a new instance of the <see cref="Layer" /> class by copying the provided <paramref name="source"/>. /// Creates a new instance of the <see cref="Layer" /> class by copying the provided <paramref name="source"/>.
/// </summary> /// </summary>
/// <param name="source">The layer to copy</param> /// <param name="source">The layer to copy</param>
/// <param name="parent">The parent of the layer</param> private Layer(Layer source) : base(source, source.Profile)
public Layer(Layer source, ProfileElement parent) : base(parent, parent.Profile)
{ {
LayerEntity = CoreJson.DeserializeObject<LayerEntity>(CoreJson.SerializeObject(source.LayerEntity, true), true) ?? new LayerEntity(); LayerEntity = source.LayerEntity;
LayerEntity.Id = Guid.NewGuid();
Profile = source.Profile; Profile = source.Profile;
Parent = parent; Parent = source;
// TODO: move to top
_renderCopies = new List<Layer>();
_general = new LayerGeneralProperties(); _general = new LayerGeneralProperties();
_transform = new LayerTransformProperties(); _transform = new LayerTransformProperties();
@ -94,6 +99,13 @@ namespace Artemis.Core
Adapter = new LayerAdapter(this); Adapter = new LayerAdapter(this);
Load(); Load();
Initialize(); Initialize();
Timeline.JumpToStart();
AddLeds(source.Leds);
Enable();
// After loading using the source entity create a new entity so the next call to Save won't mess with the source, just in case.
LayerEntity = new LayerEntity();
} }
/// <summary> /// <summary>
@ -373,7 +385,7 @@ namespace Artemis.Core
if (ShouldBeEnabled) if (ShouldBeEnabled)
Enable(); Enable();
else if (Timeline.IsFinished && !Children.Any()) else if (Timeline.IsFinished && !_renderCopies.Any())
Disable(); Disable();
if (Timeline.Delta == TimeSpan.Zero) if (Timeline.Delta == TimeSpan.Zero)
@ -383,22 +395,26 @@ namespace Artemis.Core
Transform.Update(Timeline); Transform.Update(Timeline);
LayerBrush?.InternalUpdate(Timeline); LayerBrush?.InternalUpdate(Timeline);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalUpdate(Timeline);
// Remove children that finished their timeline and update the rest
for (int index = 0; index < Children.Count; index++)
{ {
Layer child = (Layer) Children[index]; if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalUpdate(Timeline);
}
// Remove render copies that finished their timeline and update the rest
for (int index = 0; index < _renderCopies.Count; index++)
{
Layer child = _renderCopies[index];
if (!child.Timeline.IsFinished) if (!child.Timeline.IsFinished)
{ {
child.Update(deltaTime); child.Update(deltaTime);
continue;
} }
else
RemoveChild(child); {
child.Dispose(); _renderCopies.Remove(child);
index--; child.Dispose();
index--;
}
} }
} }
@ -408,20 +424,16 @@ namespace Artemis.Core
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
RenderSelf(canvas, basePosition); RenderLayer(canvas, basePosition);
RenderChildren(canvas, basePosition); RenderCopies(canvas, basePosition);
} }
private void RenderSelf(SKCanvas canvas, SKPointI basePosition) private void RenderLayer(SKCanvas canvas, SKPointI basePosition)
{ {
// Ensure the layer is ready // Ensure the layer is ready
if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized || !Leds.Any()) if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized || !Leds.Any())
return; return;
// Render children first so they go below
for (int i = Children.Count - 1; i >= 0; i--)
Children[i].Render(canvas, basePosition);
// Ensure the brush is ready // Ensure the brush is ready
if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false) if (LayerBrush == null || LayerBrush?.BaseProperties?.PropertiesInitialized == false)
return; return;
@ -432,7 +444,7 @@ namespace Artemis.Core
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low}; SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try try
{ {
canvas.Save(); using SKAutoCanvasRestore _ = new(canvas);
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y); canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
using SKPath clipPath = new(Path); using SKPath clipPath = new(Path);
clipPath.Transform(SKMatrix.CreateTranslation(Bounds.Left * -1, Bounds.Top * -1)); clipPath.Transform(SKMatrix.CreateTranslation(Bounds.Left * -1, Bounds.Top * -1));
@ -478,18 +490,16 @@ namespace Artemis.Core
} }
finally finally
{ {
canvas.Restore();
layerPaint.DisposeSelfAndProperties(); layerPaint.DisposeSelfAndProperties();
} }
Timeline.ClearDelta(); Timeline.ClearDelta();
} }
private void RenderChildren(SKCanvas canvas, SKPointI basePosition) private void RenderCopies(SKCanvas canvas, SKPointI basePosition)
{ {
// Render children first so they go below for (int i = _renderCopies.Count - 1; i >= 0; i--)
for (int i = Children.Count - 1; i >= 0; i--) _renderCopies[i].Render(canvas, basePosition);
Children[i].Render(canvas, basePosition);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -549,23 +559,24 @@ namespace Artemis.Core
else else
Timeline.JumpToEnd(); Timeline.JumpToEnd();
while (Children.Any()) while (_renderCopies.Any())
{ {
Children[0].Dispose(); _renderCopies[0].Dispose();
RemoveChild(Children[0]); _renderCopies.RemoveAt(0);
} }
} }
/// <summary> /// <summary>
/// Creates a copy of this layer as a child and plays it once /// Creates a copy of this layer and renders it alongside this layer for as long as its timeline lasts.
/// </summary> /// </summary>
public void CreateCopyAsChild() /// <param name="max">The total maximum of render copies to keep</param>
public void CreateRenderCopy(int max)
{ {
Layer copy = new(this, this); if (_renderCopies.Count >= max)
copy.AddLeds(Leds); return;
copy.Enable();
copy.Timeline.JumpToStart(); Layer copy = new(this);
AddChild(copy); _renderCopies.Add(copy);
} }
internal void CalculateRenderProperties() internal void CalculateRenderProperties()
@ -623,26 +634,23 @@ namespace Artemis.Core
if (LayerBrush == null) if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering"); throw new ArtemisCoreException("The layer is not yet ready for rendering");
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
try
{ {
canvas.SaveLayer(layerPaint); if (!baseLayerEffect.Suspended)
canvas.ClipPath(renderPath); baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
// Restore the blend mode before doing the actual render
layerPaint.BlendMode = SKBlendMode.SrcOver;
LayerBrush.InternalRender(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
} }
finally using SKAutoCanvasRestore _ = new(canvas);
canvas.ClipPath(renderPath);
// Restore the blend mode before doing the actual render
layerPaint.BlendMode = SKBlendMode.SrcOver;
LayerBrush.InternalRender(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{ {
canvas.Restore(); if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint);
} }
} }

View File

@ -46,6 +46,11 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// </summary> /// </summary>
IObservable<int> PixelsPerSecond { get; } IObservable<int> PixelsPerSecond { get; }
/// <summary>
/// Gets an observable of the suspended state.
/// </summary>
IObservable<bool> SuspendedEditing { get; }
/// <summary> /// <summary>
/// Gets a source list of all available editor tools. /// Gets a source list of all available editor tools.
/// </summary> /// </summary>
@ -87,6 +92,12 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// <param name="pixelsPerSecond">The new pixels per second.</param> /// <param name="pixelsPerSecond">The new pixels per second.</param>
void ChangePixelsPerSecond(int pixelsPerSecond); void ChangePixelsPerSecond(int pixelsPerSecond);
/// <summary>
/// Changes the current suspended state.
/// </summary>
/// <param name="suspend">The new suspended state.</param>
void ChangeSuspendedEditing(bool suspend);
/// <summary> /// <summary>
/// Selects the provided keyframe. /// Selects the provided keyframe.
/// </summary> /// </summary>

View File

@ -62,8 +62,6 @@ internal class ProfileEditorService : IProfileEditorService
}); });
} }
public IObservable<bool> SuspendedEditing { get; }
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration) private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
{ {
if (profileConfiguration == null) if (profileConfiguration == null)
@ -107,6 +105,7 @@ internal class ProfileEditorService : IProfileEditorService
public IObservable<RenderProfileElement?> ProfileElement { get; } public IObservable<RenderProfileElement?> ProfileElement { get; }
public IObservable<ILayerProperty?> LayerProperty { get; } public IObservable<ILayerProperty?> LayerProperty { get; }
public IObservable<ProfileEditorHistory?> History { get; } public IObservable<ProfileEditorHistory?> History { get; }
public IObservable<bool> SuspendedEditing { get; }
public IObservable<TimeSpan> Time { get; } public IObservable<TimeSpan> Time { get; }
public IObservable<bool> Playing { get; } public IObservable<bool> Playing { get; }
public IObservable<int> PixelsPerSecond { get; } public IObservable<int> PixelsPerSecond { get; }
@ -180,6 +179,25 @@ internal class ProfileEditorService : IProfileEditorService
_timeSubject.OnNext(time); _timeSubject.OnNext(time);
} }
public void ChangeSuspendedEditing(bool suspend)
{
if (_suspendedEditingSubject.Value == suspend)
return;
_suspendedEditingSubject.OnNext(suspend);
if (suspend)
{
Pause();
_profileService.RenderForEditor = false;
}
else
{
if (_profileConfigurationSubject.Value != null)
_profileService.RenderForEditor = true;
Tick(_timeSubject.Value);
}
}
public void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle) public void SelectKeyframe(ILayerPropertyKeyframe? keyframe, bool expand, bool toggle)
{ {
if (keyframe == null) if (keyframe == null)

View File

@ -5,6 +5,7 @@
xmlns:controls="clr-namespace:Artemis.UI.Controls" xmlns:controls="clr-namespace:Artemis.UI.Controls"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="350" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="350"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.PropertiesView"
x:DataType="local:PropertiesViewModel"> x:DataType="local:PropertiesViewModel">

View File

@ -31,6 +31,7 @@ public class PropertiesViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty; private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond; private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement; private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
/// <inheritdoc /> /// <inheritdoc />
public PropertiesViewModel(IProfileEditorService profileEditorService, public PropertiesViewModel(IProfileEditorService profileEditorService,
@ -55,6 +56,7 @@ public class PropertiesViewModel : ActivatableViewModelBase
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d); _profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
_pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d); _pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d);
_layerProperty = profileEditorService.LayerProperty.ToProperty(this, vm => vm.LayerProperty).DisposeWith(d); _layerProperty = profileEditorService.LayerProperty.ToProperty(this, vm => vm.LayerProperty).DisposeWith(d);
_suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
Disposable.Create(() => Disposable.Create(() =>
{ {
_settingsService.SaveAllSettings(); _settingsService.SaveAllSettings();
@ -94,11 +96,13 @@ public class PropertiesViewModel : ActivatableViewModelBase
public RenderProfileElement? ProfileElement => _profileElement?.Value; public RenderProfileElement? ProfileElement => _profileElement?.Value;
public Layer? Layer => _profileElement?.Value as Layer; public Layer? Layer => _profileElement?.Value as Layer;
public ILayerProperty? LayerProperty => _layerProperty?.Value; public ILayerProperty? LayerProperty => _layerProperty?.Value;
public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0; public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
public IObservable<bool> Playing => _profileEditorService.Playing; public IObservable<bool> Playing => _profileEditorService.Playing;
public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0); public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0);
private void UpdatePropertyGroups() private void UpdatePropertyGroups()
{ {
if (ProfileElement == null) if (ProfileElement == null)

View File

@ -59,7 +59,7 @@
</ItemsControl> </ItemsControl>
<!-- The middle layer contains visualizers --> <!-- The middle layer contains visualizers -->
<ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False"> <ItemsControl Items="{CompiledBinding Visualizers}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}">
<ItemsControl.Styles> <ItemsControl.Styles>
<Style Selector="ContentPresenter"> <Style Selector="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Left" Value="{Binding X}" />
@ -74,7 +74,7 @@
</ItemsControl> </ItemsControl>
<!-- The top layer contains tools --> <!-- The top layer contains tools -->
<ItemsControl Items="{CompiledBinding Tools}" ClipToBounds="False"> <ItemsControl Items="{CompiledBinding Tools}" ClipToBounds="False" IsVisible="{CompiledBinding !SuspendedEditing}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<Grid /> <Grid />

View File

@ -22,6 +22,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase
private readonly SourceList<IVisualizerViewModel> _visualizers; private readonly SourceList<IVisualizerViewModel> _visualizers;
private readonly IProfileEditorVmFactory _vmFactory; private readonly IProfileEditorVmFactory _vmFactory;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration; private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private ReadOnlyObservableCollection<IToolViewModel> _tools; private ReadOnlyObservableCollection<IToolViewModel> _tools;
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory) public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
@ -38,12 +39,10 @@ public class VisualEditorViewModel : ActivatableViewModelBase
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_profileConfiguration = profileEditorService.ProfileConfiguration _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
.ToProperty(this, vm => vm.ProfileConfiguration) _suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
.DisposeWith(d); profileEditorService.ProfileConfiguration.Subscribe(CreateVisualizers).DisposeWith(d);
profileEditorService.ProfileConfiguration
.Subscribe(CreateVisualizers)
.DisposeWith(d);
profileEditorService.Tools profileEditorService.Tools
.Connect() .Connect()
.AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Filter(t => t.IsSelected).Bind(out ReadOnlyObservableCollection<IToolViewModel> tools) .AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Filter(t => t.IsSelected).Bind(out ReadOnlyObservableCollection<IToolViewModel> tools)
@ -71,6 +70,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase
} }
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
public ObservableCollection<ArtemisDevice> Devices { get; } public ObservableCollection<ArtemisDevice> Devices { get; }
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; } public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }

View File

@ -13,9 +13,19 @@
<UserControl.Resources> <UserControl.Resources>
<converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter"></converters:DoubleToGridLengthConverter> <converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter"></converters:DoubleToGridLengthConverter>
</UserControl.Resources> </UserControl.Resources>
<UserControl.Styles>
<Style Selector="Border.suspended-editing">
<Setter Property="Margin" Value="-10" />
<Setter Property="Background" Value="{DynamicResource SmokeFillColorDefault}" />
<Setter Property="IsVisible" Value="{CompiledBinding SuspendedEditing}" />
<Setter Property="CornerRadius" Value="{DynamicResource CardCornerRadius}" />
</Style>
</UserControl.Styles>
<UserControl.KeyBindings> <UserControl.KeyBindings>
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z"></KeyBinding> <KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z"></KeyBinding>
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y"></KeyBinding> <KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y"></KeyBinding>
<KeyBinding Command="{CompiledBinding ToggleSuspend}" Gesture="F5"></KeyBinding>
<KeyBinding Command="{CompiledBinding ToggleAutoSuspend}" Gesture="Shift+F5"></KeyBinding>
</UserControl.KeyBindings> </UserControl.KeyBindings>
<UserControl.Styles> <UserControl.Styles>
<Style Selector="GridSplitter.editor-grid-splitter-vertical"> <Style Selector="GridSplitter.editor-grid-splitter-vertical">
@ -78,7 +88,23 @@
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" /> <GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True"> <Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
<ContentControl Content="{CompiledBinding PropertiesViewModel}" /> <Panel>
<ContentControl Content="{CompiledBinding PropertiesViewModel}" />
<Border Classes="suspended-editing">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="16">
<avalonia:MaterialIcon Kind="TimerOffOutline" Width="125" Height="125" HorizontalAlignment="Center" />
<TextBlock Classes="h4" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 10">
Timeline suspended
</TextBlock>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
The profile is currently running in normal mode and the timeline cannot be edited.
</TextBlock>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
Press F5 to switch between editor mode and normal mode. Auto-switching can be disabled in the run menu.
</TextBlock>
</StackPanel>
</Border>
</Panel>
</Border> </Border>
</Grid> </Grid>
@ -91,13 +117,19 @@
<RowDefinition Height="{CompiledBinding ConditionsHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}" /> <RowDefinition Height="{CompiledBinding ConditionsHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border Grid.Row="0" Classes="card card-condensed" Margin="4 0 4 4"> <Border Grid.Row="0" Classes="card card-condensed" Margin="4 0 4 4">
<ContentControl Content="{CompiledBinding ProfileTreeViewModel}" /> <Panel>
<ContentControl Content="{CompiledBinding ProfileTreeViewModel}" />
<Border Classes="suspended-editing" />
</Panel>
</Border> </Border>
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" /> <GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
<Border Grid.Row="2" Classes="card card-condensed" Margin="4"> <Border Grid.Row="2" Classes="card card-condensed" Margin="4">
<ContentControl Content="{CompiledBinding DisplayConditionScriptViewModel}"></ContentControl> <Panel>
<ContentControl Content="{CompiledBinding DisplayConditionScriptViewModel}"></ContentControl>
<Border Classes="suspended-editing" />
</Panel>
</Border> </Border>
</Grid> </Grid>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
@ -17,9 +18,11 @@ namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorViewModel : MainScreenViewModel public class ProfileEditorViewModel : MainScreenViewModel
{ {
private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history; private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration; private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private ReadOnlyObservableCollection<IToolViewModel>? _tools; private ReadOnlyObservableCollection<IToolViewModel>? _tools;
/// <inheritdoc /> /// <inheritdoc />
@ -34,6 +37,7 @@ public class ProfileEditorViewModel : MainScreenViewModel
StatusBarViewModel statusBarViewModel) StatusBarViewModel statusBarViewModel)
: base(hostScreen, "profile-editor") : base(hostScreen, "profile-editor")
{ {
_profileEditorService = profileEditorService;
_settingsService = settingsService; _settingsService = settingsService;
VisualEditorViewModel = visualEditorViewModel; VisualEditorViewModel = visualEditorViewModel;
ProfileTreeViewModel = profileTreeViewModel; ProfileTreeViewModel = profileTreeViewModel;
@ -41,11 +45,12 @@ public class ProfileEditorViewModel : MainScreenViewModel
DisplayConditionScriptViewModel = displayConditionScriptViewModel; DisplayConditionScriptViewModel = displayConditionScriptViewModel;
StatusBarViewModel = statusBarViewModel; StatusBarViewModel = statusBarViewModel;
TitleBarViewModel = profileEditorTitleBarViewModel; TitleBarViewModel = profileEditorTitleBarViewModel;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d); _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
_history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d); _history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d);
_suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
profileEditorService.Tools.Connect() profileEditorService.Tools.Connect()
.Filter(t => t.ShowInToolbar) .Filter(t => t.ShowInToolbar)
.Sort(SortExpressionComparer<IToolViewModel>.Ascending(vm => vm.Order)) .Sort(SortExpressionComparer<IToolViewModel>.Ascending(vm => vm.Order))
@ -54,6 +59,9 @@ public class ProfileEditorViewModel : MainScreenViewModel
.DisposeWith(d); .DisposeWith(d);
Tools = tools; Tools = tools;
}); });
ToggleSuspend = ReactiveCommand.Create(ExecuteToggleSuspend);
ToggleAutoSuspend = ReactiveCommand.Create(ExecuteToggleAutoSuspend);
} }
public VisualEditorViewModel VisualEditorViewModel { get; } public VisualEditorViewModel VisualEditorViewModel { get; }
@ -70,12 +78,24 @@ public class ProfileEditorViewModel : MainScreenViewModel
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public ProfileEditorHistory? History => _history?.Value; public ProfileEditorHistory? History => _history?.Value;
public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
public PluginSetting<double> TreeWidth => _settingsService.GetSetting("ProfileEditor.TreeWidth", 350.0); public PluginSetting<double> TreeWidth => _settingsService.GetSetting("ProfileEditor.TreeWidth", 350.0);
public PluginSetting<double> ConditionsHeight => _settingsService.GetSetting("ProfileEditor.ConditionsHeight", 300.0); public PluginSetting<double> ConditionsHeight => _settingsService.GetSetting("ProfileEditor.ConditionsHeight", 300.0);
public PluginSetting<double> PropertiesHeight => _settingsService.GetSetting("ProfileEditor.PropertiesHeight", 300.0); public PluginSetting<double> PropertiesHeight => _settingsService.GetSetting("ProfileEditor.PropertiesHeight", 300.0);
public ReactiveCommand<Unit, Unit> ToggleSuspend { get; }
public ReactiveCommand<Unit, Unit> ToggleAutoSuspend { get; }
public void OpenUrl(string url) public void OpenUrl(string url)
{ {
Utilities.OpenUrl(url); Utilities.OpenUrl(url);
} }
private void ExecuteToggleSuspend()
{
_profileEditorService.ChangeSuspendedEditing(!SuspendedEditing);
}
private void ExecuteToggleAutoSuspend()
{
}
} }