using Avalonia.Media; using Avalonia.Threading; using Nonemm.Core; using Nonemm.Core.Country; using Nonemm.Session; namespace Nonemm.App.Windows; /// N1MM's grey line window: the world with the night drawn on it, our own /// station on it, and the stations being called and spotted. It follows the /// clock, redrawing every minute. public sealed partial class GraylineWindow : RefreshableWindow { private static readonly IBrush HomeColour = new SolidColorBrush(Color.FromRgb(0xFF, 0xFF, 0xFF)); private static readonly IBrush CallColour = new SolidColorBrush(Color.FromRgb(0xFF, 0x6B, 0x4A)); private static readonly IBrush SpotColour = new SolidColorBrush(Color.FromRgb(0x6A, 0xC8, 0xFF)); private readonly AppSession session; private readonly DispatcherTimer clock; public GraylineWindow(AppSession session) { this.session = session; InitializeComponent(); clock = new DispatcherTimer { Interval = TimeSpan.FromMinutes(1) }; clock.Tick += (_, _) => Refresh(); clock.Start(); session.Changed += WhenSessionChanged; Closed += (_, _) => { clock.Stop(); session.Changed -= WhenSessionChanged; }; Refresh(); } private OperatingPosition? Radio => session.Position; public override void Refresh() { Map.Moment = DateTime.UtcNow; Map.Markers = Markers(); Map.InvalidateVisual(); } private IReadOnlyList Markers() { List markers = []; if (GridSquare.TryParse(session.Settings.Station.GridSquare, out GridSquare home)) { markers.Add(new MapMarker( session.Settings.Station.Callsign, home.Latitude, home.Longitude, HomeColour)); } foreach (Station station in Stations()) { markers.Add(new MapMarker(station.Call, station.Latitude, station.Longitude, station.Colour)); } return markers; } /// The station being called, and every station spotted on the band we are /// on. Both are placed by the country file, which holds a position for each /// country: it is the middle of the country rather than the station itself, /// which is as much as a callsign says. private IReadOnlyList Stations() { List found = []; if (Radio is { } position && Where(position.Entry.Call) is { } called) { found.Add(new Station( position.Entry.Call.Trim().ToUpperInvariant(), called.Latitude, called.Longitude, CallColour)); } if (Radio is { } here && Bands.ForFrequency(here.Frequency) is { } band) { foreach (Spotting.Spot spot in session.Bandmap.On(band).Where(s => s.IsStation)) { string call = spot.Call.Text; if (found.All(s => s.Call != call) && Where(call) is { } place) { found.Add(new Station(call, place.Latitude, place.Longitude, SpotColour)); } } } return found; } private CountryLookup? Where(string call) => call.Trim().Length == 0 ? null : session.Countries?.Find(call.Trim()); private void WhenSessionChanged(object? sender, EventArgs e) => Dispatcher.UIThread.Post(Refresh); private sealed record Station(string Call, double Latitude, double Longitude, IBrush Colour); }