루프를 키보드로 닫는다 — Tab 다음, Shift+Tab 이전, Enter 안, Esc 밖
이 도구의 일은 "컨트롤 하나 골라 값 하나 바꾸고 다음으로"의 반복인데, <b>그 루프가 마우스 없이는 한 바퀴도 돌지 않았다.</b> 선택을 옮길 키가 하나도 없었다 — 캔버스 키맵 18개 중 선택 이동 0, 편집 시작 0. 화살표는 선택이 아니라 컨트롤을 민다. Label 59,997 + TextBox 28,574 의 문구를 고치는 일이 전부 더블클릭에 묶여 있었다. <b>읽는 순서로 돈다 — z-order 가 아니다.</b> 계획서는 z-order 순회를 적었지만, Tab 을 누르는 이유는 "서식을 위에서 아래로 훑으며 문구를 고치는 것"이다. 기록지는 위→아래·왼쪽→오른쪽으로 읽으므로 그 순서가 손에 맞는다. z-order 는 겹침을 정하는 축이라 화면상 순서와 무관하게 튄다. 같은 줄로 볼 만큼 가까우면(8px = 그리드 두 칸) X 로 가른다. EMR 런타임 입력 순서인 ApplyTabOrder 와는 다른 개념이다 — 그건 저장되는 값이다. <b>범위는 형제다.</b> 컨테이너 안으로는 Enter, 밖으로는 Esc. 전체를 한 줄로 이으면 패널 안 라디오 15,026개를 지나야 다음 항목에 닿는다. Esc 가 이제 한 겹만 나간다. 전에는 항상 선택을 통째로 풀어서, 패널 자식을 고른 뒤 Esc 를 누르면 부모로 돌아가는 대신 선택이 사라졌다. 드래그 중 Esc 는 그대로 드래그 취소다. 선택이 옮겨지면 RevealControl 로 화면에 데려온다 — 레이어 목록 화살표 이동에 이것이 없어서 화면 밖 컨트롤을 눈 없이 편집할 수 있었다. PreviewKeyDown 에 붙인다. KeyDown 이면 WPF 포커스 관리자가 Tab 을 먼저 먹어 포커스가 캔버스 밖(좌측 패널·문서 탭·플로팅 바)으로 나간다. 검사는 배선이 아니라 <b>한 바퀴가 도는지</b>를 본다 — 9건: 읽는 순서 · 끝에서 처음으로 되돌아옴 · 역방향 · 컨테이너 안 · 밖 · 나갈 곳 없음 · 편집 시작. 컨테이너는 GroupSelection 이 아니라 문서에 직접 만든다 — 그 경로에는 거절 조건(라디오·z 불연속)이 있어 막히면 순회 대신 그룹 규칙을 재게 된다 (실제로 처음에 그렇게 막혔다). 게이트: 테스트 285/285, --edit-smoke 0실패(신규 9), --dialog-shots 넘침 0(대조군 4/4), --scale-budget 5/5, --maxrect 0실패, --cleartype 11/11, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1b49f07dd9
commit
d6052610b9
@@ -793,6 +793,88 @@ public sealed class DesignerViewModel : ViewModelBase
|
||||
/// 있는지 알 수 없었다. 다중 페이지 서식이면 다른 페이지에 있을 수도 있어 더 그렇다.
|
||||
/// 스크롤은 뷰포트 크기를 알아야 하므로 캔버스 뷰에 위임한다.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 형제 중 다음(또는 이전) 컨트롤을 고른다 — Tab / Shift+Tab.
|
||||
///
|
||||
/// <b>왜 z-order 가 아니라 읽는 순서인가.</b> 계획서는 z-order 순회를 적었지만,
|
||||
/// 이 도구에서 Tab 을 누르는 이유는 "서식을 위에서 아래로 훑으며 문구를 고치는 것"이다.
|
||||
/// 기록지는 위에서 아래·왼쪽에서 오른쪽으로 읽으므로 그 순서가 손에 맞는다.
|
||||
/// z-order 는 겹침을 정하는 축이라 화면상 순서와 무관하게 튄다.
|
||||
/// (EMR 런타임 입력 순서인 <see cref="ApplyTabOrder"/> 와는 다른 개념이다 — 그건 저장되는 값이다.)
|
||||
///
|
||||
/// 범위는 <b>형제</b>다. 컨테이너 안으로는 Enter, 밖으로는 Esc 로 오간다 —
|
||||
/// 전체를 한 줄로 이으면 패널 안 라디오 15,026개를 지나야 다음 항목에 닿는다.
|
||||
/// </summary>
|
||||
public void SelectSibling(bool backward)
|
||||
{
|
||||
var page = Selection.ActivePage ?? SelectedPage ?? Pages.FirstOrDefault();
|
||||
if (page is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var current = Selection.Primary;
|
||||
var siblings = SiblingsOf(current, page);
|
||||
if (siblings.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 읽는 순서 — 같은 줄로 볼 만큼 가까우면(8px) X 로 가른다. 그리드가 4px 이라 그 두 칸이다.
|
||||
var ordered = siblings
|
||||
.OrderBy(c => Math.Round(c.Y / 8))
|
||||
.ThenBy(c => c.X)
|
||||
.ThenBy(c => c.Model.Id, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var index = current is null ? -1 : ordered.IndexOf(current);
|
||||
var next = index < 0
|
||||
? ordered[backward ? ^1 : 0]
|
||||
: ordered[((index + (backward ? -1 : 1)) % ordered.Count + ordered.Count) % ordered.Count];
|
||||
|
||||
Selection.SetSingle(next);
|
||||
// 눈에 보이지 않는 것을 고쳐 놓는 일이 생기지 않게 화면으로 데려온다 —
|
||||
// 레이어 목록 화살표 이동에 이것이 없어서 화면 밖 컨트롤을 눈 없이 편집할 수 있었다.
|
||||
RevealControl(next);
|
||||
}
|
||||
|
||||
/// <summary>Enter — 컨테이너면 안으로 들어가고, 글자를 담는 것이면 편집을 시작한다</summary>
|
||||
public bool EnterSelection()
|
||||
{
|
||||
if (Selection.Primary is not { } primary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (primary is ContainerViewModel container && container.Children.FirstOrDefault() is { } child)
|
||||
{
|
||||
Selection.SetSingle(child);
|
||||
RevealControl(child);
|
||||
return true;
|
||||
}
|
||||
if (IsTextEditable(primary))
|
||||
{
|
||||
RequestInlineEdit(primary);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Esc — 컨테이너 밖으로 한 겹 나간다. 나갈 곳이 없으면 false(호출부가 선택을 푼다)</summary>
|
||||
public bool SelectParent()
|
||||
{
|
||||
if (Selection.Primary?.Parent is not { } parent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Selection.SetSingle(parent);
|
||||
RevealControl(parent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>이 컨트롤과 같은 부모를 가진 것들 — 부모가 없으면 페이지 최상위</summary>
|
||||
private List<ControlViewModel> SiblingsOf(ControlViewModel? viewModel, PageViewModel page)
|
||||
=> viewModel?.Parent is ContainerViewModel parent
|
||||
? parent.Children.ToList()
|
||||
: ControlsOfPage(page).Where(c => c.Parent is null).ToList();
|
||||
|
||||
public void RevealControl(ControlViewModel viewModel)
|
||||
{
|
||||
// 다른 페이지의 컨트롤이면 그 페이지를 활성으로 — 안 그러면 선택만 되고 화면은 안 따라간다
|
||||
|
||||
Reference in New Issue
Block a user