Add hack to EmuApi, add WIP external tool AutoGenConfig
This commit is contained in:
parent
f08013e312
commit
eab44d2d94
|
@ -47,8 +47,20 @@ namespace BizHawk.Client.Common
|
|||
|
||||
private readonly Action<string> LogCallback;
|
||||
|
||||
/// <summary>Using this property to get a reference to <see cref="Global.Config">Global.Config</see> is a terrible, horrible, no good, very bad idea. That's why it's not in the <see cref="IEmu">interface</see>.</summary>
|
||||
public Config ForbiddenConfigReference
|
||||
{
|
||||
get
|
||||
{
|
||||
ForbiddenConfigReferenceUsed = true;
|
||||
return Global.Config;
|
||||
}
|
||||
}
|
||||
|
||||
public Action FrameAdvanceCallback { get; set; }
|
||||
|
||||
public bool ForbiddenConfigReferenceUsed { get; private set; }
|
||||
|
||||
public Action YieldCallback { get; set; }
|
||||
|
||||
public void DisplayVsync(bool enabled) => Global.Config.VSync = enabled;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="$(ProjectDir)../Common.props" />
|
||||
<Import Project="$(ProjectDir)../NET48ExternalToolForm.props" />
|
||||
<ItemGroup>
|
||||
<Compile Update="CustomMainForm.cs" SubType="Form" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public static class ConfigEditorUIGenerators
|
||||
{
|
||||
public static readonly IDictionary<Type, IConfigPropEditorUIGen<Control>> FallbackGenerators = new Dictionary<Type, IConfigPropEditorUIGen<Control>> {
|
||||
[typeof(bool)] = new CheckBoxForBoolEditorUIGen(),
|
||||
[typeof(string)] = new TextBoxForStringEditorUIGen()
|
||||
};
|
||||
|
||||
public static readonly IConfigPropEditorUIGen<GroupBox> FinalFallbackGenerator = new UnrepresentablePropEditorUIGen();
|
||||
|
||||
private static Color GetComparisonColorRefT<T>(string prop, T? currentValue, CustomMainForm parent, Func<T?, T?, bool> equalityFunc)
|
||||
where T : class
|
||||
=> equalityFunc(currentValue, parent.BaselineValues[prop] as T)
|
||||
? GetInitComparisonColorRefT(prop, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, CustomMainForm.DefaultValues[prop] as T)
|
||||
? CustomMainForm.ComparisonColors.ChangedUnset
|
||||
: CustomMainForm.ComparisonColors.Changed;
|
||||
|
||||
private static Color GetComparisonColorValT<T>(string prop, T? currentValue, CustomMainForm parent, Func<T?, T?, bool> equalityFunc)
|
||||
where T : struct
|
||||
=> equalityFunc(currentValue, parent.BaselineValues[prop]?.Let(it => (T) it))
|
||||
? GetInitComparisonColorValT(prop, currentValue, equalityFunc)
|
||||
: equalityFunc(currentValue, CustomMainForm.DefaultValues[prop]?.Let(it => (T) it))
|
||||
? CustomMainForm.ComparisonColors.ChangedUnset
|
||||
: CustomMainForm.ComparisonColors.Changed;
|
||||
|
||||
private static Color GetInitComparisonColorRefT<T>(string prop, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
where T : class
|
||||
=> equalityFunc(currentValue, CustomMainForm.DefaultValues[prop] as T)
|
||||
? CustomMainForm.ComparisonColors.UnchangedDefault
|
||||
: CustomMainForm.ComparisonColors.Unchanged;
|
||||
|
||||
private static Color GetInitComparisonColorValT<T>(string prop, T? currentValue, Func<T?, T?, bool> equalityFunc)
|
||||
where T : struct
|
||||
=> equalityFunc(currentValue, CustomMainForm.DefaultValues[prop]?.Let(it => (T) it))
|
||||
? CustomMainForm.ComparisonColors.UnchangedDefault
|
||||
: CustomMainForm.ComparisonColors.Unchanged;
|
||||
|
||||
private static CustomMainForm GetMainFormParent(Control c)
|
||||
{
|
||||
var parent = c.Parent;
|
||||
while (!(parent is CustomMainForm)) parent = parent.Parent;
|
||||
return (CustomMainForm) parent;
|
||||
}
|
||||
|
||||
private static string GetPropertyNameDesc(PropertyInfo pi)
|
||||
=> pi.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault()
|
||||
?.Let(it => $"{pi.Name}: {((DescriptionAttribute) it).Description}")
|
||||
?? pi.Name;
|
||||
|
||||
public struct ComparisonColors
|
||||
{
|
||||
public Color Changed;
|
||||
public Color ChangedInvalid;
|
||||
public Color ChangedUnset;
|
||||
public Color Unchanged;
|
||||
public Color UnchangedDefault;
|
||||
}
|
||||
|
||||
public interface IConfigPropEditorUIGen<out TControl>
|
||||
where TControl : Control
|
||||
{
|
||||
TControl GenerateControl(PropertyInfo pi, Config 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)
|
||||
);
|
||||
|
||||
public CheckBox GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
{
|
||||
if (pi.PropertyType != typeof(bool)) throw new Exception();
|
||||
var baseline = (bool) pi.GetValue(config);
|
||||
baselineValues[pi.Name] = baseline;
|
||||
return new CheckBox
|
||||
{
|
||||
AutoSize = true,
|
||||
Checked = baseline,
|
||||
ForeColor = GetInitComparisonColorValT<bool>(pi.Name, baseline, BoolEquality),
|
||||
Tag = pi,
|
||||
Text = GetPropertyNameDesc(pi)
|
||||
}.Also(it => it.Click += CheckBoxClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private class TextBoxForStringEditorUIGen : IConfigPropEditorUIGen<FlowLayoutPanel>
|
||||
{
|
||||
private static readonly Func<string?, string?, bool> StringEquality = string.Equals;
|
||||
|
||||
public FlowLayoutPanel GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
{
|
||||
if (pi.PropertyType != typeof(string)) throw new Exception();
|
||||
var baseline = (string) pi.GetValue(config);
|
||||
baselineValues[pi.Name] = 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)
|
||||
},
|
||||
ForeColor = GetInitComparisonColorRefT(pi.Name, 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)
|
||||
);
|
||||
}
|
||||
|
||||
private class UnrepresentablePropEditorUIGen : IConfigPropEditorUIGen<GroupBox>
|
||||
{
|
||||
public GroupBox GenerateControl(PropertyInfo pi, Config config, IDictionary<string, object?> baselineValues)
|
||||
=> new GroupBox {
|
||||
AutoSize = true,
|
||||
Controls = {
|
||||
new FlowLayoutPanel {
|
||||
AutoSize = true,
|
||||
Controls = { new Label { AutoSize = true, Text = $"no editor found for type {pi.PropertyType}" } },
|
||||
Location = new Point(4, 16),
|
||||
MaximumSize = new Size(int.MaxValue, 20)
|
||||
}
|
||||
},
|
||||
MaximumSize = new Size(int.MaxValue, 40),
|
||||
Text = pi.Name
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.ApiHawk;
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
using static BizHawk.Client.EmuHawk.ConfigEditorUIGenerators;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public class CustomMainForm : Form, IExternalToolForm
|
||||
{
|
||||
private static readonly IList<(PropertyInfo, IConfigPropEditorUIGen<Control>)> CachedControlGenerators;
|
||||
|
||||
public static ComparisonColors ComparisonColors = new ComparisonColors
|
||||
{
|
||||
Changed = Color.FromArgb(unchecked((int) 0xFF9F3F00)),
|
||||
ChangedInvalid = Color.DarkRed,
|
||||
ChangedUnset = Color.FromArgb(unchecked((int) 0xFF9F1F5F)),
|
||||
Unchanged = Color.FromArgb(unchecked((int) 0xFF00003F)),
|
||||
UnchangedDefault = Color.Black
|
||||
};
|
||||
|
||||
public static readonly IDictionary<string, object?> DefaultValues;
|
||||
|
||||
static CustomMainForm()
|
||||
{
|
||||
CachedControlGenerators = new List<(PropertyInfo, IConfigPropEditorUIGen<Control>)>();
|
||||
DefaultValues = new Dictionary<string, object?>();
|
||||
foreach (var pi in typeof(Config).GetProperties())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>value types: default(T); ref types: calls default (no-arg) ctor if it exists, else null</returns>
|
||||
private static object? TrueGenericDefault(Type t)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Activator.CreateInstance(t);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly IDictionary<string, object?> BaselineValues = new Dictionary<string, object?>();
|
||||
|
||||
[RequiredApi]
|
||||
private IEmu? EmuHawkAPI { get; set; }
|
||||
|
||||
public override string Text => "AutoGenConfig";
|
||||
|
||||
public bool UpdateBefore => false;
|
||||
|
||||
public CustomMainForm()
|
||||
{
|
||||
ClientSize = new Size(640, 720);
|
||||
SuspendLayout();
|
||||
Controls.Add(new FlowLayoutPanel {
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||
BorderStyle = BorderStyle.FixedSingle,
|
||||
Controls = {
|
||||
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.ChangedInvalid, Text = "invalid" },
|
||||
new Label { AutoSize = true, ForeColor = ComparisonColors.Changed, Text = "custom A => custom B" }
|
||||
},
|
||||
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 {
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
|
||||
AutoScroll = true,
|
||||
FlowDirection = FlowDirection.TopDown,
|
||||
Location = new Point(4, 32),
|
||||
Size = new Size(ClientSize.Width - 8, ClientSize.Height - 64),
|
||||
WrapContents = false
|
||||
});
|
||||
Controls.Add(new FlowLayoutPanel {
|
||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
||||
AutoScroll = true,
|
||||
AutoSize = true,
|
||||
Controls = {
|
||||
new Button {
|
||||
Size = new Size(128, 24),
|
||||
Text = "Discard Changes"
|
||||
}.Also(it => it.Click += (clickEventSender, clickEventArgs) => Close()),
|
||||
new Button {
|
||||
Size = new Size(128, 24),
|
||||
Text = "Review and Save..."
|
||||
}.Also(it => it.Click += (clickEventSender, clickEventArgs) => Close())
|
||||
},
|
||||
FlowDirection = FlowDirection.RightToLeft,
|
||||
Location = new Point(ClientSize.Width - 201, ClientSize.Height - 31),
|
||||
WrapContents = false
|
||||
});
|
||||
Load += (loadEventSender, loadEventArgs) =>
|
||||
{
|
||||
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());
|
||||
};
|
||||
ResumeLayout();
|
||||
}
|
||||
|
||||
public bool AskSaveChanges() => true;
|
||||
|
||||
public void FastUpdate() {}
|
||||
|
||||
public void NewUpdate(ToolFormUpdateType type) {}
|
||||
|
||||
public void Restart() {}
|
||||
|
||||
public void UpdateValues() {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using BizHawk.Client.ApiHawk;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("AutoGenConfig")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("AutoGenConfig")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: BizHawkExternalTool("AutoGenConfig")]
|
||||
[assembly: BizHawkExternalToolUsage()]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("34ef725e-ecfd-4e25-b7fd-5c995dd62eef")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
internal static class ScopingExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Also<T>(this T receiver, Action<T> action) where T : notnull
|
||||
{
|
||||
action(receiver);
|
||||
return receiver;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Let<T>(this T receiver, Action<T> func) where T : notnull => func(receiver);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TReturn Let<TRec, TReturn>(this TRec receiver, Func<TRec, TReturn> func) where TRec : notnull => func(receiver);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../.build_net48_from_cwd_debug.sh
|
|
@ -0,0 +1 @@
|
|||
../.build_net48_from_cwd_release.sh
|
Loading…
Reference in New Issue