From 4850b00de72f33a7e44efa71993ed3c32bf503ea Mon Sep 17 00:00:00 2001
From: itsNate <261014605+itsnateai@users.noreply.github.com>
Date: Thu, 4 Jun 2026 06:21:16 -0600
Subject: [PATCH 1/2] feat(settings): add group dividers to tray-click action
dropdowns
---
UI/SettingsForm.cs | 45 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/UI/SettingsForm.cs b/UI/SettingsForm.cs
index 47de8f0..cdfa4d7 100644
--- a/UI/SettingsForm.cs
+++ b/UI/SettingsForm.cs
@@ -46,6 +46,10 @@ public class SettingsForm : EqSwitchForm
private NumericUpDown _nudLogTrimThreshold = null!;
// ─── Tray Click controls (Left)
+ // 2026-06-04: non-selectable group divider inserted into the click-action dropdowns — a
+ // U+2500 box-drawing line. WireTraySeparatorBounce keeps it unpickable so it can never be
+ // committed/saved as an action (which AppConfig.Validate would reject → silent reset to None).
+ private const string TrayActionSeparator = "──────────────";
private ComboBox _cboSingleClick = null!;
private ComboBox _cboDoubleClick = null!;
private ComboBox _cboTripleClick = null!;
@@ -613,7 +617,20 @@ private TabPage BuildGeneralTab()
// "LaunchOne"/"LaunchAll" via the tray action maps) — "Launch Two" because the
// action launches the configured client count, which is 2 by default. See the
// _trayDisplayActionMap / _trayActionDisplayMap entries below.
- var clickActions = new[] { "None", "Auto-Login1", "AutoLoginTeam1", "TogglePiP", "Launch One", "Launch Two", "FixWindows", "SwapWindows", "Settings", "ShowHelp", "Auto-Login2", "Auto-Login3", "Auto-Login4", "AutoLoginTeam2", "AutoLoginTeam3", "AutoLoginTeam4", "AutoLoginTeam5", "AutoLoginTeam6" };
+ // 2026-06-04: TrayActionSeparator rows partition the list into four logical groups —
+ // primary (None / Auto-Login1 / Team1) | window + utility | extra Auto-Logins | extra Teams.
+ // Dividers are made non-selectable below (WireTraySeparatorBounce). All 5 click combos
+ // share this one list.
+ var clickActions = new[]
+ {
+ "None", "Auto-Login1", "AutoLoginTeam1",
+ TrayActionSeparator,
+ "TogglePiP", "Launch One", "Launch Two", "FixWindows", "SwapWindows", "Settings", "ShowHelp",
+ TrayActionSeparator,
+ "Auto-Login2", "Auto-Login3", "Auto-Login4",
+ TrayActionSeparator,
+ "AutoLoginTeam2", "AutoLoginTeam3", "AutoLoginTeam4", "AutoLoginTeam5", "AutoLoginTeam6"
+ };
const int cboW = 140;
var cardTray = DarkTheme.MakeCard(page, "🖱", "Tray Click Actions", DarkTheme.CardBlue, 10, y, 480, 154);
@@ -645,6 +662,10 @@ private TabPage BuildGeneralTab()
lblTriple.Font = TrackFont(new Font("Segoe UI Semibold", 9f));
_cboMiddleDoubleClick = DarkTheme.AddCardComboBox(cardTray, 325, 75, cboW, clickActions);
+ // 2026-06-04: make the group dividers in clickActions non-selectable on every click combo.
+ foreach (var cb in new[] { _cboSingleClick, _cboDoubleClick, _cboTripleClick, _cboMiddleClick, _cboMiddleDoubleClick })
+ WireTraySeparatorBounce(cb);
+
// v3.23.0: live readout of what each "AutoLogin 1-4" dropdown entry currently fires,
// so the slots aren't mysterious. Assigned via the Quick Login button on the
// Characters card; refreshed here (values already staged in InitializeForm) and on
@@ -4323,6 +4344,28 @@ private static string TrayActionToDisplay(string action) =>
/// Convert dropdown display name back to config action name.
private static string TrayDisplayToAction(string display) =>
_trayDisplayActionMap.TryGetValue(display, out var action) ? action : display;
+
+ ///
+ /// Make the divider rows in the tray-click dropdowns
+ /// non-selectable: if a divider is selected (mouse click, or arrow-key + commit), revert to the
+ /// last real selection. This guarantees a divider can never be committed and saved as a tray
+ /// action — TrayDisplayToAction would pass the divider string through verbatim and
+ /// AppConfig.Validate would then reject it, silently resetting the click to "None". Mirrors the
+ /// separator bounce in QuickLoginSlotsDialog. Re-entrancy is safe: reverting to lastIndex (a
+ /// real row) re-fires the handler, which takes the else-branch and terminates.
+ ///
+ private static void WireTraySeparatorBounce(ComboBox cb)
+ {
+ int lastIndex = cb.SelectedIndex;
+ cb.SelectedIndexChanged += (s, _) =>
+ {
+ var box = (ComboBox)s!;
+ if ((box.SelectedItem as string) == TrayActionSeparator)
+ box.SelectedIndex = lastIndex; // dividers aren't actions — bounce back
+ else
+ lastIndex = box.SelectedIndex;
+ };
+ }
}
///
From 5473bf427ac225a7ca7697f65313f4450ecc75e1 Mon Sep 17 00:00:00 2001
From: itsNate <261014605+itsnateai@users.noreply.github.com>
Date: Thu, 4 Jun 2026 06:34:54 -0600
Subject: [PATCH 2/2] fix(settings): make tray-action dividers
keyboard-skippable
---
UI/SettingsForm.cs | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/UI/SettingsForm.cs b/UI/SettingsForm.cs
index cdfa4d7..aa40a65 100644
--- a/UI/SettingsForm.cs
+++ b/UI/SettingsForm.cs
@@ -4347,12 +4347,14 @@ private static string TrayDisplayToAction(string display) =>
///
/// Make the divider rows in the tray-click dropdowns
- /// non-selectable: if a divider is selected (mouse click, or arrow-key + commit), revert to the
- /// last real selection. This guarantees a divider can never be committed and saved as a tray
- /// action — TrayDisplayToAction would pass the divider string through verbatim and
- /// AppConfig.Validate would then reject it, silently resetting the click to "None". Mirrors the
- /// separator bounce in QuickLoginSlotsDialog. Re-entrancy is safe: reverting to lastIndex (a
- /// real row) re-fires the handler, which takes the else-branch and terminates.
+ /// non-selectable: when a divider is landed on, skip PAST it in the direction of travel so
+ /// keyboard arrows can cross group boundaries (a mouse-click on a divider lands on the adjacent
+ /// real row); fall back to the last real selection only if the list edge is reached. A divider
+ /// can therefore never be committed and saved as a tray action — TrayDisplayToAction would pass
+ /// the divider string through verbatim and AppConfig.Validate would then reject it, silently
+ /// resetting the click to "None". Mirrors the separator skip in QuickLoginSlotsDialog.
+ /// Re-entrancy is safe: setting SelectedIndex to a real row re-fires the handler, which takes
+ /// the non-divider branch (updating lastIndex) and terminates.
///
private static void WireTraySeparatorBounce(ComboBox cb)
{
@@ -4360,10 +4362,19 @@ private static void WireTraySeparatorBounce(ComboBox cb)
cb.SelectedIndexChanged += (s, _) =>
{
var box = (ComboBox)s!;
- if ((box.SelectedItem as string) == TrayActionSeparator)
- box.SelectedIndex = lastIndex; // dividers aren't actions — bounce back
- else
+ if ((box.SelectedItem as string) != TrayActionSeparator)
+ {
lastIndex = box.SelectedIndex;
+ return;
+ }
+ // Skip past the divider in the direction of travel (inferred from lastIndex, since the
+ // event doesn't say which arrow was pressed); while-loop tolerates adjacent dividers.
+ // Edge guard: if no real row lies that way, revert to the last real index.
+ int dir = box.SelectedIndex > lastIndex ? 1 : -1;
+ int i = box.SelectedIndex + dir;
+ while (i >= 0 && i < box.Items.Count && (box.Items[i] as string) == TrayActionSeparator)
+ i += dir;
+ box.SelectedIndex = (i >= 0 && i < box.Items.Count) ? i : lastIndex;
};
}
}