Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions StreamDeckSimHub.Plugin/ActionEditor/ActionEditorManager.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright (C) 2025 Martin Renner
// Copyright (C) 2026 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using System.Collections.Concurrent;
using System.Windows;
using CommunityToolkit.Mvvm.Messaging;
using NLog;
using StreamDeckSimHub.Plugin.ActionEditor.ViewModels;
using StreamDeckSimHub.Plugin.Actions.GenericButton.Model;
using StreamDeckSimHub.Plugin.SimHub;
using StreamDeckSimHub.Plugin.Tools;
Expand All @@ -18,13 +19,16 @@ public class ActionEditorManager : IRecipient<GenericButtonEditorClosedEvent>
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly ConcurrentDictionary<string, GenericButtonEditor> _actionEditors = new();
private readonly SettingsConverter _settingsConverter;
private readonly ImageManager _imageManager;
private readonly ISimHubConnection _simHubConnection;
private readonly ShakeItStructureFetcher _shakeItStructureFetcher;

public ActionEditorManager(
ImageManager imageManager, ISimHubConnection simHubConnection, ShakeItStructureFetcher shakeItStructureFetcher)
SettingsConverter settingsConverter, ImageManager imageManager, ISimHubConnection simHubConnection,
ShakeItStructureFetcher shakeItStructureFetcher)
{
_settingsConverter = settingsConverter;
_imageManager = imageManager;
_simHubConnection = simHubConnection;
_shakeItStructureFetcher = shakeItStructureFetcher;
Expand All @@ -43,12 +47,14 @@ public Window ShowGenericButtonEditor(string actionUuid, Settings settings)
}

Logger.Debug("Showing new editor for action {ActionUuid}", actionUuid);
var newEditor =
new GenericButtonEditor(actionUuid, settings, _imageManager, _simHubConnection, _shakeItStructureFetcher)
{
WindowStartupLocation =
WindowStartupLocation.CenterScreen, // show on the same screen as the Stream Deck software
};
var newEditor = new GenericButtonEditor(actionUuid)
{
WindowStartupLocation =
WindowStartupLocation.CenterScreen, // show on the same screen as the Stream Deck software
};
var viewModel = new SettingsViewModel(settings, _settingsConverter, _imageManager, _simHubConnection,
_shakeItStructureFetcher, newEditor);
newEditor.SetViewModel(viewModel);
_actionEditors.TryAdd(actionUuid, newEditor);
newEditor.Show();

Expand Down
26 changes: 22 additions & 4 deletions StreamDeckSimHub.Plugin/ActionEditor/GenericButtonEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,23 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Icon}" Width="16" Height="16" Margin="0,0,5,0" />
<TextBlock Grid.Column="1" Text="{Binding DisplayName}" VerticalAlignment="Center" />
<Button Grid.Column="2" Background="Transparent" BorderBrush="Transparent" Focusable="False"
Click="DisplayItemDelete_OnClick">
Command="{Binding DataContext.CloneDisplayItemCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
ToolTip="Clone display item">
<Image Source="{StaticResource DiCloneRounded}" Height="16" />
</Button>
<Button Grid.Column="3" Background="Transparent" BorderBrush="Transparent" Focusable="False"
Click="DisplayItemDelete_OnClick"
ToolTip="Delete display item">
<Image Source="{StaticResource DiDeleteRounded}" Height="16" />
</Button>
<TextBlock Grid.Column="3" Text="☰" FontSize="16" Cursor="SizeAll" HorizontalAlignment="Right"
<TextBlock Grid.Column="4" Text="☰" FontSize="16" Cursor="SizeAll" HorizontalAlignment="Right"
ToolTip="Drag to reorder"
behaviors:ListBoxDragDropBehavior.IsDragHandle="True" />
</Grid>
</DataTemplate>
Expand Down Expand Up @@ -157,14 +166,23 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Icon}" Width="16" Height="16" Margin="0,0,5,0" />
<TextBlock Grid.Column="1" Text="{Binding DisplayName}" VerticalAlignment="Center" />
<Button Grid.Column="2" Background="Transparent" BorderBrush="Transparent" Focusable="False"
Click="CommandItemDelete_OnClick">
Command="{Binding DataContext.CloneCommandItemCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
ToolTip="Clone command item">
<Image Source="{StaticResource DiCloneRounded}" Height="16" />
</Button>
<Button Grid.Column="3" Background="Transparent" BorderBrush="Transparent" Focusable="False"
Click="CommandItemDelete_OnClick"
ToolTip="Delete command item">
<Image Source="{StaticResource DiDeleteRounded}" Height="16" />
</Button>
<TextBlock Grid.Column="3" Text="☰" FontSize="16" Cursor="SizeAll" HorizontalAlignment="Right"
<TextBlock Grid.Column="4" Text="☰" FontSize="16" Cursor="SizeAll" HorizontalAlignment="Right"
ToolTip="Drag to reorder"
behaviors:ListBoxDragDropBehavior.IsDragHandle="True" />
</Grid>
</DataTemplate>
Expand Down
24 changes: 14 additions & 10 deletions StreamDeckSimHub.Plugin/ActionEditor/GenericButtonEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
using NLog;
using StreamDeckSimHub.Plugin.ActionEditor.Behaviors;
using StreamDeckSimHub.Plugin.ActionEditor.ViewModels;
using StreamDeckSimHub.Plugin.Actions.GenericButton.Model;
using StreamDeckSimHub.Plugin.SimHub;
using StreamDeckSimHub.Plugin.Tools;

