From 73095c141591af6b491d46682c8027d7649a23c4 Mon Sep 17 00:00:00 2001 From: Msystech Date: Thu, 20 Aug 2026 18:43:10 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=B4=20=EA=B3=84=EC=B8=B5=EA=B3=BC=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=EB=A5=BC=20=EB=A7=90=ED=95=98=EA=B2=8C=20=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 했다. 원인은 색이 아니라 산수였다. ## 부모와 자식이 같은 x 에 그려지고 있었다 계층을 나타내는 수단은 들여쓰기 하나뿐인데(평탄한 ListBox 다), 그 폭이 셰브론 폭에 정확히 상쇄되고 있었다. - 접기 셰브론은 폭 16, 자식이 없으면 Collapsed → 폭이 통째로 사라진다 - 컨테이너 자식 들여쓰기는 정확히 16 그래서 패널의 아이콘과 그 직속 자식의 아이콘이 같은 x 에 놓였다. 목록만 보면 부모-자식이 아니라 형제로 읽힌다. 3단 중첩에서는 어느 행이 누구 밑인지 따라갈 수가 없다. 셰브론 자리를 항상 남기도록 고쳤다 — 이제 깊이 한 단계가 16px 사다리로 보인다. ## 접힌 컨테이너가 무엇을 품었는지 말하지 않았다 페이지 행에는 (7), 그룹 폴더에는 (3)이 이미 붙는데 컨테이너만 없었다. 접힌 패널이 3개를 품었는지 300개를 품었는지 펴 보기 전에는 알 수 없었고, 그 패널을 지우거나 옮기기 전에 무엇이 함께 가는지도 알 수 없었다. 중첩까지 세어 붙인다 — 그 수가 곧 "이걸 지우면 몇 개가 같이 지워지나"다. ## 고른 컨테이너의 범위가 안 보였다 캔버스에서 패널을 끌면 자식이 전부 따라 움직이는데 목록은 그 사실을 말하지 않았다. 그룹을 통째로 고르면 멤버가 네이비 밴드로 묶이는 기구가 이미 있었는데 컨테이너에는 그 갈래가 없었다. 조상 중에 선택된 컨테이너가 있으면 같은 밴드를 준다. 그룹 판정보다 뒤에 둬서 기존 그룹 색을 이기지 않는다. ## 판정 - 단위 시험 388 · edit-smoke 384건 전건 통과 (레이어 4건 추가) - --dialog-shots 69장 전건 통과 (레이어 샷 4종을 눈으로 대조 — 사다리·개수·밴드 확인) - --modal-check · --maxrect · --scale-budget 전건 통과 - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 ## 아직 남은 것 행 높이가 1줄/2줄로 갈린다 — 표시명이 Id 와 다를 때만 둘째 줄(Id)이 뜨기 때문이다 (ControlViewModel.LayerId). 목록을 훑을 때 리듬이 깨지는데, 고치려면 '항상 두 줄'(공간 낭비)이나 '한 줄에 합치기'(정보 밀도 변화) 중 하나를 골라야 해서 이번 범위에 넣지 않았다. 캔버스에서 고른 것을 목록이 따라 스크롤·펼치는 것(조사에서 medium 으로 나온 항목)도 남았다. Co-Authored-By: Claude Fable 5 --- src/SheetMe.Designer/Diagnostics/EditSmoke.cs | 58 +++++++++++++++++++ .../Controls/TypedControlViewModels.cs | 24 ++++++++ .../ViewModels/DesignerViewModel.cs | 19 ++++++ .../Views/LayerPanelView.xaml | 38 ++++++++++-- 4 files changed, 134 insertions(+), 5 deletions(-) diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index 4eae119..dd8b223 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -1112,6 +1112,64 @@ public static class EditSmoke $"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}"); } + // 15-a0000) 레이어 목록이 계층과 범위를 말하는가. + // + // 사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 한 곳이다. 원인은 색이 아니라 산수였다 — + // 셰브론이 자식 없는 행에서 Collapsed 로 사라지며 폭 16 이 통째로 없어지는데 + // 컨테이너 자식 들여쓰기도 정확히 16 이라, 부모와 자식의 아이콘이 같은 x 에 놓였다. + { + var tree = new DesignerViewModel(new FormDesignDataBusiness().CreateNew()); + tree.AddControlAt("RadioButton", new Point(100, 100)); + tree.AddControlAt("RadioButton", new Point(100, 140)); + tree.Selection.Set(tree.Pages[0].Controls.ToList(), tree.Pages[0].Controls[0]); + tree.GroupSelection(); + var wrapped = tree.Pages[0].Controls.OfType().First(); + + Check("레이어: 컨테이너가 자식 수를 말한다", wrapped.LayerChildCountText == "(2)", + wrapped.LayerChildCountText); + + // 중첩까지 센다 — "이걸 지우면 몇 개가 같이 지워지나"가 그 수다. + // 중첩 구조는 모델로 직접 짠다(Ctrl+G 를 두 번 겹치는 경로는 이 검사의 대상이 아니다). + var nestDoc = new FormDocument(); + var nestPage = new FormPage { Width = 720, Height = 856 }; + nestDoc.Pages.Add(nestPage); + var outerModel = new ControlElement + { + Type = "Panel", Id = "바깥", + Bounds = new LayoutRect { X = 20, Y = 20, W = 300, H = 200 }, + }; + var innerModel = new ControlElement + { + Type = "Panel", Id = "안쪽", + Bounds = new LayoutRect { X = 10, Y = 10, W = 200, H = 120 }, + }; + for (var i = 0; i < 2; i++) + { + innerModel.Children.Add(new ControlElement + { + Type = "RadioButton", Id = $"보기{i}", + Bounds = new LayoutRect { X = 10, Y = 10 + (i * 30), W = 100, H = 20 }, + }); + } + outerModel.Children.Add(innerModel); + nestPage.Controls.Add(outerModel); + var nested = new DesignerViewModel(nestDoc); + + var outer = nested.Pages[0].Controls.OfType().First(); + var inner = outer.Children.OfType().First(); + Check("레이어: 자식 수는 중첩까지 센다", outer.LayerChildCountText == "(3)", + $"{outer.LayerChildCountText} (바깥 = 안쪽 패널 1 + 라디오 2)"); + + // 컨테이너를 고르면 그 안이 밴드로 묶여야 한다 + nested.Selection.SetSingle(outer); + Check("레이어: 고른 컨테이너의 안쪽이 범위로 표시된다", + inner.Highlight == LayerHighlight.Navy && outer.Highlight == LayerHighlight.Blue, + $"바깥={outer.Highlight} 안쪽={inner.Highlight}"); + Check("레이어: 손자까지 범위에 들어간다", + inner.Children.All(c => c.Highlight == LayerHighlight.Navy), + string.Join(",", inner.Children.Select(c => c.Highlight.ToString()))); + } + // 15-a000) 데이터소스를 실사용과 같은 경로로 만들었을 때도 러너가 찾는가. // // 이 검사가 없어서 배선 전체가 실사용에서 죽어 있는 것을 오래 못 봤다. diff --git a/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs b/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs index e59bc3a..f5b58c5 100644 --- a/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs +++ b/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs @@ -136,6 +136,30 @@ public abstract class ContainerViewModel : ControlViewModel /// 레이어 목록에서 펼침 셰브론을 그릴지 — 자식이 있을 때만 public override bool HasLayerChildren => Children.Count > 0; + /// + /// 레이어 목록에 붙는 자식 수 — 접혀 있어도 무엇이 딸려 있는지 알 수 있어야 한다. + /// + /// 페이지 행에는 (7), 그룹 폴더에는 (3)이 이미 붙는데 컨테이너만 없었다. + /// 그래서 접힌 패널이 3개를 품었는지 300개를 품었는지 펴 보기 전에는 알 수 없었고, + /// 그 패널을 지우거나 옮기기 전에 무엇이 함께 가는지도 알 수 없었다. + /// 중첩까지 세어 실제로 딸려 가는 수를 말한다. + /// + public string LayerChildCountText => $"({CountAll(Children)})"; + + private static int CountAll(IEnumerable items) + { + var total = 0; + foreach (var item in items) + { + total++; + if (item is ContainerViewModel container) + { + total += CountAll(container.Children); + } + } + return total; + } + protected ContainerViewModel(ControlElement model) : base(model) { } /// 시각 재해석 — 자식에게 상속 컨텍스트 전파(WinForms Font/ForeColor 상속) diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs index 1f2116b..3a273f1 100644 --- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs @@ -2611,6 +2611,19 @@ public sealed class DesignerViewModel : ViewModelBase => total.TryGetValue(id, out var t) && t > 0 && selected.TryGetValue(id, out var s) && s == t; + // 조상 중에 '선택된 컨테이너'가 있는가 — 선택한 패널의 범위를 목록에 그리기 위한 판정 + static bool HasSelectedContainerAncestor(ControlViewModel viewModel) + { + for (var parent = viewModel.Parent; parent is not null; parent = parent.Parent) + { + if (parent.IsSelected) + { + return true; + } + } + return false; + } + var states = new LayerHighlight[LayerRows.Count]; for (var i = 0; i < LayerRows.Count; i++) { @@ -2620,6 +2633,12 @@ public sealed class DesignerViewModel : ViewModelBase ControlViewModel c when c.Model.GroupId is { } gid && GroupFullySelected(gid) => LayerHighlight.Navy, ControlViewModel c when c.IsSelected => LayerHighlight.Blue, LayerGroupViewModel g when GroupFullySelected(g.GroupId) => LayerHighlight.Blue, + // 선택한 컨테이너 안에 있는 것 — 그룹 판정보다 뒤에 둔다(그룹 색을 이기면 안 된다). + // + // 캔버스에서 패널을 끌면 자식이 전부 따라 움직이는데 목록은 그 사실을 말하지 않았다. + // 밴드가 붙으면 "이걸 지우면 무엇이 같이 지워지나"가 선택 즉시 보인다 — + // 접었다 펴 보지 않아도 된다. + ControlViewModel c when HasSelectedContainerAncestor(c) => LayerHighlight.Navy, _ => LayerHighlight.None, }; } diff --git a/src/SheetMe.Designer/Views/LayerPanelView.xaml b/src/SheetMe.Designer/Views/LayerPanelView.xaml index bd508b7..ca96be7 100644 --- a/src/SheetMe.Designer/Views/LayerPanelView.xaml +++ b/src/SheetMe.Designer/Views/LayerPanelView.xaml @@ -283,13 +283,41 @@ + + + + + + - - + + + +