1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 17:53:32 +00:00

Profile editor - Fixed new elements not rendering while paused

Profile editor - Use default brush on new layers
Profile tree - Implement drag & drop
This commit is contained in:
Robert 2022-03-09 00:10:41 +01:00
parent f5a902f5a5
commit 376a9142d3
14 changed files with 335 additions and 229 deletions

View File

@ -185,7 +185,7 @@ namespace Artemis.Core
// No point rendering if all children are disabled // No point rendering if all children are disabled
if (!Children.Any(c => c is RenderProfileElement {Enabled: true})) if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
return; return;
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low}; SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try try
{ {
@ -266,7 +266,7 @@ namespace Artemis.Core
/// Occurs when a property affecting the rendering properties of this folder has been updated /// Occurs when a property affecting the rendering properties of this folder has been updated
/// </summary> /// </summary>
public event EventHandler? RenderPropertiesUpdated; public event EventHandler? RenderPropertiesUpdated;
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {

View File

@ -27,5 +27,7 @@ namespace Artemis.Core.Services
/// Returns the descriptor of the default layer brush /// Returns the descriptor of the default layer brush
/// </summary> /// </summary>
LayerBrushDescriptor? GetDefaultLayerBrush(); LayerBrushDescriptor? GetDefaultLayerBrush();
void ApplyDefaultBrush(Layer layer);
} }
} }

View File

@ -46,5 +46,13 @@ namespace Artemis.Core.Services
defaultReference.Value.BrushType ??= "SolidBrush"; defaultReference.Value.BrushType ??= "SolidBrush";
return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor; return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor;
} }
/// <inheritdoc />
public void ApplyDefaultBrush(Layer layer)
{
LayerBrushDescriptor? brush = GetDefaultLayerBrush();
if (brush != null)
layer.ChangeLayerBrush(brush.CreateInstance(layer, null));
}
} }
} }

View File

@ -47,6 +47,8 @@ public class AddProfileElement : IProfileEditorCommand, IDisposable
{ {
_isAdded = true; _isAdded = true;
_target.AddChild(_subject, _index); _target.AddChild(_subject, _index);
_subject.Enable();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -134,7 +134,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
return; return;
if (_preview.DiscardPreview() && _preview.PreviewValue != null) if (_preview.DiscardPreview() && _preview.PreviewValue != null)
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, Time)); ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.OriginalValue, Time));
_preview = null; _preview = null;
} }

View File

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Artemis.UI.Converters;
public class ColorOpacityConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Color color && double.TryParse(parameter?.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double multiplier))
return new Color((byte) (color.A * multiplier), color.R, color.G, color.B);
return value;
}
/// <inheritdoc />
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value;
}
}

View File

@ -55,7 +55,9 @@
</TextBox> </TextBox>
<controls:ColorPickerButton Classes="contained-color-picker-button" <controls:ColorPickerButton Classes="contained-color-picker-button"
Color="{CompiledBinding InputValue, Converter={StaticResource SKColorToColor2Converter}}" Color="{CompiledBinding InputValue, Converter={StaticResource SKColorToColor2Converter}}"
ShowAcceptDismissButtons="False" /> ShowAcceptDismissButtons="False"
FlyoutOpened="ColorPickerButton_OnFlyoutOpened"
FlyoutClosed="ColorPickerButton_OnFlyoutClosed" />
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,8 +1,10 @@
using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using FluentAvalonia.UI.Controls;
namespace Artemis.UI.DefaultTypes.PropertyInput namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
@ -17,5 +19,15 @@ namespace Artemis.UI.DefaultTypes.PropertyInput
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void ColorPickerButton_OnFlyoutOpened(ColorPickerButton sender, EventArgs args)
{
ViewModel?.StartPreview();
}
private void ColorPickerButton_OnFlyoutClosed(ColorPickerButton sender, EventArgs args)
{
ViewModel?.ApplyPreview();
}
} }
} }

View File

