From c8bc1418299cfe454f15d6b2931df1497491a2df Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sat, 24 Oct 2026 01:21:45 +0000 Subject: [PATCH] Improve the bandmap window Remove the status bar and the band name, add the 75 and 150 kHz spans, add buttons for the callsign size (saved in the settings), draw the scale in the form's text colour so it shows in the dark theme, mark the carrier on the VFO bar and place a sideband bar on its side of the carrier, and show who spotted a station, when, and the comment when the pointer is on it. Co-Authored-By: Claude Sonnet 5.5 --- src/Nonemm.App/Configuration/Settings.cs | 3 + src/Nonemm.App/Windows/BandmapView.cs | 63 ++++++++++++++----- src/Nonemm.App/Windows/BandmapWindow.axaml | 17 ++--- src/Nonemm.App/Windows/BandmapWindow.axaml.cs | 36 ++++++----- src/Nonemm.Spotting/SpotDescription.cs | 24 +++++++ src/Nonemm.Spotting/VfoMarker.cs | 29 ++++++--- .../BandmapLayoutTests.cs | 27 +++++--- .../SpotDescriptionTests.cs | 27 ++++++++ 8 files changed, 170 insertions(+), 56 deletions(-) create mode 100644 src/Nonemm.Spotting/SpotDescription.cs create mode 100644 tests/Nonemm.Spotting.Tests/SpotDescriptionTests.cs diff --git a/src/Nonemm.App/Configuration/Settings.cs b/src/Nonemm.App/Configuration/Settings.cs index db33102..2856346 100644 --- a/src/Nonemm.App/Configuration/Settings.cs +++ b/src/Nonemm.App/Configuration/Settings.cs @@ -60,6 +60,9 @@ public sealed record Settings /// the receiver nears an edge. public bool BandmapCentred { get; init; } + /// The height of the callsigns on the bandmap, in points. + public int BandmapTextSize { get; init; } = 12; + /// 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/BandmapView.cs b/src/Nonemm.App/Windows/BandmapView.cs index ca715ea..6b8649d 100644 --- a/src/Nonemm.App/Windows/BandmapView.cs +++ b/src/Nonemm.App/Windows/BandmapView.cs @@ -19,10 +19,16 @@ public sealed class BandmapView : Control private const double ScaleX = 52; private const double LabelX = 96; private const double TickLength = 6; - private const double LabelHeight = 15; - private const double TextSize = 12; + private const double ScaleTextSize = 12; + + /// Callsigns are drawn this much taller than their text, so the lines of + /// two neighbours do not touch. + private const double LabelPadding = 3; private const double VfoWidth = 4; + /// How far the carrier tick reaches past each side of the bar. + private const double CarrierOverhang = 3; + /// The scale stops short of the top and bottom edges so the first and last /// frequency numbers are not cut in half. private const double Inset = 9; @@ -46,6 +52,11 @@ public sealed class BandmapView : Control public IReadOnlyList Spots { get; set; } = []; + /// The height of the callsigns, in points. + public double TextSize { get; set; } = 12; + + private double LabelHeight => TextSize + LabelPadding; + public IReadOnlyList Vfos { get; set; } = []; public Frequency Low { get; set; } = Bands.Band20M.Low; @@ -121,7 +132,7 @@ public sealed class BandmapView : Control private void DrawScale(DrawingContext context, IBrush ink) { - Pen line = new(Opaque(ink, 0.5), 1); + Pen line = new(Opaque(ink, 0.7), 1); context.DrawLine(line, new Point(ScaleX, Inset), new Point(ScaleX, Inset + scale.Height)); foreach (Frequency tick in BandmapLayout.Ticks(scale, TickStep)) { @@ -129,7 +140,8 @@ public sealed class BandmapView : Control context.DrawLine(line, new Point(ScaleX - TickLength, y), new Point(ScaleX, y)); FormattedText text = Text( tick.Kilohertz.ToString("0.#", CultureInfo.InvariantCulture), - Opaque(ink, 0.6)); + Opaque(ink, 0.8), + ScaleTextSize); context.DrawText(text, new Point(ScaleX - TickLength - 4 - text.Width, y - (text.Height / 2))); } } @@ -142,9 +154,13 @@ public sealed class BandmapView : Control double bottom = scale.YFor(vfo.High) + Inset; // a narrow mode would be less than a pixel tall double height = Math.Max(3, bottom - top); + IBrush brush = BrushFor(vfo.Role); + context.FillRectangle(brush, new Rect(ScaleX - (VfoWidth / 2), top, VfoWidth, height)); + // the carrier, which is not in the middle of a sideband + double carrier = Math.Round(scale.YFor(vfo.Carrier) + Inset); context.FillRectangle( - BrushFor(vfo.Role), - new Rect(ScaleX - (VfoWidth / 2), top, VfoWidth, height)); + brush, + new Rect(ScaleX - (VfoWidth / 2) - CarrierOverhang, carrier - 1, VfoWidth + (2 * CarrierOverhang), 2)); } } @@ -152,10 +168,12 @@ public sealed class BandmapView : Control { IBrush colour = ColourOf(label.Spot); context.DrawLine( - new Pen(Opaque(ink, 0.35), 1), + new Pen(Opaque(ink, 0.55), 1), new Point(ScaleX + 2, label.MarkY), new Point(LabelX - 2, label.LabelY + (LabelHeight / 2))); - context.DrawText(Text(label.Spot.Call.Text, colour), new Point(LabelX, label.LabelY)); + context.DrawText( + Text(label.Spot.Call.Text, colour, TextSize), + new Point(LabelX, label.LabelY + (LabelPadding / 2))); } protected override void OnPointerWheelChanged(PointerWheelEventArgs e) @@ -168,26 +186,37 @@ public sealed class BandmapView : Control } } + private BandmapLabel? LabelAt(Point at) => + at.X < LabelX + ? null + : placed.FirstOrDefault(l => at.Y >= l.LabelY && at.Y <= l.LabelY + LabelHeight); + + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + ToolTip.SetTip( + this, + LabelAt(e.GetPosition(this)) is { } label ? SpotDescription.Of(label.Spot) : null); + } + protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); Point at = e.GetPosition(this); - foreach (BandmapLabel label in placed) + if (LabelAt(at) is { } label) { - if (at.X >= LabelX && at.Y >= label.LabelY && at.Y <= label.LabelY + LabelHeight) - { - SpotPicked?.Invoke(this, label.Spot); - return; - } + SpotPicked?.Invoke(this, label.Spot); + return; } FrequencyPicked?.Invoke(this, scale.At(at.Y - Inset)); } - private FormattedText Text(string text, IBrush brush) => - new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, TextSize, brush); + private FormattedText Text(string text, IBrush brush, double size) => + new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, size, brush); + /// The form's text colour, which the theme sets in both light and dark. private IBrush Foreground() => - this.FindResource("SystemControlForegroundBaseHighBrush") as IBrush ?? Brushes.Gray; + this.FindResource("FormForeground") as IBrush ?? Brushes.Gray; private static IBrush Opaque(IBrush ink, double opacity) => ink is ISolidColorBrush solid diff --git a/src/Nonemm.App/Windows/BandmapWindow.axaml b/src/Nonemm.App/Windows/BandmapWindow.axaml index c458438..e2c0bf6 100644 --- a/src/Nonemm.App/Windows/BandmapWindow.axaml +++ b/src/Nonemm.App/Windows/BandmapWindow.axaml @@ -2,15 +2,18 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Nonemm.App.Windows" x:Class="Nonemm.App.Windows.BandmapWindow" - Title="Bandmap" Width="280" Height="620"> + Title="Bandmap" Width="320" Height="620"> - - + + +