diff --git a/src/Nonemm.App/Configuration/Settings.cs b/src/Nonemm.App/Configuration/Settings.cs index 8950731..52b2404 100644 --- a/src/Nonemm.App/Configuration/Settings.cs +++ b/src/Nonemm.App/Configuration/Settings.cs @@ -16,6 +16,12 @@ public sealed record Settings /// dark screen. Config > Manage Skins, Colors and Fonts picks one. public string Theme { get; init; } = "light"; + /// Minimize and restore the other windows with the entry window. + public bool MinimizeWindowsTogether { get; init; } = true; + + /// Raise the other windows that are not minimized when one is activated. + public bool RaiseWindowsTogether { get; init; } = true; + public int ContestNumber { get; init; } public StoredStation Station { get; init; } = new(); diff --git a/src/Nonemm.App/Dialogs/ThemeDialog.axaml b/src/Nonemm.App/Dialogs/ThemeDialog.axaml index fa0ec62..9def726 100644 --- a/src/Nonemm.App/Dialogs/ThemeDialog.axaml +++ b/src/Nonemm.App/Dialogs/ThemeDialog.axaml @@ -22,6 +22,8 @@ + + diff --git a/src/Nonemm.App/Dialogs/ThemeDialog.axaml.cs b/src/Nonemm.App/Dialogs/ThemeDialog.axaml.cs index b358ab2..4cf93a8 100644 --- a/src/Nonemm.App/Dialogs/ThemeDialog.axaml.cs +++ b/src/Nonemm.App/Dialogs/ThemeDialog.axaml.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; +using Nonemm.App.Configuration; using Nonemm.App.Theming; namespace Nonemm.App.Dialogs; @@ -11,9 +12,15 @@ namespace Nonemm.App.Dialogs; /// shows what each one paints a station. public sealed partial class ThemeDialog : Window { - public ThemeDialog(string current) + private readonly Settings settings; + + public ThemeDialog(Settings settings) { + this.settings = settings; + string current = settings.Theme; InitializeComponent(); + MinimizeBox.IsChecked = settings.MinimizeWindowsTogether; + RaiseBox.IsChecked = settings.RaiseWindowsTogether; ThemeBox.ItemsSource = Themes.All.Select(Label).ToList(); ThemeBox.SelectedIndex = Math.Max(0, Themes.All.ToList().FindIndex(t => t.Name == Themes.Find(current).Name)); } @@ -61,7 +68,12 @@ public sealed partial class ThemeDialog : Window }, }; - private void OnOk(object? sender, RoutedEventArgs e) => Close(Picked.Name); + private void OnOk(object? sender, RoutedEventArgs e) => Close(settings with + { + Theme = Picked.Name, + MinimizeWindowsTogether = MinimizeBox.IsChecked == true, + RaiseWindowsTogether = RaiseBox.IsChecked == true, + }); private void OnCancel(object? sender, RoutedEventArgs e) => Close(null); } diff --git a/src/Nonemm.App/Windows/EntryWindow.Menu.cs b/src/Nonemm.App/Windows/EntryWindow.Menu.cs index bb31722..21e7d93 100644 --- a/src/Nonemm.App/Windows/EntryWindow.Menu.cs +++ b/src/Nonemm.App/Windows/EntryWindow.Menu.cs @@ -610,14 +610,14 @@ public sealed partial class EntryWindow private async void OnThemeSettings(object? sender, RoutedEventArgs e) { - ThemeDialog dialog = new(session.Settings.Theme); - if (await dialog.ShowDialog(this) is not { } picked) + ThemeDialog dialog = new(session.Settings); + if (await dialog.ShowDialog(this) is not { } updated) { return; } - session.Save(session.Settings with { Theme = picked }); - Themes.Use(picked); - Status($"{picked} colours"); + session.Save(updated); + Themes.Use(updated.Theme); + Status($"{updated.Theme} colours"); } private async void OnQtcSetup(object? sender, RoutedEventArgs e) @@ -752,6 +752,7 @@ public sealed partial class EntryWindow // No owner: an owned window is transient-for the entry window, so the // window manager keeps it above the entry window and it cannot be // lowered. Closing the entry window closes these (see the Closed handler in the constructor). + window.Activated += (_, _) => RaiseTogether(window); window.Show(); return window; } diff --git a/src/Nonemm.App/Windows/EntryWindow.axaml.cs b/src/Nonemm.App/Windows/EntryWindow.axaml.cs index e647215..ae9e612 100644 --- a/src/Nonemm.App/Windows/EntryWindow.axaml.cs +++ b/src/Nonemm.App/Windows/EntryWindow.axaml.cs @@ -84,11 +84,13 @@ public sealed partial class EntryWindow : Window PropertyChanged += (_, e) => { if (e.Property == WindowStateProperty + && session.Settings.MinimizeWindowsTogether && WindowState is WindowState.Minimized or WindowState.Normal) { FollowWindowState(WindowState); } }; + Activated += (_, _) => RaiseTogether(this); Closed += (_, _) => { foreach (Window window in openWindows.Values.ToList()) @@ -192,6 +194,27 @@ public sealed partial class EntryWindow : Window } } + private long lastRaise; + + /// Brings the other windows that are not minimized above whatever else is + /// on screen, then puts `front` back on top. Activating them fires their + /// own Activated events, so raises within half a second are ignored. + private void RaiseTogether(Window front) + { + if (!session.Settings.RaiseWindowsTogether || Environment.TickCount64 - lastRaise < 500) + { + return; + } + lastRaise = Environment.TickCount64; + IEnumerable others = Placed().Concat(secondRadio is null ? [] : [secondRadio]) + .Where(w => w != front && w.WindowState != WindowState.Minimized); + foreach (Window window in others) + { + window.Activate(); + } + front.Activate(); + } + /// The keyboard follows the operator to the other radio. private void FollowActiveRadio(int number) {