diff --git a/BizHawk.Client.EmuHawk/config/DisplayConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/DisplayConfig.Designer.cs index bf0c9e8537..50f04c9da6 100644 --- a/BizHawk.Client.EmuHawk/config/DisplayConfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/DisplayConfig.Designer.cs @@ -32,8 +32,6 @@ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DisplayConfig)); this.btnDialogCancel = new BizHawk.WinForms.Controls.SzButtonEx(); this.btnDialogOK = new BizHawk.WinForms.Controls.SzButtonEx(); - this.rbDispMethodOpenGL = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.lblDispMethodOpenGL = new BizHawk.WinForms.Controls.LocLabelEx(); this.tcDialog = new System.Windows.Forms.TabControl(); this.tpScaling = new BizHawk.WinForms.Controls.TabPageEx(); this.flpTpScaling = new BizHawk.WinForms.Controls.SzColumnsToRightFLP(); @@ -41,12 +39,12 @@ this.lblUserPrescale = new BizHawk.WinForms.Controls.LabelEx(); this.nudUserPrescale = new BizHawk.WinForms.Controls.SzNUDEx(); this.lblUserPrescaleUnits = new BizHawk.WinForms.Controls.LabelEx(); - this.grpFilter = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpFilter = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpFilter); this.tlpGrpFilter = new System.Windows.Forms.TableLayoutPanel(); - this.rbFilterNone = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbFilterUser = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbFilterHq2x = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbFilterScanline = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbFilterNone = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFilter); + this.rbFilterUser = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFilter); + this.rbFilterHq2x = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFilter); + this.rbFilterScanline = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFilter); this.flpFilterUser = new BizHawk.WinForms.Controls.SingleColumnFLP(); this.btnFilterUser = new BizHawk.WinForms.Controls.SzButtonEx(); this.lblFilterUser = new BizHawk.WinForms.Controls.LabelEx(); @@ -54,24 +52,24 @@ this.lblFilterScanlineAlpha = new BizHawk.WinForms.Controls.LabelEx(); this.tbFilterScanlineAlpha = new BizHawk.Client.EmuHawk.TransparentTrackBar(); this.cbAutoPrescale = new BizHawk.WinForms.Controls.CheckBoxEx(); - this.grpFinalFilter = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpFinalFilter = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpFinalFilter); this.flpGrpFinalFilter = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); - this.rbFinalFilterNone = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbFinalFilterBilinear = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbFinalFilterBicubic = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbFinalFilterNone = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFinalFilter); + this.rbFinalFilterBilinear = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFinalFilter); + this.rbFinalFilterBicubic = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpFinalFilter); this.cbLetterbox = new BizHawk.WinForms.Controls.CheckBoxEx(); - this.grpAspectRatio = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpAspectRatio = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpAspectRatio); this.flpGrpAspectRatio = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); - this.rbARSquare = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbARSquare = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAspectRatio); this.lblAspectRatioNonSquare = new BizHawk.WinForms.Controls.LabelEx(); - this.rbARBySystem = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbARBySystem = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAspectRatio); this.flpCustomSize = new BizHawk.WinForms.Controls.SingleRowFLP(); - this.rbARCustomSize = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbARCustomSize = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAspectRatio); this.txtARCustomWidth = new BizHawk.WinForms.Controls.SzTextBoxEx(); this.lblARCustomSizeSeparator = new BizHawk.WinForms.Controls.LabelEx(); this.txtARCustomHeight = new BizHawk.WinForms.Controls.SzTextBoxEx(); this.flpCustomAR = new BizHawk.WinForms.Controls.SingleRowFLP(); - this.rbARCustomRatio = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbARCustomRatio = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAspectRatio); this.txtARCustomRatioH = new BizHawk.WinForms.Controls.SzTextBoxEx(); this.lblARCustomRatioSeparator = new BizHawk.WinForms.Controls.LabelEx(); this.txtARCustomRatioV = new BizHawk.WinForms.Controls.SzTextBoxEx(); @@ -89,23 +87,25 @@ this.btnDefaults = new BizHawk.WinForms.Controls.LocSzButtonEx(); this.tpDispMethod = new BizHawk.WinForms.Controls.TabPageEx(); this.flpTpDispMethod = new BizHawk.WinForms.Controls.SingleColumnFLP(); - this.grpDispMethod = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpDispMethod = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpDispMethod); this.flpGrpDispMethod = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); this.flpD3DSection = new BizHawk.WinForms.Controls.SingleColumnFLP(); - this.rbDispMethodD3D = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbDispMethodD3D = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispMethod); this.lblDispMethodD3D = new BizHawk.WinForms.Controls.LocLabelEx(); this.flpD3DAltVSync = new BizHawk.WinForms.Controls.SingleRowFLP(); this.cbD3DAltVSync = new BizHawk.WinForms.Controls.CheckBoxEx(); this.lblD3DAltVSync = new BizHawk.WinForms.Controls.SzLabelEx(); - this.rbDispMethodGDIPlus = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbDispMethodOpenGL = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispMethod); + this.lblDispMethodOpenGL = new BizHawk.WinForms.Controls.LocLabelEx(); + this.rbDispMethodGDIPlus = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispMethod); this.lblDispMethodGDIPlus = new BizHawk.WinForms.Controls.LocLabelEx(); this.lblDispMethodRestartWarning = new BizHawk.WinForms.Controls.LabelEx(); this.tpMisc = new BizHawk.WinForms.Controls.TabPageEx(); - this.grpDispFeatures = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpDispFeatures = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpDispFeatures); this.flpGrpDispFeatures = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); - this.rbDispFeaturesFull = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbDispFeaturesMinimal = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbDispFeaturesNothing = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbDispFeaturesFull = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispFeatures); + this.rbDispFeaturesMinimal = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispFeatures); + this.rbDispFeaturesNothing = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpDispFeatures); this.tpWindow = new BizHawk.WinForms.Controls.TabPageEx(); this.flpTpWindow = new BizHawk.WinForms.Controls.SingleColumnFLP(); this.flpWindowFSGroups = new BizHawk.WinForms.Controls.SingleRowFLP(); diff --git a/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs b/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs index 5fa9be43ac..3a747552cf 100644 --- a/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/EmuHawkOptions.Designer.cs @@ -60,15 +60,15 @@ this.cbBackupSaveRAM = new BizHawk.WinForms.Controls.CheckBoxEx(); this.flpAutoSaveRAM = new BizHawk.WinForms.Controls.SingleRowFLP(); this.cbAutoSaveRAM = new BizHawk.WinForms.Controls.CheckBoxEx(); - this.grpAutoSaveRAM = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpAutoSaveRAM = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpAutoSaveRAM); this.flpGrpAutoSaveRAM = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); this.lblAutoSaveRAM = new BizHawk.WinForms.Controls.LabelEx(); this.flpAutoSaveRAMFreq = new BizHawk.WinForms.Controls.SingleRowFLP(); this.lblAutoSaveRAMFreqDesc = new BizHawk.WinForms.Controls.LabelEx(); - this.cbAutoSaveRAMFreq5s = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.AutoSaveRAMFreq5min = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.cbAutoSaveRAMFreq5s = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAutoSaveRAM); + this.AutoSaveRAMFreq5min = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAutoSaveRAM); this.flpAutoSaveRAMFreqCustom = new BizHawk.WinForms.Controls.SingleRowFLP(); - this.rbAutoSaveRAMFreqCustom = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbAutoSaveRAMFreqCustom = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpAutoSaveRAM); this.nudAutoSaveRAMFreqCustom = new BizHawk.WinForms.Controls.SzNUDEx(); this.lblAutoSaveRAMFreqCustomUnits = new BizHawk.WinForms.Controls.LabelEx(); this.flpFrameAdvPastLag = new BizHawk.WinForms.Controls.SingleColumnFLP(); @@ -82,10 +82,10 @@ this.flpMoviesInAWE = new BizHawk.WinForms.Controls.SingleColumnFLP(); this.cbMoviesInAWE = new BizHawk.WinForms.Controls.CheckBoxEx(); this.lblMoviesInAWE = new BizHawk.WinForms.Controls.LocLabelEx(); - this.grpLuaEngine = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpLuaEngine = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpLuaEngine); this.flpGrpLuaEngine = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); - this.rbKopiLua = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbLuaInterface = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbKopiLua = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpLuaEngine); + this.rbLuaInterface = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpLuaEngine); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); this.flpDialogButtons = new BizHawk.WinForms.Controls.LocSzSingleRowFLP(); this.tcDialog.SuspendLayout(); diff --git a/BizHawk.Client.EmuHawk/config/SoundConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/SoundConfig.Designer.cs index f24210546f..45f2783a66 100644 --- a/BizHawk.Client.EmuHawk/config/SoundConfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/SoundConfig.Designer.cs @@ -36,11 +36,11 @@ this.cbMasterEnable = new BizHawk.WinForms.Controls.CheckBoxEx(); this.lblMasterEnable = new BizHawk.WinForms.Controls.LocSzLabelEx(); this.cbMuteFrameAdvance = new BizHawk.WinForms.Controls.CheckBoxEx(); - this.grpSoundMethod = new BizHawk.WinForms.Controls.SzGroupBoxEx(); + this.grpSoundMethod = new BizHawk.WinForms.Controls.SzGroupBoxEx(out var trackerGrpSoundMethod); this.flpGrpSoundMethod = new BizHawk.WinForms.Controls.LocSingleColumnFLP(); - this.rbSoundMethodDirectSound = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbSoundMethodXAudio2 = new BizHawk.WinForms.Controls.RadioButtonEx(); - this.rbSoundMethodOpenAL = new BizHawk.WinForms.Controls.RadioButtonEx(); + this.rbSoundMethodDirectSound = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpSoundMethod); + this.rbSoundMethodXAudio2 = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpSoundMethod); + this.rbSoundMethodOpenAL = new BizHawk.WinForms.Controls.RadioButtonEx(trackerGrpSoundMethod); this.grpVolume.SuspendLayout(); this.flpGrpVolume.SuspendLayout(); this.flpFullSpeed.SuspendLayout(); diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/GroupBoxExBase.cs b/BizHawk.WinForms.Controls/GroupBoxEx/GroupBoxExBase.cs new file mode 100644 index 0000000000..7f62bf6efc --- /dev/null +++ b/BizHawk.WinForms.Controls/GroupBoxEx/GroupBoxExBase.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; +using System.Windows.Forms; + +namespace BizHawk.WinForms.Controls +{ + public abstract class GroupBoxExBase : GroupBox + { + public readonly RadioButtonGroupTracker Tracker; + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new int TabIndex => base.TabIndex; + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new bool TabStop => base.TabStop; + + protected GroupBoxExBase(out IRadioButtonReadOnlyTracker tracker) => tracker = Tracker = new RadioButtonGroupTracker(); + } +} diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/IRadioButtonReadOnlyTracker.cs b/BizHawk.WinForms.Controls/GroupBoxEx/IRadioButtonReadOnlyTracker.cs new file mode 100644 index 0000000000..17d7031d98 --- /dev/null +++ b/BizHawk.WinForms.Controls/GroupBoxEx/IRadioButtonReadOnlyTracker.cs @@ -0,0 +1,14 @@ +using System.Windows.Forms; + +namespace BizHawk.WinForms.Controls +{ + /// Functions as a write-only collection of ITrackedRadioButtons. + /// Elements should have unique Names; breaking this rule is UB, not checked at runtime. + /// + public interface IRadioButtonReadOnlyTracker + { + void Add(ITrackedRadioButton rb); + + void UpdateDeselected(string name); + } +} diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/ITrackedRadioButton.cs b/BizHawk.WinForms.Controls/GroupBoxEx/ITrackedRadioButton.cs new file mode 100644 index 0000000000..b9ef99d4a6 --- /dev/null +++ b/BizHawk.WinForms.Controls/GroupBoxEx/ITrackedRadioButton.cs @@ -0,0 +1,12 @@ +namespace BizHawk.WinForms.Controls +{ + public interface ITrackedRadioButton + { + /// Does not declare a setter intentionally, use . + bool Checked { get; } + + string Name { get; } + + void UncheckFromTracker(); + } +} diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/LocSzGroupBoxEx.cs b/BizHawk.WinForms.Controls/GroupBoxEx/LocSzGroupBoxEx.cs index 7faff32f68..64fa44ee8f 100644 --- a/BizHawk.WinForms.Controls/GroupBoxEx/LocSzGroupBoxEx.cs +++ b/BizHawk.WinForms.Controls/GroupBoxEx/LocSzGroupBoxEx.cs @@ -1,20 +1,15 @@ using System.ComponentModel; -using System.Windows.Forms; namespace BizHawk.WinForms.Controls { /// - public class LocSzGroupBoxEx : GroupBox + public class LocSzGroupBoxEx : GroupBoxExBase { [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new bool AutoSize => base.AutoSize; - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new int TabIndex => base.TabIndex; + public LocSzGroupBoxEx() : base(out _) {} - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new bool TabStop => base.TabStop; + public LocSzGroupBoxEx(out IRadioButtonReadOnlyTracker tracker) : base(out tracker) {} } } diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/RadioButtonGroupTracker.cs b/BizHawk.WinForms.Controls/GroupBoxEx/RadioButtonGroupTracker.cs new file mode 100644 index 0000000000..10574a0259 --- /dev/null +++ b/BizHawk.WinForms.Controls/GroupBoxEx/RadioButtonGroupTracker.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Windows.Forms; + +namespace BizHawk.WinForms.Controls +{ + /// + /// Functions as a collection of ITrackedRadioButtons.
+ /// Using this in our custom GroupBoxes circumvents the direct-child restriction on radio buttons. + /// With that gone, we're free to use nested FLPs in our layouts, the cost being the complexity of this class and its related types. + ///
+ /// Elements should have unique Names; breaking this rule is UB, not checked at runtime. + /// + public sealed class RadioButtonGroupTracker : List, IRadioButtonReadOnlyTracker + { + public void UpdateDeselected(string name) + { + foreach (var rb in this) if (rb.Name != name) rb.UncheckFromTracker(); + } + } +} diff --git a/BizHawk.WinForms.Controls/GroupBoxEx/SzGroupBoxEx.cs b/BizHawk.WinForms.Controls/GroupBoxEx/SzGroupBoxEx.cs index c7b3c06ae6..2cbc9a415b 100644 --- a/BizHawk.WinForms.Controls/GroupBoxEx/SzGroupBoxEx.cs +++ b/BizHawk.WinForms.Controls/GroupBoxEx/SzGroupBoxEx.cs @@ -1,11 +1,10 @@ using System.ComponentModel; using System.Drawing; -using System.Windows.Forms; namespace BizHawk.WinForms.Controls { /// - public class SzGroupBoxEx : GroupBox + public class SzGroupBoxEx : GroupBoxExBase { [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new bool AutoSize => base.AutoSize; @@ -13,12 +12,8 @@ namespace BizHawk.WinForms.Controls [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Point Location => base.Location; - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new int TabIndex => base.TabIndex; + public SzGroupBoxEx() : base(out _) {} - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new bool TabStop => base.TabStop; + public SzGroupBoxEx(out IRadioButtonReadOnlyTracker tracker) : base(out tracker) {} } } diff --git a/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonEx.cs b/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonEx.cs index 7bd303bad6..6e8c80d16d 100644 --- a/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonEx.cs +++ b/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonEx.cs @@ -15,6 +15,8 @@ namespace BizHawk.WinForms.Controls [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Size Size => base.Size; - public RadioButtonEx() => base.AutoSize = true; + public RadioButtonEx() {} + + public RadioButtonEx(IRadioButtonReadOnlyTracker tracker) : base(tracker) => base.AutoSize = true; } } diff --git a/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonExBase.cs b/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonExBase.cs index a5a9f8f43a..64c5a6cb36 100644 --- a/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonExBase.cs +++ b/BizHawk.WinForms.Controls/RadioButtonEx/RadioButtonExBase.cs @@ -3,12 +3,15 @@ using System.Windows.Forms; namespace BizHawk.WinForms.Controls { - public abstract class RadioButtonExBase : RadioButton + public abstract class RadioButtonExBase : RadioButton, ITrackedRadioButton { + /// use to prevent recursion + protected bool CheckedChangedCausedByTracker { get; private set; } + [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public new int TabIndex => base.TabIndex; - + public new int TabIndex => base.TabIndex; + [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new bool TabStop => base.TabStop; @@ -16,5 +19,23 @@ namespace BizHawk.WinForms.Controls [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new bool UseVisualStyleBackColor => base.UseVisualStyleBackColor; + + protected RadioButtonExBase() {} + + protected RadioButtonExBase(IRadioButtonReadOnlyTracker tracker) + { + tracker.Add(this); + CheckedChanged += (changedSender, changedArgs) => + { + if (((RadioButtonExBase) changedSender).Checked) tracker.UpdateDeselected(Name); + }; + } + + public void UncheckFromTracker() + { + CheckedChangedCausedByTracker = true; + Checked = false; + CheckedChangedCausedByTracker = false; + } } }