[200]SheetMe Studio Designer 와 6개 표면을 대조한 결과, 뼈대는 이미 같았다 — WindowChrome 파라미터, 5열 골격, 색 토큰 다크·라이트 64개 값 전부, Button/TextBox/Menu/ TabItem/ComboBox/CheckBox 등 스타일이 바이트 단위로 일치한다. 실제 격차는 "껍데기 안에 무엇이 들어가는가" 셋이었다: 스크롤바, 타이틀바 구성, 좌측 패널·인스펙터 정보 밀도. 이번 커밋은 그중 전 화면에 깔려 체감이 가장 큰 스크롤바와 테마 기반을 맞춘다. [스크롤바] [200] 통일 규격 이식 — 9px 폭(기존 11px), 상하 14px 화살표 버튼, 막대 CornerRadius 5. 막대 색은 토큰이 아니라 리터럴 중립 그레이(#8A8F96/#A6ACB3/#C4CAD0)다 — 다크·라이트 양쪽에서 같은 회색으로 보여야 배경에 따라 튀지 않는다([200] 과 동일한 의도적 선택). MinThumbTrack.cs 를 [200] src/Ui/SheetMe.Ui 에서 복사 이식(vendoring, 헤더에 동기화 의무 명시). 기본 WPF Track 은 Thumb.MinHeight 를 무시해 항목이 많은 목록에서 막대가 실처럼 가늘어지는데, 기존 ScrollThumb 의 MinHeight 48 이 바로 그래서 먹지 않고 있었다. MinThumbTrack 이 arrange 를 직접 해 최소 길이 44px 를 보장한다. [창 기본값] Window 암시 스타일에 FontSize 13 + Foreground B.Ink 추가, Background 를 B.Panel → B.AppBg 로. 이게 없어 대화상자 8종이 WPF 기본 12px + 시스템 검정으로 렌더돼 메인 셸(13px)과 결이 어긋나 있었다. 토큰 참조는 DynamicResource 유지 — [200] 의 StaticResource 를 그대로 베끼면 다크에서 값이 굳는다. [아이콘 색 인자] 컨버터 ConverterParameter 를 "크기" 단독에서 "크기|브러시키" 로 확장. 구분자가 없으면 종전 동작(크기만, B.Muted)을 그대로 유지해 기존 호출부 7곳이 안전하다. 지금까지 B.Muted 가 하드코딩돼 있어 활성 문서 탭 아이콘 강조나 레이어 타입 아이콘 색 구분이 구조적으로 불가능했다. 후속 배치의 선행 조건이다. 팔레트 아이콘 매핑도 정리 — MaskedTextBox 가 TextBox 와 같은 아이콘이라 구분이 안 됐다. [그 외] DocTabItem 비활성 탭에 하단 테두리를 넣고 활성만 끊어 크롬식 룩으로. BarToolToggle(플로팅 바 선택/손 — 켜짐을 B.Accent2 로 은은하게) 과 MiniToggle 키 스타일 신설. 암시 ToggleButton 스타일은 인스펙터·레이어 토글도 쓰므로 손대지 않고 키로 분리했다. B.FloatShadow 토큰 신설 — 플로팅 바 그림자가 고정값 하나라 라이트에서 흰 배경에 검은 그림자가 그대로 얹혀 있었다. 다크 20/3/0.5, 라이트 26/5/0.14 로 분리. 기존 34개 토큰 값은 손대지 않았다. 검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0. **종이 렌더 P062 픽셀 대조 차이 0** — 암시 스타일 변경이 용지에 새지 않음을 확인. x:Key 없는 암시 스타일 개수도 이식 전과 동일(25개)하게 유지했다. 남은 배치: C(인스펙터 접이식 섹션·세그 토글) · D(좌측 패널·타이틀바·빈 상태) · E(선택 오버레이) · F(캔버스 크롬 — 종이에 닿는 유일한 배치) · G(토스트·ThemedDialog). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
5.5 KiB
C#
134 lines
5.5 KiB
C#
using System.Globalization;
|
|
using System.Windows.Data;
|
|
using System.Windows.Media;
|
|
|
|
namespace SheetMe.Designer.Controls;
|
|
|
|
/// <summary>
|
|
/// 팔레트 타입 → (Lucide 아이콘, 카테고리) 매핑 — [200]SheetMe 팔레트 타일 룩 이식.
|
|
/// Core(ControlRegistry)는 UI 무관하게 유지하고 표시 계층인 여기서만 매핑한다.
|
|
/// </summary>
|
|
public static class PaletteIconCatalog
|
|
{
|
|
#region Member Fields
|
|
private static readonly Dictionary<string, (string Icon, string Category)> Map = new(StringComparer.Ordinal)
|
|
{
|
|
["Label"] = ("type", "표시"),
|
|
["PictureBox"] = ("image", "표시"),
|
|
["Line"] = ("minus", "표시"),
|
|
// MaskedTextBox 는 TextBox 와 아이콘이 겹쳐 구분이 안 됐다 — [200] 어휘로 분리
|
|
["TextBox"] = ("text-cursor-input", "입력"),
|
|
["MaskedTextBox"] = ("type", "입력"),
|
|
["ComboBox"] = ("list", "입력"),
|
|
["ListBox"] = ("list-checks", "입력"),
|
|
["CheckList"] = ("list-checks", "입력"),
|
|
["DateTimePicker"] = ("calendar", "입력"),
|
|
["CalcBox"] = ("sigma", "입력"),
|
|
["CheckBox"] = ("square-check", "선택"),
|
|
["RadioButton"] = ("circle-dot", "선택"),
|
|
["Panel"] = ("package", "컨테이너"),
|
|
["GroupBox"] = ("box", "컨테이너"),
|
|
["Button"] = ("mouse-pointer-click", "동작/데이터"),
|
|
["DataTable"] = ("table", "동작/데이터"),
|
|
};
|
|
#endregion
|
|
|
|
#region Methods
|
|
/// <summary>타입의 Lucide 아이콘명(미지정 타입은 square)</summary>
|
|
public static string IconOf(string type) => Map.TryGetValue(type, out var entry) ? entry.Icon : "square";
|
|
|
|
/// <summary>타입의 팔레트 카테고리(그룹 헤더)</summary>
|
|
public static string CategoryOf(string type) => Map.TryGetValue(type, out var entry) ? entry.Category : "기타";
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이콘 컨버터 공용 파라미터 해석 — <c>"크기"</c> 또는 <c>"크기|브러시키"</c>.
|
|
///
|
|
/// 브러시 키를 생략하면 종전처럼 <c>B.Muted</c> 를 쓴다(하위호환). 색을 인자로 받을 수 있어야
|
|
/// 활성 문서 탭 아이콘을 강조색으로, 레이어 타입 아이콘을 본문색으로 굽는 것이 가능해진다.
|
|
/// </summary>
|
|
internal static class IconParam
|
|
{
|
|
public static (double Size, Brush Brush) Parse(object? parameter, double defaultSize)
|
|
{
|
|
var size = defaultSize;
|
|
var key = "B.Muted";
|
|
|
|
if (parameter is string text && text.Length > 0)
|
|
{
|
|
var bar = text.IndexOf('|');
|
|
var sizePart = bar >= 0 ? text[..bar] : text;
|
|
if (bar >= 0 && bar + 1 < text.Length)
|
|
{
|
|
key = text[(bar + 1)..].Trim();
|
|
}
|
|
if (double.TryParse(sizePart, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
|
|
{
|
|
size = parsed;
|
|
}
|
|
}
|
|
|
|
var brush = System.Windows.Application.Current?.TryFindResource(key) as Brush ?? Brushes.Gray;
|
|
return (size, brush);
|
|
}
|
|
}
|
|
|
|
/// <summary>팔레트 타입명 → Lucide 아이콘 비주얼(FrameworkElement) 컨버터. parameter=<c>"크기"</c> 또는 <c>"크기|브러시키"</c>(기본 18, B.Muted)</summary>
|
|
public sealed class TypeToIconConverter : IValueConverter
|
|
{
|
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
{
|
|
if (value is not string type)
|
|
{
|
|
return null;
|
|
}
|
|
var (size, brush) = IconParam.Parse(parameter, 18);
|
|
return LucideIcons.Icon(PaletteIconCatalog.IconOf(type), size, brush);
|
|
}
|
|
|
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>팔레트 타입명 → 카테고리명 컨버터(CollectionViewSource 그룹핑용)</summary>
|
|
public sealed class TypeToCategoryConverter : IValueConverter
|
|
{
|
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> value is string type ? PaletteIconCatalog.CategoryOf(type) : "기타";
|
|
|
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>불리언 반전 컨버터(선택 도구 토글 = !IsHandTool 표시용)</summary>
|
|
public sealed class InverseBoolConverter : IValueConverter
|
|
{
|
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> value is not true;
|
|
|
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> value is not true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lucide 아이콘명 → 비주얼 컨버터(문서 탭 file-text, 플로팅 바 layout-grid 등 고정 아이콘용).
|
|
/// Binding Source 에 아이콘명 문자열을 넣어 사용 — 항목마다 새 비주얼이 생성되어 공유 부모 충돌이 없다.
|
|
/// parameter=<c>"크기"</c> 또는 <c>"크기|브러시키"</c>(기본 14, B.Muted).
|
|
/// </summary>
|
|
public sealed class IconNameToVisualConverter : IValueConverter
|
|
{
|
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
{
|
|
if (value is not string name)
|
|
{
|
|
return null;
|
|
}
|
|
var (size, brush) = IconParam.Parse(parameter, 14);
|
|
return LucideIcons.Icon(name, size, brush);
|
|
}
|
|
|
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
=> throw new NotSupportedException();
|
|
}
|