@ -1,15 +1,11 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop; using Avalonia.Xaml.Interactions.DragAndDrop;
@ -34,7 +30,7 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
if (sender is ItemsControl itemsControl) if (sender is ItemsControl itemsControl)
{ {
foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl)) foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl))
SetDraggingPseudoClasses(treeViewItem, TreeDropType.After, false); SetDraggingPseudoClasses(treeViewItem, TreeDropType.None);
} }
return result; return result;
@ -42,8 +38,11 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
public override void Cancel(object? sender, RoutedEventArgs e) public override void Cancel(object? sender, RoutedEventArgs e)
{ {
if (e.Source is IControl control && control.FindAncestorOfType<TreeViewItem>() != null) if (sender is ItemsControl itemsControl)
SetDraggingPseudoClasses(control.FindAncestorOfType<TreeViewItem>(), TreeDropType.After, false); {
foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl))
SetDraggingPseudoClasses(treeViewItem, TreeDropType.None);
}
base.Cancel(sender, e); base.Cancel(sender, e);
} }
@ -102,6 +101,9 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
if (e.DragEffects != DragDropEffects.Move) if (e.DragEffects != DragDropEffects.Move)
return false; return false;
foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(treeView))
SetDraggingPseudoClasses(treeViewItem, TreeDropType.None);
if (bExecute) if (bExecute)
{ {
if (dropType == TreeDropType.Into) if (dropType == TreeDropType.Into)
@ -118,7 +120,7 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
} }
else else
{ {
SetDraggingPseudoClasses((IControl) targetVisual, dropType, true); SetDraggingPseudoClasses((IControl) targetVisual, dropType);
} }
return true; return true;
@ -130,7 +132,7 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
foreach (ItemContainerInfo containerInfo in currentNode.ItemContainerGenerator.Containers) foreach (ItemContainerInfo containerInfo in currentNode.ItemContainerGenerator.Containers)
{ {
if (containerInfo.ContainerControl is TreeViewItem treeViewItem && containerInfo.Item is TreeItemViewModel viewModel) if (containerInfo.ContainerControl is TreeViewItem treeViewItem && containerInfo.Item is TreeItemViewModel)
{ {
result.Add(treeViewItem); result.Add(treeViewItem);
if (treeViewItem.ItemContainerGenerator.Containers.Any()) if (treeViewItem.ItemContainerGenerator.Containers.Any())
@ -141,9 +143,16 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
return result; return result;
} }
private void SetDraggingPseudoClasses(IControl control, TreeDropType type, bool isDragging) private void SetDraggingPseudoClasses(IControl control, TreeDropType type)
{ {
if (isDragging) if (type == TreeDropType.None)
{
((IPseudoClasses) control.Classes).Remove(":dragging");
((IPseudoClasses) control.Classes).Remove(":dragging-before");
((IPseudoClasses) control.Classes).Remove(":dragging-after");
((IPseudoClasses) control.Classes).Remove(":dragging-into");
}
else
{ {
((IPseudoClasses) control.Classes).Add(":dragging"); ((IPseudoClasses) control.Classes).Add(":dragging");
if (type == TreeDropType.Before) if (type == TreeDropType.Before)
@ -165,19 +174,13 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
((IPseudoClasses) control.Classes).Add(":dragging-into"); ((IPseudoClasses) control.Classes).Add(":dragging-into");
} }
} }
else
{
((IPseudoClasses) control.Classes).Remove(":dragging");
((IPseudoClasses) control.Classes).Remove(":dragging-before");
((IPseudoClasses) control.Classes).Remove(":dragging-after");
((IPseudoClasses) control.Classes).Remove(":dragging-into");
}
} }
private enum TreeDropType private enum TreeDropType
{ {
Before, Before,
After, After,
Into Into,
None
} }
} }

View File

