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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2026 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace StreamDeckSimHub.Plugin.ActionEditor.Behaviors;

/// <summary>
/// Modifies a Button to open its ContextMenu on left click instead of right click.
/// </summary>
public static class OpenContextMenuOnClickBehavior
{
public static readonly DependencyProperty ApplyProperty =
DependencyProperty.RegisterAttached(
"Apply",
typeof(bool),
typeof(OpenContextMenuOnClickBehavior),
new PropertyMetadata(false, OnApplyChanged));

public static void SetApply(DependencyObject element, bool value) => element.SetValue(ApplyProperty, value);

public static bool GetApply(DependencyObject element) => (bool)element.GetValue(ApplyProperty);

private static void OnApplyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is not true) return;
if (d is not Button button) return;

ContextMenuService.SetIsEnabled(button, false);
button.Click += Button_Click;
button.PreviewMouseRightButtonDown += SuppressRightClick;
}

private static void Button_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.ContextMenu != null)
{
button.ContextMenu.PlacementTarget = button;
button.ContextMenu.Placement = PlacementMode.Bottom;
button.ContextMenu.IsOpen = true;
}
}

private static void SuppressRightClick(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
}
}
29 changes: 17 additions & 12 deletions StreamDeckSimHub.Plugin/ActionEditor/GenericButtonEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,16 @@

<Grid Grid.Row="0" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Display" FontWeight="Bold" Margin="0,0,0,4" VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Width="110" Margin="0,0,4,0" ItemsSource="{Binding DisplayItemTypes}"
SelectedItem="{Binding SelectedAddDisplayItemType}" />
<Button Grid.Column="2" Content="Add" Command="{Binding AddSelectedDisplayItemCommand}" Padding="8,0" />
<TextBlock Grid.Column="0" Text="Display" FontWeight="Bold" VerticalAlignment="Center" />
<Button Grid.Column="1" Content="Add Display Item" Padding="8,2"
behaviors:OpenContextMenuOnClickBehavior.Apply="True" HorizontalAlignment="Right">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding DisplayItemTypes}" />
</Button.ContextMenu>
</Button>
</Grid>

<ListBox Grid.Row="1"
Expand Down Expand Up @@ -116,14 +118,17 @@

<Grid Grid.Row="0" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Commands" FontWeight="Bold" Margin="0,0,0,4" VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Width="110" Margin="0,0,4,0" ItemsSource="{Binding CommandItemTypes}"
SelectedItem="{Binding SelectedAddCommandItemType}" />
<Button Grid.Column="2" Content="Add" Command="{Binding AddSelectedCommandItemCommand}" Padding="8,0" />
<TextBlock Grid.Column="0" Text="Commands" FontWeight="Bold" VerticalAlignment="Center" />
<Button Grid.Column="1" Content="Add Command Item" Padding="8,2"
behaviors:OpenContextMenuOnClickBehavior.Apply="True"
IsEnabled="{Binding CanAddCommandItem}">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding CommandItemTypes}" />
</Button.ContextMenu>
</Button>
</Grid>