namespace StreamDeckSimHub.Plugin.ActionEditor;

Expand All @@ -21,21 +18,28 @@ public partial class GenericButtonEditor
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
private readonly string _actionUuid;

public GenericButtonEditor(
string actionUuid, Settings settings, ImageManager imageManager,
ISimHubConnection simHubConnection, ShakeItStructureFetcher shakeItStructureFetcher)
[Obsolete("Only for XAML designer support. Use the constructor with actionUuid instead.", false)]
public GenericButtonEditor() : this(string.Empty)
{
}

public GenericButtonEditor(string actionUuid)
{
_actionUuid = actionUuid;
InitializeComponent();
DataContext = new SettingsViewModel(settings, imageManager, simHubConnection, shakeItStructureFetcher, this);

// Set up drag-drop delegates for the ListBoxes
// Set up drag-and-drop delegates for the ListBoxes
SetupDragDropBehaviors();
}

public void SetViewModel(SettingsViewModel settingsViewModel)
{
DataContext = settingsViewModel;
}

private void SetupDragDropBehaviors()
{
// Set up DisplayItems drag-drop behavior
// Set up DisplayItems drag-and-drop behavior
var displayItemsBehavior = Interaction.GetBehaviors(DisplayItemsListBox)
.OfType<ListBoxDragDropBehavior>()
.FirstOrDefault();
Expand All @@ -44,7 +48,7 @@ private void SetupDragDropBehaviors()
displayItemsBehavior.OnItemDropped = OnDisplayItemDropped;
}

// Set up CommandItems drag-drop behavior
// Set up CommandItems drag-and-drop behavior
var commandItemsBehavior = Interaction.GetBehaviors(CommandItemsListBox)
.OfType<ListBoxDragDropBehavior>()
.FirstOrDefault();
Expand Down
32 changes: 30 additions & 2 deletions StreamDeckSimHub.Plugin/ActionEditor/IconResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,41 @@
<GeometryDrawing Brush="#ff000000">
<GeometryDrawing.Geometry>
<PathGeometry
Figures="M 6 19 c 0 1.1 0.9 2 2 2 h 8 c 1.1 0 2 -0.9 2 -2 V 9 c 0 -1.1 -0.9 -2 -2 -2 H 8 c -1.1 0 -2 0.9 -2 2 z M 18 4 h -2.5 l -0.71 -0.71 c -0.18 -0.18 -0.44 -0.29 -0.7 -0.29 H 9.91 c -0.26 0 -0.52 0.11 -0.7 0.29 L 8.5 4 H 6 c -0.55 0 -1 0.45 -1 1 s 0.45 1 1 1 h 12 c 0.55 0 1 -0.45 1 -1 s -0.45 -1 -1 -1"
FillRule="Nonzero" />
Figures="M 6 19 c 0 1.1 0.9 2 2 2 h 8 c 1.1 0 2 -0.9 2 -2 V 7 H 6 z M 9.5 4 l 0.71 -0.71 c 0.18 -0.18 0.44 -0.29 0.7 -0.29 h 2.18 c 0.26 0 0.52 0.11 0.7 0.29 L 14.5 4 H 18 c 0.55 0 1 0.45 1 1 s -0.45 1 -1 1 H 6 c -0.55 0 -1 -0.45 -1 -1 s 0.45 -1 1 -1 z M 10 9.5 c 0 -0.41 0.34 -0.75 0.75 -0.75 s 0.75 0.34 0.75 0.75 v 8 c 0 0.41 -0.34 0.75 -0.75 0.75 s -0.75 -0.34 -0.75 -0.75 z M 12.5 9.5 c 0 -0.41 0.34 -0.75 0.75 -0.75 s 0.75 0.34 0.75 0.75 v 8 c 0 0.41 -0.34 0.75 -0.75 0.75 s -0.75 -0.34 -0.75 -0.75 z"
FillRule="EvenOdd" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingImage Drawing="{StaticResource DeleteRounded}" x:Key="DiDeleteRounded" />