@ -8,8 +8,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{ {
public class FolderTreeItemViewModel : TreeItemViewModel public class FolderTreeItemViewModel : TreeItemViewModel
{ {
public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) public FolderTreeItemViewModel(TreeItemViewModel? parent,
: base(parent, folder, windowService, profileEditorService, rgbService, profileEditorVmFactory) Folder folder,
IWindowService windowService,
IProfileEditorService profileEditorService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(parent, folder, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
Folder = folder; Folder = folder;
} }

View File

@ -8,8 +8,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{ {
public class LayerTreeItemViewModel : TreeItemViewModel public class LayerTreeItemViewModel : TreeItemViewModel
{ {
public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer, IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) public LayerTreeItemViewModel(TreeItemViewModel? parent,
: base(parent, layer, windowService, profileEditorService, rgbService, profileEditorVmFactory) Layer layer,
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, layer, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
Layer = layer; Layer = layer;
} }

View File

@ -6,14 +6,18 @@
xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree" xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree"
xmlns:profileBehaviors="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Behaviors" xmlns:profileBehaviors="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.Behaviors"
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors" xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ProfileTreeView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ProfileTreeView"
x:DataType="profileTree:ProfileTreeViewModel"> x:DataType="profileTree:ProfileTreeViewModel">
<UserControl.Resources>
<converters:ColorOpacityConverter x:Key="ColorOpacityConverter" />
</UserControl.Resources>
<UserControl.Styles> <UserControl.Styles>
<StyleInclude Source="avares://Avalonia.Xaml.Interactions/Draggable/Styles.axaml" /> <StyleInclude Source="avares://Avalonia.Xaml.Interactions/Draggable/Styles.axaml" />
<Style Selector="TreeView#ProfileTreeView"> <Style Selector="TreeView#ProfileTreeView">
<Style.Resources> <Style.Resources>
<profileBehaviors:ProfileTreeViewDropHandler x:Key="ProfileTreeViewDropHandler" /> <profileBehaviors:ProfileTreeViewDropHandler x:Key="ProfileTreeViewDropHandler" />
</Style.Resources> </Style.Resources>
<Setter Property="(Interaction.Behaviors)"> <Setter Property="(Interaction.Behaviors)">
<BehaviorCollectionTemplate> <BehaviorCollectionTemplate>
@ -29,24 +33,56 @@
<Setter Property="(Interaction.Behaviors)"> <Setter Property="(Interaction.Behaviors)">
<BehaviorCollectionTemplate> <BehaviorCollectionTemplate>
<BehaviorCollection> <BehaviorCollection>
<behaviors:SimpleContextDragBehavior /> <behaviors:SimpleContextDragBehavior />
</BehaviorCollection> </BehaviorCollection>
</BehaviorCollectionTemplate> </BehaviorCollectionTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-before"> <Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-before">
<Setter Property="Background" Value="DarkOrange" /> <Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,28">
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.0" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.05" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0.25}" Offset="0.05" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0}" Offset="0.25" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style> </Style>
<Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-into"> <Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-into">
<Setter Property="Background" Value="Blue" /> <Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,28">
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.0" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.05" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0.25}" Offset="0.05" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0}" Offset="0.25" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0}" Offset="0.75" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0.25}" Offset="0.95" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.95" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style> </Style>
<Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-after"> <Style Selector="TreeView#ProfileTreeView TreeViewItem:dragging-after">
<Setter Property="Background" Value="Green" /> <Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,28">
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0}" Offset="0.75" />
<GradientStop Color="{Binding Source={StaticResource SystemAccentColorLight3}, Converter={StaticResource ColorOpacityConverter}, ConverterParameter=0.25}" Offset="0.95" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="0.95" />
<GradientStop Color="{StaticResource SystemAccentColorLight3}" Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
<TreeView Name="ProfileTreeView" Classes="no-right-margin draggable" Items="{CompiledBinding Children}" SelectedItem="{CompiledBinding SelectedChild}" SelectionChanged="ProfileTreeView_OnSelectionChanged"> <TreeView Name="ProfileTreeView" Classes="no-right-margin draggable" Items="{CompiledBinding Children}" SelectedItem="{CompiledBinding SelectedChild}"
SelectionChanged="ProfileTreeView_OnSelectionChanged">
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem"> <Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />

View File

@ -15,13 +15,15 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
{ {
public class ProfileTreeViewModel : TreeItemViewModel public class ProfileTreeViewModel : TreeItemViewModel
{ {
private readonly IProfileEditorService _profileEditorService;
private TreeItemViewModel? _selectedChild; private TreeItemViewModel? _selectedChild;
public ProfileTreeViewModel(IWindowService windowService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory, IRgbService rgbService) public ProfileTreeViewModel(IWindowService windowService,
: base(null, null, windowService, profileEditorService, rgbService, profileEditorVmFactory) IProfileEditorService profileEditorService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(null, null, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
_profileEditorService = profileEditorService;
this.WhenActivated(d => this.WhenActivated(d =>
{ {
profileEditorService.ProfileConfiguration.WhereNotNull().Subscribe(configuration => profileEditorService.ProfileConfiguration.WhereNotNull().Subscribe(configuration =>
@ -46,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
profileEditorService.ChangeCurrentProfileElement(renderProfileElement); profileEditorService.ChangeCurrentProfileElement(renderProfileElement);
}); });
ClearSelection = ReactiveCommand.Create(() => _profileEditorService.ChangeCurrentProfileElement(null)); ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null));
} }
public ReactiveCommand<Unit, Unit> ClearSelection { get; } public ReactiveCommand<Unit, Unit> ClearSelection { get; }

View File

@ -8,7 +8,6 @@ using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
@ -16,220 +15,225 @@ using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public abstract class TreeItemViewModel : ActivatableViewModelBase
{ {
public abstract class TreeItemViewModel : ActivatableViewModelBase private readonly IProfileEditorService _profileEditorService;
private readonly IProfileEditorVmFactory _profileEditorVmFactory;
private readonly IWindowService _windowService;
private RenderProfileElement? _currentProfileElement;
private bool _isExpanded;
private ProfileElement? _profileElement;
private string? _renameValue;
private bool _renaming;
protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement,
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory)
{ {
private readonly IProfileEditorVmFactory _profileEditorVmFactory; _windowService = windowService;
private readonly IWindowService _windowService; _profileEditorService = profileEditorService;
private readonly IProfileEditorService _profileEditorService; _profileEditorVmFactory = profileEditorVmFactory;
private bool _isExpanded;
private ProfileElement? _profileElement;
private RenderProfileElement? _currentProfileElement;
private bool _renaming;
private string? _renameValue;
protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, IWindowService windowService, IProfileEditorService profileEditorService, IRgbService rgbService, Parent = parent;
IProfileEditorVmFactory profileEditorVmFactory) ProfileElement = profileElement;
AddLayer = ReactiveCommand.Create(() =>
{ {
_windowService = windowService; if (ProfileElement is Layer targetLayer)
_profileEditorService = profileEditorService;
_profileEditorVmFactory = profileEditorVmFactory;
Parent = parent;
ProfileElement = profileElement;
AddLayer = ReactiveCommand.Create(() =>
{ {
if (ProfileElement is Layer targetLayer) Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName());
{ layerBrushService.ApplyDefaultBrush(layer);
Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName());
layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds));
profileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order));
}
else if (ProfileElement != null)
{
Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName());
layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds));
profileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0));
}
});
AddFolder = ReactiveCommand.Create(() => layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds));
{ profileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order));
if (ProfileElement is Layer targetLayer)
profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order));
else if (ProfileElement != null)
profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0));
});
Rename = ReactiveCommand.Create(() =>
{
Renaming = true;
RenameValue = ProfileElement?.Name;
});
Duplicate = ReactiveCommand.Create(() => throw new NotImplementedException());
Copy = ReactiveCommand.Create(() => throw new NotImplementedException());
Paste = ReactiveCommand.Create(() => throw new NotImplementedException());
Delete = ReactiveCommand.Create(() =>
{
if (ProfileElement is RenderProfileElement renderProfileElement)
profileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement));
});
this.WhenActivated(d =>
{
_profileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d);
SubscribeToProfileElement(d);
CreateTreeItems();
});
}
public ProfileElement? ProfileElement
{
get => _profileElement;
set => RaiseAndSetIfChanged(ref _profileElement, value);
}
public bool IsExpanded
{
get => _isExpanded;
set => RaiseAndSetIfChanged(ref _isExpanded, value);
}
public bool Renaming
{
get => _renaming;
set => RaiseAndSetIfChanged(ref _renaming, value);
}
public TreeItemViewModel? Parent { get; set; }
public ObservableCollection<TreeItemViewModel> Children { get; } = new();
public ReactiveCommand<Unit, Unit> AddLayer { get; }
public ReactiveCommand<Unit, Unit> AddFolder { get; }
public ReactiveCommand<Unit, Unit> Rename { get; }
public ReactiveCommand<Unit, Unit> Duplicate { get; }
public ReactiveCommand<Unit, Unit> Copy { get; }
public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public abstract bool SupportsChildren { get; }
public string? RenameValue
{
get => _renameValue;
set => RaiseAndSetIfChanged(ref _renameValue, value);
}
public async Task ShowBrokenStateExceptions()
{
if (ProfileElement == null)
return;
List<IBreakableModel> broken = ProfileElement.GetBrokenHierarchy().Where(b => b.BrokenStateException != null).ToList();
foreach (IBreakableModel current in broken)
{
_windowService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!);
if (broken.Last() != current)
if (!await _windowService.ShowConfirmContentDialog("Broken state", "Do you want to view the next exception?"))
return;
} }
} else if (ProfileElement != null)
public void SubmitRename()
{
if (ProfileElement == null)
{ {
Renaming = false; Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName());
return; layerBrushService.ApplyDefaultBrush(layer);
layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds));
profileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0));
} }
});
_profileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); AddFolder = ReactiveCommand.Create(() =>
Renaming = false;
}
public void CancelRename()
{ {
Renaming = false; if (ProfileElement is Layer targetLayer)
} profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order));
else if (ProfileElement != null)
profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0));
});
public void InsertElement(TreeItemViewModel elementViewModel, int targetIndex) Rename = ReactiveCommand.Create(() =>
{ {
if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex) Renaming = true;
return; RenameValue = ProfileElement?.Name;
});
if (ProfileElement != null && elementViewModel.ProfileElement != null) Duplicate = ReactiveCommand.Create(() => throw new NotImplementedException());
_profileEditorService.ExecuteCommand(new MoveProfileElement(ProfileElement, elementViewModel.ProfileElement, targetIndex)); Copy = ReactiveCommand.Create(() => throw new NotImplementedException());
} Paste = ReactiveCommand.Create(() => throw new NotImplementedException());
protected void SubscribeToProfileElement(CompositeDisposable d) Delete = ReactiveCommand.Create(() =>
{ {
if (ProfileElement == null) if (ProfileElement is RenderProfileElement renderProfileElement)
return; profileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement));
});
Observable.FromEventPattern<ProfileElementEventArgs>(x => ProfileElement.ChildAdded += x, x => ProfileElement.ChildAdded -= x) this.WhenActivated(d =>
.Subscribe(c => AddTreeItemIfMissing(c.EventArgs.ProfileElement)).DisposeWith(d);
Observable.FromEventPattern<ProfileElementEventArgs>(x => ProfileElement.ChildRemoved += x, x => ProfileElement.ChildRemoved -= x)
.Subscribe(c => RemoveTreeItemsIfFound(c.Sender, c.EventArgs.ProfileElement)).DisposeWith(d);
}
protected void RemoveTreeItemsIfFound(object? sender, ProfileElement profileElement)
{ {
List<TreeItemViewModel> toRemove = Children.Where(t => t.ProfileElement == profileElement).ToList(); _profileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d);
foreach (TreeItemViewModel treeItemViewModel in toRemove) SubscribeToProfileElement(d);
Children.Remove(treeItemViewModel); CreateTreeItems();
});
}
if (_currentProfileElement != profileElement) public ProfileElement? ProfileElement
return; {
get => _profileElement;
set => RaiseAndSetIfChanged(ref _profileElement, value);
}
// Find a good candidate for a new selection, preferring the next sibling, falling back to the previous sibling and finally the parent public bool IsExpanded
ProfileElement? parent = sender as ProfileElement; {
ProfileElement? newSelection = null; get => _isExpanded;
if (parent != null) set => RaiseAndSetIfChanged(ref _isExpanded, value);
{ }
newSelection = parent.Children.FirstOrDefault(c => c.Order == profileElement.Order) ?? parent.Children.FirstOrDefault(c => c.Order == profileElement.Order - 1);
if (newSelection == null && parent is Folder {IsRootFolder: false})
newSelection = parent;
}
_profileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement); public bool Renaming
} {
get => _renaming;
set => RaiseAndSetIfChanged(ref _renaming, value);
}
protected void AddTreeItemIfMissing(ProfileElement profileElement) public TreeItemViewModel? Parent { get; set; }
public ObservableCollection<TreeItemViewModel> Children { get; } = new();
public ReactiveCommand<Unit, Unit> AddLayer { get; }
public ReactiveCommand<Unit, Unit> AddFolder { get; }
public ReactiveCommand<Unit, Unit> Rename { get; }
public ReactiveCommand<Unit, Unit> Duplicate { get; }
public ReactiveCommand<Unit, Unit> Copy { get; }
public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public abstract bool SupportsChildren { get; }
public string? RenameValue
{
get => _renameValue;
set => RaiseAndSetIfChanged(ref _renameValue, value);
}
public async Task ShowBrokenStateExceptions()
{
if (ProfileElement == null)
return;
List<IBreakableModel> broken = ProfileElement.GetBrokenHierarchy().Where(b => b.BrokenStateException != null).ToList();
foreach (IBreakableModel current in broken)
{ {
if (Children.Any(t => t.ProfileElement == profileElement)) _windowService.ShowExceptionDialog($"{current.BrokenDisplayName} - {current.BrokenState}", current.BrokenStateException!);
return; if (broken.Last() != current)
if (!await _windowService.ShowConfirmContentDialog("Broken state", "Do you want to view the next exception?"))
if (profileElement is Folder folder) return;
Children.Insert(folder.Parent.Children.IndexOf(folder), _profileEditorVmFactory.FolderTreeItemViewModel(this, folder));
else if (profileElement is Layer layer)
Children.Insert(layer.Parent.Children.IndexOf(layer), _profileEditorVmFactory.LayerTreeItemViewModel(this, layer));
// Select the newly added element
if (profileElement is RenderProfileElement renderProfileElement)
_profileEditorService.ChangeCurrentProfileElement(renderProfileElement);
}
protected void CreateTreeItems()
{
if (Children.Any())
Children.Clear();
if (ProfileElement == null)
return;
foreach (ProfileElement profileElement in ProfileElement.Children)
{
if (profileElement is Folder folder)
Children.Add(_profileEditorVmFactory.FolderTreeItemViewModel(this, folder));
else if (profileElement is Layer layer)
Children.Add(_profileEditorVmFactory.LayerTreeItemViewModel(this, layer));
}
} }
} }
public void SubmitRename()
{
if (ProfileElement == null)
{
Renaming = false;
return;
}
_profileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue));
Renaming = false;
}
public void CancelRename()
{
Renaming = false;
}
public void InsertElement(TreeItemViewModel elementViewModel, int targetIndex)
{
if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex)
return;
if (ProfileElement != null && elementViewModel.ProfileElement != null)
_profileEditorService.ExecuteCommand(new MoveProfileElement(ProfileElement, elementViewModel.ProfileElement, targetIndex));
}
protected void SubscribeToProfileElement(CompositeDisposable d)
{
if (ProfileElement == null)
return;
Observable.FromEventPattern<ProfileElementEventArgs>(x => ProfileElement.ChildAdded += x, x => ProfileElement.ChildAdded -= x)
.Subscribe(c => AddTreeItemIfMissing(c.EventArgs.ProfileElement)).DisposeWith(d);
Observable.FromEventPattern<ProfileElementEventArgs>(x => ProfileElement.ChildRemoved += x, x => ProfileElement.ChildRemoved -= x)
.Subscribe(c => RemoveTreeItemsIfFound(c.Sender, c.EventArgs.ProfileElement)).DisposeWith(d);
}
protected void RemoveTreeItemsIfFound(object? sender, ProfileElement profileElement)
{
List<TreeItemViewModel> toRemove = Children.Where(t => t.ProfileElement == profileElement).ToList();
foreach (TreeItemViewModel treeItemViewModel in toRemove)
Children.Remove(treeItemViewModel);
if (_currentProfileElement != profileElement)
return;
// Find a good candidate for a new selection, preferring the next sibling, falling back to the previous sibling and finally the parent
ProfileElement? parent = sender as ProfileElement;
ProfileElement? newSelection = null;
if (parent != null)
{
newSelection = parent.Children.FirstOrDefault(c => c.Order == profileElement.Order) ?? parent.Children.FirstOrDefault(c => c.Order == profileElement.Order - 1);
if (newSelection == null && parent is Folder {IsRootFolder: false})
newSelection = parent;
}
_profileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement);
}
protected void AddTreeItemIfMissing(ProfileElement profileElement)
{
if (Children.Any(t => t.ProfileElement == profileElement))
return;
if (profileElement is Folder folder)
Children.Insert(folder.Parent.Children.IndexOf(folder), _profileEditorVmFactory.FolderTreeItemViewModel(this, folder));
else if (profileElement is Layer layer)
Children.Insert(layer.Parent.Children.IndexOf(layer), _profileEditorVmFactory.LayerTreeItemViewModel(this, layer));
// Select the newly added element
if (profileElement is RenderProfileElement renderProfileElement)
_profileEditorService.ChangeCurrentProfileElement(renderProfileElement);
}
protected void CreateTreeItems()
{
if (Children.Any())
Children.Clear();
if (ProfileElement == null)
return;
foreach (ProfileElement profileElement in ProfileElement.Children)
{
if (profileElement is Folder folder)
Children.Add(_profileEditorVmFactory.FolderTreeItemViewModel(this, folder));
else if (profileElement is Layer layer)
Children.Add(_profileEditorVmFactory.LayerTreeItemViewModel(this, layer));
}
}
} }