Zoom the text of the log, check, available and bandmap windows

Ctrl+wheel and Ctrl+plus / Ctrl+minus change the text size of the
window in front, and Ctrl+0 puts it back. Each size is kept in the
settings file.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 11:37:35 +00:00
co-authored by Claude Sonnet 5.5
parent 0b2f8e2820
commit 45040dff83
7 changed files with 126 additions and 14 deletions
+8
View File
@@ -63,6 +63,14 @@ public sealed record Settings
/// The height of the callsigns on the bandmap, in points. /// The height of the callsigns on the bandmap, in points.
public int BandmapTextSize { get; init; } = 12; 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 /// 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. /// to find the station by ear. N1MM calls it randomising.
public bool RandomizeSpots { get; init; } public bool RandomizeSpots { get; init; }
@@ -1,3 +1,4 @@
using Nonemm.App.Configuration;
using Nonemm.App.Theming; using Nonemm.App.Theming;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
@@ -23,6 +24,11 @@ public sealed partial class AvailableWindow : RefreshableWindow
this.session = session; this.session = session;
this.tune = tune; this.tune = tune;
InitializeComponent(); InitializeComponent();
_ = new FontZoom(this, new Settings().AvailableFontSize, () => session.Settings.AvailableFontSize, size =>
{
session.Save(session.Settings with { AvailableFontSize = size });
Refresh();
});
MultsOnlyBox.IsCheckedChanged += (_, _) => Refresh(); MultsOnlyBox.IsCheckedChanged += (_, _) => Refresh();
session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh); session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh);
Refresh(); Refresh();
@@ -70,7 +76,7 @@ public sealed partial class AvailableWindow : RefreshableWindow
panel.Children.Add(new TextBlock panel.Children.Add(new TextBlock
{ {
Text = $"{band.Name} {stations.Count}", Text = $"{band.Name} {stations.Count}",
FontSize = 11, FontSize = session.Settings.AvailableFontSize - 2,
Opacity = 0.7, Opacity = 0.7,
Margin = new Avalonia.Thickness(0, 0, 0, 4), Margin = new Avalonia.Thickness(0, 0, 0, 4),
}); });
@@ -87,7 +93,7 @@ public sealed partial class AvailableWindow : RefreshableWindow
{ {
Text = Label(station), Text = Label(station),
FontFamily = Fonts.Mono, FontFamily = Fonts.Mono,
FontSize = 13, FontSize = session.Settings.AvailableFontSize,
Foreground = Verdicts.Colour(station.Verdict), Foreground = Verdicts.Colour(station.Verdict),
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
Cursor = new Cursor(StandardCursorType.Hand), Cursor = new Cursor(StandardCursorType.Hand),
@@ -1,3 +1,4 @@
using Nonemm.App.Configuration;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using Nonemm.Contests; using Nonemm.Contests;
@@ -25,9 +26,6 @@ public sealed partial class BandmapWindow : RefreshableWindow
("whole band", Frequency.Zero), ("whole band", Frequency.Zero),
]; ];
private const int MinTextSize = 8;
private const int MaxTextSize = 24;
private readonly AppSession session; private readonly AppSession session;
private readonly Action<Frequency, string> tune; private readonly Action<Frequency, string> tune;
@@ -46,6 +44,11 @@ public sealed partial class BandmapWindow : RefreshableWindow
Array.FindIndex(Spans, s => s.Span.Hertz == session.Settings.BandmapSpanHertz)); Array.FindIndex(Spans, s => s.Span.Hertz == session.Settings.BandmapSpanHertz));
CentreBox.IsChecked = session.Settings.BandmapCentred; CentreBox.IsChecked = session.Settings.BandmapCentred;
View.TextSize = session.Settings.BandmapTextSize; View.TextSize = session.Settings.BandmapTextSize;
_ = new FontZoom(this, new Settings().BandmapTextSize, () => (int)View.TextSize, size =>
{
View.TextSize = size;
SaveView();
});
SmallerButton.Click += (_, _) => ResizeText(-1); SmallerButton.Click += (_, _) => ResizeText(-1);
LargerButton.Click += (_, _) => ResizeText(1); LargerButton.Click += (_, _) => ResizeText(1);
SpanBox.SelectionChanged += (_, _) => SaveView(); SpanBox.SelectionChanged += (_, _) => SaveView();
@@ -60,7 +63,7 @@ public sealed partial class BandmapWindow : RefreshableWindow
private void ResizeText(int by) 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(); SaveView();
} }
+10 -4
View File
@@ -1,3 +1,4 @@
using Nonemm.App.Configuration;
using Nonemm.App.Theming; using Nonemm.App.Theming;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
@@ -17,6 +18,11 @@ public sealed partial class CheckWindow : RefreshableWindow
{ {
this.session = session; this.session = session;
InitializeComponent(); InitializeComponent();
_ = new FontZoom(this, new Settings().CheckFontSize, () => session.Settings.CheckFontSize, size =>
{
session.Save(session.Settings with { CheckFontSize = size });
Refresh();
});
Refresh(); Refresh();
} }
@@ -37,19 +43,19 @@ public sealed partial class CheckWindow : RefreshableWindow
int at = 0; int at = 0;
foreach (CheckColumn column in columns) foreach (CheckColumn column in columns)
{ {
Control panel = BuildColumn(column); Control panel = BuildColumn(column, session.Settings.CheckFontSize);
Grid.SetColumn(panel, at++); Grid.SetColumn(panel, at++);
Columns.Children.Add(panel); 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) }; StackPanel panel = new() { Margin = new Avalonia.Thickness(0, 0, 8, 0) };
panel.Children.Add(new TextBlock panel.Children.Add(new TextBlock
{ {
Text = $"{Heading(column.Source)} {Count(column)}", Text = $"{Heading(column.Source)} {Count(column)}",
FontSize = 11, FontSize = fontSize - 3,
Opacity = 0.7, Opacity = 0.7,
Margin = new Avalonia.Thickness(0, 0, 0, 4), Margin = new Avalonia.Thickness(0, 0, 0, 4),
}); });
@@ -59,7 +65,7 @@ public sealed partial class CheckWindow : RefreshableWindow
{ {
Text = candidate.Text, Text = candidate.Text,
FontFamily = Fonts.Mono, FontFamily = Fonts.Mono,
FontSize = 14, FontSize = fontSize,
Foreground = Verdicts.Colour(candidate.Verdict), Foreground = Verdicts.Colour(candidate.Verdict),
HorizontalAlignment = HorizontalAlignment.Left, HorizontalAlignment = HorizontalAlignment.Left,
}); });
+70
View File
@@ -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<int> size;
private readonly Action<int> apply;
public FontZoom(InputElement target, int normal, Func<int> size, Action<int> 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);
}
}
}
+4 -4
View File
@@ -11,7 +11,7 @@
columns are sized from the width of the text in this font --> columns are sized from the width of the text in this font -->
<Style Selector="DataGridCell TextBlock"> <Style Selector="DataGridCell TextBlock">
<Setter Property="FontFamily" Value="{x:Static theming:Fonts.Mono}" /> <Setter Property="FontFamily" Value="{x:Static theming:Fonts.Mono}" />
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="{DynamicResource LogFontSize}" />
<!-- half of what the theme leaves either side of the text, so more <!-- half of what the theme leaves either side of the text, so more
columns fit across the window --> columns fit across the window -->
<Setter Property="Margin" Value="6,0" /> <Setter Property="Margin" Value="6,0" />
@@ -44,7 +44,7 @@
sideways as the box opens --> sideways as the box opens -->
<Style Selector="DataGridCell TextBox"> <Style Selector="DataGridCell TextBox">
<Setter Property="FontFamily" Value="{x:Static theming:Fonts.Mono}" /> <Setter Property="FontFamily" Value="{x:Static theming:Fonts.Mono}" />
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="{DynamicResource LogFontSize}" />
<Setter Property="MinHeight" Value="0" /> <Setter Property="MinHeight" Value="0" />
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="6,0" /> <Setter Property="Padding" Value="6,0" />
@@ -53,11 +53,11 @@
</Style> </Style>
</Grid.Styles> </Grid.Styles>
<DataGrid Name="Rows" Grid.Row="0" GridLinesVisibility="Horizontal" CanUserSortColumns="True" <DataGrid Name="Rows" Grid.Row="0" GridLinesVisibility="Horizontal" CanUserSortColumns="True"
CanUserReorderColumns="True" FontSize="12" FontFamily="{x:Static theming:Fonts.Mono}" /> CanUserReorderColumns="True" FontFamily="{x:Static theming:Fonts.Mono}" />
<GridSplitter Grid.Row="1" Height="5" ResizeDirection="Rows" Background="{DynamicResource DataGridGridLinesBrush}" /> <GridSplitter Grid.Row="1" Height="5" ResizeDirection="Rows" Background="{DynamicResource DataGridGridLinesBrush}" />
<DataGrid Name="Earlier" Grid.Row="2" IsReadOnly="True" GridLinesVisibility="Horizontal" <DataGrid Name="Earlier" Grid.Row="2" IsReadOnly="True" GridLinesVisibility="Horizontal"
CanUserSortColumns="False" HeadersVisibility="None" CanUserSortColumns="False" HeadersVisibility="None"
FontSize="12" FontFamily="{x:Static theming:Fonts.Mono}" /> FontFamily="{x:Static theming:Fonts.Mono}" />
</Grid> </Grid>
</DockPanel> </DockPanel>
</local:RefreshableWindow> </local:RefreshableWindow>
+19
View File
@@ -1,3 +1,4 @@
using Nonemm.App.Configuration;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Avalonia; using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
@@ -63,9 +64,27 @@ public sealed partial class LogWindow : RefreshableWindow
}; };
Colour(Rows); Colour(Rows);
Colour(Earlier); Colour(Earlier);
ApplyFontSize();
_ = new FontZoom(this, new Settings().LogFontSize, () => session.Settings.LogFontSize, size =>
{
session.Save(session.Settings with { LogFontSize = size });
ApplyFontSize();
builtFor = "";
Refresh();
});
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 /// What the operator picked. The grid raises this for the picks this window
/// makes as well, and those are already known. /// makes as well, and those are already known.
private void RememberPicked(object? sender, SelectionChangedEventArgs e) private void RememberPicked(object? sender, SelectionChangedEventArgs e)