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