diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index e0ceb38..082da2e 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -37,6 +37,7 @@ public static class EditSmoke var designer = new DesignerViewModel(business.CreateNew()); var none = new PointerContext(false, false, false, 1); var alt = new PointerContext(false, false, true, 1); + var doubleClick = new PointerContext(false, false, false, 2); var ctrl = new PointerContext(true, false, false, 1); ControlViewModel Find(string type) @@ -251,6 +252,32 @@ public static class EditSmoke && wrap.Children.Any(c => c.X == 100 && c.Y == 40), string.Join(" ", wrap?.Children.Select(c => $"({c.X},{c.Y})") ?? Array.Empty())); + // 자식을 집어도 패널이 잡히고, 끌면 묶음째 움직인다 — 이게 '그룹처럼'의 알맹이다 + var kid = wrap!.Children[0]; + var kidWorld = grp.WorldBoundsOf(kid); + var grab = new Point(kidWorld.X + kidWorld.Width / 2, kidWorld.Y + kidWorld.Height / 2); + grp.Selection.Clear(); + grp.Interaction.PointerDown(grab, none); + Check("그룹: 자식을 집으면 패널이 잡힌다", + grp.Selection.Items.Count == 1 && ReferenceEquals(grp.Selection.Items[0], wrap), + $"실제 {grp.Selection.Items.Count}개 / {grp.Selection.Items.FirstOrDefault()?.Type}"); + + var panelBefore = wrap.X; + var kidLocalBefore = kid.X; + grp.Interaction.PointerMove(new Point(grab.X + 40, grab.Y), alt); + grp.Interaction.PointerUp(new Point(grab.X + 40, grab.Y), alt); + Check("그룹: 자식을 끌면 패널째 움직인다", + wrap.X == panelBefore + 40 && kid.X == kidLocalBefore, + $"패널 {wrap.X}(시작 {panelBefore}) 자식상대 {kid.X}(시작 {kidLocalBefore})"); + + // 안으로 들어가려면 더블클릭 + grp.Selection.Clear(); + grp.Interaction.PointerDown(new Point(grab.X + 40, grab.Y), doubleClick); + grp.Interaction.PointerUp(new Point(grab.X + 40, grab.Y), doubleClick); + Check("그룹: 더블클릭이면 안으로 들어간다", + grp.Selection.Items.Count == 1 && ReferenceEquals(grp.Selection.Items[0], kid), + $"실제 {grp.Selection.Items.FirstOrDefault()?.Type}"); + // 풀기 — 절대좌표 복원 grp.Selection.SetSingle(wrap!); grp.UngroupSelection(); @@ -259,8 +286,8 @@ public static class EditSmoke && !grp.Pages[0].Controls.OfType().Any(), $"최상위 {grp.Pages[0].Controls.Count}"); Check("그룹 풀기: 좌표가 절대값으로 복원된다", - grp.Pages[0].Controls.Any(c => c.X == 100 && c.Y == 100) - && grp.Pages[0].Controls.Any(c => c.X == 200 && c.Y == 140), + grp.Pages[0].Controls.Any(c => c.X == 140 && c.Y == 100) + && grp.Pages[0].Controls.Any(c => c.X == 240 && c.Y == 140), string.Join(" ", grp.Pages[0].Controls.Select(c => $"({c.X},{c.Y})"))); // 가드 ① 라디오는 부모가 곧 배타 단위 — 일부만 묶으면 '택1'이 갈라진다 diff --git a/src/SheetMe.Designer/Services/InteractionController.cs b/src/SheetMe.Designer/Services/InteractionController.cs index 4268f0f..ffe1c34 100644 --- a/src/SheetMe.Designer/Services/InteractionController.cs +++ b/src/SheetMe.Designer/Services/InteractionController.cs @@ -89,12 +89,24 @@ public sealed class InteractionController var hit = designer.HitTestControl(world); if (hit is not null) { - // 더블클릭 → 인라인 텍스트 편집(드래그 시작 안 함) - if (ctx.ClickCount >= 2 && DesignerViewModel.IsTextEditable(hit)) + // 더블클릭 — 두 뜻이 겹친다. 먼저 '안으로 들어가기'다. + // 패널 안의 것을 집으면 평소에는 패널이 잡히는데(묶음째 움직이도록), + // 그 안의 하나만 손보려면 들어갈 길이 있어야 한다. 이미 들어와 있으면 그때 글자 편집이다. + if (ctx.ClickCount >= 2) { - mode = Mode.None; - designer.RequestInlineEdit(hit); - return; + // 최상위 컨트롤은 들어갈 곳이 없다 — 예전처럼 바로 글자 편집이다 + if (hit.Parent is not null && !hit.IsSelected) + { + mode = Mode.None; + designer.Selection.SetSingle(hit); + return; + } + if (DesignerViewModel.IsTextEditable(hit)) + { + mode = Mode.None; + designer.RequestInlineEdit(hit); + return; + } } downControl = hit; diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs index 33fd5d1..d99cd5c 100644 --- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs @@ -1849,9 +1849,31 @@ public sealed class DesignerViewModel : ViewModelBase } } + /// + /// 이 컨트롤을 클릭했을 때 실제로 잡히는 것 — 최상위 컨테이너까지 거슬러 올라간다. + /// 그룹이 진짜 패널이 되면서, 자식을 집으면 그 하나만 끌려가던 문제를 여기서 막는다. + /// + public ControlViewModel SelectionUnitOf(ControlViewModel viewModel) + { + var unit = viewModel; + while (unit.Parent is { } parent) + { + unit = parent; + } + return unit; + } + /// 그룹 동료 확장 — GroupId 가 같은 페이지 내 컨트롤 전체(클릭 선택이 그룹 단위가 되도록) public List GroupMatesOf(ControlViewModel viewModel) { + // 패널 안의 것은 패널이 곧 선택 단위다 — 자식 하나를 집어도 묶음 전체가 함께 움직여야 한다. + // 안으로 들어가려면 더블클릭한다(InteractionController 가 처리). + var unit = SelectionUnitOf(viewModel); + if (!ReferenceEquals(unit, viewModel)) + { + return new List { unit }; + } + if (viewModel.Model.GroupId is not { } groupId) { return new List { viewModel };