Files
OpenNest/OpenNest.Desktop/ColorSchemeSerializer.cs
T
ajandClaude Opus 5.5 cc3711c11c refactor(desktop): move colour schemes into OpenNest.Desktop
ColorScheme, ColorSchemeRegistry, ColorSchemeSerializer and the Schemes
folder move into the toolkit-neutral library, keeping the OpenNest
namespace, so the Avalonia app can share the built-in palettes, the
scheme files and the recolour-on-open rule.

ColorScheme is now pure colour data with a Version that changes with
any colour. Its GDI+ pens and brushes move to GdiSchemeTools in the
WinForms project (still one set per thread, rebuilt after a colour
change), reached through scheme.Gdi(). The legacy part outline uses
ColorShade.Dark, a port of ControlPaint.Dark's HLS arithmetic; a
WinForms test compares the two across a colour sweep on Windows.

The registry no longer walks Application.OpenForms: Apply raises
Applied, and EditNestForm subscribes once to recolour every open nest
window as before. The saved scheme is read under the same
ActiveColorScheme setting (SavedSchemeName/ApplySaved for the shared
user-settings file); WinForms still reads it through Settings.Default.
Schemes/*.json now copies beside both desktop apps from OpenNest.Desktop.

Pure scheme tests move to OpenNest.Desktop.Tests, with new ones for
built-in round trips, scheme files, bad files, the saved name and the
recolour rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:23:48 -05:00

131 lines
6.0 KiB
C#

using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text.Json;
namespace OpenNest
{
public static class ColorSchemeSerializer
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
public static string Serialize(ColorScheme scheme)
{
var dto = new ColorSchemeDto
{
Name = scheme.Name,
BackgroundColor = ToHex(scheme.BackgroundColor),
LayoutOutlineColor = ToHex(scheme.LayoutOutlineColor),
LayoutFillColor = ToHex(scheme.LayoutFillColor),
BoundingBoxColor = ToHex(scheme.BoundingBoxColor),
RapidColor = ToHex(scheme.RapidColor),
OriginColor = ToHex(scheme.OriginColor),
EdgeSpacingColor = ToHex(scheme.EdgeSpacingColor),
PreviewPartColor = ToHex(scheme.PreviewPartColor),
PartOutlineColor = scheme.PartOutlineColor.IsEmpty ? null : ToHex(scheme.PartOutlineColor),
SelectedPartColor = scheme.SelectedPartColor.IsEmpty ? null : ToHex(scheme.SelectedPartColor),
EtchColor = ToHex(scheme.EtchColor),
LibraryBackgroundColor = ToHex(scheme.LibraryBackgroundColor),
LibraryFillColor = ToHex(scheme.LibraryFillColor),
LibraryOutlineColor = ToHex(scheme.LibraryOutlineColor),
LibraryEtchColor = ToHex(scheme.LibraryEtchColor),
LibraryBendColor = ToHex(scheme.LibraryBendColor),
PartColors = scheme.PartColors.Select(ToHex).ToArray(),
UseGoldenAngleColors = scheme.UseGoldenAngleColors,
};
return JsonSerializer.Serialize(dto, JsonOptions);
}
public static ColorScheme Deserialize(string json)
{
var dto =
JsonSerializer.Deserialize<ColorSchemeDto>(json, JsonOptions)
?? throw new JsonException("ColorScheme JSON was null");
var scheme = new ColorScheme
{
Name = dto.Name ?? "Unnamed",
BackgroundColor = FromHex(dto.BackgroundColor),
LayoutOutlineColor = FromHex(dto.LayoutOutlineColor),
LayoutFillColor = FromHex(dto.LayoutFillColor),
BoundingBoxColor = FromHex(dto.BoundingBoxColor),
RapidColor = FromHex(dto.RapidColor),
OriginColor = FromHex(dto.OriginColor),
EdgeSpacingColor = FromHex(dto.EdgeSpacingColor),
PreviewPartColor = FromHex(dto.PreviewPartColor),
PartOutlineColor = string.IsNullOrWhiteSpace(dto.PartOutlineColor) ? Color.Empty : FromHex(dto.PartOutlineColor),
SelectedPartColor = string.IsNullOrWhiteSpace(dto.SelectedPartColor) ? Color.Empty : FromHex(dto.SelectedPartColor),
EtchColor = string.IsNullOrWhiteSpace(dto.EtchColor) ? Color.Lime : FromHex(dto.EtchColor),
PartColors = (dto.PartColors ?? new string[0]).Select(FromHex).ToArray(),
UseGoldenAngleColors = dto.UseGoldenAngleColors,
};
// Schemes saved before the library style keep its defaults.
scheme.LibraryBackgroundColor = FromHexOr(dto.LibraryBackgroundColor, scheme.LibraryBackgroundColor);
scheme.LibraryFillColor = FromHexOr(dto.LibraryFillColor, scheme.LibraryFillColor);
scheme.LibraryOutlineColor = FromHexOr(dto.LibraryOutlineColor, scheme.LibraryOutlineColor);
scheme.LibraryEtchColor = FromHexOr(dto.LibraryEtchColor, scheme.LibraryEtchColor);
scheme.LibraryBendColor = FromHexOr(dto.LibraryBendColor, scheme.LibraryBendColor);
return scheme;
}
private static Color FromHexOr(string hex, Color fallback) =>
string.IsNullOrWhiteSpace(hex) ? fallback : FromHex(hex);
private static string ToHex(Color c) =>
"#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
private static Color FromHex(string hex)
{
if (string.IsNullOrWhiteSpace(hex))
return Color.Black;
var h = hex.TrimStart('#');
if (h.Length < 6)
return Color.Black;
var r = byte.Parse(
h.Substring(0, 2),
NumberStyles.HexNumber,
CultureInfo.InvariantCulture
);
var g = byte.Parse(
h.Substring(2, 2),
NumberStyles.HexNumber,
CultureInfo.InvariantCulture
);
var b = byte.Parse(
h.Substring(4, 2),
NumberStyles.HexNumber,
CultureInfo.InvariantCulture
);
return Color.FromArgb(r, g, b);
}
private class ColorSchemeDto
{
public string Name { get; set; }
public string BackgroundColor { get; set; }
public string LayoutOutlineColor { get; set; }
public string LayoutFillColor { get; set; }
public string BoundingBoxColor { get; set; }
public string RapidColor { get; set; }
public string OriginColor { get; set; }
public string EdgeSpacingColor { get; set; }
public string PreviewPartColor { get; set; }
public string PartOutlineColor { get; set; }
public string SelectedPartColor { get; set; }
public string EtchColor { get; set; }
public string LibraryBackgroundColor { get; set; }
public string LibraryFillColor { get; set; }
public string LibraryOutlineColor { get; set; }
public string LibraryEtchColor { get; set; }
public string LibraryBendColor { get; set; }
public string[] PartColors { get; set; }
public bool UseGoldenAngleColors { get; set; }
}
}
}