Files
SheetMe/src/SheetMe.Designer/Views/InspectorView.xaml
T
MsystechandClaude Opus 5 280594362a Effect·팝업이 ClearType 을 끄고 있었다 — 글자가 흐린 이유
사용자 지적이 맞았다. Effect 가 걸린 요소는 하위 트리가 중간 서피스로 렌더링되면서
ClearType 이 꺼지고, 글자가 회색 안티에일리어싱으로 그려진다. 13px 한글이 특히 뭉개진다.

저장소는 이 사실을 이미 알고 있었다 — MessageDialogView.xaml:18 과 TagPickerDialogView.xaml:11 이
"AllowsTransparency 를 켜면 ClearType 이 꺼져 13px 한글이 뭉개진다"고 적어 두었다.
그런데 그 판단이 <b>창에만</b> 적용되고 Effect 와 Popup 에는 적용되지 않았다.
ClearTypeHint 는 저장소 전체에 0 곳이었다.

추론으로 끝내지 않았다. RenderTargetBitmap 으로는 이 질문에 답할 수 없다 —
그 자체가 알파를 가진 중간 서피스라서 Effect 가 있든 없든 양쪽 다 회색으로 나오고,
검사가 통과하면서 아무것도 증명하지 못한다. 그래서 진짜 화면을 BitBlt 으로 떠서
글리프의 채널 어긋난 픽셀(ClearType 은 R≠G≠B, 회색 AA 는 R=G=B)을 셌다.

--cleartype 표본 11종 실측:
  A 아무것도 없음            색번짐 1115  ← 대조군. 이게 0 이면 판정 불가로 끝낸다
  B Effect                    색번짐    0  ← 지적한 그대로
  C1 힌트를 Effect 요소 자신에게  색번짐    0  ← 듣지 않는다
  C2 힌트를 안쪽 불투명 배경에    색번짐 1344  ← 여기가 맞는 자리
  D 레이어드 팝업              색번짐    0  ← 두 번째 원인
  E 팝업 + 힌트                색번짐 1115  ← AllowsTransparency 를 끌 필요가 없다
  I 힌트 바깥에 ScrollViewer   색번짐    0  ← ScrollViewer 도 중간 서피스를 연다
  J 힌트를 ScrollViewer 안쪽에  색번짐 1115
  F 앱 콤보 드롭다운(실제 템플릿) 색번짐    0 → 수리 후 1115

두 번 헛짚었고 두 번 다 측정이 정정했다.
CornerRadius 를 의심했으나 아니었고, ScrollViewer 가 범인이었다.
그리고 F 를 처음 통과시켰을 때는 선택된 콤보 항목의 파란 배경(B.Sel, 채널 차이 61)이
색번짐으로 잡힌 것이었다 — 색번짐 4359 인데 글자픽셀은 1075 였다.
그래서 배경이 유채색이면 판정을 거부하는 장치를 넣었다.

규칙: 힌트는 Effect·ScrollViewer·레이어드 팝업 <b>안쪽</b>의 불투명 배경 요소에 건다.

적용한 곳 — 글자가 많은 순:
  콤보 드롭다운(DesignerTheme) · 메뉴 하위팝업(배치·정렬 명령이 다 여기 있다) ·
  색 팝오버 · 컨트롤 추가 플라이아웃 · 줌 플라이아웃

남긴 곳: 플로팅 바(글자는 줌 퍼센트뿐), QueryEditorWindow 의 Card 스타일
(Effect 가 DataGrid·편집기의 중첩 ScrollViewer 를 감싸서 힌트를 넣을 자리가 여럿이다 —
그림자를 빼는 쪽이 맞아 보이고, 그건 디자인 결정이라 따로 다룬다).

게이트: --cleartype 11/11, 테스트 277/277, --edit-smoke 0실패, --modal-check 0실패,
--maxrect 0실패, --dialog-shots 57파일, --db-smoke 1271건 diff 0,
--db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일(종이 렌더 불변).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 16:11:23 +09:00

