diff --git a/src/SheetMe.Designer/Behaviors/CanvasKeyboardBehavior.cs b/src/SheetMe.Designer/Behaviors/CanvasKeyboardBehavior.cs index 2cb1dbe..c5f8d27 100644 --- a/src/SheetMe.Designer/Behaviors/CanvasKeyboardBehavior.cs +++ b/src/SheetMe.Designer/Behaviors/CanvasKeyboardBehavior.cs @@ -50,6 +50,13 @@ public sealed class CanvasKeyboardBehavior : Behavior switch (key) { + // Ctrl+방향키는 크기다 — 위치에는 미세 조정 수단이 있는데 크기만 없었다. + // 3px 늘리려고 300px 떨어진 인스펙터 W 칸까지 마우스로 왕복하던 것을 없앤다. + // Shift 를 함께 누르면 위치와 같은 큰 걸음(step)을 쓴다. + case Key.Left when ctrl: designer.ResizeSelection(-step, 0); break; + case Key.Right when ctrl: designer.ResizeSelection(step, 0); break; + case Key.Up when ctrl: designer.ResizeSelection(0, -step); break; + case Key.Down when ctrl: designer.ResizeSelection(0, step); break; case Key.Left: designer.NudgeSelection(-step, 0); break; case Key.Right: designer.NudgeSelection(step, 0); break; case Key.Up: designer.NudgeSelection(0, -step); break; @@ -78,6 +85,19 @@ public sealed class CanvasKeyboardBehavior : Behavior case Key.G when ctrl && shift: designer.UngroupSelection(); break; case Key.G when ctrl: designer.GroupSelection(); break; + // 선택 확장 — 정렬·간격 명령이 전제하는 '올바른 선택'을 만드는 수단. + // 마퀴는 교차 판정이라 행 띠를 스치는 것이 전부 딸려 오고(실측 침입자 중앙값 7개), + // 그래서 지금까지는 "드래그 → 딸려온 것 빼기"가 매번 붙었다. + case Key.D1 when alt: designer.SelectSame(DesignerViewModel.SameKind.Row); break; + case Key.D2 when alt: designer.SelectSame(DesignerViewModel.SameKind.Column); break; + case Key.D3 when alt: designer.SelectSame(DesignerViewModel.SameKind.Type); break; + case Key.D4 when alt: designer.SelectSame(DesignerViewModel.SameKind.Size); break; + + // 잠금·숨김 — 상태와 레이어 패널의 자물쇠는 있었는데 캔버스에서 켤 방법이 없었다. + // 배경 괘선 40개를 잠그려면 레이어 트리에서 40번 눌러야 했다. + case Key.L when ctrl: designer.ToggleLockSelection(); break; + case Key.H when ctrl && shift: designer.ToggleHiddenSelection(); break; + // 배치 명령 — 전에는 단축키가 0개였고 전부 로고 아래 4단 메뉴 안에만 있었다. // 20px→32px 간격 조정이 4px 씩 12왕복이고 매번 마우스가 좌상단까지 갔다. // 자리는 Figma 와 같게 둔다(Alt+A/D/W/S/H/V) — 이미 그 손버릇을 가진 사람이 있다. diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index b790a59..1f04387 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -1095,6 +1095,88 @@ public static class EditSmoke $"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}"); } + // 15-a0) 선택 확장 — 정렬·간격 명령이 전제하는 '올바른 선택'을 만드는 수단. + // + // 마퀴는 교차 판정이라 행 띠를 스치는 것이 전부 딸려 온다(실측 787건: + // 행 무리의 94.2%에 침입자, 중앙값 7개·p90 41개). 그래서 지금까지는 + // "드래그 → 딸려온 것 Ctrl+클릭으로 빼기"가 매번 앞에 붙었다. + { + var pick = new DesignerViewModel(new FormDesignDataBusiness().CreateNew()); + ControlViewModel Put(string type, double x, double y, double w, double h) + { + pick.AddControlAt(type, new Point(x, y)); + var vm = pick.Pages[0].Controls[^1]; + vm.X = x; vm.Y = y; vm.Width = w; vm.Height = h; + return vm; + } + // 한 줄에 셋(Y 동일), 그 아래 하나, 그리고 같은 열에 하나 + var row1 = Put("TextBox", 100, 200, 80, 20); + var row2 = Put("TextBox", 200, 200, 80, 20); + var row3 = Put("Label", 300, 201, 60, 20); // 1px 어긋남 — 눈으로 맞춘 서식의 현실 + var below = Put("TextBox", 100, 260, 80, 20); // 같은 열(X=100) + var far = Put("Label", 500, 400, 60, 20); + + pick.Selection.SetSingle(row1); + var added = pick.SelectSame(DesignerViewModel.SameKind.Row); + Check("선택 확장: 같은 행을 잡는다", pick.Selection.Items.Count == 3 + && pick.Selection.Items.Contains(row2) && pick.Selection.Items.Contains(row3), + $"{pick.Selection.Items.Count}개 (더해진 {added})"); + Check("선택 확장: 1px 어긋난 것도 같은 행으로 본다", pick.Selection.Items.Contains(row3), + "눈으로 맞춘 서식에서 일부만 잡히면 정렬 후에도 남은 것을 못 알아챈다"); + Check("선택 확장: 딴 데 있는 것은 안 잡힌다", !pick.Selection.Items.Contains(far)); + + pick.Selection.SetSingle(row1); + pick.SelectSame(DesignerViewModel.SameKind.Column); + Check("선택 확장: 같은 열을 잡는다", pick.Selection.Items.Count == 2 + && pick.Selection.Items.Contains(below), $"{pick.Selection.Items.Count}개"); + + pick.Selection.SetSingle(row1); + pick.SelectSame(DesignerViewModel.SameKind.Type); + Check("선택 확장: 같은 종류를 잡는다", + pick.Selection.Items.All(c => c.Type == "TextBox") && pick.Selection.Items.Count == 3, + $"{pick.Selection.Items.Count}개"); + + pick.Selection.SetSingle(row1); + pick.SelectSame(DesignerViewModel.SameKind.Size); + Check("선택 확장: 같은 크기를 잡는다", pick.Selection.Items.Count == 3, + $"{pick.Selection.Items.Count}개 (80×20 인 것)"); + + // 잠긴 것은 빼야 한다 — 편집하려고 넓히는 선택인데 안 움직이는 것이 섞이면 + // 정렬을 눌렀을 때 "왜 이건 그대로지"가 된다 + row2.Model.Locked = true; + pick.Selection.SetSingle(row1); + pick.SelectSame(DesignerViewModel.SameKind.Row); + Check("선택 확장: 잠긴 것은 넣지 않는다", !pick.Selection.Items.Contains(row2), + $"{pick.Selection.Items.Count}개"); + row2.Model.Locked = false; + + // 크기 조절 — 위치에는 미세 조정이 있는데 크기만 없었다 + pick.Selection.SetSingle(row1); + var w0 = row1.Width; + pick.ResizeSelection(3, 0); + Check("크기 조절: Ctrl+방향키로 1px 씩 넓힌다", Math.Abs(row1.Width - (w0 + 3)) < 0.5, + $"{w0} → {row1.Width}"); + pick.ResizeSelection(-9999, 0); + Check("크기 조절: 1 아래로는 안 내려간다", row1.Width >= 1, $"{row1.Width}"); + + // 잠금 토글 — 상태와 레이어 자물쇠는 있었는데 캔버스에서 켤 방법이 없었다 + pick.Selection.Set(new[] { row1, row2 }.ToList(), row1); + pick.ToggleLockSelection(); + Check("잠금: 선택 전체를 한 번에 잠근다", row1.Model.Locked && row2.Model.Locked); + Check("잠금: 잠긴 것이 화면에 표시된다", pick.Overlay.LockMarks.Count >= 2, + $"표시 {pick.Overlay.LockMarks.Count}개"); + pick.ToggleLockSelection(); + Check("잠금: 다시 누르면 풀린다", !row1.Model.Locked && !row2.Model.Locked); + Check("잠금: 풀면 표시도 사라진다", pick.Overlay.LockMarks.Count == 0, + $"표시 {pick.Overlay.LockMarks.Count}개"); + var lockedId = row1.Id; + pick.Undo.Undo(); + // 되돌리기는 문서를 통째로 갈아끼운다 — 옛 VM 참조는 죽는다. Id 로 다시 찾는다. + var revived = pick.Pages[0].Controls.FirstOrDefault(c => c.Id == lockedId); + Check("잠금: 되돌리기로 원복된다", revived is not null && revived.Model.Locked, + revived is null ? "컨트롤을 못 찾음" : $"Locked={revived.Model.Locked}"); + } + // 15-b0) 잠긴 것 위는 '빈 곳'이 아니다 — 우클릭이 선택을 지우면 안 된다. // // 우클릭이 커서 아래를 선택하도록 고치면서 생긴 함정이다. HitTestControl 은 잠금·숨김을 diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs index b0c0dd8..1f2116b 100644 --- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs @@ -331,6 +331,15 @@ public sealed class DesignerViewModel : ViewModelBase UngroupCommand = new Command((sender, e) => UngroupSelection()); CutCommand = new Command((sender, e) => CutSelection()); SizeToControlCommand = new Command((object param) => SizeToControl(param as string ?? "Both")); + SelectSameCommand = new Command((object param) => + { + if (Enum.TryParse(param as string, out var kind)) + { + SelectSame(kind); + } + }); + LockCommand = new Command((sender, e) => ToggleLockSelection()); + HideCommand = new Command((sender, e) => ToggleHiddenSelection()); SpacingCommand = new Command((object param) => AdjustSpacing(param as string ?? string.Empty)); CenterInPageCommand = new Command((object param) => CenterInPage(param as string ?? "H")); TabOrderCommand = new Command((sender, e) => ToggleTabOrderMode()); @@ -388,6 +397,15 @@ public sealed class DesignerViewModel : ViewModelBase /// 같은 크기로(파라미터: Both/Width/Height — 기준 컨트롤에 맞춤) public ICustomCommand? SizeToControlCommand { get; set; } + /// 같은 행·열·종류·크기로 선택 넓히기 — 인자는 이름 + public ICustomCommand? SelectSameCommand { get; set; } + + /// 선택 잠그기/풀기 + public ICustomCommand? LockCommand { get; set; } + + /// 선택 숨기기/보이기 + public ICustomCommand? HideCommand { get; set; } + /// 간격 조절(파라미터: HorizEqual/HorizIncrease/HorizDecrease/HorizConcat/VertEqual/...) public ICustomCommand? SpacingCommand { get; set; } @@ -865,11 +883,42 @@ public sealed class DesignerViewModel : ViewModelBase public void RefreshOverlay() { Overlay.UpdateSelection(SelectionWorldBounds(), Selection.Items.Count > 0); + RefreshLockMarks(); if (Selection.Primary is not null) { Selection.ActivePage = PageOf(Selection.Primary) ?? Selection.ActivePage; } } + + /// + /// 잠긴 컨트롤 표시를 다시 모은다 — 오버레이(편집 크롬) 층에만 그린다. + /// + /// 종이 층에 넣으면 --db-render 게이트가 그것을 찍어 레거시와의 픽셀 동일성이 깨진다. + /// 숨김은 0.25 투명이라 티가 나는데 잠금은 아무 표시가 없어, 클릭도 드래그도 안 되는 이유를 + /// 화면에서 알 수 없었다. + /// + private void RefreshLockMarks() + { + Overlay.LockMarks.Clear(); + foreach (var page in Pages) + { + foreach (var vm in ControlsOfPage(page)) + { + if (!vm.Model.Locked || vm.Model.Hidden) + { + continue; + } + var world = WorldBoundsOf(vm); + Overlay.LockMarks.Add(new LockMarkInfo + { + X = world.X, + Y = world.Y, + W = Math.Max(1, world.Width), + H = Math.Max(1, world.Height), + }); + } + } + } #endregion #region Events @@ -1748,6 +1797,156 @@ public sealed class DesignerViewModel : ViewModelBase Selection.NotifyBoundsChanged(); } + /// + /// 선택 확장의 기준 — 같은 줄로 볼 만큼 가까운가. + /// + /// 정확히 같은 값만 묶으면 눈으로 맞춘 서식에서 일부만 잡힌다 — 8개 중 5개를 정렬해 + /// 놓고 나머지 3개가 남은 것을 못 알아채는 것이 가장 나쁜 결과다. 2px 는 이 서식의 + /// 컨트롤 높이(대부분 20px 이상)보다 훨씬 작아 이웃 줄을 삼키지 않는다. + /// 격자 크기에 묶지 않는다 — 격자를 바꿨다고 선택 규칙이 달라지면 예측할 수 없다. + /// + private const double SelectSameTolerance = 2.0; + + /// 선택 확장의 종류 — 우클릭 메뉴·단축키가 공유한다 + public enum SameKind + { + /// 같은 행(Y 가 같다) + Row, + /// 같은 열(X 가 같다) + Column, + /// 같은 종류(타입이 같다) + Type, + /// 같은 크기(너비·높이가 같다) + Size, + } + + /// + /// 주 선택과 같은 줄·열·종류·크기인 형제를 선택에 더한다 — 반환은 더해진 개수. + /// + /// 왜 필요한가. 정렬 6종·간격 4종·같은 크기가 이미 다 있는데, 그 명령들이 전제하는 + /// "올바른 선택"을 만들 수단이 없었다. 표 서식에서 가장 흔한 편집이 + /// "이 행의 입력칸 8개를 조금 높이고 아래로 붙이자"인데, 그 8개를 고르는 길이 + /// Ctrl+클릭 8번뿐이다. 마퀴는 교차 판정이라 행 띠를 스치는 것이 전부 딸려 오고, + /// 실측(787건)으로 행 무리의 94.2%에 침입자가 있으며 그 수가 중앙값 7개·p90 41개다. + /// 즉 지금은 "드래그 → 딸려온 것 빼기"가 매번 붙는다. + /// + /// 범위는 같은 부모의 형제다 — 정렬 명령이 쓰는 범위와 같아야 결과를 예측할 수 있다. + /// 순수 계산이라 종이 렌더·저장 형식에 전혀 닿지 않는다. + /// + public int SelectSame(SameKind kind) + { + var anchor = Selection.Primary ?? Selection.Items.FirstOrDefault(); + var page = (anchor is null ? null : PageOf(anchor)) ?? Selection.ActivePage ?? Pages.FirstOrDefault(); + if (anchor is null || page is null) + { + return 0; + } + static bool Near(double left, double right) => Math.Abs(left - right) <= SelectSameTolerance; + + var matches = SiblingsOf(anchor, page).Where(candidate => kind switch + { + SameKind.Row => Near(candidate.Y, anchor.Y), + SameKind.Column => Near(candidate.X, anchor.X), + SameKind.Type => string.Equals(candidate.Type, anchor.Type, StringComparison.Ordinal), + SameKind.Size => Near(candidate.Width, anchor.Width) && Near(candidate.Height, anchor.Height), + _ => false, + }).ToList(); + + // 잠금·숨김은 뺀다 — 편집하려고 넓히는 선택인데 편집할 수 없는 것이 섞이면 + // 정렬을 눌렀을 때 "왜 이건 안 움직이지"가 된다 + matches = matches.Where(c => !c.Model.Locked && !c.Model.Hidden).ToList(); + + var before = Selection.Items.Count; + var merged = Selection.Items.Union(matches).Distinct().ToList(); + if (merged.Count == before) + { + return 0; + } + Selection.Set(merged, anchor); + return merged.Count - before; + } + + /// + /// 선택 크기를 키우거나 줄인다 — 의 빠져 있던 짝. + /// + /// 왜 필요한가. 위치는 방향키로 1px 미세 조정이 되는데 크기만 그 수단이 없었다. + /// 입력칸 폭을 3px 늘리려면 오른쪽 인스펙터 W 칸으로 가서 숫자를 읽고 3 을 더해 입력한다 — + /// 캔버스와 300px 떨어진 패널 사이를 마우스로 왕복한다. 마우스 핸들은 판정 반경이 + /// 화면 6px 이라 작은 컨트롤에서 3px 를 정확히 집기 어렵고, 스냅이 붙으면 원하는 값에 못 선다. + /// + /// 넛지와 같은 400ms 코얼레스를 쓴다 — 연타가 Undo 스무 개로 쌓이면 안 된다. + /// + public void ResizeSelection(double dw, double dh) + { + if (Selection.Items.Count == 0) + { + return; + } + Undo.SnapshotForNudge(); + foreach (var vm in Selection.Items) + { + if (vm.Model.Locked) + { + continue; + } + vm.Width = Math.Max(1, Math.Round(vm.Width + dw)); + vm.Height = Math.Max(1, Math.Round(vm.Height + dh)); + // 선은 두께 축을 끌어서 바꿀 수 없다 — 레거시 MLine 이 매번 BorderWidth 로 되돌린다. + // 허용하면 디자이너에만 두꺼운 선이 보이고 EMR 은 원래 두께로 그린다. + if (SheetMe.Core.Catalog.LineGeometry.NormalizeThickness(vm.Model)) + { + vm.NotifyAllChanged(); + } + } + Selection.NotifyBoundsChanged(); + } + + /// + /// 선택을 잠그거나 푼다 — 하나라도 안 잠겼으면 전부 잠근다(레이어 패널의 묶음 토글과 같은 규칙). + /// + /// 왜 캔버스에서 되어야 하나. 배경 괘선 40개를 잠가 두고 그 위에서 라벨만 만지고 싶을 때, + /// 지금은 좌측 패널을 레이어 탭으로 바꾸고 트리에서 40행을 찾아 13px 자물쇠를 40번 눌러야 한다. + /// 캔버스에서 마퀴로 40개를 잡아 놔도 한 번에 잠글 수단이 없었다. + /// + /// 잠금 해제는 캔버스에서 못 한다. 히트 판정이 잠긴 것을 후보에서 빼므로 우클릭조차 닿지 않는다 — + /// 해제 경로는 레이어 패널에 있다. 그래서 캔버스 메뉴 이름은 '선택 잠그기'로 한정한다. + /// 잠금·숨김은 저장되지 않는 편집 전용 상태다 — 영속처럼 읽히는 문구를 쓰지 않는다. + /// + public void ToggleLockSelection() + { + if (Selection.Items.Count == 0) + { + return; + } + Undo.Snapshot(); + var locking = Selection.Items.Any(vm => !vm.Model.Locked); + foreach (var vm in Selection.Items) + { + vm.Model.Locked = locking; + vm.NotifyAllChanged(); + } + RebuildLayerRows(); + RefreshOverlay(); + } + + /// 선택을 숨기거나 보인다 — 하나라도 보이면 전부 숨긴다 + public void ToggleHiddenSelection() + { + if (Selection.Items.Count == 0) + { + return; + } + Undo.Snapshot(); + var hiding = Selection.Items.Any(vm => !vm.Model.Hidden); + foreach (var vm in Selection.Items) + { + vm.Model.Hidden = hiding; + vm.NotifyAllChanged(); + } + RebuildLayerRows(); + RefreshOverlay(); + } + /// 활성 페이지 전체 선택 public void SelectAllOnActivePage() { diff --git a/src/SheetMe.Designer/ViewModels/SelectionOverlayViewModel.cs b/src/SheetMe.Designer/ViewModels/SelectionOverlayViewModel.cs index faf1689..b3531d2 100644 --- a/src/SheetMe.Designer/ViewModels/SelectionOverlayViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/SelectionOverlayViewModel.cs @@ -55,6 +55,22 @@ public sealed class SpacingMarkInfo public double TextY { get; init; } } +/// 잠긴 컨트롤 하나의 자리 — 월드 좌표. 오버레이가 점선 테두리로 그린다. +public sealed class LockMarkInfo +{ + /// 좌상단 X(월드) + public double X { get; init; } + + /// 좌상단 Y(월드) + public double Y { get; init; } + + /// 너비(월드) + public double W { get; init; } + + /// 높이(월드) + public double H { get; init; } +} + /// 탭순서 배지 1개 — 월드 좌표(컨트롤 좌상단) public sealed class TabBadgeInfo { @@ -172,6 +188,17 @@ public sealed class SelectionOverlayViewModel : ViewModelBase /// 탭순서 배지 목록(탭순서 편집 모드 전용) public ObservableCollection TabBadges { get; } = new(); + + /// + /// 잠긴 컨트롤의 테두리 — 편집 크롬이라 종이에는 그려지지 않는다. + /// + /// 이 층에 두는 이유가 있다. 종이 층(PageView)에 표시를 넣으면 --db-render 게이트가 + /// 그것을 찍어 md5 가 깨진다 — 그 게이트는 레거시와 픽셀이 같다는 것을 잠그고 있다. + /// + /// 표시가 필요한 이유: 숨김은 0.25 투명이라 티가 나는데 잠금은 아무 표시가 없었다. + /// 클릭도 드래그도 안 되는데 이유가 화면에 없으니 "왜 이것만 안 잡히지"만 남는다. + /// + public ObservableCollection LockMarks { get; } = new(); #endregion #region Methods diff --git a/src/SheetMe.Designer/Views/DesignerCanvasView.xaml b/src/SheetMe.Designer/Views/DesignerCanvasView.xaml index 6f23e0d..b1450fb 100644 --- a/src/SheetMe.Designer/Views/DesignerCanvasView.xaml +++ b/src/SheetMe.Designer/Views/DesignerCanvasView.xaml @@ -26,6 +26,20 @@ --> + + + + + + + + + + diff --git a/src/SheetMe.Designer/Views/MainView.xaml b/src/SheetMe.Designer/Views/MainView.xaml index fd5408f..3b98d36 100644 --- a/src/SheetMe.Designer/Views/MainView.xaml +++ b/src/SheetMe.Designer/Views/MainView.xaml @@ -29,6 +29,25 @@ + + + + + + + + + + diff --git a/src/SheetMe.Designer/Views/MainView.xaml.cs b/src/SheetMe.Designer/Views/MainView.xaml.cs index 668903d..57fb1b9 100644 --- a/src/SheetMe.Designer/Views/MainView.xaml.cs +++ b/src/SheetMe.Designer/Views/MainView.xaml.cs @@ -232,10 +232,24 @@ public partial class MainView : Window { if (e.Key == System.Windows.Input.Key.F4) { + // 인스펙터가 접혀 있으면 먼저 편다 — 안 그러면 보이지도 않는 칸으로 포커스가 들어간다 + if (panelsCollapsed) + { + TogglePanels(); + } Inspector.FocusFirstRow(); e.Handled = true; return; } + // Ctrl+\ — 좌우 패널을 함께 접었다 편다('종이만 보기'). + // 개별 토글 두 개를 두지 않는다: 목적이 하나라 키도 하나여야 손이 기억한다. + if (e.Key is System.Windows.Input.Key.OemBackslash or System.Windows.Input.Key.Oem5 + && System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Control) + { + TogglePanels(); + e.Handled = true; + return; + } // Ctrl+O — 서식 목록으로. 전에는 OS 파일 대화상자로 갔다. // 손이 먼저 가는 키인데 디스크로 가고, 운영 서식 1,271건은 전부 DB 에 있다. if (e.Key == System.Windows.Input.Key.O @@ -246,6 +260,47 @@ public partial class MainView : Window } } + /// 지금 좌우 패널이 접혀 있는가 — 접힌 폭을 '사용자가 정한 폭'으로 오해하지 않기 위한 상태 + private bool panelsCollapsed; + + /// 펼쳤을 때의 폭 — 접는 순간 기억해 두었다가 그대로 되돌린다 + private GridLength expandedLeft; + private GridLength expandedRight; + private double expandedLeftMin; + private double expandedRightMin; + + /// + /// 좌우 패널을 함께 접었다 편다 — 종이만 보기. + /// + /// 좌 248 + 스플리터 5 + 스플리터 5 + 우 300 = 558px 가 항상 점유된다. + /// MinWidth 180/250 때문에 스플리터를 끝까지 밀어도 사라지지 않고, 보기 메뉴에도 항목이 없었다. + /// 가로 용지를 120% 로만 올려도 캔버스에 가로 스크롤이 생기는데 '종이만 보기' 상태가 없었다. + /// + /// MinWidth 도 함께 0 으로 내려야 실제로 사라진다 — Width=0 만으로는 최소폭이 이긴다. + /// 되돌릴 때 쓰려고 펼친 폭을 따로 기억한다(접힌 0 을 사용자가 정한 폭으로 착각하면 안 된다). + /// + private void TogglePanels() + { + if (panelsCollapsed) + { + LeftColumn.MinWidth = expandedLeftMin; + RightColumn.MinWidth = expandedRightMin; + LeftColumn.Width = expandedLeft; + RightColumn.Width = expandedRight; + panelsCollapsed = false; + return; + } + expandedLeft = LeftColumn.Width; + expandedRight = RightColumn.Width; + expandedLeftMin = LeftColumn.MinWidth; + expandedRightMin = RightColumn.MinWidth; + LeftColumn.MinWidth = 0; + RightColumn.MinWidth = 0; + LeftColumn.Width = new GridLength(0); + RightColumn.Width = new GridLength(0); + panelsCollapsed = true; + } + /// 파일 메뉴·빈 화면 버튼에서 서식 목록으로 보낸다 private void OnOpenFromListClick(object sender, RoutedEventArgs e) => FocusSheetList(); diff --git a/src/SheetMe.Designer/Views/SelectionOverlayView.xaml b/src/SheetMe.Designer/Views/SelectionOverlayView.xaml index 6f38576..d7a8d77 100644 --- a/src/SheetMe.Designer/Views/SelectionOverlayView.xaml +++ b/src/SheetMe.Designer/Views/SelectionOverlayView.xaml @@ -39,6 +39,33 @@ + + + + + + + + + + + + + + + + +