<ListBox Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private async void OnLoaded(object sender, RoutedEventArgs e)
catch (Exception ex)
{
// No MessageBox here, because we don't want to disturb the user when opening the editor.
_logger.Error(ex, "Failed to fetch Control Mapper Roles or ShakeIt Profiles from SimHub");
_logger.Warn("Failed to fetch Control Mapper Roles and/or ShakeIt Profiles from SimHub. Is SimHub not running? Cause: " + ex.Message);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public partial class CommandItemKeypressViewModel(
IViewModel parentViewModel,
StreamDeckAction parentAction) : CommandItemViewModel(model, parentViewModel, parentAction)
{
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiKeyboardOutlinedGray") as ImageSource;
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource(CommandItemKeypress.UiIcon) as ImageSource;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayName))]
Expand Down Expand Up @@ -107,7 +107,8 @@ public partial class CommandItemSimHubControlViewModel(
IViewModel parentViewModel,
StreamDeckAction parentAction) : CommandItemViewModel(model, parentViewModel, parentAction)
{
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiSimHubControlGray") as ImageSource;
public override ImageSource? Icon =>
ParentViewModel.ParentWindow.FindResource(CommandItemSimHubControl.UiIcon) as ImageSource;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayName))] // see CommandItemSimHubControl.RawDisplayName which uses Control
Expand Down Expand Up @@ -137,7 +138,7 @@ public partial class CommandItemSimHubRoleViewModel(
StreamDeckAction parentAction)
: CommandItemViewModel(model, parentViewModel, parentAction)
{
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiSimHubRoleGray") as ImageSource;
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource(CommandItemSimHubRole.UiIcon) as ImageSource;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayName))] // see CommandItemSimHubRole.RawDisplayName which uses Control
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 System.ComponentModel;
Expand All @@ -13,7 +13,6 @@
using StreamDeckSimHub.Plugin.ActionEditor.Tools;
using StreamDeckSimHub.Plugin.ActionEditor.Views.Controls;
using StreamDeckSimHub.Plugin.Actions.GenericButton.Model;
using StreamDeckSimHub.Plugin.PropertyLogic;
using StreamDeckSimHub.Plugin.Tools;
using Color = SixLabors.ImageSharp.Color;
using Point = SixLabors.ImageSharp.Point;
Expand All @@ -24,9 +23,15 @@
/// <summary>
/// Base ViewModel for all DisplayItems
/// </summary>
public abstract partial class DisplayItemViewModel(DisplayItem model, IViewModel parentViewModel)
public abstract partial class DisplayItemViewModel(DisplayItem model, IViewModel parentViewModel, byte? _)

Check warning on line 26 in StreamDeckSimHub.Plugin/ActionEditor/ViewModels/DisplayItemViewModels.cs

View workflow job for this annotation

GitHub Actions / build

Parameter '_' is unread.

Check warning on line 26 in StreamDeckSimHub.Plugin/ActionEditor/ViewModels/DisplayItemViewModels.cs

View workflow job for this annotation

GitHub Actions / build

Parameter '_' is unread.

Check warning on line 26 in StreamDeckSimHub.Plugin/ActionEditor/ViewModels/DisplayItemViewModels.cs

View workflow job for this annotation

GitHub Actions / build

Parameter '_' is unread.

Check warning on line 26 in StreamDeckSimHub.Plugin/ActionEditor/ViewModels/DisplayItemViewModels.cs

View workflow job for this annotation

GitHub Actions / build

Parameter '_' is unread.
: ItemViewModel(model, parentViewModel), IDataErrorInfo
{
protected DisplayItemViewModel(DisplayItem model, IViewModel parentViewModel) : this(model, parentViewModel, null)
{
}

#region Element Data

[ObservableProperty] private float _transparency = model.DisplayParameters.Transparency;

[ObservableProperty]
Expand Down Expand Up @@ -94,6 +99,8 @@
model.DisplayParameters.Rotation = value;
}

#endregion

public string Error => string.Empty;