946 lines
63 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<UserControl x:Class="SheetMe.Designer.Views.InspectorView"
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:ins="clr-namespace:SheetMe.Designer.ViewModels.Inspector"
xmlns:ctl="clr-namespace:SheetMe.Designer.Controls"
xmlns:bh="clr-namespace:SheetMe.Designer.Behaviors"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance ins:InspectorViewModel}">
<UserControl.Resources>
<ctl:IconNameToVisualConverter x:Key="IconName"/>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
<!--
탭 — 밑줄형. 알약은 '고르는 필터'로 읽히는데 탭은 화면을 가르는 것이지 값이 아니다.
좌측 패널 탭 스타일을 재사용할 수 없다: 저쪽은 TabControl 의 키 없는 암시 스타일이고
템플릿 안에 알약 컨테이너가 박혀 있어, 고치면 레이어·팔레트 패널이 통째로 바뀐다.
미선택도 두께 2 를 Transparent 로 잡아 둔다 — 안 그러면 고르는 순간 글자가 2px 튄다.
밑줄과 글자는 같은 토큰이어야 한다. B.Accent 는 라이트에서 3.0:1 이라 글자로 못 쓴다.
-->
<Style x:Key="TabUnderline" TargetType="ToggleButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="bd" Background="Transparent" BorderBrush="Transparent"
BorderThickness="0,0,0,2" Padding="0,7,0,5" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ToggleButton}}"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.AccentText}"/>
<Setter Property="Foreground" Value="{DynamicResource B.AccentText}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
스위치 — 참/거짓 속성. 체크박스는 17px 사각형이 목록 오른쪽에 흩어져 정렬 축이 없다.
손잡이는 켬·끔 모두 흰색이다. 색이 아니라 <b>위치</b>가 상태를 말하게 한다.
불확정(다중선택 값 불일치)은 가운데 — 꺼짐으로 보이면 조용한 덮어쓰기 사고가 된다.
미끄러지지 않고 튄다: 이 저장소에는 Storyboard 가 한 곳도 없다. 여기만 움직이면 물성이 달라진다.
-->
<Style x:Key="SwitchToggle" TargetType="ToggleButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<!--
스위치가 앞, 글씨가 뒤다. 라벨을 72px 칸에 밀어 넣으면
"저장본 재조회" 같은 이름이 두세 줄로 접혀 행이 뚱뚱해진다.
앞으로 빼면 글씨가 남은 폭을 다 쓰고 스위치끼리 세로 축도 또렷해진다.
라벨까지 한 ToggleButton 안이라 글씨를 눌러도 켜진다.
-->
<DockPanel Background="Transparent">
<Border x:Name="track" DockPanel.Dock="Left" Width="36" Height="20"
CornerRadius="10" SnapsToDevicePixels="True"
VerticalAlignment="Center"
Background="{DynamicResource B.SwitchOff}"
BorderThickness="1" BorderBrush="Transparent">
<Ellipse x:Name="knob" Width="14" Height="14" Margin="3,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Fill="{DynamicResource B.OnAccent}"/>
</Border>
<ContentPresenter Margin="8,0,0,0" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ToggleButton}}"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="track" Property="Background" Value="{DynamicResource B.AccentFill}"/>
<Setter TargetName="knob" Property="HorizontalAlignment" Value="Right"/>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter TargetName="knob" Property="HorizontalAlignment" Value="Center"/>
<Setter TargetName="track" Property="Opacity" Value="0.55"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="track" Property="Opacity" Value="0.85"/>
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="track" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="track" Property="Opacity" Value="0.45"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 3탭 알약 — 좌측 패널 탭과 같은 결(선택만 채움) -->
<Style x:Key="TabPill" TargetType="ToggleButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="bd" Background="Transparent" CornerRadius="6" Padding="0,5" Margin="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ToggleButton}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Surface}"/>
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 탭 배지 — 0 이면 그 탭에 볼 게 없다는 뜻이라 흐리게 -->
<Style x:Key="TabBadge" TargetType="TextBlock">
<Setter Property="FontSize" Value="10.5"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Margin" Value="4,1,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Style.Triggers>
<Trigger Property="Text" Value="0">
<Setter Property="Opacity" Value="0.45"/>
</Trigger>
</Style.Triggers>
</Style>
<!--
라벨 폭. 실측상 가장 긴 라벨이 11자("입력 파라미터 컨트롤", "선택지(줄바꿈 구분)")라
86px 에서는 "액션 대상 컨…" 처럼 잘렸다. 92px + 줄바꿈이면 두 줄로 접혀 전부 읽힌다
(자르기보다 낫다 — 잘린 라벨은 툴팁을 띄워봐야 아는 정보가 된다).
-->
<Style x:Key="RowLabel" TargetType="TextBlock">
<Setter Property="Width" Value="72"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="0,0,6,0"/>
</Style>
<!--
인스펙터 전용 입력 치수 — 전역 TextBox 는 MinHeight 30 이라 한 화면에 몇 행 못 들어간다
(TextBox 하나만 21행이면 700px 를 넘겨 스크롤이 필수가 된다). 여기서만 26 으로 낮춘다.
전역 스타일을 낮추면 검색칩·대화상자 입력까지 같이 줄어드니 반드시 지역 스타일이어야 한다.
-->
<Style x:Key="RowInput" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="MinHeight" Value="26"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<!-- ComboBox 는 테마가 MinHeight 가 아니라 Height 를 '고정'한다 — MinHeight 만 낮추면
선택 행만 30px 로 남아 다른 행과 4px 어긋난다. 반드시 Height 를 내려야 한다. -->
<Style x:Key="RowCombo" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="Height" Value="26"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<!-- 쌍 행의 짧은 라벨(X/Y/너비/높이) — 값 칸을 최대한 넓게 남긴다 -->
<Style x:Key="PairLabel" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="11.5"/>
<Setter Property="Margin" Value="0,0,5,0"/>
<Setter Property="MinWidth" Value="22"/>
</Style>
<!--
정렬 버튼 — 액션이지 상태가 아니다. SegmentToggle 처럼 채우면 '켜져 있다'로 읽힌다.
채움 없이 호버 반응만 남긴다. 30x26 / radius 6 / 사이 2px 은 [200] 와이어프레임 .icn 실측값.
-->
<Style x:Key="AlignBtn" TargetType="Button">
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,0,2,0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="bd" Background="Transparent" CornerRadius="6"
BorderThickness="1" BorderBrush="Transparent" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="bd" Property="Opacity" Value="0.45"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 정렬 기준 알약 — 둘 중 하나만 켜지는 선택지라 RadioButton 이다(토글 두 개면 둘 다 꺼질 수 있다) -->
<Style x:Key="ScopePill" TargetType="RadioButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontSize" Value="11.5"/>
<Setter Property="Margin" Value="0,0,4,0"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="bd" Background="{DynamicResource B.Input}" CornerRadius="6"
BorderThickness="1" BorderBrush="{DynamicResource B.InputBorder}"
Padding="9,3" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Accent}"/>
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 세그먼트 한 칸 — 붙어 있는 아이콘 토글(굵게/기울임/밑줄/취소선).
폭을 고정하지 않는다 — UniformGrid 가 가로를 꽉 채워 나눈다(목업의 "가로 꽉"). -->
<Style x:Key="SegmentToggle" TargetType="ToggleButton">
<Setter Property="Height" Value="26"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="bd" Background="{DynamicResource B.Input}"
BorderBrush="{DynamicResource B.InputBorder}" BorderThickness="1"
Margin="0,0,2,0" CornerRadius="6" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Line2}"/>
</Trigger>
<!-- 켜짐은 채움 + 강조 테두리 둘 다 — 채움만으로는 라이트에서 1.1:1 이라 안 읽힌다 -->
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Accent2}"/>
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
"여러 값" 표시 — 다중선택에서 값이 갈리면 편집기가 빈 칸으로 보인다.
그 빈 칸은 "값 없음"과 구별되지 않아, 사용자가 여기에 무심코 입력하면 서로 다르던 값이
전부 한 값으로 덮인다. 비파괴 오버레이로만 알린다(입력을 가로막지 않는다).
-->
<Style x:Key="MixedHint" TargetType="TextBlock">
<Setter Property="Text" Value="여러 값"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="7,0,0,0"/>
<Setter Property="IsHitTestVisible" Value="False"/>
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMixed}" Value="True"/>
<Condition Binding="{Binding ValueText}" Value=""/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
<!--
편집기 위에 겹치는 변형 — 입력 중에는 비운다.
값은 LostFocus 에만 소스로 올라가므로 ValueText 는 타이핑 중 계속 "" 이고, 겹친 안내가
방금 친 글자 위에 그대로 남는다.
-->
<Style x:Key="MixedHintOverlay" TargetType="TextBlock" BasedOn="{StaticResource MixedHint}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource AncestorType=Grid}}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- 접이식 섹션 헤더 — 클릭으로 접기/펴기. 인스펙터는 고급 속성까지 켜면 수십 행이 된다 -->
<DataTemplate DataType="{x:Type ins:SectionRowViewModel}">
<Button Command="{Binding ToggleCommand}" Cursor="Hand" HorizontalContentAlignment="Stretch"
Margin="0,0,0,2" Padding="0,3">
<Button.Template>
<ControlTemplate TargetType="Button">
<!-- 구분선은 섹션 '위'에 긋는다 — 아래에 그으면 마지막 섹션 밑에 뜬 선이 남는다 -->
<Border x:Name="bd" Background="Transparent" CornerRadius="5" Padding="3,9,3,5" Margin="0,4,0,0"
BorderThickness="0,1,0,0" BorderBrush="{DynamicResource B.Line}">
<DockPanel>
<ContentControl DockPanel.Dock="Left" Margin="0,0,5,0" VerticalAlignment="Center"
IsTabStop="False" Focusable="False"
Content="{Binding ChevronIcon, Converter={StaticResource IconName}, ConverterParameter=12}"/>
<!-- 개수는 접혔을 때만. 펼쳤으면 바로 아래가 답이라 잡음이다 -->
<TextBlock x:Name="count" DockPanel.Dock="Right" Text="{Binding CountText}" FontSize="11"
Foreground="{DynamicResource B.Muted}" VerticalAlignment="Center" Margin="6,0,2,0"
Visibility="Collapsed"/>
<TextBlock Text="{Binding Label}" FontWeight="Bold" FontSize="12"
Foreground="{DynamicResource B.Ink}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<DataTrigger Binding="{Binding IsExpanded}" Value="False">
<Setter TargetName="count" Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:TextRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
<Grid>
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
</Grid>
</DockPanel>
</DataTemplate>
<!-- 두 값 한 줄 — X|Y, 너비|높이. 자식 행이 값·커밋을 그대로 들고 있다 -->
<DataTemplate DataType="{x:Type ins:PairRowViewModel}">
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<TextBlock Text="{Binding LeftLabel}" Style="{StaticResource PairLabel}"/>
<Grid DataContext="{Binding Left}">
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True"
bh:InspectorFieldBehavior.Spin="True" ToolTip="↑/↓ 1씩 · Shift 10씩 · Enter 확정 · Esc 되돌리기"
Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Style="{StaticResource MixedHintOverlay}" Text="여러"/>
</Grid>
</DockPanel>
<DockPanel Grid.Column="2">
<TextBlock Text="{Binding RightLabel}" Style="{StaticResource PairLabel}"/>
<Grid DataContext="{Binding Right}">
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True"
bh:InspectorFieldBehavior.Spin="True" ToolTip="↑/↓ 1씩 · Shift 10씩 · Enter 확정 · Esc 되돌리기"
Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Style="{StaticResource MixedHintOverlay}" Text="여러"/>
</Grid>
</DockPanel>
</Grid>
</DataTemplate>
<!--
정렬 격자 — 3열 UniformGrid 라 9값은 3×3, 3값은 1×3 으로 알아서 접힌다.
선택 표시는 클릭으로만 바뀌므로 ToggleButton 이 아니라 Button 을 쓰고(토글은 두 번 눌러
해제되어 값이 비는 사고가 난다) 현재 값과 같은 칸만 강조한다.
-->
<DataTemplate DataType="{x:Type ins:AlignRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top" Margin="0,4,6,0"/>
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><UniformGrid Columns="3"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="26" Padding="0" Margin="0,0,2,2" Cursor="Hand"
ToolTip="{Binding Value}"
Command="{Binding DataContext.SelectCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Value}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="bd" Background="{DynamicResource B.Input}"
BorderBrush="{DynamicResource B.InputBorder}" BorderThickness="1"
CornerRadius="6" SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Line2}"/>
</Trigger>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Accent2}"/>
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
<!-- 아이콘이 있으면 아이콘, 없으면 방향 화살표 -->
<Grid>
<ContentControl IsTabStop="False" Focusable="False"
Content="{Binding Icon, Converter={StaticResource IconName}, ConverterParameter=14|B.Ink}"
Visibility="{Binding HasIcon, Converter={StaticResource BoolToVisibility}}"/>
<TextBlock Text="{Binding Glyph}" FontSize="13" HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="{DynamicResource B.Ink}"
Visibility="{Binding HasGlyph, Converter={StaticResource BoolToVisibility}}"/>
</Grid>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</DataTemplate>
<!-- 아이콘 토글 묶음 — 굵게/기울임/밑줄/취소선 -->
<DataTemplate DataType="{x:Type ins:SegmentRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<!-- 가로를 꽉 채워 나눈다 — 라벨을 72 로 줄인 만큼이 그대로 칸으로 간다 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><UniformGrid Rows="1"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Style="{StaticResource SegmentToggle}"
IsChecked="{Binding Toggle.IsOn}"
IsThreeState="{Binding Toggle.IsMixed, Mode=OneWay}"
ToolTip="{Binding ToolTip}"
Content="{Binding Icon, Converter={StaticResource IconName}, ConverterParameter=14|B.Ink}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:MultilineTextRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top"
Margin="0,4,0,0" ToolTip="{Binding Label}"/>
<Grid>
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="48" MaxHeight="120"
VerticalScrollBarVisibility="Auto"/>
<TextBlock Style="{StaticResource MixedHintOverlay}" VerticalAlignment="Top" Margin="7,7,0,0"/>
</Grid>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:NumberRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
<Grid>
<!-- 글꼴 크기처럼 소수를 실제로 받는 칸만 Ctrl 미세조정(0.1)을 켠다 -->
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True"
bh:InspectorFieldBehavior.Spin="True" bh:InspectorFieldBehavior.FineStep="0.1"
ToolTip="↑/↓ 1씩 · Shift 10씩 · Ctrl 0.1씩 · Enter 확정 · Esc 되돌리기"
Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
</Grid>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:ToggleRowViewModel}">
<DockPanel Margin="0,3">
<TextBlock DockPanel.Dock="Right" Style="{StaticResource MixedHint}"/>
<!-- 3상태: 다중선택에서 값이 갈리면 불확정(IsOn=null). 꺼짐으로 보이면 안 된다 -->
<ToggleButton Style="{StaticResource SwitchToggle}"
IsChecked="{Binding IsOn}" IsThreeState="{Binding IsMixed, Mode=OneWay}"
Content="{Binding Label}" ToolTip="{Binding Label}"
AutomationProperties.Name="{Binding Label}"/>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:ChoiceRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
<Grid>
<ComboBox Style="{StaticResource RowCombo}" ItemsSource="{Binding Choices}" SelectedItem="{Binding ValueText}"/>
<TextBlock Style="{StaticResource MixedHintOverlay}" Margin="9,0,0,0"/>
</Grid>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:TagPickerRowViewModel}">
<StackPanel Margin="0,2">
<DockPanel>
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<!-- 26px 칩 옆에서 "…" 는 점 하나로 보인다 — 아이콘 버튼으로 -->
<Button DockPanel.Dock="Right" Width="28" Height="26" Padding="0" Margin="4,0,0,0"
Command="{Binding BrowseCommand}" ToolTip="전체 목록에서 선택"
Content="{Binding Source=list, Converter={StaticResource IconName}, ConverterParameter=13}"/>
<Grid>
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
ToolTip="직접 입력하거나 아래 제안을 누르세요"/>
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
</Grid>
</DockPanel>
<!-- 이 컨트롤 타입에서 실제로 쓰이는 태그(빈도순). 아직 안 골랐을 때만 뜬다 —
이미 정해진 행 아래에도 칩을 깔면 좁은 인스펙터가 금방 길어진다. -->
<ItemsControl ItemsSource="{Binding Suggestions}" Margin="72,5,0,2"
Visibility="{Binding ShowSuggestions, Converter={StaticResource BoolToVisibility}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SuggestChip}" Content="{Binding}"
Command="{Binding DataContext.PickCommand,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:ReadOnlyRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<TextBlock Text="{Binding ValueText}" FontSize="11" Foreground="{DynamicResource B.Muted}"
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
</DockPanel>
</DataTemplate>
<!-- 전체 속성 — 목록의 탈출구라 채움 없는 테두리 버튼으로 둔다(값이 아니라 이동이다) -->
<DataTemplate DataType="{x:Type ins:ToggleAdvancedRowViewModel}">
<Button Command="{Binding ToggleCommand}" Cursor="Hand"
Margin="0,10,0,2" HorizontalAlignment="Stretch" FontSize="11">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="bd" Background="Transparent" CornerRadius="6" Padding="6,5"
BorderThickness="1" BorderBrush="{DynamicResource B.Line2}"
SnapsToDevicePixels="True">
<TextBlock Text="{Binding ButtonText}" HorizontalAlignment="Center"
Foreground="{DynamicResource B.Muted}" FontSize="11"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:AddPropertyRowViewModel}">
<StackPanel Margin="0,6,0,2">
<DockPanel>
<Button DockPanel.Dock="Right" Content="추가" Padding="8,2" Margin="4,0,0,0"
Command="{Binding AddCommand}"
ToolTip="레거시 컨트롤의 속성 이름을 정확히 입력하세요 (예: EnterTabYon, AutoHeight)"/>
<TextBox Text="{Binding KeyText, UpdateSourceTrigger=PropertyChanged}" FontSize="12"
ToolTip="새 속성 키 입력 — 아래 제안을 눌러도 됩니다"/>
</DockPanel>
<!-- 이 타입에서 실제로 쓰이는 키 제안(운영 DB 집계). 누르면 바로 추가된다.
철자를 외워 맞히게 두면 라벨의 소문자 visible 같은 함정에서 조용히 틀린다. -->
<ItemsControl ItemsSource="{Binding Matches}" Margin="0,6,0,0"
Visibility="{Binding HasMatches, Converter={StaticResource BoolToVisibility}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SuggestChip}" Content="{Binding}"
Command="{Binding DataContext.PickCommand,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
ToolTip="{Binding StringFormat='{}{0} 속성을 추가합니다'}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding MoreText}" FontSize="10.5" Margin="0,0,0,2"
Foreground="{DynamicResource B.Muted}"
Visibility="{Binding HasMore, Converter={StaticResource BoolToVisibility}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:DataTableFieldRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<Button DockPanel.Dock="Right" Content="편집…" Padding="8,2" Height="26" Margin="4,0,0,0"
Command="{Binding EditCommand}"
ToolTip="데이터소스 배선 편집기 — 문법을 직접 치지 않아도 됩니다"/>
<TextBlock Text="{Binding Summary}" FontSize="11" Foreground="{DynamicResource B.Muted}"
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
ToolTip="{Binding ValueText}"/>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:QueryRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<Button DockPanel.Dock="Right" Content="편집…" Padding="8,2" Height="26" Margin="4,0,0,0"
Command="{Binding EditCommand}" ToolTip="쿼리 편집기 열기 (데이터소스 더블클릭과 동일)"/>
<TextBlock Text="{Binding Summary}" FontSize="11" Foreground="{DynamicResource B.Muted}"
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
ToolTip="{Binding ValueText}"/>
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:MaskRowViewModel}">
<StackPanel Margin="0,2">
<DockPanel>
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<Button DockPanel.Dock="Right" Width="28" Height="26" Padding="0" Margin="4,0,0,0"
Command="{Binding BrowseCommand}"
ToolTip="마스크 편집기 — 자주 쓰는 마스크 목록과 미리보기"
Content="{Binding Source=list, Converter={StaticResource IconName}, ConverterParameter=13}"/>
<Grid>
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
FontFamily="Consolas, D2Coding, 굴림체"
ToolTip="0=필수 숫자, 9=선택 숫자, #=숫자·부호 — 오른쪽 버튼에서 목록·미리보기"/>
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
</Grid>
</DockPanel>
<!-- 화면에 실제로 어떻게 보이는지 — 마스크 문법을 몰라도 결과로 확인 -->
<TextBlock Text="{Binding Preview}" FontSize="11" Margin="0,2,0,0"
FontFamily="Consolas, D2Coding, 굴림체"
Foreground="{DynamicResource B.Muted}" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</DataTemplate>
<!-- 팔레트 견본 하나 — 윤곽은 테마색이 아니라 견본 자신의 명도로 정한다
(테마 테두리를 쓰면 라이트에서 흰 견본이, 다크에서 검정 견본이 빈칸으로 보인다) -->
<DataTemplate x:Key="ColorSwatchTemplate" DataType="{x:Type ins:ColorSwatchViewModel}">
<Button Width="22" Height="22" Margin="0,0,4,4" Padding="0" Cursor="Hand"
ToolTip="{Binding ToolTip}"
Command="{Binding DataContext.ApplySwatchCommand,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="sw" Background="{Binding Preview}" BorderBrush="{Binding Edge}"
BorderThickness="1" CornerRadius="4" SnapsToDevicePixels="True"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="sw" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
<Setter TargetName="sw" Property="BorderThickness" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
<DataTemplate DataType="{x:Type ins:ColorRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"
ToolTip="{Binding LabelToolTip}"/>
<!--
스와치 = 인라인 팔레트 토글. 실측상 배경색의 84%가 White 와 "224, 224, 224"
단 두 값이라, 그 자리에서 바로 고르는 편이 대화상자를 여는 것보다 빠르다.
임의 색은 팔레트 아래 '자세히…' 로 기존 피커(SV/Hue/HEX/RGB)를 연다.
-->
<Grid DockPanel.Dock="Right" Margin="4,0,0,0">
<ToggleButton x:Name="SwatchToggle" Width="30" Height="26" Padding="0" Cursor="Hand"
IsChecked="{Binding IsPaletteOpen, Mode=TwoWay}"
ToolTip="클릭하여 색 고르기">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="bd" Background="{Binding Preview}" BorderBrush="{DynamicResource B.Line2}"
BorderThickness="1" CornerRadius="4" SnapsToDevicePixels="True"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Popup IsOpen="{Binding IsPaletteOpen, Mode=TwoWay}" StaysOpen="False" AllowsTransparency="True"
PlacementTarget="{Binding ElementName=SwatchToggle}" Placement="Bottom"
HorizontalOffset="-176" VerticalOffset="4">
<Border Background="{DynamicResource B.Surface}" BorderBrush="{DynamicResource B.Line2}"
BorderThickness="1" CornerRadius="8" Padding="10" Effect="{DynamicResource B.FloatShadow}">
<!-- Effect 와 레이어드 팝업이 각각 중간 서피스를 만들어 ClearType 을 끈다.
힌트는 Effect 가 걸린 바깥 Border 에 걸면 듣지 않으므로, 안쪽 불투명 배경에 건다. -->
<StackPanel Width="212" Background="{DynamicResource B.Surface}"
RenderOptions.ClearTypeHint="Enabled">
<TextBlock Text="배경" FontSize="11" Foreground="{DynamicResource B.Muted}" Margin="0,0,0,5"/>
<ItemsControl ItemsSource="{Binding BackgroundSwatches}"
ItemTemplate="{StaticResource ColorSwatchTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Text="글자·강조" FontSize="11" Foreground="{DynamicResource B.Muted}" Margin="0,8,0,5"/>
<ItemsControl ItemsSource="{Binding ForegroundSwatches}"
ItemTemplate="{StaticResource ColorSwatchTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Border Height="1" Background="{DynamicResource B.Line}" Margin="0,9,0,8"/>
<DockPanel>
<Button DockPanel.Dock="Right" Content="자세히…" Height="28" Padding="10,0"
Command="{Binding PickCommand}"
ToolTip="색상 피커 — 채도·명도·색상환, HEX·RGB 직접 입력"/>
<Button HorizontalAlignment="Left" Content="비우기" Height="28" Padding="10,0"
Style="{StaticResource Subtle}" Command="{Binding ClearCommand}"
ToolTip="속성값을 지웁니다(기본값·상속으로 되돌림)"/>
</DockPanel>
</StackPanel>
</Border>
</Popup>
</Grid>
<Grid>
<TextBox Style="{StaticResource RowInput}" bh:InspectorFieldBehavior.CommitOnEnter="True" Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}"
ToolTip="R, G, B 또는 색 이름 (예: 224, 224, 224 / White) — 오른쪽 스와치 클릭 시 피커"/>
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
</Grid>
</DockPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DockPanel>
<!-- 제목 — 아이콘으로 무엇을 편집 중인지 즉시 읽히고, ⋮ 가 컨트롤 단위 명령의 자리다.
이름만 남기고 타입은 툴팁으로 보낸다(Summary 가 "TextBox — TextBox4" 형태). -->
<DockPanel DockPanel.Dock="Top" Margin="10,8,6,4"
Visibility="{Binding HasSelection, Converter={StaticResource BoolToVisibility}}">
<ContentControl DockPanel.Dock="Left" Margin="0,0,7,0" VerticalAlignment="Center"
IsTabStop="False" Focusable="False"
Content="{Binding HeaderIcon, Converter={StaticResource IconName}, ConverterParameter=16|B.Muted}"/>
<Button DockPanel.Dock="Right" Width="24" Height="24" Padding="0"
Style="{StaticResource Subtle}" ToolTip="이 컨트롤에 대한 명령"
AutomationProperties.Name="더보기"
Visibility="{Binding IsSingleSelection, Converter={StaticResource BoolToVisibility}}"
Content="{Binding Source=ellipsis-vertical, Converter={StaticResource IconName}, ConverterParameter=14|B.Muted}"/>
<TextBlock Text="{Binding HeaderName}" FontWeight="Bold" FontSize="12.5"
Foreground="{DynamicResource B.Ink}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" ToolTip="{Binding Summary}"/>
</DockPanel>
<!--
디자인 / 데이터 / 동작 — 밑줄 탭.
배지를 뺐다. 개수가 0 인 탭은 RefreshTabCounts 가 탭 자체를 접으므로
배지가 "0"을 보여 줄 기회가 없고, 누르기 전에 정확한 개수를 알아야 할 일이 없다.
-->
<Border DockPanel.Dock="Top" Margin="10,2,10,8"
BorderBrush="{DynamicResource B.Line}" BorderThickness="0,0,0,1"
Visibility="{Binding ShowTabStrip, Converter={StaticResource BoolToVisibility}}">
<UniformGrid Rows="1">
<ToggleButton Style="{StaticResource TabUnderline}" IsChecked="{Binding IsDesignTab, Mode=TwoWay}"
Content="디자인" ToolTip="글자·색·정렬·크기 등 보이는 방식"/>
<ToggleButton Style="{StaticResource TabUnderline}" IsChecked="{Binding IsDataTab, Mode=TwoWay}"
Content="데이터"
Visibility="{Binding ShowDataTab, Converter={StaticResource BoolToVisibility}}"
ToolTip="자동 채움·조회·초기값·입력 형식 등 값에 관한 것"/>
<ToggleButton Style="{StaticResource TabUnderline}" IsChecked="{Binding IsBehaviorTab, Mode=TwoWay}"
Content="동작"
Visibility="{Binding ShowBehaviorTab, Converter={StaticResource BoolToVisibility}}"
ToolTip="액션 태그·대상 컨트롤 등 조작했을 때 벌어지는 일"/>
</UniformGrid>
</Border>
<!--
정렬 — 기준은 언제나 '담고 있는 것'이다. 선택 1개면 페이지(컨테이너 안이면 그 컨테이너),
2개 이상이면 선택 영역. 툴팁이 지금 무엇이 기준인지 말해 준다.
행 VM(Rows)이 아니라 여기 두는 이유 둘: AlignRowViewModel 은 이미 '텍스트 정렬'로
이름과 템플릿을 선점했고, 정렬은 스크롤과 무관하게 늘 보이는 편이 조작에 맞다.
-->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="12,2,10,6"
ToolTipService.ShowOnDisabled="True"
Visibility="{Binding ShowAlignBar, Converter={StaticResource BoolToVisibility}}">
<Button Style="{StaticResource AlignBtn}" Command="{Binding AlignCommand}" CommandParameter="Left"
AutomationProperties.Name="왼쪽 맞춤"
ToolTip="{Binding AlignScopeText, StringFormat=왼쪽 맞춤 — {0}}"
Content="{Binding Source=align-obj-left, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding AlignCommand}" CommandParameter="CenterH"
AutomationProperties.Name="가로 가운데 맞춤"
ToolTip="{Binding AlignScopeText, StringFormat=가로 가운데 — {0}}"
Content="{Binding Source=align-obj-center-h, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding AlignCommand}" CommandParameter="Right"
AutomationProperties.Name="오른쪽 맞춤"
ToolTip="{Binding AlignScopeText, StringFormat=오른쪽 맞춤 — {0}}"
Content="{Binding Source=align-obj-right, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<!-- 가로 축과 세로 축 사이 틈 8px — 참조 .align .gap -->
<Button Style="{StaticResource AlignBtn}" Margin="8,0,2,0" Command="{Binding AlignCommand}" CommandParameter="Top"
IsEnabled="{Binding CanAlignVertical}" AutomationProperties.Name="위 맞춤"
ToolTip="{Binding VerticalScopeText, StringFormat=위 맞춤 — {0}}"
Content="{Binding Source=align-obj-top, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding AlignCommand}" CommandParameter="CenterV"
IsEnabled="{Binding CanAlignVertical}" AutomationProperties.Name="세로 가운데 맞춤"
ToolTip="{Binding VerticalScopeText, StringFormat=세로 가운데 — {0}}"
Content="{Binding Source=align-obj-middle, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding AlignCommand}" CommandParameter="Bottom"
IsEnabled="{Binding CanAlignVertical}" AutomationProperties.Name="아래 맞춤"
ToolTip="{Binding VerticalScopeText, StringFormat=아래 맞춤 — {0}}"
Content="{Binding Source=align-obj-bottom, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
</StackPanel>
<!--
간격 균등 — 사이가 있어야 나눌 것이 있다(3개 이상). 없으면 회색으로 남기지 않고 접는다.
정렬 6개와 같은 줄에 두면 8칸 276px 가 되어 패널 최소 폭(250)에서 잘린다 — 줄을 나눈다.
-->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="12,0,10,6"
Visibility="{Binding CanDistribute, Converter={StaticResource BoolToVisibility}}">
<TextBlock Text="간격" VerticalAlignment="Center" FontSize="11.5" Margin="0,0,8,0"
Foreground="{DynamicResource B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding DistributeCommand}" CommandParameter="HorizEqual"
AutomationProperties.Name="가로 간격 균등" ToolTip="가로 간격을 고르게"
Content="{Binding Source=distribute-h, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
<Button Style="{StaticResource AlignBtn}" Command="{Binding DistributeCommand}" CommandParameter="VertEqual"
AutomationProperties.Name="세로 간격 균등" ToolTip="세로 간격을 고르게"
Content="{Binding Source=distribute-v, Converter={StaticResource IconName}, ConverterParameter=18|B.Muted}"/>
</StackPanel>
<!--
정렬 기준 — 여럿을 골라 놓고도 '서로 맞추기'와 '종이에 맞추기'는 다른 작업이다.
왼쪽 문구는 선택에 따라 바뀐다: 여럿이면 '선택 영역', 하나면 '부모'(또는 '페이지').
-->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="12,0,10,10"
Visibility="{Binding ShowAlignScope, Converter={StaticResource BoolToVisibility}}">
<TextBlock Text="기준" VerticalAlignment="Center" FontSize="11.5" Margin="0,0,8,0"
Foreground="{DynamicResource B.Muted}"/>
<RadioButton Style="{StaticResource ScopePill}" GroupName="AlignScope"
IsChecked="{Binding AlignToSelection, Mode=TwoWay}"
Content="{Binding AlignSelectionScopeLabel}"/>
<RadioButton Style="{StaticResource ScopePill}" GroupName="AlignScope"
IsChecked="{Binding AlignToPage, Mode=TwoWay}" Content="페이지"
ToolTip="{Binding PageScopeTip}"/>
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Rows}" Margin="10,0,10,10">
<!-- 접힌 섹션의 행은 컨테이너만 숨긴다 — 행 VM 을 재생성하지 않아 편집 상태가 유지된다 -->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Style.Triggers>
<DataTrigger Binding="{Binding IsRowVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<!-- 선택은 있는데 이 탭이 빈 경우 — 빈 화면은 고장으로 읽힌다 -->
<TextBlock Text="{Binding EmptyTabText}" FontSize="12" TextAlignment="Center" TextWrapping="Wrap"
Foreground="{DynamicResource B.Muted}" HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="24,0">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTabEmpty}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- 빈 상태 — 선택이 없으면 "선택 없음" 한 줄만 떠 있어 패널이 고장난 것처럼 보였다 -->
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24,0">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelection, FallbackValue=False}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<ContentControl HorizontalAlignment="Center" IsTabStop="False" Focusable="False" Opacity="0.45"
Content="{Binding Source=mouse-pointer-click, Converter={StaticResource IconName}, ConverterParameter=30}"/>
<TextBlock Text="컨트롤을 선택하면 속성이 표시됩니다" FontSize="12" TextAlignment="Center"
TextWrapping="Wrap" Foreground="{DynamicResource B.Muted}" Margin="0,12,0,0"/>
</StackPanel>
</Grid>
</UserControl>