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; }; } }