public string this[string columnName]
Expand Down Expand Up @@ -122,7 +129,7 @@
public partial class DisplayItemImageViewModel(DisplayItemImage model, ImageManager imageManager, IViewModel parentViewModel)
: DisplayItemViewModel(model, parentViewModel)
{
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiInsertPhotoOutlinedGray") as ImageSource;
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource(DisplayItemImage.UiIcon) as ImageSource;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayName))] // see DisplayItemImage.DisplayName which uses RelativePath
Expand Down Expand Up @@ -158,7 +165,7 @@
public partial class DisplayItemTextViewModel(DisplayItemText model, IViewModel parentViewModel)
: DisplayItemViewModel(model, parentViewModel), IFontSelectable, IColorSelectable
{
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiTextFieldsGray") as ImageSource;
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource(DisplayItemText.UiIcon) as ImageSource;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayName))] // see DisplayItemText.DisplayName which uses Text
Expand Down Expand Up @@ -200,7 +207,6 @@
/// </summary>
public partial class DisplayItemValueViewModel : DisplayItemViewModel, IFontSelectable, IColorSelectable
{
private readonly NCalcHandler _ncalcHandler = new();
private readonly DisplayItemValue _model;

public DisplayItemValueViewModel(DisplayItemValue model, IViewModel parentViewModel) : base(model, parentViewModel)
Expand All @@ -210,15 +216,15 @@
{
ExpressionLabel = "Expression:",
ExpressionToolTip = "Please enter a valid NCalc expression, that returns a value",
Example="round( [DataCorePlugin.GameData.Fuel], 1)",
Example = "round( [DataCorePlugin.GameData.Fuel], 1)",
FetchShakeItProfilesCallback = FetchShakeItProfilesCallback
};
_displayFormat = model.DisplayFormat;
_font = model.Font;
_imageSharpColor = model.Color;
}

public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource("DiAttachMoneyGray") as ImageSource;
public override ImageSource? Icon => ParentViewModel.ParentWindow.FindResource(DisplayItemValue.UiIcon) as ImageSource;

[ObservableProperty] private ExpressionControlViewModel _expressionControlPropertyViewModel;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
Expand Down Expand Up @@ -52,11 +53,15 @@ partial void OnNameChanged(string value)
public ObservableCollection<IFlatCommandItemsViewModel> FlatCommandItems { get; } = [];

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AddSelectedCommandItemCommand))]
[NotifyPropertyChangedFor(nameof(SelectedItem))]
[NotifyPropertyChangedFor(nameof(IsAnyItemSelected))]
private IFlatCommandItemsViewModel? _selectedFlatCommandItem;

partial void OnSelectedFlatCommandItemChanged(IFlatCommandItemsViewModel? oldValue, IFlatCommandItemsViewModel? newValue)
{
CanAddCommandItem = newValue != null;
}

/// <summary>
/// Returns the currently selected DisplayItem or CommandItem, or null if none is selected.
/// </summary>
Expand Down Expand Up @@ -113,6 +118,23 @@ public SettingsViewModel(Settings settings, ImageManager imageManager, ISimHubCo
NewVersion = "You are using the latest version.";
NewVersionBrush = new SolidColorBrush(Color.FromRgb(30, 120, 30));
}

DisplayItemTypes =
[
// @formatter:off
new MenuItem { Header = DisplayItemImage.UiName, Icon = new Image { Source = ParentWindow.FindResource(DisplayItemImage.UiIcon) as ImageSource }, Command = AddDisplayItemCommand, CommandParameter = DisplayItemImage.UiName},
new MenuItem { Header = DisplayItemText.UiName, Icon = new Image { Source = ParentWindow.FindResource(DisplayItemText.UiIcon) as ImageSource }, Command = AddDisplayItemCommand, CommandParameter = DisplayItemText.UiName},
new MenuItem { Header = DisplayItemValue.UiName, Icon = new Image { Source = ParentWindow.FindResource(DisplayItemValue.UiIcon) as ImageSource }, Command = AddDisplayItemCommand, CommandParameter = DisplayItemValue.UiName}
// @formatter:on
];
CommandItemTypes =
[
// @formatter:off
new MenuItem { Header = CommandItemKeypress.UiName, Icon = new Image { Source = ParentWindow.FindResource(CommandItemKeypress.UiIcon) as ImageSource }, Command = AddCommandItemCommand, CommandParameter = CommandItemKeypress.UiName},
new MenuItem { Header = CommandItemSimHubControl.UiName, Icon = new Image { Source = ParentWindow.FindResource(CommandItemSimHubControl.UiIcon) as ImageSource }, Command = AddCommandItemCommand, CommandParameter = CommandItemSimHubControl.UiName},
new MenuItem { Header = CommandItemSimHubRole.UiName, Icon = new Image { Source = ParentWindow.FindResource(CommandItemSimHubRole.UiIcon) as ImageSource }, Command = AddCommandItemCommand, CommandParameter = CommandItemSimHubRole.UiName}
// @formatter:on
];
}

