Plugins/GUI: Implement conditional option enabling

This commit is contained in:
Katy Coe
2020-12-28 08:33:12 +01:00
parent 096b2d9c5d
commit 99f1c38b4c
4 changed files with 82 additions and 20 deletions

View File

@@ -48,7 +48,7 @@ namespace Il2CppInspectorGUI
}
public override void Write(Utf8JsonWriter writer, IPluginOption value, JsonSerializerOptions options) {
JsonSerializer.Serialize(writer, value, value.GetType(), options);
JsonSerializer.Serialize(writer, new PluginOptionState { Name = value.Name, Value = value.Value }, typeof(PluginOptionState), options);
}
}
@@ -75,6 +75,8 @@ namespace Il2CppInspectorGUI
[JsonIgnore]
public bool Required { get; set; }
public object Value { get; set; }
[JsonIgnore]
public Func<bool> If { get; set; }
}
// Application startup

View File

@@ -13,10 +13,16 @@
Closing="Window_Closing">
<Window.Resources>
<local:OptionTemplateSelector x:Key="OptionTemplateSelector"/>
<local:OptionConditionConverter x:Key="OptionConditionConverter" />
<local2:HexStringValueConverter x:Key="HexStringValueConverter" />
<local2:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" />
<BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter" />
<!-- Conditional option enabler -->
<Style x:Key="OptionCondition" TargetType="FrameworkElement">
<Setter Property="IsEnabled" Value="{Binding Converter={StaticResource OptionConditionConverter}}" />
</Style>
<!-- Validation error style -->
<Style x:Key="ValidationStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding ElementName=valueControl, Path=(Validation.Errors)[0].ErrorContent}"/>
@@ -38,6 +44,15 @@
</StackPanel>
</DataTemplate>
<!-- Some elements don't gray out disabled text by default -->
<Style x:Key="GreyWhenDisabled" TargetType="FrameworkElement">
<Style.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Configure ListBox to display configuration controls nicely -->
<Style x:Key="OptionItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
@@ -65,7 +80,7 @@
<!-- Option label template -->
<DataTemplate x:Key="OptionLabelTemplate">
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Top">
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Top" Style="{StaticResource GreyWhenDisabled}">
<TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap" Margin="0,4,0,0"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
@@ -75,10 +90,10 @@
<!-- Free text -->
<DataTemplate x:Key="TextTemplate">
<DockPanel>
<DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Top" Padding="2" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
<TextBox Name="valueControl" VerticalAlignment="Top" Padding="2" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" TextChanged="valueControl_Changed" />
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
@@ -86,11 +101,11 @@
<!-- File path -->
<DataTemplate x:Key="FilePathTemplate">
<DockPanel>
<DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<Button Name="btnFilePathSelector" DockPanel.Dock="Right" Width="70" Height="25" VerticalAlignment="Top" Margin="4,0,4,0" Click="btnFilePathSelector_Click">Browse</Button>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Top" HorizontalAlignment="Stretch" TextAlignment="Right" Padding="2" Margin="0,0,4,0" IsReadOnly="True" BorderBrush="Transparent" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
<TextBox Name="valueControl" VerticalAlignment="Top" HorizontalAlignment="Stretch" TextAlignment="Right" Padding="2" Margin="0,0,4,0" IsReadOnly="True" BorderBrush="Transparent" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" TextChanged="valueControl_Changed" />
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
@@ -98,10 +113,10 @@
<!-- Decimal number -->
<DataTemplate x:Key="NumberDecimalTemplate">
<DockPanel>
<DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Center" Padding="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>
<TextBox Name="valueControl" VerticalAlignment="Center" Padding="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" TextChanged="valueControl_Changed"/>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
@@ -109,12 +124,12 @@
<!-- Hex number -->
<DataTemplate x:Key="NumberHexTemplate">
<DockPanel>
<DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<DockPanel HorizontalAlignment="Stretch">
<Label Padding="0" Margin="0,2,4,0">0x</Label>
<TextBox Name="valueControl" DockPanel.Dock="Right" Padding="2" Text="{Binding Value, Converter={StaticResource HexStringValueConverter}, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="txtHexString_PreviewTextInput"/>
<TextBox Name="valueControl" DockPanel.Dock="Right" Padding="2" Text="{Binding Value, Converter={StaticResource HexStringValueConverter}, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="txtHexString_PreviewTextInput" TextChanged="valueControl_Changed"/>
</DockPanel>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
@@ -123,21 +138,21 @@
<!-- Boolean tickbox (no validation required) -->
<DataTemplate x:Key="BooleanTemplate">
<DockPanel Margin="350,0,0,0">
<DockPanel Margin="350,0,0,0" Name="optionPanel" Style="{StaticResource OptionCondition}">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Top" Margin="0,4,2,4"
Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
<CheckBox Name="valueControl" DockPanel.Dock="Right" VerticalAlignment="Center" Margin="0,4,2,4" IsChecked="{Binding Value}">
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" />
<CheckBox Name="valueControl" DockPanel.Dock="Right" VerticalAlignment="Center" Margin="0,4,2,4" IsChecked="{Binding Value}" Checked="valueControl_Changed" Unchecked="valueControl_Changed">
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" Style="{StaticResource GreyWhenDisabled}"/>
</CheckBox>
</DockPanel>
</DataTemplate>
<!-- Drop-down choices -->
<DataTemplate x:Key="ChoiceDropdownTemplate">
<DockPanel>
<DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<ComboBox Name="valueControl" ItemsSource="{Binding Choices}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Value}"/>
<ComboBox Name="valueControl" ItemsSource="{Binding Choices}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Value}" SelectionChanged="valueControl_Changed"/>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
@@ -145,16 +160,17 @@
<!-- Radio button choices -->
<DataTemplate x:Key="ChoiceListTemplate">
<StackPanel>
<GroupBox Header="{Binding Description}" Margin="5" Padding="5">
<ListBox Name="valueControl" ItemsSource="{Binding Choices}" SelectedValuePath="Key" SelectedValue="{Binding Value}" BorderBrush="Transparent">
<StackPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<GroupBox Header="{Binding Description}" Margin="5" Padding="5" Style="{StaticResource GreyWhenDisabled}">
<ListBox Name="valueControl" ItemsSource="{Binding Choices}" SelectedValuePath="Key" SelectedValue="{Binding Value}" BorderBrush="Transparent" BorderThickness="0" SelectionChanged="valueControl_Changed">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"
Content="{Binding Value}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Focusable="False"
IsHitTestVisible="False"/>
IsHitTestVisible="False"
Style="{StaticResource GreyWhenDisabled}"/>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
@@ -30,7 +31,7 @@ using Ookii.Dialogs.Wpf;
namespace Il2CppInspectorGUI
{
// Class which selects the correct control to display for each plugin option
public class OptionTemplateSelector : DataTemplateSelector
internal class OptionTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate FilePathTemplate { get; set; }
@@ -49,6 +50,21 @@ namespace Il2CppInspectorGUI
}
}
// Process the 'If' property to enable/disable options
internal class OptionConditionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value == null || targetType != typeof(bool))
return DependencyProperty.UnsetValue;
return ((IPluginOption) value).If();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
/// <summary>
/// Interaction logic for PluginConfigurationDialog.xaml
/// </summary>
@@ -213,5 +229,21 @@ namespace Il2CppInspectorGUI
// Replace options in ListBox
lstOptions.ItemsSource = Plugin.Options;
}
// Force all the If evaluations on each option to be re-evaluated each time an option is changed
private void valueControl_Changed(object sender, RoutedEventArgs e) {
// Ignore changes when listbox is first populated
if (lstOptions.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
foreach (var item in lstOptions.Items) {
var listBoxItem = lstOptions.ItemContainerGenerator.ContainerFromItem(item);
var presenter = FindVisualChild<ContentPresenter>(listBoxItem);
var dataTemplate = presenter.ContentTemplateSelector.SelectTemplate(item, listBoxItem);
if (dataTemplate.FindName("optionPanel", presenter) is FrameworkElement boundControl)
boundControl.IsEnabled = ((IPluginOption) item).If();
}
}
}
}