<DrawingGroup x:Key="CloneRoundedClassic">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0.0,0.0,24.0,24.0" />
</DrawingGroup.ClipGeometry>
<GeometryDrawing Brush="#ff000000">
<GeometryDrawing.Geometry>
<PathGeometry
Figures="M 16 1 H 4 c -1.1 0 -2 0.9 -2 2 v 12 h 2 V 3 h 12 z M 19 5 H 8 c -1.1 0 -2 0.9 -2 2 v 14 c 0 1.1 0.9 2 2 2 h 11 c 1.1 0 2 -0.9 2 -2 V 7 c 0 -1.1 -0.9 -2 -2 -2 z m 0 16 H 8 V 7 h 11 z"
FillRule="Nonzero" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingImage Drawing="{StaticResource CloneRoundedClassic}" x:Key="DiCloneRoundedClassic" />

<DrawingGroup x:Key="CloneRounded">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0.0,0.0,24.0,24.0" />
</DrawingGroup.ClipGeometry>
<GeometryDrawing Brush="#ff000000">
<GeometryDrawing.Geometry>
<PathGeometry
Figures="M 7 7 c 0 -1.1 0.9 -2 2 -2 h 9 c 1.1 0 2 0.9 2 2 v 12 c 0 1.1 -0.9 2 -2 2 H 9 c -1.1 0 -2 -0.9 -2 -2 z M 9 7 h 9 v 12 H 9 z M 5 3 h 9 c 1.1 0 2 0.9 2 2 v 0.5 h -1.5 V 5 c 0 -0.28 -0.22 -0.5 -0.5 -0.5 H 5 c -0.28 0 -0.5 0.22 -0.5 0.5 v 11 c 0 0.28 0.22 0.5 0.5 0.5 h 0.5 V 18 H 5 c -1.1 0 -2 -0.9 -2 -2 V 5 c 0 -1.1 0.9 -2 2 -2 z"
FillRule="EvenOdd" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
<DrawingImage Drawing="{StaticResource CloneRounded}" x:Key="DiCloneRounded" />

<DrawingGroup x:Key="TextFieldsGray">
<DrawingGroup.ClipGeometry>
<RectangleGeometry Rect="0.0,0.0,24.0,24.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace StreamDeckSimHub.Plugin.ActionEditor.ViewModels;
public partial class SettingsViewModel : ObservableObject, IViewModel
{
private readonly Settings _settings;
private readonly SettingsConverter _settingsConverter;
private readonly ImageManager _imageManager;
private readonly ISimHubConnection _simHubConnection;
private readonly ShakeItStructureFetcher _shakeItStructureFetcher;
Expand Down Expand Up @@ -86,10 +87,11 @@ partial void OnSelectedFlatCommandItemChanged(IFlatCommandItemsViewModel? value)
if (value != null) SelectedDisplayItem = null; // CommandItem selected -> no DisplayItem selected
}

