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

@@ -37,6 +37,12 @@ namespace Il2CppInspector.PluginAPI.V100
/// Becomes the current value of the option when supplied by the user /// Becomes the current value of the option when supplied by the user
/// </summary> /// </summary>
public object Value { get; set; } public object Value { get; set; }
/// <summary>
/// A condition that determines whether the option is enabled,
/// based on the settings of other options or any other desired criteria
/// </summary>
public Func<bool> If { get; set; }
} }
/// <summary> /// <summary>
@@ -100,6 +106,12 @@ namespace Il2CppInspector.PluginAPI.V100
} }
} }
/// <summary>
/// This can be set to a predicate that determines whether the option is enabled in the GUI
/// By default, enable all options unless overridden
/// </summary>
public Func<bool> If { get; set; } = () => true;
/// <summary> /// <summary>
/// Optional validation function for the option in addition to basic automatic validation /// Optional validation function for the option in addition to basic automatic validation
/// Must either throw an exception or return true /// Must either throw an exception or return true

View File

@@ -48,7 +48,7 @@ namespace Il2CppInspectorGUI
} }
public override void Write(Utf8JsonWriter writer, IPluginOption value, JsonSerializerOptions options) { 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] [JsonIgnore]
public bool Required { get; set; } public bool Required { get; set; }
public object Value { get; set; } public object Value { get; set; }
[JsonIgnore]
public Func<bool> If { get; set; }
} }
// Application startup // Application startup

View File

@@ -13,10 +13,16 @@
Closing="Window_Closing"> Closing="Window_Closing">
<Window.Resources> <Window.Resources>
<local:OptionTemplateSelector x:Key="OptionTemplateSelector"/> <local:OptionTemplateSelector x:Key="OptionTemplateSelector"/>
<local:OptionConditionConverter x:Key="OptionConditionConverter" />
<local2:HexStringValueConverter x:Key="HexStringValueConverter" /> <local2:HexStringValueConverter x:Key="HexStringValueConverter" />
<local2:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" /> <local2:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" />
<BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter" /> <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 --> <!-- Validation error style -->
<Style x:Key="ValidationStyle" TargetType="TextBlock"> <Style x:Key="ValidationStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding ElementName=valueControl, Path=(Validation.Errors)[0].ErrorContent}"/> <Setter Property="Text" Value="{Binding ElementName=valueControl, Path=(Validation.Errors)[0].ErrorContent}"/>
@@ -38,6 +44,15 @@
</StackPanel> </StackPanel>
</DataTemplate> </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 --> <!-- Configure ListBox to display configuration controls nicely -->
<Style x:Key="OptionItemStyle" TargetType="{x:Type ListBoxItem}"> <Style x:Key="OptionItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
@@ -65,7 +80,7 @@
<!-- Option label template --> <!-- Option label template -->
<DataTemplate x:Key="OptionLabelTemplate"> <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 Text="{Binding Path=Description}" TextWrapping="Wrap" Margin="0,4,0,0"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/> <TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock> </TextBlock>
@@ -75,10 +90,10 @@
<!-- Free text --> <!-- Free text -->
<DataTemplate x:Key="TextTemplate"> <DataTemplate x:Key="TextTemplate">
<DockPanel> <DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" /> <ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0"> <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}" /> <ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
@@ -86,11 +101,11 @@
<!-- File path --> <!-- File path -->
<DataTemplate x:Key="FilePathTemplate"> <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> <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}" /> <ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0"> <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}" /> <ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
@@ -98,10 +113,10 @@
<!-- Decimal number --> <!-- Decimal number -->
<DataTemplate x:Key="NumberDecimalTemplate"> <DataTemplate x:Key="NumberDecimalTemplate">
<DockPanel> <DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" /> <ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0"> <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}" /> <ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
@@ -109,12 +124,12 @@
<!-- Hex number --> <!-- Hex number -->
<DataTemplate x:Key="NumberHexTemplate"> <DataTemplate x:Key="NumberHexTemplate">
<DockPanel> <DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" /> <ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0"> <StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<DockPanel HorizontalAlignment="Stretch"> <DockPanel HorizontalAlignment="Stretch">
<Label Padding="0" Margin="0,2,4,0">0x</Label> <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> </DockPanel>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" /> <ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel> </StackPanel>
@@ -123,21 +138,21 @@
<!-- Boolean tickbox (no validation required) --> <!-- Boolean tickbox (no validation required) -->
<DataTemplate x:Key="BooleanTemplate"> <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" <TextBlock DockPanel.Dock="Left" VerticalAlignment="Top" Margin="0,4,2,4"
Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/> 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}"> <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}" /> <TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" Style="{StaticResource GreyWhenDisabled}"/>
</CheckBox> </CheckBox>
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
<!-- Drop-down choices --> <!-- Drop-down choices -->
<DataTemplate x:Key="ChoiceDropdownTemplate"> <DataTemplate x:Key="ChoiceDropdownTemplate">
<DockPanel> <DockPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" /> <ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0"> <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}" /> <ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
@@ -145,16 +160,17 @@
<!-- Radio button choices --> <!-- Radio button choices -->
<DataTemplate x:Key="ChoiceListTemplate"> <DataTemplate x:Key="ChoiceListTemplate">
<StackPanel> <StackPanel Name="optionPanel" Style="{StaticResource OptionCondition}">
<GroupBox Header="{Binding Description}" Margin="5" Padding="5"> <GroupBox Header="{Binding Description}" Margin="5" Padding="5" Style="{StaticResource GreyWhenDisabled}">
<ListBox Name="valueControl" ItemsSource="{Binding Choices}" SelectedValuePath="Key" SelectedValue="{Binding Value}" BorderBrush="Transparent"> <ListBox Name="valueControl" ItemsSource="{Binding Choices}" SelectedValuePath="Key" SelectedValue="{Binding Value}" BorderBrush="Transparent" BorderThickness="0" SelectionChanged="valueControl_Changed">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}" <RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"
Content="{Binding Value}" Content="{Binding Value}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Focusable="False" Focusable="False"
IsHitTestVisible="False"/> IsHitTestVisible="False"
Style="{StaticResource GreyWhenDisabled}"/>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>

View File

@@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -30,7 +31,7 @@ using Ookii.Dialogs.Wpf;
namespace Il2CppInspectorGUI namespace Il2CppInspectorGUI
{ {
// Class which selects the correct control to display for each plugin option // 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 TextTemplate { get; set; }
public DataTemplate FilePathTemplate { 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> /// <summary>
/// Interaction logic for PluginConfigurationDialog.xaml /// Interaction logic for PluginConfigurationDialog.xaml
/// </summary> /// </summary>
@@ -213,5 +229,21 @@ namespace Il2CppInspectorGUI
// Replace options in ListBox // Replace options in ListBox
lstOptions.ItemsSource = Plugin.Options; 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();
}
}
} }
} }