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.
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; }
@@ -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),
@@ -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<Frequency, string> 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();
}
+10 -4
View File
@@ -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,
});
+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 -->
<Style Selector="DataGridCell TextBlock">
<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
columns fit across the window -->
<Setter Property="Margin" Value="6,0" />
@@ -44,7 +44,7 @@
sideways as the box opens -->
<Style Selector="DataGridCell TextBox">
<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="Margin" Value="0" />
<Setter Property="Padding" Value="6,0" />
@@ -53,11 +53,11 @@
</Style>
</Grid.Styles>
<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}" />
<DataGrid Name="Earlier" Grid.Row="2" IsReadOnly="True" GridLinesVisibility="Horizontal"
CanUserSortColumns="False" HeadersVisibility="None"
FontSize="12" FontFamily="{x:Static theming:Fonts.Mono}" />
FontFamily="{x:Static theming:Fonts.Mono}" />
</Grid>
</DockPanel>
</local:RefreshableWindow>
+19
View File
@@ -1,3 +1,4 @@
using Nonemm.App.Configuration;
using System.Collections.ObjectModel;
using Avalonia;
using Avalonia.Collections;
@@ -63,7 +64,25 @@ 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