## 패널 안에 그린 마퀴가 아무것도 안 잡았다 마퀴가 최상위만 훑고 컨테이너 안으로 안 들어갔다. 패널 안 체크박스 5개를 한 번에 고르려고 안쪽을 끌면 패널만 잡히거나 아무것도 안 잡혀서, Ctrl+클릭 반복이나 레이어 목록으로 우회했다. 컨테이너 자식은 전체 컨트롤의 <b>18.3%</b>(실측 18,749개/컨테이너 4,099개)라 드문 경우가 아니다. 규칙은 <b>'담김'</b>이다 — 마퀴가 컨테이너 사각형 안에 들어가 있으면 그 안의 것을 고른다 (내 상자가 패널보다 작으니 그 안을 고르려는 뜻이다). 걸치기만 하면 예전처럼 패널이 잡힌다. 모드도 수정키도 없이 <b>그린 모양만으로</b> 갈리므로 예측할 수 있다. 한 군데를 더 고쳐야 했다. 클릭 선택이 쓰는 GroupMatesOf 는 자식을 부모 컨테이너로 되돌리는데 (패널 안의 것을 집으면 패널이 움직여야 하므로 옳다) 마퀴가 그것을 그대로 쓰고 있었다 — 안쪽을 정확히 골라 놓고도 결과가 다시 패널 하나가 되어 위 규칙이 아무 일도 못 한다. 마퀴 전용 경로를 따로 뒀다. 걸치기 동작이 그대로인 것도 검사로 고정했다. ## 끄는 동안 숫자가 캔버스에 없었다 라벨 폭을 종이 칸에 맞추는 동안 숫자는 300px 떨어진 인스펙터에만 떴다 — 끌고, 손 떼고, 오른쪽 보고, 다시 끈다. 간격 수치 칩은 <b>균등 스냅이 성립할 때만</b> 나타나므로 "지금 이게 몇 px 인가"는 드래그 중에 알 방법이 아예 없었다. 이동이면 좌표, 크기 조절이면 크기를 보여 준다 — 지금 <b>바꾸고 있는 값</b>이 그것이다. 오버레이 층이라 종이에는 없고, 줌이 걸린 층이라 역배율로 글자 크기를 화면 고정으로 만든다. ## 눌러도 아무 일이 없는 ⋮ 버튼 Command·Click·ContextMenu 가 <b>전부 없었다</b>. 그런데 툴팁은 '이 컨트롤에 대한 명령'이라 "단일 선택일 때만 되는 뭔가가 있다"고 오해시켰다. 없는 버튼보다 죽은 버튼이 나쁘다 — 컨트롤 단위 명령은 캔버스 우클릭 메뉴에 이미 다 있다. ## 주석이 논증한 값과 실제 값이 달랐다 라벨 폭 주석이 "92px + 줄바꿈이면 두 줄로 접혀 전부 읽힌다"고 근거까지 적어 놓고 값은 72 였다. 그래서 '액션 대상 컨…' 처럼 잘렸다. 92 로 맞췄다. 같은 자리에서 설명 행(ReadOnlyRow)이 값처럼 잘리고 있었다 — "타입이 달라 값의 뜻이 다른 속성 2개 — 타입별로 따로 고르세요" 에서 <b>지시에 해당하는 뒷부분</b>이 사라져 무엇을 하라는 건지 알 수 없었다. 접어서 다 보여 준다. ## 판정 - 단위 시험 388 · edit-smoke 396건 전건 통과 (마퀴 2건 · 치수 칩 4건 추가) - --dialog-shots 69장 전건 통과 (인스펙터 8종을 눈으로 대조 — 라벨·설명 잘림 없음) - --modal-check · --maxrect · --scale-budget 전건 통과 - --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
197 lines
11 KiB
XML
197 lines
11 KiB
XML
<UserControl x:Class="SheetMe.Designer.Views.SelectionOverlayView"
|
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
xmlns:vm="clr-namespace:SheetMe.Designer.ViewModels"
|
|
xmlns:v="clr-namespace:SheetMe.Designer.Views"
|
|
mc:Ignorable="d"
|
|
d:DataContext="{d:DesignInstance vm:SelectionOverlayViewModel}"
|
|
IsHitTestVisible="False">
|
|
|
|
<!-- 선택/마퀴/가이드 표시 전용 오버레이 — 입력은 월드 파이프라인이 처리(기하 히트테스트) -->
|
|
<Canvas>
|
|
|
|
<!-- 선택 박스 -->
|
|
<Rectangle Canvas.Left="{Binding SelX}" Canvas.Top="{Binding SelY}"
|
|
Width="{Binding SelW}" Height="{Binding SelH}"
|
|
Stroke="#1E7BE8" StrokeThickness="1"
|
|
Visibility="{Binding HasSelection, Converter={StaticResource BoolToVisibility}}"/>
|
|
|
|
<!-- 리사이즈 핸들 8개 -->
|
|
<ItemsControl ItemsSource="{Binding Handles}"
|
|
Visibility="{Binding ShowHandles, Converter={StaticResource BoolToVisibility}}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<Canvas/>
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemContainerStyle>
|
|
<Style TargetType="ContentPresenter">
|
|
<Setter Property="Canvas.Left" Value="{Binding X}"/>
|
|
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
|
|
</Style>
|
|
</ItemsControl.ItemContainerStyle>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate>
|
|
<Rectangle Width="8" Height="8" Fill="White" Stroke="#1E7BE8" StrokeThickness="1"/>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!--
|
|
잠긴 컨트롤 표시 — 편집 크롬이므로 종이(PageView)에는 없다.
|
|
그쪽에 넣으면 db-render md5 가 깨진다(레거시와의 픽셀 동일성을 잠그는 게이트다).
|
|
숨김은 0.25 투명으로 티가 나는데 잠금은 아무 표시가 없어,
|
|
안 잡히는 이유를 화면에서 알 방법이 없었다.
|
|
-->
|
|
<ItemsControl ItemsSource="{Binding LockMarks}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<Canvas/>
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemContainerStyle>
|
|
<Style TargetType="ContentPresenter">
|
|
<Setter Property="Canvas.Left" Value="{Binding X}"/>
|
|
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
|
|
</Style>
|
|
</ItemsControl.ItemContainerStyle>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate>
|
|
<Rectangle Width="{Binding W}" Height="{Binding H}"
|
|
Stroke="#8C8C8C" StrokeThickness="1" StrokeDashArray="2 2"
|
|
Fill="Transparent" Opacity="0.7"/>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!--
|
|
끄는 동안의 치수·좌표 — 편집 크롬이라 종이에는 없다.
|
|
줌이 걸린 층이라 역배율로 되돌려 글자 크기를 화면 고정으로 만든다
|
|
(간격 수치 칩이 이미 같은 방식을 쓴다).
|
|
-->
|
|
<Border Canvas.Left="{Binding DragBadgeX}" Canvas.Top="{Binding DragBadgeY}"
|
|
Background="{DynamicResource B.AccentFill}" CornerRadius="3" Padding="5,1"
|
|
RenderTransformOrigin="0,0"
|
|
Visibility="{Binding ShowDragBadge, Converter={StaticResource BoolToVisibility}}">
|
|
<Border.RenderTransform>
|
|
<ScaleTransform ScaleX="{Binding InverseZoom}" ScaleY="{Binding InverseZoom}"/>
|
|
</Border.RenderTransform>
|
|
<TextBlock Text="{Binding DragBadgeText}" FontSize="11"
|
|
Foreground="{DynamicResource B.OnAccent}"/>
|
|
</Border>
|
|
|
|
<!-- 마퀴(러버밴드) -->
|
|
<Rectangle Canvas.Left="{Binding MarX}" Canvas.Top="{Binding MarY}"
|
|
Width="{Binding MarW}" Height="{Binding MarH}"
|
|
Stroke="#1E7BE8" StrokeThickness="1" StrokeDashArray="3 2" Fill="#181E7BE8"
|
|
Visibility="{Binding HasMarquee, Converter={StaticResource BoolToVisibility}}"/>
|
|
|
|
<!-- 탭순서 배지 (편집 모드 전용) -->
|
|
<ItemsControl ItemsSource="{Binding TabBadges}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<Canvas/>
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemContainerStyle>
|
|
<Style TargetType="ContentPresenter">
|
|
<Setter Property="Canvas.Left" Value="{Binding X}"/>
|
|
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
|
|
</Style>
|
|
</ItemsControl.ItemContainerStyle>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate>
|
|
<Border MinWidth="18" Height="18" CornerRadius="9" Padding="4,0"
|
|
BorderThickness="1.5" BorderBrush="White">
|
|
<Border.Style>
|
|
<Style TargetType="Border">
|
|
<Setter Property="Background" Value="#9AA6B4"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsAssigned}" Value="True">
|
|
<Setter Property="Background" Value="#1E7BE8"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Border.Style>
|
|
<TextBlock Text="{Binding Text}" Foreground="White" FontSize="10" FontWeight="Bold"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
</Border>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!-- ══ 스냅 표시 ══
|
|
선택 박스·핸들보다 뒤에 둔다. 앞에 두면 선택한 컨트롤 밑에 깔려 정작 지금 붙은 자리가 안 보인다.
|
|
선 두께와 대시는 화면 1px 로 고정한다 — 줌이 걸린 층이라 InverseZoom 을 곱한다.
|
|
(StrokeDashArray 는 두께의 배수 단위라 자동으로 따라온다.)
|
|
EdgeMode=Aliased: 월드 좌표가 화면에서 비정수로 떨어지면 1px 선이 2px 회색으로 번진다.
|
|
전부 축정렬 직선이라 부작용이 없다. -->
|
|
|
|
<!-- 정렬 가이드선 -->
|
|
<ItemsControl ItemsSource="{Binding Guides}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<Canvas/>
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate>
|
|
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}"
|
|
Stroke="{DynamicResource B.CanvasGuide}" RenderOptions.EdgeMode="Aliased"
|
|
StrokeThickness="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"/>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!-- 간격 균등 — 막대 하나에 양끝 갈고리, 가운데 수치 -->
|
|
<ItemsControl ItemsSource="{Binding Spacings}">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate>
|
|
<Canvas/>
|
|
</ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate>
|
|
<Canvas>
|
|
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}"
|
|
Stroke="{DynamicResource B.CanvasSpacing}" RenderOptions.EdgeMode="Aliased"
|
|
StrokeThickness="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"/>
|
|
<Line X1="{Binding CapAX1}" Y1="{Binding CapAY1}" X2="{Binding CapAX2}" Y2="{Binding CapAY2}"
|
|
Stroke="{DynamicResource B.CanvasSpacing}" RenderOptions.EdgeMode="Aliased"
|
|
StrokeThickness="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"/>
|
|
<Line X1="{Binding CapBX1}" Y1="{Binding CapBY1}" X2="{Binding CapBX2}" Y2="{Binding CapBY2}"
|
|
Stroke="{DynamicResource B.CanvasSpacing}" RenderOptions.EdgeMode="Aliased"
|
|
StrokeThickness="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"/>
|
|
|
|
<!-- 수치 칩. 막대가 정렬 가이드선과 같은 자리에 놓이는 경우가 흔해서
|
|
막대만으로는 '간격을 재고 있다'가 안 읽힌다 — 숫자가 그걸 가른다.
|
|
크기 0 Canvas 에 담아 그 점을 중심으로 놓고, 줌이 걸린 층이라 역배율로 되돌려
|
|
글자 크기를 화면 고정으로 만든다. -->
|
|
<!-- 좌상단 기준으로 놓는다. 가운데 정렬을 XAML 로 하려면 0 크기 컨테이너에 담아야 하는데,
|
|
그러면 자식이 폭 0 으로 측정돼 글자가 통째로 사라진다(실제로 그렇게 만들어 봤다).
|
|
그래서 중심 보정은 뷰모델이 좌표에 미리 넣어 둔다. -->
|
|
<Border Canvas.Left="{Binding TextX}" Canvas.Top="{Binding TextY}"
|
|
Background="{DynamicResource B.CanvasSpacing}" CornerRadius="2" Padding="3,0"
|
|
RenderTransformOrigin="0,0">
|
|
<Border.RenderTransform>
|
|
<ScaleTransform
|
|
ScaleX="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"
|
|
ScaleY="{Binding DataContext.InverseZoom,
|
|
RelativeSource={RelativeSource AncestorType=v:SelectionOverlayView}}"/>
|
|
</Border.RenderTransform>
|
|
<TextBlock Text="{Binding Text}" Foreground="White" FontSize="10" FontWeight="Bold"/>
|
|
</Border>
|
|
</Canvas>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</Canvas>
|
|
</UserControl>
|