Plugins/GUI: Implement conditional option enabling
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user