diff --git a/src/Nonemm.App/Configuration/Settings.cs b/src/Nonemm.App/Configuration/Settings.cs index bdf9e76..6a0a4b1 100644 --- a/src/Nonemm.App/Configuration/Settings.cs +++ b/src/Nonemm.App/Configuration/Settings.cs @@ -63,6 +63,14 @@ public sealed record Settings /// The height of the callsigns on the bandmap, in points. public int BandmapTextSize { get; init; } = 12; + /// The text size of the log, check and available windows, in points. Ctrl + /// with the mouse wheel or plus and minus changes the one in front. + public int LogFontSize { get; init; } = 12; + + public int CheckFontSize { get; init; } = 14; + + public int AvailableFontSize { get; init; } = 13; + /// Moves each incoming CW spot by a few tens of hertz, so the operator has /// to find the station by ear. N1MM calls it randomising. public bool RandomizeSpots { get; init; } diff --git a/src/Nonemm.App/Windows/AvailableWindow.axaml.cs b/src/Nonemm.App/Windows/AvailableWindow.axaml.cs index b00caf9..006e0c5 100644 --- a/src/Nonemm.App/Windows/AvailableWindow.axaml.cs +++ b/src/Nonemm.App/Windows/AvailableWindow.axaml.cs @@ -1,3 +1,4 @@ +using Nonemm.App.Configuration; using Nonemm.App.Theming; using Avalonia.Controls; using Avalonia.Input; @@ -23,6 +24,11 @@ public sealed partial class AvailableWindow : RefreshableWindow this.session = session; this.tune = tune; InitializeComponent(); + _ = new FontZoom(this, new Settings().AvailableFontSize, () => session.Settings.AvailableFontSize, size => + { + session.Save(session.Settings with { AvailableFontSize = size }); + Refresh(); + }); MultsOnlyBox.IsCheckedChanged += (_, _) => Refresh(); session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh); Refresh(); @@ -70,7 +76,7 @@ public sealed partial class AvailableWindow : RefreshableWindow panel.Children.Add(new TextBlock { Text = $"{band.Name} {stations.Count}", - FontSize = 11, + FontSize = session.Settings.AvailableFontSize - 2, Opacity = 0.7, Margin = new Avalonia.Thickness(0, 0, 0, 4), }); @@ -87,7 +93,7 @@ public sealed partial class AvailableWindow : RefreshableWindow { Text = Label(station), FontFamily = Fonts.Mono, - FontSize = 13, + FontSize = session.Settings.AvailableFontSize, Foreground = Verdicts.Colour(station.Verdict), HorizontalAlignment = HorizontalAlignment.Left, Cursor = new Cursor(StandardCursorType.Hand), diff --git a/src/Nonemm.App/Windows/BandmapWindow.axaml.cs b/src/Nonemm.App/Windows/BandmapWindow.axaml.cs index e618e52..a74c39b 100644 --- a/src/Nonemm.App/Windows/BandmapWindow.axaml.cs +++ b/src/Nonemm.App/Windows/BandmapWindow.axaml.cs @@ -1,3 +1,4 @@ +using Nonemm.App.Configuration; using Avalonia.Media; using Avalonia.Threading; using Nonemm.Contests; @@ -25,9 +26,6 @@ public sealed partial class BandmapWindow : RefreshableWindow ("whole band", Frequency.Zero), ]; - private const int MinTextSize = 8; - private const int MaxTextSize = 24; - private readonly AppSession session; private readonly Action tune; @@ -46,6 +44,11 @@ public sealed partial class BandmapWindow : RefreshableWindow Array.FindIndex(Spans, s => s.Span.Hertz == session.Settings.BandmapSpanHertz)); CentreBox.IsChecked = session.Settings.BandmapCentred; View.TextSize = session.Settings.BandmapTextSize; + _ = new FontZoom(this, new Settings().BandmapTextSize, () => (int)View.TextSize, size => + { + View.TextSize = size; + SaveView(); + }); SmallerButton.Click += (_, _) => ResizeText(-1); LargerButton.Click += (_, _) => ResizeText(1); SpanBox.SelectionChanged += (_, _) => SaveView(); @@ -60,7 +63,7 @@ public sealed partial class BandmapWindow : RefreshableWindow private void ResizeText(int by) { - View.TextSize = Math.Clamp(View.TextSize + by, MinTextSize, MaxTextSize); + View.TextSize = Math.Clamp(View.TextSize + by, FontZoom.Smallest, FontZoom.Largest); SaveView(); } diff --git a/src/Nonemm.App/Windows/CheckWindow.axaml.cs b/src/Nonemm.App/Windows/CheckWindow.axaml.cs index 5bb8ddb..b137e36 100644 --- a/src/Nonemm.App/Windows/CheckWindow.axaml.cs +++ b/src/Nonemm.App/Windows/CheckWindow.axaml.cs @@ -1,3 +1,4 @@ +using Nonemm.App.Configuration; using Nonemm.App.Theming; using Avalonia.Controls; using Avalonia.Layout; @@ -17,6 +18,11 @@ public sealed partial class CheckWindow : RefreshableWindow { this.session = session; InitializeComponent(); + _ = new FontZoom(this, new Settings().CheckFontSize, () => session.Settings.CheckFontSize, size => + { + session.Save(session.Settings with { CheckFontSize = size }); + Refresh(); + }); Refresh(); } @@ -37,19 +43,19 @@ public sealed partial class CheckWindow : RefreshableWindow int at = 0; foreach (CheckColumn column in columns) { - Control panel = BuildColumn(column); + Control panel = BuildColumn(column, session.Settings.CheckFontSize); Grid.SetColumn(panel, at++); Columns.Children.Add(panel); } } - private static Control BuildColumn(CheckColumn column) + private static Control BuildColumn(CheckColumn column, int fontSize) { StackPanel panel = new() { Margin = new Avalonia.Thickness(0, 0, 8, 0) }; panel.Children.Add(new TextBlock { Text = $"{Heading(column.Source)} {Count(column)}", - FontSize = 11, + FontSize = fontSize - 3, Opacity = 0.7, Margin = new Avalonia.Thickness(0, 0, 0, 4), }); @@ -59,7 +65,7 @@ public sealed partial class CheckWindow : RefreshableWindow { Text = candidate.Text, FontFamily = Fonts.Mono, - FontSize = 14, + FontSize = fontSize, Foreground = Verdicts.Colour(candidate.Verdict), HorizontalAlignment = HorizontalAlignment.Left, }); diff --git a/src/Nonemm.App/Windows/FontZoom.cs b/src/Nonemm.App/Windows/FontZoom.cs new file mode 100644 index 0000000..3c18e08 --- /dev/null +++ b/src/Nonemm.App/Windows/FontZoom.cs @@ -0,0 +1,70 @@ +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Nonemm.App.Windows; + +/// Ctrl+wheel and Ctrl+plus / Ctrl+minus on a window change the size of its +/// text, and Ctrl+0 puts it back to `normal`. The handlers run before the controls inside get the event, so a grid +/// or a scroll viewer does not take the wheel first. +public sealed class FontZoom +{ + public const int Smallest = 8; + public const int Largest = 32; + + private readonly int normal; + private readonly Func size; + private readonly Action apply; + + public FontZoom(InputElement target, int normal, Func size, Action apply) + { + this.normal = normal; + this.size = size; + this.apply = apply; + target.AddHandler(InputElement.PointerWheelChangedEvent, OnWheel, RoutingStrategies.Tunnel); + target.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Tunnel); + } + + private void OnWheel(object? sender, PointerWheelEventArgs e) + { + if (e.KeyModifiers.HasFlag(KeyModifiers.Control) && e.Delta.Y != 0) + { + Step(e.Delta.Y > 0 ? 1 : -1); + e.Handled = true; + } + } + + private void OnKeyDown(object? sender, KeyEventArgs e) + { + if (!e.KeyModifiers.HasFlag(KeyModifiers.Control)) + { + return; + } + if (e.Key is Key.D0 or Key.NumPad0) + { + Set(normal); + e.Handled = true; + return; + } + int by = e.Key switch + { + Key.OemPlus or Key.Add => 1, + Key.OemMinus or Key.Subtract => -1, + _ => 0, + }; + if (by != 0) + { + Step(by); + e.Handled = true; + } + } + + private void Step(int by) => Set(Math.Clamp(size() + by, Smallest, Largest)); + + private void Set(int wanted) + { + if (wanted != size()) + { + apply(wanted); + } + } +} diff --git a/src/Nonemm.App/Windows/LogWindow.axaml b/src/Nonemm.App/Windows/LogWindow.axaml index 9d2a6d7..47b4c77 100644 --- a/src/Nonemm.App/Windows/LogWindow.axaml +++ b/src/Nonemm.App/Windows/LogWindow.axaml @@ -11,7 +11,7 @@ columns are sized from the width of the text in this font --> + CanUserReorderColumns="True" FontFamily="{x:Static theming:Fonts.Mono}" /> + FontFamily="{x:Static theming:Fonts.Mono}" /> diff --git a/src/Nonemm.App/Windows/LogWindow.axaml.cs b/src/Nonemm.App/Windows/LogWindow.axaml.cs index cd3d361..fbcafd8 100644 --- a/src/Nonemm.App/Windows/LogWindow.axaml.cs +++ b/src/Nonemm.App/Windows/LogWindow.axaml.cs @@ -1,3 +1,4 @@ +using Nonemm.App.Configuration; using System.Collections.ObjectModel; using Avalonia; using Avalonia.Collections; @@ -63,9 +64,27 @@ public sealed partial class LogWindow : RefreshableWindow }; Colour(Rows); Colour(Earlier); + ApplyFontSize(); + _ = new FontZoom(this, new Settings().LogFontSize, () => session.Settings.LogFontSize, size => + { + session.Save(session.Settings with { LogFontSize = size }); + ApplyFontSize(); + builtFor = ""; + Refresh(); + }); Refresh(); } + /// The cells take their size from the `LogFontSize` resource, and the + /// grids need it too because the column widths are measured from it. + private void ApplyFontSize() + { + double size = session.Settings.LogFontSize; + Resources["LogFontSize"] = size; + Rows.FontSize = size; + Earlier.FontSize = size; + } + /// What the operator picked. The grid raises this for the picks this window /// makes as well, and those are already known. private void RememberPicked(object? sender, SelectionChangedEventArgs e)