diff --git a/TrayApp.Test/TrayApplicationTests.cs b/TrayApp.Test/TrayApplicationTests.cs index 056bf29..314e4f2 100644 --- a/TrayApp.Test/TrayApplicationTests.cs +++ b/TrayApp.Test/TrayApplicationTests.cs @@ -148,6 +148,32 @@ public void TrayIconClick_WithTheToggleDisabled_DoesNotRunTheToolsSetter() Assert.AreEqual(0, setterCalls); } + [TestMethod] + public void TrayIconClick_WithTheFirstToggleDisabled_LeavesTheSecondToggleAlone() + { + int firstSetterCalls = 0; + int secondSetterCalls = 0; + int remembered = 0; + + TrayAppDefinition app = TrayAppBuilder.Create("demo") + .DisplayName("Demo") + .Toggle("Keep awake", () => false, _ => firstSetterCalls++, isEnabled: () => false) + .Toggle("Start at login", () => false, _ => secondSetterCalls++) + .Build(); + + TrayMenu menu = new(app.DisplayName, app.Status, app.Items); + using TrayApplication host = new(app, menu, duration: null, onToggled: _ => remembered++); + menu.Refresh(); + + host.OnTrayIconClicked(this, EventArgs.Empty); + + // The icon follows the first toggle, so a click on it that flipped the second would change - and + // remember - a setting the icon gives no sign of. A disabled first toggle makes the click do nothing. + Assert.AreEqual(0, firstSetterCalls); + Assert.AreEqual(0, secondSetterCalls); + Assert.AreEqual(0, remembered); + } + [TestMethod] public void TrayIconClick_WithTheToggleEnabled_RunsTheToolsSetter() { diff --git a/TrayApp/Tray/TrayApplication.cs b/TrayApp/Tray/TrayApplication.cs index ef7ce5c..dfa4a73 100644 --- a/TrayApp/Tray/TrayApplication.cs +++ b/TrayApp/Tray/TrayApplication.cs @@ -214,14 +214,20 @@ private void BuildNativeMenu() } /// - /// Flips the first toggle the user could have clicked in the menu. + /// Flips the first toggle in the menu, the one the icon shows by default, and does nothing when that + /// toggle is disabled. /// /// - /// A disabled toggle is skipped, because this is the only activation path with nothing between the + /// A disabled first toggle is not passed over for the next one. The icon follows the first toggle unless + /// the tool says otherwise, so flipping a later one would change - and, with a persistAs key, + /// remember - a setting the icon gives no sign of. + /// + /// A disabled toggle is left alone because this is the only activation path with nothing between the /// click and the tool's setter. The menu path is projected onto s that /// the toolkit greys out and refuses to raise Click for, so the tray icon is where a tool's /// isEnabled predicate would otherwise be silently ignored - the menu would show the action /// as unavailable while a click on the icon ran it anyway. + /// /// /// Internal so that it can be driven without a platform: everything above this reads menu state /// that populates with no display attached. @@ -231,9 +237,13 @@ internal void OnTrayIconClicked(object? sender, EventArgs e) { foreach ((TrayMenuItem model, _) in entries) { - if (model is TrayToggleItem { IsEnabled: true }) + if (model is TrayToggleItem toggle) { - Activate(model); + if (toggle.IsEnabled) + { + Activate(model); + } + return; } }