색상 행에 인라인 팔레트 — 스와치를 누르면 그 자리에서 고르고, '자세히…' 로 피커

색을 바꾸려면 매번 대화상자를 열어야 했다. 실측해 보니 그럴 이유가 없다 —
--db-colors 진단을 신설해 운영 디자인 1,271건의 BackColor·ForeColor 60,319건을 집계한 결과
상위 두 값이 전체의 84%다: White 29,010건(48%), "224, 224, 224" 21,497건(36%).
뒤는 꼬리가 길고 얇다(Transparent 792 · Silver 557 · Window 440 · Gainsboro 415 …).
ForeColor 는 Black 4,920건(85%) · ControlText 345건이 사실상 전부.

스와치를 토글로 바꾸고 팝업에 실사용 상위 값을 배경 12종 / 글자·강조 12종으로 깔았다.
표준 색상표를 늘어놓지 않은 것은 의도다 — 목적은 자주 쓰는 값을 한 번에 고르게 하는 것이지
색을 발명하게 하는 것이 아니다. 임의 색은 팝업 아래 '자세히…' 로 기존 피커
(채도·명도 사각형 + 색상 슬라이더 + HEX·RGB + 프리셋·최근색)를 연다.

핵심은 견본이 레거시 원문 값을 그대로 넣는다는 점이다. 명명색을 RGB 로 풀면
원문과 달라져 왕복 diff 가 생기고, 시스템색이 갖는 의미(Window/Control/ControlText)도 잃는다.
'Window' 를 고르면 "Window" 가 저장된다. 툴팁에는 실사용 건수를 함께 띄운다.

'비우기'는 기본값을 저장하는 것이 아니라 키를 지운다 — 레거시 PropertyGrid 의 재설정과
같고, 원래 BackColor 가 없던 페이지(12.7%)를 원상태로 되돌릴 수 있어야 한다.

함께 고친 것: 페이지 속성 모드에서 '컨트롤을 선택하면 속성이 표시됩니다' 안내가 행과 같이
뜨던 것(HasSelection 이 컨트롤 선택만 보고 있었다). 탭 스트립은 페이지 모드에서 숨긴다.

편집 스모크 4건 추가(원문 값 적용 / 명명색 보존 / 적용 후 닫힘 / 비우기는 키 제거).
회귀: 테스트 124/124, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0,
종이 렌더 P062 바이트 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-13 10:56:47 +09:00
co-authored by Claude Opus 5
parent ac36f93a03
commit e84337705f
7 changed files with 337 additions and 21 deletions
@@ -512,6 +512,8 @@ public sealed class QueryRowViewModel : PropertyRowViewModel
/// <summary>색 행 — 레거시 invariant 문자열("R, G, B"/명명색) + 미리보기 스와치</summary>
public sealed class ColorRowViewModel : PropertyRowViewModel
{
private bool isPaletteOpen;
/// <summary>미리보기 브러시</summary>
public Brush Preview
{
@@ -531,9 +533,47 @@ public sealed class ColorRowViewModel : PropertyRowViewModel
/// <summary>색상 피커 열기 — 확정 시 레거시 invariant 형식("R, G, B")으로 반영</summary>
public M.Framework.WPF.ICustomCommand? PickCommand { get; set; }
/// <summary>인라인 팔레트 열림 — 스와치를 누르면 토글된다</summary>
public bool IsPaletteOpen
{
get => isPaletteOpen;
set => SetProperty(ref isPaletteOpen, value);
}
/// <summary>자주 쓰는 배경색 견본</summary>
public IReadOnlyList<ColorSwatchViewModel> BackgroundSwatches { get; } =
Core.Catalog.LegacyColorCatalog.Backgrounds.Select(e => new ColorSwatchViewModel(e.Value, e.Usage)).ToList();
/// <summary>자주 쓰는 글자색·강조 견본</summary>
public IReadOnlyList<ColorSwatchViewModel> ForegroundSwatches { get; } =
Core.Catalog.LegacyColorCatalog.Foregrounds.Select(e => new ColorSwatchViewModel(e.Value, e.Usage)).ToList();
/// <summary>견본 선택 — 레거시 원문 값을 그대로 넣고 팔레트를 닫는다</summary>
public M.Framework.WPF.ICustomCommand? ApplySwatchCommand { get; set; }
/// <summary>비우기 — 속성값을 지운다(상속/기본값으로 되돌림)</summary>
public M.Framework.WPF.ICustomCommand? ClearCommand { get; set; }
public ColorRowViewModel(string label) : base(label)
{
PickCommand = new M.Framework.WPF.Command((sender, e) => OnPick());
PickCommand = new M.Framework.WPF.Command((sender, e) =>
{
IsPaletteOpen = false;
OnPick();
});
ApplySwatchCommand = new M.Framework.WPF.Command((object? parameter) =>
{
if (parameter is ColorSwatchViewModel swatch)
{
ValueText = swatch.Value;
}
IsPaletteOpen = false;
});
ClearCommand = new M.Framework.WPF.Command((sender, e) =>
{
ValueText = string.Empty;
IsPaletteOpen = false;
});
}
protected override void OnValueApplied() => OnPropertyChanged(nameof(Preview));
@@ -556,6 +596,44 @@ public sealed class ColorRowViewModel : PropertyRowViewModel
}
}
/// <summary>
/// 색 견본 하나 — 인라인 팔레트 항목.
/// <see cref="Value"/> 는 화면 표시용 hex 가 아니라 <b>레거시 원문 그대로</b>(명명색 또는 "R, G, B")다.
/// 명명색을 RGB 로 풀어 저장하면 원문과 달라져 왕복 diff 가 생기고, 시스템색이 갖는 의미(Window/Control)도 잃는다.
/// </summary>
public sealed class ColorSwatchViewModel
{
/// <summary>저장될 레거시 값</summary>
public string Value { get; }
/// <summary>견본 채움</summary>
public Brush Preview { get; }
/// <summary>견본 윤곽 — 배경이 아니라 견본 자신의 명도로 정한다</summary>
public Brush Edge { get; }
/// <summary>툴팁 — 값 + 실사용 건수</summary>
public string ToolTip { get; }
public ColorSwatchViewModel(string value, string usage)
{
Value = value;
var (a, r, g, b) = Core.Serialization.LegacyFormat.ParseColor(value);
var fill = new SolidColorBrush(Color.FromArgb(a, r, g, b));
fill.Freeze();
Preview = fill;
var luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0;
var edge = new SolidColorBrush(luminance > 0.6
? Color.FromRgb(0x8A, 0x8A, 0x8A)
: Color.FromRgb(0xD0, 0xD0, 0xD0));
edge.Freeze();
Edge = edge;
ToolTip = usage.Length > 0 ? $"{value} ({usage})" : value;
}
}
/// <summary>읽기 전용 행 — 중첩/바이너리/참조 등 raw 편집 불가 값 표시</summary>
public sealed class ReadOnlyRowViewModel : PropertyRowViewModel
{