#region IViewModel
Expand Down Expand Up @@ -148,17 +170,12 @@ public async Task<IList<Profile>> FetchShakeItMotorsProfiles()
#region AddDisplayItem

/// List of available display item types
public ObservableCollection<string> DisplayItemTypes { get; } =
[
DisplayItemImage.UiName, DisplayItemText.UiName, DisplayItemValue.UiName
];

[ObservableProperty] private string _selectedAddDisplayItemType = DisplayItemImage.UiName;
public ObservableCollection<MenuItem> DisplayItemTypes { get; }

[RelayCommand]
private void AddSelectedDisplayItem()
private void AddDisplayItem(string type)
{
switch (SelectedAddDisplayItemType)
switch (type)
{
case DisplayItemImage.UiName:
AddDisplayItem(DisplayItemImage.Create());
Expand Down Expand Up @@ -196,17 +213,14 @@ private DisplayItemViewModel DisplayItemToViewModel(DisplayItem displayItem)
#region AddCommandItem

/// List of available command item types
public ObservableCollection<string> CommandItemTypes { get; } =
[
CommandItemKeypress.UiName, CommandItemSimHubControl.UiName, CommandItemSimHubRole.UiName
];
public ObservableCollection<MenuItem> CommandItemTypes { get; }

[ObservableProperty] private string _selectedAddCommandItemType = CommandItemKeypress.UiName;
[ObservableProperty] private bool _canAddCommandItem;

[RelayCommand(CanExecute = nameof(CanExecuteAddCommandItem))]
private void AddSelectedCommandItem()
[RelayCommand]
private void AddCommandItem(string type)
{
switch (SelectedAddCommandItemType)
switch (type)
{
case CommandItemKeypress.UiName:
AddCommandItem(CommandItemKeypress.Create());
Expand All @@ -220,8 +234,6 @@ private void AddSelectedCommandItem()
}
}

private bool CanExecuteAddCommandItem() => SelectedFlatCommandItem != null;

private void AddCommandItem(CommandItem newItem)
{
// Determine the action of the currently selected list item
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<UserControl x:Class="StreamDeckSimHub.Plugin.ActionEditor.Views.Controls.DisplayElementControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StreamDeckSimHub.Plugin.ActionEditor.Views.Controls"
xmlns:viewModels="clr-namespace:StreamDeckSimHub.Plugin.ActionEditor.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=viewModels:DisplayItemViewModel, IsDesignTimeCreatable=False}"
d:DesignHeight="600" d:DesignWidth="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <!-- Name -->
<RowDefinition Height="Auto" /> <!-- Detail data (see DataTemplates above) -->
<RowDefinition Height="Auto" /> <!-- Display Parameters -->
<RowDefinition Height="Auto" /> <!-- Conditions -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Label Grid.Row="0" Grid.Column="0">Item Name:</Label>
<TextBox Grid.Row="0" Grid.Column="1" MinWidth="150"
VerticalContentAlignment="Center"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
ToolTip="Is only used for the list on the left side!" />

<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="{Binding}">
<d:ContentControl.Content>
<Border Padding="10" BorderBrush="Gray" BorderThickness="1" Margin="0,5">
<TextBlock Text="Placeholder for concrete DisplayItem" />
</Border>
</d:ContentControl.Content>
</ContentControl>

<local:DisplayParametersControl Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,20,0,0" />

<local:ExpressionControl Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,20,0,0"
DataContext="{Binding ExpressionControlConditionViewModel}" />
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Windows.Controls;

namespace StreamDeckSimHub.Plugin.ActionEditor.Views.Controls;

public partial class DisplayElementControl : UserControl
{
public DisplayElementControl()
{
InitializeComponent();
}
}
Loading
Loading