More work on AutoGenConfig
This commit is contained in:
parent
74e63cd566
commit
fd06504882
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class ConfigGroupingStructAttribute : Attribute {}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
<Import Project="$(ProjectDir)../Common.props" />
|
||||
<Import Project="$(ProjectDir)../NET48ExternalToolForm.props" />
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Compile Update="AutoGenConfigForm.cs" SubType="Form" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -16,13 +17,15 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
[ExternalTool("AutoGenConfig")]
|
||||
public class AutoGenConfigForm : Form, IExternalToolForm
|
||||
{
|
||||
private static readonly IList<(PropertyInfo, IConfigPropEditorUIGen<Control>)> CachedControlGenerators;
|
||||
private static readonly IList<(string, FieldInfo)> CachedGroupings;
|
||||
|
||||
private static readonly IList<(string, PropertyInfo, IConfigPropEditorUIGen<Control>)> CachedPropEditorUIGenerators;
|
||||
|
||||
public static ComparisonColors ComparisonColors = new ComparisonColors
|
||||
{
|
||||
Changed = Color.FromArgb(unchecked((int) 0xFF9F3F00)),
|
||||
ChangedInvalid = Color.DarkRed,
|
||||
ChangedUnset = Color.FromArgb(unchecked((int) 0xFF9F1F5F)),
|
||||
Changed = Color.FromArgb(unchecked((int) 0xFFBF5F1F)),
|
||||
ChangedInvalid = Color.FromArgb(unchecked((int) 0xFF9F0000)),
|
||||
ChangedUnset = Color.FromArgb(unchecked((int) 0xFFBF1F5F)),
|
||||
Unchanged = Color.FromArgb(unchecked((int) 0xFF00003F)),
|
||||
UnchangedDefault = Color.Black
|
||||
};
|
||||
|
@ -31,15 +34,27 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
|
||||
static AutoGenConfigForm()
|
||||
{
|
||||
CachedControlGenerators = new List<(PropertyInfo, IConfigPropEditorUIGen<Control>)>();
|
||||
CachedGroupings = new List<(string, FieldInfo)>();
|
||||
CachedPropEditorUIGenerators = new List<(string, PropertyInfo, IConfigPropEditorUIGen<Control>)>();
|
||||
DefaultValues = new Dictionary<string, object?>();
|
||||
foreach (var pi in typeof(Config).GetProperties())
|
||||
static void TraversePropertiesOf(Type type, string nesting)
|
||||
{
|
||||
CachedControlGenerators.Add((pi, FallbackGenerators.TryGetValue(pi.PropertyType, out var gen) ? gen : FinalFallbackGenerator));
|
||||
DefaultValues[pi.Name] = pi.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault()
|
||||
?.Let(it => ((DefaultValueAttribute) it).Value)
|
||||
?? TrueGenericDefault(pi.PropertyType);
|
||||
foreach (var pi in type.GetProperties()
|
||||
.Where(pi => pi.GetCustomAttributes(typeof(EditableAttribute), false).All(attr => ((EditableAttribute) attr).AllowEdit)))
|
||||
{
|
||||
CachedPropEditorUIGenerators.Add((nesting, pi, FallbackGenerators.TryGetValue(pi.PropertyType, out var gen) ? gen : FinalFallbackGenerator));
|
||||
DefaultValues[$"{nesting}/{pi.Name}"] = pi.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault()
|
||||
?.Let(it => ((DefaultValueAttribute) it).Value)
|
||||
?? TrueGenericDefault(pi.PropertyType);
|
||||
}
|
||||
foreach (var fi in type.GetFields()
|
||||
.Where(fi => fi.CustomAttributes.Any(cad => cad.AttributeType == typeof(ConfigGroupingStructAttribute))))
|
||||
{
|
||||
CachedGroupings.Add((nesting, fi));
|
||||
TraversePropertiesOf(fi.FieldType, $"{nesting}/{fi.Name}");
|
||||
}
|
||||
}
|
||||
TraversePropertiesOf(typeof(Config), string.Empty);
|
||||
}
|
||||
|
||||
/// <returns>value types: default(T); ref types: calls default (no-arg) ctor if it exists, else null</returns>
|
||||
|
@ -60,6 +75,8 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
[RequiredApi]
|
||||
private IEmu? EmuHawkAPI { get; set; }
|
||||
|
||||
public readonly IDictionary<string, Control> GroupingUIs = new Dictionary<string, Control>();
|
||||
|
||||
public override string Text => "AutoGenConfig";
|
||||
|
||||
public bool UpdateBefore => false;
|
||||
|
@ -75,17 +92,16 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
new Label { AutoSize = true, Text = "Legend:" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.UnchangedDefault, Text = "default, unchanged" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.Unchanged, Text = "custom, unchanged" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.ChangedUnset, Text = "custom => default" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.ChangedUnset, Text = "default, was custom" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.ChangedInvalid, Text = "invalid" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.Changed, Text = "custom A => custom B" }
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.Changed, Text = "custom, changed" }
|
||||
},
|
||||
Location = new Point(4, 4),
|
||||
Padding = new Padding(0, 4, 0, 0),
|
||||
Size = new Size(ClientSize.Width - 8, 24),
|
||||
WrapContents = false
|
||||
});
|
||||
FlowLayoutPanel flpMain;
|
||||
Controls.Add(flpMain = new FlowLayoutPanel {
|
||||
Controls.Add(GroupingUIs[string.Empty] = new FlowLayoutPanel {
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
||||
AutoScroll = true,
|
||||
FlowDirection = FlowDirection.TopDown,
|
||||
|
@ -113,8 +129,38 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
});
|
||||
Load += (loadEventSender, loadEventArgs) =>
|
||||
{
|
||||
// This magic works so long as `GroupingUIs[""]` is set to the main FLP before loading, and we create all the GroupBoxes before trying to populate them.
|
||||
foreach (var (nesting, fi) in CachedGroupings)
|
||||
{
|
||||
GroupingUIs[nesting].Controls.Add(new GroupBox {
|
||||
Controls = {
|
||||
new FlowLayoutPanel {
|
||||
AutoScroll = true,
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Fill,
|
||||
FlowDirection = FlowDirection.TopDown,
|
||||
WrapContents = false
|
||||
}.Also(it => GroupingUIs[$"{nesting}/{fi.Name}"] = it)
|
||||
},
|
||||
Size = new Size(400, 300),
|
||||
Text = fi.Name
|
||||
});
|
||||
}
|
||||
var config = (EmuHawkAPI as EmuApi ?? throw new Exception("required API wasn't fulfilled")).ForbiddenConfigReference;
|
||||
flpMain.Controls.AddRange(CachedControlGenerators.Select(it => it.Item2.GenerateControl(it.Item1, config, BaselineValues)).ToArray());
|
||||
var groupings = new Dictionary<string, object> { [string.Empty] = config };
|
||||
void TraverseGroupings(object groupingObj, string nesting)
|
||||
{
|
||||
foreach (var (_, fi) in CachedGroupings.Where(tuple => tuple.Item1 == nesting))
|
||||
{
|
||||
var newNesting = $"{nesting}/{fi.Name}";
|
||||
TraverseGroupings(groupings[newNesting] = fi.GetValue(groupingObj), newNesting);
|
||||
}
|
||||
}
|
||||
TraverseGroupings(config, string.Empty);
|
||||
foreach (var (nesting, pi, gen) in CachedPropEditorUIGenerators)
|
||||
{
|
||||
GroupingUIs[nesting].Controls.Add(gen.GenerateControl(nesting, pi, groupings[nesting], BaselineValues));
|
||||
}
|
||||
};
|
||||
ResumeLayout();
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
namespace BizHawk.Experiment.AutoGenConfig
|
||||
{
|
||||
public static class ConfigEditorUIGenerators
|
||||
{
|
||||
public static readonly IDictionary<Type, IConfigPropEditorUIGen<Control>> FallbackGenerators = new Dictionary<Type, IConfigPropEditorUIGen<Control>> {
|
||||
[typeof(bool)] = new CheckBoxForBoolEditorUIGen(),
|
||||
[typeof(int)] = new NumericUpDownForInt32EditorUIGen(),
|
||||
[typeof(string)] = new TextBoxForStringEditorUIGen()
|
||||
};
|
||||
|
||||
public static readonly IConfigPropEditorUIGen<GroupBox> FinalFallbackGenerator = new UnrepresentablePropEditorUIGen();
|
||||
|
||||
private static Color GetComparisonColorRefT<T>(string prop, T? currentValue, AutoGenConfigForm parent, Func<T?, T?, bool> equalityFunc)
|
||||
private static Color GetComparisonColorRefT<T>(string nestedName, T? currentValue, AutoGenConfigForm parentForm, Func<T?, T?, bool> equalityFunc)
|
||||
where T : class
|
||||
=> equalityFunc(currentValue, parent.BaselineValues[prop] as T)
|
||||
? GetInitComparisonColorRefT(prop, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[prop] as T)
|
||||
=> equalityFunc(currentValue, parentForm.BaselineValues[nestedName] as T)
|
||||
? GetInitComparisonColorRefT(nestedName, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[nestedName] as T)
|
||||
? AutoGenConfigForm.ComparisonColors.ChangedUnset
|
||||
: AutoGenConfigForm.ComparisonColors.Changed;
|
||||
|
||||
private static Color GetComparisonColorValT<T>(string prop, T? currentValue, AutoGenConfigForm parent, Func<T?, T?, bool> equalityFunc)
|
||||
private static Color GetComparisonColorValT<T>(string nestedName, T? currentValue, AutoGenConfigForm parentForm, Func<T?, T?, bool> equalityFunc)
|
||||
where T : struct
|
||||
=> equalityFunc(currentValue, parent.BaselineValues[prop]?.Let(it => (T) it))
|
||||
? GetInitComparisonColorValT(prop, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[prop]?.Let(it => (T) it))
|
||||
=> equalityFunc(currentValue, parentForm.BaselineValues[nestedName]?.Let(it => (T) it))
|
||||
? GetInitComparisonColorValT(nestedName, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[nestedName]?.Let(it => (T) it))
|
||||
? AutoGenConfigForm.ComparisonColors.ChangedUnset
|
||||
: AutoGenConfigForm.ComparisonColors.Changed;
|
||||
|
||||
private static Color GetInitComparisonColorRefT<T>(string prop, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
private static Color GetInitComparisonColorRefT<T>(string nestedName, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
where T : class
|
||||
=> equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[prop] as T)
|
||||
=> equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[nestedName] as T)
|
||||
? AutoGenConfigForm.ComparisonColors.UnchangedDefault
|
||||
: AutoGenConfigForm.ComparisonColors.Unchanged;
|
||||
|
||||
private static Color GetInitComparisonColorValT<T>(string prop, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
private static Color GetInitComparisonColorValT<T>(string nestedName, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
where T : struct
|
||||
=> equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[prop]?.Let(it => (T) it))
|
||||
=> equalityFunc(currentValue, AutoGenConfigForm.DefaultValues[nestedName]?.Let(it => (T) it))
|
||||
? AutoGenConfigForm.ComparisonColors.UnchangedDefault
|
||||
: AutoGenConfigForm.ComparisonColors.Unchanged;
|
||||
|
||||
|
@ -71,31 +71,73 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
public interface IConfigPropEditorUIGen<out TControl>
|
||||
where TControl : Control
|
||||
{
|
||||
TControl GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues);
|
||||
TControl GenerateControl(string nesting, PropertyInfo pi, object config, IDictionary<string, object?> baselineValues);
|
||||
}
|
||||
|
||||
private class CheckBoxForBoolEditorUIGen : IConfigPropEditorUIGen<CheckBox>
|
||||
{
|
||||
private static bool BoolEquality(bool? a, bool? b) => a == b;
|
||||
|
||||
private static void CheckBoxClickHandler(object clickEventSender, EventArgs clickEventArgs)
|
||||
=> ((CheckBox) clickEventSender).Let(cb =>
|
||||
cb.ForeColor = GetComparisonColorValT<bool>(((PropertyInfo) cb.Tag).Name, cb.Checked, GetMainFormParent(cb), BoolEquality)
|
||||
private static void CheckBoxClickHandler(object changedEventSender, EventArgs changedEventArgs)
|
||||
=> ((CheckBox) changedEventSender).Let(cb =>
|
||||
cb.ForeColor = GetComparisonColorValT<bool>(cb.Name, cb.Checked, GetMainFormParent(cb), BoolEquality)
|
||||
);
|
||||
|
||||
public CheckBox GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
public CheckBox GenerateControl(string nesting, PropertyInfo pi, object config, IDictionary<string, object?> baselineValues)
|
||||
{
|
||||
if (pi.PropertyType != typeof(bool)) throw new Exception();
|
||||
var baseline = (bool) pi.GetValue(config);
|
||||
baselineValues[pi.Name] = baseline;
|
||||
var nestedName = $"{nesting}/{pi.Name}";
|
||||
baselineValues[nestedName] = baseline;
|
||||
return new CheckBox
|
||||
{
|
||||
AutoSize = true,
|
||||
Checked = baseline,
|
||||
ForeColor = GetInitComparisonColorValT<bool>(pi.Name, baseline, BoolEquality),
|
||||
Tag = pi,
|
||||
ForeColor = GetInitComparisonColorValT<bool>(nestedName, baseline, BoolEquality),
|
||||
Name = nestedName,
|
||||
Text = GetPropertyNameDesc(pi)
|
||||
}.Also(it => it.Click += CheckBoxClickHandler);
|
||||
}.Also(it => it.CheckedChanged += CheckBoxClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private class NumericUpDownForInt32EditorUIGen : IConfigPropEditorUIGen<FlowLayoutPanel>
|
||||
{
|
||||
private static bool IntEquality(int? a, int? b) => a == b;
|
||||
|
||||
private static void NumericUpDownChangedHandler(object changedEventSender, EventArgs changedEventArgs)
|
||||
=> ((NumericUpDown) changedEventSender).Let(nud =>
|
||||
nud.Parent.ForeColor = GetComparisonColorValT<int>(nud.Name, (int) nud.Value, GetMainFormParent(nud), IntEquality)
|
||||
);
|
||||
|
||||
public FlowLayoutPanel GenerateControl(string nesting, PropertyInfo pi, object config, IDictionary<string, object?> baselineValues)
|
||||
{
|
||||
if (pi.PropertyType != typeof(int)) throw new Exception();
|
||||
var baseline = (int) pi.GetValue(config);
|
||||
var nestedName = $"{nesting}/{pi.Name}";
|
||||
baselineValues[nestedName] = baseline;
|
||||
return new FlowLayoutPanel {
|
||||
AutoSize = true,
|
||||
Controls = {
|
||||
new Label { Anchor = AnchorStyles.None, AutoSize = true, Text = GetPropertyNameDesc(pi) },
|
||||
new NumericUpDown
|
||||
{
|
||||
Maximum = int.MaxValue,
|
||||
Minimum = int.MinValue,
|
||||
Name = nestedName,
|
||||
Size = new Size(72, 20),
|
||||
Value = baseline
|
||||
}.Also(it =>
|
||||
{
|
||||
if (pi.GetCustomAttributes(typeof(RangeAttribute), false).FirstOrDefault() is RangeAttribute range)
|
||||
{
|
||||
it.Maximum = (int) range.Maximum;
|
||||
it.Minimum = (int) range.Minimum;
|
||||
}
|
||||
it.ValueChanged += NumericUpDownChangedHandler;
|
||||
})
|
||||
},
|
||||
ForeColor = GetInitComparisonColorValT<int>(nestedName, baseline, IntEquality)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,30 +145,31 @@ namespace BizHawk.Experiment.AutoGenConfig
|
|||
{
|
||||
private static readonly Func<string?, string?, bool> StringEquality = string.Equals;
|
||||
|
||||
public FlowLayoutPanel GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
public FlowLayoutPanel GenerateControl(string nesting, PropertyInfo pi, object config, IDictionary<string, object?> baselineValues)
|
||||
{
|
||||
if (pi.PropertyType != typeof(string)) throw new Exception();
|
||||
var baseline = (string) pi.GetValue(config);
|
||||
baselineValues[pi.Name] = baseline;
|
||||
var nestedName = $"{nesting}/{pi.Name}";
|
||||
baselineValues[nestedName] = baseline;
|
||||
return new FlowLayoutPanel {
|
||||
AutoSize = true,
|
||||
Controls = {
|
||||
new Label { Anchor = AnchorStyles.None, AutoSize = true, Text = GetPropertyNameDesc(pi) },
|
||||
new TextBox { AutoSize = true, Tag = pi, Text = baseline }.Also(it => it.TextChanged += TextBoxChangedHandler)
|
||||
new TextBox { AutoSize = true, Name = nestedName, Text = baseline }.Also(it => it.TextChanged += TextBoxChangedHandler)
|
||||
},
|
||||
ForeColor = GetInitComparisonColorRefT(pi.Name, baseline, StringEquality)
|
||||
ForeColor = GetInitComparisonColorRefT(nestedName, baseline, StringEquality)
|
||||
};
|
||||
}
|
||||
|
||||
private static void TextBoxChangedHandler(object changedEventSender, EventArgs changedEventArgs)
|
||||
=> ((TextBox) changedEventSender).Let(tb =>
|
||||
tb.Parent.ForeColor = GetComparisonColorRefT(((PropertyInfo) tb.Tag).Name, tb.Text, GetMainFormParent(tb), StringEquality)
|
||||
tb.Parent.ForeColor = GetComparisonColorRefT(tb.Name, tb.Text, GetMainFormParent(tb), StringEquality)
|
||||
);
|
||||
}
|
||||
|
||||
private class UnrepresentablePropEditorUIGen : IConfigPropEditorUIGen<GroupBox>
|
||||
{
|
||||
public GroupBox GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
public GroupBox GenerateControl(string nesting, PropertyInfo pi, object config, IDictionary<string, object?> baselineValues)
|
||||
=> new GroupBox {
|
||||
AutoSize = true,
|
||||
Controls = {
|
||||
|
|
Loading…
Reference in New Issue