public SettingsViewModel(Settings settings, ImageManager imageManager, ISimHubConnection simHubConnection,
ShakeItStructureFetcher shakeItStructureFetcher, Window parentWindow)
public SettingsViewModel(Settings settings, SettingsConverter settingsConverter, ImageManager imageManager,
ISimHubConnection simHubConnection, ShakeItStructureFetcher shakeItStructureFetcher, Window parentWindow)
{
_settings = settings;
_settingsConverter = settingsConverter;
_imageManager = imageManager;
_simHubConnection = simHubConnection;
_shakeItStructureFetcher = shakeItStructureFetcher;
Expand Down Expand Up @@ -292,6 +294,56 @@ private CommandItemViewModel CommandItemToViewModel(CommandItem commandItem, Str

#region RemoveItem

[RelayCommand]
private void CloneDisplayItem(DisplayItemViewModel item)
{
var displayItem = (DisplayItem)item.GetModel();
var clone = _settingsConverter.CloneDisplayItem(displayItem, StreamDeckKeyInfoBuilder.DefaultKeyInfo);
clone.Name = !string.IsNullOrWhiteSpace(item.Name) ? item.Name + " (copy)" : item.DisplayName + " (copy)";

var modelIndex = _settings.DisplayItems.IndexOf(displayItem);
if (modelIndex < 0)
{
_settings.DisplayItems.Add(clone);
var appendedVm = DisplayItemToViewModel(clone);
DisplayItems.Add(appendedVm);
SelectedDisplayItem = appendedVm;
return;
}

_settings.DisplayItems.Insert(modelIndex + 1, clone);
var vm = DisplayItemToViewModel(clone);

var vmIndex = DisplayItems.IndexOf(item);
DisplayItems.Insert(vmIndex + 1, vm);
SelectedDisplayItem = vm;
}

[RelayCommand]
private void CloneCommandItem(CommandItemViewModel commandItemViewModel)
{
var commandItem = (CommandItem)commandItemViewModel.GetModel();
var action = commandItemViewModel.ParentAction;
var clone = _settingsConverter.CloneCommandItem(commandItem);
clone.Name = !string.IsNullOrWhiteSpace(commandItem.Name) ? commandItem.Name + " (copy)" : commandItem.DisplayName + " (copy)";

var modelList = _settings.CommandItems[action];
var modelIndex = modelList.IndexOf(commandItem);
if (modelIndex < 0)
{
modelList.Add(clone);
}
else
{
modelList.Insert(modelIndex + 1, clone);
}

var vm = CommandItemToViewModel(clone, action);
var vmIndex = FlatCommandItems.IndexOf(commandItemViewModel);
FlatCommandItems.Insert(vmIndex + 1, vm);
SelectedFlatCommandItem = vm;
}

public void RemoveDisplayItem(DisplayItemViewModel item)
{
// Remove from the underlying model
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2025 Martin Renner
// Copyright (C) 2026 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using StreamDeckSimHub.Plugin.Actions.GenericButton.Model;
Expand All @@ -8,7 +8,7 @@

namespace StreamDeckSimHub.Plugin.ActionEditor.ViewModels;

public class SettingsViewModelDesignTime() : SettingsViewModel(Settings, null!, null!, null!, null!)
public class SettingsViewModelDesignTime() : SettingsViewModel(Settings, null!, null!, null!, null!, null!)
{
private static readonly Settings Settings = new()
{
Expand All @@ -23,4 +23,4 @@ static SettingsViewModelDesignTime()
Settings.CommandItems[StreamDeckAction.KeyUp].Add(CommandItemKeypress.Create());
Settings.CommandItems[StreamDeckAction.KeyUp].Add(CommandItemSimHubControl.Create());
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ public SettingsDto SettingsToDto(Settings settings)
return settingsDto;
}

public DisplayItem CloneDisplayItem(DisplayItem displayItem, StreamDeckKeyInfo keyInfo)
{
var dto = DisplayItemToDto(displayItem)
?? throw new InvalidOperationException($"Failed to clone DisplayItem of type {displayItem.GetType().FullName}.");
return DisplayItemToModel(dto, keyInfo)
?? throw new InvalidOperationException($"Failed to clone DisplayItem DTO of type {dto.GetType().FullName}.");
}

public CommandItem CloneCommandItem(CommandItem commandItem)
{
var dto = CommandItemToDto(commandItem)
?? throw new InvalidOperationException($"Failed to clone CommandItem of type {commandItem.GetType().FullName}.");
return CommandItemToModel(dto)
?? throw new InvalidOperationException($"Failed to clone CommandItem DTO of type {dto.GetType().FullName}.");
}

private BlinkOverride BlinkOverrideToModel(BlinkOverrideDto dto)
{
return new BlinkOverride
Expand Down Expand Up @@ -433,4 +449,4 @@ private NCalcHolder ExpressionToNCalcHolder(string expressionString, Dictionary<
}

#endregion
}
}
Loading