Two themes, and one place that decides what colour a station is
Colours came from static brushes scattered over the windows. They now come from a Theme, and everything that paints a station, a spot or a log row asks Themes.Current as it paints, so changing the theme changes every window without reopening it. light is N1MM's own palette, read out of ApplicationStyles: the pale blue behind a window, ivory on a button, white boxes, and the four colours a station is painted in. dark is the same states in colours for a dark screen, which is roughly what the program looked like before. Config > Manage Skins, Colors and Fonts picks one, under N1MM's name for the same dialog, and shows what each theme paints a callsign. The choice is kept in settings.json as Theme, and light is the default. The window chrome follows through resources the Fluent controls read, so a text box and a button take the theme's colours in every state. Fonts and skins are not part of it. Looked at under Xvfb in both themes: the entry window, the theme dialog, and the log window with its rows still coloured by what each contact was worth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace Nonemm.App.Theming;
|
||||
|
||||
/// The colours one theme paints with. The names follow N1MM's
|
||||
/// `ApplicationStyles`, because the light theme is N1MM's own palette and the
|
||||
/// windows are laid out the same way.
|
||||
public sealed record Theme
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// Which set of control colours the theme sits on, so a text box or a
|
||||
/// scroll bar looks right under the colours above.
|
||||
public required ThemeVariant Variant { get; init; }
|
||||
|
||||
/// Behind a window, and the text on it.
|
||||
public required Color FormBackground { get; init; }
|
||||
|
||||
public required Color FormForeground { get; init; }
|
||||
|
||||
public required Color ButtonBackground { get; init; }
|
||||
|
||||
/// Inside an entry box or a dialog box, and the text in it.
|
||||
public required Color FieldBackground { get; init; }
|
||||
|
||||
public required Color FieldForeground { get; init; }
|
||||
|
||||
/// A log row, and every second one.
|
||||
public required Color GridBackground { get; init; }
|
||||
|
||||
public required Color GridAlternateBackground { get; init; }
|
||||
|
||||
/// Worth a contact, no new multiplier.
|
||||
public required Color Worth { get; init; }
|
||||
|
||||
public required Color WorthBackground { get; init; }
|
||||
|
||||
/// New in one multiplier.
|
||||
public required Color Multiplier { get; init; }
|
||||
|
||||
public required Color MultiplierBackground { get; init; }
|
||||
|
||||
/// New in more than one.
|
||||
public required Color Multipliers { get; init; }
|
||||
|
||||
public required Color MultipliersBackground { get; init; }
|
||||
|
||||
public required Color Dupe { get; init; }
|
||||
|
||||
public required Color DupeBackground { get; init; }
|
||||
|
||||
/// The frequency we are calling CQ on.
|
||||
public required Color Cq { get; init; }
|
||||
|
||||
/// Behind a line that is in the log, one that is not filled in yet, and one
|
||||
/// that cannot be read.
|
||||
public required Color GoodBackground { get; init; }
|
||||
|
||||
public required Color EmptyBackground { get; init; }
|
||||
|
||||
public required Color BadBackground { get; init; }
|
||||
|
||||
/// The boxes while quick editing a logged contact.
|
||||
public required Color QuickEdit { get; init; }
|
||||
|
||||
/// The light that says a radio is answering, and the one that says
|
||||
/// something is going out on the air.
|
||||
public required Color RadioLight { get; init; }
|
||||
|
||||
public required Color TransmitLight { get; init; }
|
||||
|
||||
/// N1MM's own defaults, from `ApplicationStyles`: the program as it looks
|
||||
/// on a fresh install.
|
||||
public static readonly Theme Light = new()
|
||||
{
|
||||
Name = "light",
|
||||
Variant = ThemeVariant.Light,
|
||||
FormBackground = Color.FromRgb(0xCD, 0xDA, 0xEB),
|
||||
FormForeground = Color.FromRgb(0x00, 0x00, 0x00),
|
||||
ButtonBackground = Color.FromRgb(0xFF, 0xFA, 0xF0),
|
||||
FieldBackground = Color.FromRgb(0xFF, 0xFF, 0xFF),
|
||||
FieldForeground = Color.FromRgb(0x00, 0x00, 0x00),
|
||||
GridBackground = Color.FromRgb(0xF2, 0xFC, 0xFF),
|
||||
GridAlternateBackground = Color.FromRgb(0xAB, 0xC0, 0xDC),
|
||||
Worth = Color.FromRgb(0x00, 0x00, 0xFF),
|
||||
WorthBackground = Color.FromRgb(0x51, 0xA8, 0xFF),
|
||||
Multiplier = Color.FromRgb(0xE8, 0x00, 0x00),
|
||||
MultiplierBackground = Color.FromRgb(0xFF, 0x37, 0x37),
|
||||
Multipliers = Color.FromRgb(0x0D, 0x9D, 0x0D),
|
||||
MultipliersBackground = Color.FromRgb(0x5F, 0xF1, 0x5F),
|
||||
Dupe = Color.FromRgb(0x80, 0x80, 0x80),
|
||||
DupeBackground = Color.FromRgb(0xD4, 0xD4, 0xD4),
|
||||
Cq = Color.FromRgb(0xFF, 0x00, 0x00),
|
||||
GoodBackground = Color.FromRgb(0xD4, 0xF5, 0xD4),
|
||||
EmptyBackground = Color.FromRgb(0xFF, 0xE4, 0xE1),
|
||||
BadBackground = Color.FromRgb(0xFF, 0xF8, 0xC4),
|
||||
QuickEdit = Color.FromRgb(0xFF, 0xFF, 0xC0),
|
||||
RadioLight = Color.FromRgb(0x0D, 0x9D, 0x0D),
|
||||
TransmitLight = Color.FromRgb(0xFF, 0x00, 0x00),
|
||||
};
|
||||
|
||||
/// For operating at night: the same states, in colours that carry on a dark
|
||||
/// background.
|
||||
public static readonly Theme Dark = new()
|
||||
{
|
||||
Name = "dark",
|
||||
Variant = ThemeVariant.Dark,
|
||||
FormBackground = Color.FromRgb(0x20, 0x22, 0x25),
|
||||
FormForeground = Color.FromRgb(0xE8, 0xE8, 0xE8),
|
||||
ButtonBackground = Color.FromRgb(0x2E, 0x31, 0x35),
|
||||
FieldBackground = Color.FromRgb(0x17, 0x19, 0x1B),
|
||||
FieldForeground = Color.FromRgb(0xE8, 0xE8, 0xE8),
|
||||
GridBackground = Color.FromRgb(0x1B, 0x1D, 0x20),
|
||||
GridAlternateBackground = Color.FromRgb(0x23, 0x26, 0x2A),
|
||||
Worth = Color.FromRgb(0x29, 0x80, 0xB9),
|
||||
WorthBackground = Color.FromRgb(0x1F, 0x4E, 0x6E),
|
||||
Multiplier = Color.FromRgb(0xC0, 0x39, 0x2B),
|
||||
MultiplierBackground = Color.FromRgb(0x6E, 0x24, 0x1C),
|
||||
Multipliers = Color.FromRgb(0x27, 0xAE, 0x60),
|
||||
MultipliersBackground = Color.FromRgb(0x1B, 0x5E, 0x36),
|
||||
Dupe = Color.FromRgb(0x7F, 0x8C, 0x8D),
|
||||
DupeBackground = Color.FromRgb(0x3A, 0x40, 0x40),
|
||||
Cq = Color.FromRgb(0xE7, 0x4C, 0x3C),
|
||||
GoodBackground = Color.FromRgb(0x1E, 0x38, 0x24),
|
||||
EmptyBackground = Color.FromRgb(0x3A, 0x24, 0x24),
|
||||
BadBackground = Color.FromRgb(0x3C, 0x36, 0x1C),
|
||||
QuickEdit = Color.FromRgb(0x4A, 0x44, 0x1E),
|
||||
RadioLight = Color.FromRgb(0x27, 0xAE, 0x60),
|
||||
TransmitLight = Color.FromRgb(0xC0, 0x39, 0x2B),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Nonemm.App.Theming;
|
||||
|
||||
/// Which theme the program is painting with. Everything that draws a colour
|
||||
/// asks here rather than holding a brush of its own, so changing the theme
|
||||
/// changes every window.
|
||||
public static class Themes
|
||||
{
|
||||
public static readonly IReadOnlyList<Theme> All = [Theme.Light, Theme.Dark];
|
||||
|
||||
private static readonly Dictionary<Color, IBrush> brushes = [];
|
||||
|
||||
public static Theme Current { get; private set; } = Theme.Light;
|
||||
|
||||
/// Raised after the theme changes, for the windows that paint what they
|
||||
/// hold rather than rebuilding it.
|
||||
public static event EventHandler? Changed;
|
||||
|
||||
public static Theme Find(string name) =>
|
||||
All.FirstOrDefault(t => string.Equals(t.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
?? Theme.Light;
|
||||
|
||||
public static void Use(string name)
|
||||
{
|
||||
Theme wanted = Find(name);
|
||||
if (wanted == Current)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Current = wanted;
|
||||
Changed?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// One brush per colour, because a brush is created for every row of every
|
||||
/// window that is painted.
|
||||
public static IBrush Brush(Color colour)
|
||||
{
|
||||
if (!brushes.TryGetValue(colour, out IBrush? brush))
|
||||
{
|
||||
brush = new SolidColorBrush(colour);
|
||||
brushes[colour] = brush;
|
||||
}
|
||||
return brush;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user