초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너. 기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49. 이 커밋에 함께 포함된 자격증명 분리: - appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환 - 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외, csproj Debug 조건부 복사라 Release 산출물에 실리지 않음) - ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경, 미치환 플레이스홀더는 '미설정'으로 간주 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace SheetMe.Designer.ViewModels;
|
||||
|
||||
/// <summary>오버레이 가이드선 1개 — 월드 좌표 수직/수평선</summary>
|
||||
public sealed class GuideLineInfo
|
||||
{
|
||||
/// <summary>수직선 여부(false=수평선)</summary>
|
||||
public bool IsVertical { get; init; }
|
||||
|
||||
/// <summary>선 위치(월드 X 또는 Y)</summary>
|
||||
public double Position { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>탭순서 배지 1개 — 월드 좌표(컨트롤 좌상단)</summary>
|
||||
public sealed class TabBadgeInfo
|
||||
{
|
||||
/// <summary>배지 X(월드)</summary>
|
||||
public double X { get; init; }
|
||||
|
||||
/// <summary>배지 Y(월드)</summary>
|
||||
public double Y { get; init; }
|
||||
|
||||
/// <summary>표시 텍스트(순번 또는 "–")</summary>
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>순번 지정 여부(지정=파랑, 미지정=회색)</summary>
|
||||
public bool IsAssigned { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>선택 핸들 1개 — 월드 좌표(중심점)</summary>
|
||||
public sealed class HandleInfo
|
||||
{
|
||||
/// <summary>핸들 인덱스(0=NW,1=N,2=NE,3=E,4=SE,5=S,6=SW,7=W)</summary>
|
||||
public int Index { get; init; }
|
||||
|
||||
/// <summary>핸들 좌상단 X(월드)</summary>
|
||||
public double X { get; init; }
|
||||
|
||||
/// <summary>핸들 좌상단 Y(월드)</summary>
|
||||
public double Y { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택/마퀴/가이드 오버레이 표시 상태 — 전부 월드 좌표.
|
||||
/// 오버레이 뷰는 이 상태를 그리기만 한다(입력 처리 없음).
|
||||
/// </summary>
|
||||
public sealed class SelectionOverlayViewModel : ViewModelBase
|
||||
{
|
||||
#region Member Fields
|
||||
/// <summary>핸들 한 변 크기(논리 px)</summary>
|
||||
public const double HandleSize = 8;
|
||||
|
||||
private bool hasSelection;
|
||||
private double selX;
|
||||
private double selY;
|
||||
private double selW;
|
||||
private double selH;
|
||||
private bool showHandles;
|
||||
private bool hasMarquee;
|
||||
private double marX;
|
||||
private double marY;
|
||||
private double marW;
|
||||
private double marH;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>선택 박스 표시 여부</summary>
|
||||
public bool HasSelection { get => hasSelection; set => SetProperty(ref hasSelection, value); }
|
||||
|
||||
/// <summary>선택 박스 X(월드)</summary>
|
||||
public double SelX { get => selX; set => SetProperty(ref selX, value); }
|
||||
|
||||
/// <summary>선택 박스 Y(월드)</summary>
|
||||
public double SelY { get => selY; set => SetProperty(ref selY, value); }
|
||||
|
||||
/// <summary>선택 박스 너비</summary>
|
||||
public double SelW { get => selW; set => SetProperty(ref selW, value); }
|
||||
|
||||
/// <summary>선택 박스 높이</summary>
|
||||
public double SelH { get => selH; set => SetProperty(ref selH, value); }
|
||||
|
||||
/// <summary>리사이즈 핸들 표시 여부(잠금 선택 시 숨김)</summary>
|
||||
public bool ShowHandles { get => showHandles; set => SetProperty(ref showHandles, value); }
|
||||
|
||||
/// <summary>핸들 8개(월드 좌표)</summary>
|
||||
public ObservableCollection<HandleInfo> Handles { get; } = new();
|
||||
|
||||
/// <summary>마퀴 표시 여부</summary>
|
||||
public bool HasMarquee { get => hasMarquee; set => SetProperty(ref hasMarquee, value); }
|
||||
|
||||
/// <summary>마퀴 X(월드)</summary>
|
||||
public double MarX { get => marX; set => SetProperty(ref marX, value); }
|
||||
|
||||
/// <summary>마퀴 Y(월드)</summary>
|
||||
public double MarY { get => marY; set => SetProperty(ref marY, value); }
|
||||
|
||||
/// <summary>마퀴 너비</summary>
|
||||
public double MarW { get => marW; set => SetProperty(ref marW, value); }
|
||||
|
||||
/// <summary>마퀴 높이</summary>
|
||||
public double MarH { get => marH; set => SetProperty(ref marH, value); }
|
||||
|
||||
/// <summary>정렬 가이드선 목록</summary>
|
||||
public ObservableCollection<GuideLineInfo> Guides { get; } = new();
|
||||
|
||||
/// <summary>탭순서 배지 목록(탭순서 편집 모드 전용)</summary>
|
||||
public ObservableCollection<TabBadgeInfo> TabBadges { get; } = new();
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>선택 박스/핸들 갱신 — bbox 는 월드 좌표</summary>
|
||||
public void UpdateSelection(Rect? bbox, bool handlesVisible)
|
||||
{
|
||||
if (bbox is null || bbox.Value.IsEmpty)
|
||||
{
|
||||
HasSelection = false;
|
||||
ShowHandles = false;
|
||||
Handles.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var rect = bbox.Value;
|
||||
HasSelection = true;
|
||||
SelX = rect.X;
|
||||
SelY = rect.Y;
|
||||
SelW = rect.Width;
|
||||
SelH = rect.Height;
|
||||
ShowHandles = handlesVisible;
|
||||
|
||||
Handles.Clear();
|
||||
if (!handlesVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var half = HandleSize / 2;
|
||||
var positions = HandlePositions(rect);
|
||||
for (var i = 0; i < positions.Length; i++)
|
||||
{
|
||||
Handles.Add(new HandleInfo { Index = i, X = positions[i].X - half, Y = positions[i].Y - half });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>핸들 중심 좌표 8개(0=NW 시계방향)</summary>
|
||||
public static Point[] HandlePositions(Rect rect) => new[]
|
||||
{
|
||||
new Point(rect.Left, rect.Top),
|
||||
new Point(rect.Left + rect.Width / 2, rect.Top),
|
||||
new Point(rect.Right, rect.Top),
|
||||
new Point(rect.Right, rect.Top + rect.Height / 2),
|
||||
new Point(rect.Right, rect.Bottom),
|
||||
new Point(rect.Left + rect.Width / 2, rect.Bottom),
|
||||
new Point(rect.Left, rect.Bottom),
|
||||
new Point(rect.Left, rect.Top + rect.Height / 2),
|
||||
};
|
||||
|
||||
/// <summary>마퀴 갱신 — null 이면 숨김</summary>
|
||||
public void UpdateMarquee(Rect? rect)
|
||||
{
|
||||
if (rect is null)
|
||||
{
|
||||
HasMarquee = false;
|
||||
return;
|
||||
}
|
||||
HasMarquee = true;
|
||||
MarX = rect.Value.X;
|
||||
MarY = rect.Value.Y;
|
||||
MarW = rect.Value.Width;
|
||||
MarH = rect.Value.Height;
|
||||
}
|
||||
|
||||
/// <summary>가이드선 교체</summary>
|
||||
public void SetGuides(double? guideX, double? guideY)
|
||||
{
|
||||
Guides.Clear();
|
||||
if (guideX is not null)
|
||||
{
|
||||
Guides.Add(new GuideLineInfo { IsVertical = true, Position = guideX.Value });
|
||||
}
|
||||
if (guideY is not null)
|
||||
{
|
||||
Guides.Add(new GuideLineInfo { IsVertical = false, Position = guideY.Value });
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user