인스펙터 리디자인 — 목업대로

우측 속성 패널을 [200] 인스펙터 목업에 맞췄다. 바뀐 것 여섯.

· 탭이 알약에서 밑줄로. 알약은 '고르는 필터'로 읽히는데 탭은 화면을 가르는 것이지 값이 아니다.
  좌측 패널 탭 스타일을 재사용할 수 없었다 — 저쪽은 TabControl 의 키 없는 암시 스타일이고
  템플릿 안에 알약 컨테이너가 박혀 있어 고치면 레이어·팔레트 패널이 통째로 바뀐다.
  미선택도 밑줄 두께 2 를 Transparent 로 잡아 둔다. 안 그러면 고르는 순간 글자가 2px 튄다.
  밑줄과 글자는 같은 토큰(B.AccentText)이다 — B.Accent 는 라이트에서 3.0:1 이라 글자로 못 쓴다.

· 탭 배지를 뺐다. 잃는 게 없다: 개수가 0 인 탭은 RefreshTabCounts 가 탭 자체를 접으므로
  배지가 "0"을 보여 줄 기회가 애초에 없고, 누르기 전에 정확한 개수를 알아야 할 작업이 없다.
  DesignCount/DataCount/BehaviorCount 는 남긴다 — ShowDataTab/ShowBehaviorTab 이 그걸 물고 있다.

· 제목 줄이 생겼다. 타입 아이콘 + 이름 + ⋮. 무엇을 편집 중인지 아이콘으로 즉시 읽힌다.
  타입은 툴팁으로 보냈다(Summary 가 "TextBox — TextBox1" 형태로 이미 그 말을 한다).
  여럿을 골랐어도 타입이 같으면 그 타입 아이콘을 쓴다.

· 참/거짓이 체크박스에서 스위치로. 17px 사각형이 목록 오른쪽에 흩어져 정렬 축이 없었다.
  손잡이는 켬·끔 모두 흰색이다 — 색이 아니라 위치가 상태를 말하게 한다.
  불확정(다중선택 값 불일치)은 가운데다. 꺼짐으로 보이면 조용한 덮어쓰기 사고가 된다.
  미끄러지지 않고 튄다: 이 저장소에는 Storyboard 가 한 곳도 없어 여기만 움직이면 물성이 달라진다.
  ToggleButton 을 유지했으므로 스크린리더가 On/Off/Indeterminate 를 그대로 읽는다.

· 섹션 머리 — 위쪽에 구분선(아래에 그으면 마지막 섹션 밑에 뜬 선이 남는다), 제목을 B.Ink 로 승격,
  개수는 접혔을 때만. 펼쳤으면 개수는 바로 아래가 답이라 잡음이다.
  목업의 오른쪽 ⌃ 는 안 그린다 — 왼쪽 셰브론이 이미 같은 상태를 말하는데 한 행에 같은 정보를
  두 번 그리면 둘이 다른 뜻이라고 읽힌다.

· 라벨 폭 92 → 72, 세그먼트는 가로 꽉. 목업의 56 은 못 준다 — 목업에 없는 데이터·동작 탭에
  11자 라벨("입력 파라미터 컨트롤")이 있고 56 이면 3줄로 접힌다. 72 가 2줄을 지키는 최소값이다.
  줄어든 20px 은 그대로 입력 칸으로 갔다(121 → 141). 태그 제안 칩의 하드코딩 들여쓰기도 같이 옮겼다.

목업에 있지만 넣지 않은 것 둘. 회전 행과 그 옆 자물쇠 — Rotation/Angle 이 Core·레지스트리 전부
0건이다. 레거시 WinForms 컨트롤에 회전이 없다. 모델부터 새로 만들 신규 기능이지 리스킨이 아니다.
자물쇠도 종횡비 잠금이 없는 기능이고, 레이어 잠금과 같은 아이콘을 쓰면 오해가 확정된다.
'자동 줄바꿈'은 그 키가 레지스트리에 없어 실제 키인 '자동 높이'로 둔다.

'선택 정보' 탭은 만들지 않기로 했다(사용자 결정). 탭은 3개 그대로, 밑줄 스타일만 입혔다.
정렬 행은 디자인 탭 상단에 그대로 둔다 — 목업이 그려진 시점보다 나중에 생긴 기능이라
목업의 부재는 '빼라'는 뜻이 아니다.

새 토큰 B.SwitchOff 를 양 테마에 넣었다. 기존 토큰으로는 못 만든다 —
B.Input 은 라이트에서 패널과 같은 색이고, 다크는 입력 테두리가 Transparent 라
36x20 알약이 #262626#151515 = 1.23:1 로 사라진다. 비텍스트 3:1 을 넘긴 값으로 골랐다.
아이콘 ellipsis-vertical / file-text 2종 추가.

게이트: 테스트 267/267, --edit-smoke 237건 0실패, --query-popup 17/17, --maxrect 10/10,
--db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일,
--dialog-shots 57파일(인스펙터 스냅샷은 전부 바뀐다 — 이번 변경의 산출물이다).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-14 11:07:06 +09:00
co-authored by Claude Opus 5
parent 7f8808eb44
commit a5083fd0bb
5 changed files with 213 additions and 47 deletions
+162 -47
View File
@@ -13,6 +13,95 @@
<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="Width" Value="36"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="track" CornerRadius="10" SnapsToDevicePixels="True"
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>
<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"/>
</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"/>
@@ -65,7 +154,7 @@
(자르기보다 낫다 — 잘린 라벨은 툴팁을 띄워봐야 아는 정보가 된다).
-->
<Style x:Key="RowLabel" TargetType="TextBlock">
<Setter Property="Width" Value="92"/>
<Setter Property="Width" Value="72"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
@@ -168,10 +257,9 @@
</Setter>
</Style>
<!-- 세그먼트 한 칸 — 붙어 있는 아이콘 토글(굵게/기울임/밑줄/취소선) -->
<!-- 폭 30 + 간격 2 = 4칸 128px. 라벨 92 와 합쳐 220px 라 패널 최소폭(250 → 행 가용 221)에 들어간다 -->
<!-- 세그먼트 한 칸 — 붙어 있는 아이콘 토글(굵게/기울임/밑줄/취소선).
폭을 고정하지 않는다 — UniformGrid 가 가로를 꽉 채워 나눈다(목업의 "가로 꽉"). -->
<Style x:Key="SegmentToggle" TargetType="ToggleButton">
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
@@ -238,18 +326,22 @@
<!-- 접이식 섹션 헤더 — 클릭으로 접기/펴기. 인스펙터는 고급 속성까지 켜면 수십 행이 된다 -->
<DataTemplate DataType="{x:Type ins:SectionRowViewModel}">
<Button Command="{Binding ToggleCommand}" Cursor="Hand" HorizontalContentAlignment="Stretch"
Margin="0,10,0,3" Padding="0,3">
Margin="0,0,0,2" Padding="0,3">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="bd" Background="Transparent" CornerRadius="5" Padding="3,4">
<!-- 구분선은 섹션 '위'에 긋는다 — 아래에 그으면 마지막 섹션 밑에 뜬 선이 남는다 -->
<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 DockPanel.Dock="Right" Text="{Binding CountText}" FontSize="11"
Foreground="{DynamicResource B.Muted}" VerticalAlignment="Center" Margin="6,0,2,0"/>
<!-- 개수는 접혔을 때만. 펼쳤으면 바로 아래가 답이라 잡음이다 -->
<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.Muted}" VerticalAlignment="Center"
Foreground="{DynamicResource B.Ink}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
</Border>
@@ -257,6 +349,9 @@
<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>
@@ -310,13 +405,13 @@
<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}" HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><UniformGrid Columns="3"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="30" Height="26" Padding="0" Margin="0,0,2,2" Cursor="Hand"
<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}">
@@ -358,9 +453,10 @@
<DataTemplate DataType="{x:Type ins:SegmentRowViewModel}">
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
<ItemsControl ItemsSource="{Binding Items}" HorizontalAlignment="Left">
<!-- 가로를 꽉 채워 나눈다 — 라벨을 72 로 줄인 만큼이 그대로 칸으로 간다 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><StackPanel Orientation="Horizontal"/></ItemsPanelTemplate>
<ItemsPanelTemplate><UniformGrid Rows="1"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
@@ -406,8 +502,10 @@
<DockPanel Margin="0,2">
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
<!-- 3상태: 다중선택에서 값이 갈리면 불확정(IsOn=null). 꺼짐으로 보이면 안 된다 -->
<CheckBox IsChecked="{Binding IsOn}" IsThreeState="{Binding IsMixed, Mode=OneWay}"
VerticalAlignment="Center"/>
<ToggleButton Style="{StaticResource SwitchToggle}"
IsChecked="{Binding IsOn}" IsThreeState="{Binding IsMixed, Mode=OneWay}"
AutomationProperties.Name="{Binding Label}"
HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Style="{StaticResource MixedHint}"/>
</DockPanel>
</DataTemplate>
@@ -438,7 +536,7 @@
</DockPanel>
<!-- 이 컨트롤 타입에서 실제로 쓰이는 태그(빈도순). 아직 안 골랐을 때만 뜬다 —
이미 정해진 행 아래에도 칩을 깔면 좁은 인스펙터가 금방 길어진다. -->
<ItemsControl ItemsSource="{Binding Suggestions}" Margin="92,5,0,2"
<ItemsControl ItemsSource="{Binding Suggestions}" Margin="72,5,0,2"
Visibility="{Binding ShowSuggestions, Converter={StaticResource BoolToVisibility}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
@@ -465,10 +563,26 @@
</DockPanel>
</DataTemplate>
<!-- 전체 속성 — 목록의 탈출구라 채움 없는 테두리 버튼으로 둔다(값이 아니라 이동이다) -->
<DataTemplate DataType="{x:Type ins:ToggleAdvancedRowViewModel}">
<Button Content="{Binding ButtonText}" Command="{Binding ToggleCommand}"
Margin="0,6,0,2" Padding="6,3" HorizontalAlignment="Stretch"
Background="{DynamicResource B.Chip}" BorderBrush="{DynamicResource B.Line2}" FontSize="11" Foreground="{DynamicResource B.Muted}"/>
<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}">
@@ -646,41 +760,42 @@
<Grid>
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{Binding Summary}" FontWeight="Bold"
Margin="10,10,10,2" Foreground="{DynamicResource B.Muted}" TextTrimming="CharacterEllipsis"/>
<!-- 제목 — 아이콘으로 무엇을 편집 중인지 즉시 읽히고, ⋮ 가 컨트롤 단위 명령의 자리다.
이름만 남기고 타입은 툴팁으로 보낸다(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>
<!--
디자인 / 데이터 / 동작 3탭 알약([200] 인스펙터 구성).
배지는 눌러보지 않아도 볼 게 있는지 알려준다 — 컨트롤에 따라 데이터·동작 탭이
통째로 비는 경우가 흔해서(라벨·선 등), 배지가 없으면 빈 탭을 계속 눌러보게 된다.
디자인 / 데이터 / 동작 — 밑줄 탭.
배지를 뺐다. 개수가 0 인 탭은 RefreshTabCounts 가 탭 자체를 접으므로
배지가 "0"을 보여 줄 기회가 없고, 누르기 전에 정확한 개수를 알아야 할 일이 없다.
-->
<Border DockPanel.Dock="Top" Margin="10,4,10,8" Padding="2" CornerRadius="8"
Background="{DynamicResource B.Chip}"
<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 TabPill}" IsChecked="{Binding IsDesignTab, Mode=TwoWay}"
ToolTip="글자·색·정렬·크기 등 보이는 방식">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="디자인"/>
<TextBlock Text="{Binding DesignCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
<ToggleButton Style="{StaticResource TabPill}" IsChecked="{Binding IsDataTab, Mode=TwoWay}"
<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="자동 채움·조회·초기값·입력 형식 등 값에 관한 것">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="데이터"/>
<TextBlock Text="{Binding DataCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
<ToggleButton Style="{StaticResource TabPill}" IsChecked="{Binding IsBehaviorTab, Mode=TwoWay}"
ToolTip="자동 채움·조회·초기값·입력 형식 등 값에 관한 것"/>
<ToggleButton Style="{StaticResource TabUnderline}" IsChecked="{Binding IsBehaviorTab, Mode=TwoWay}"
Content="동작"
Visibility="{Binding ShowBehaviorTab, Converter={StaticResource BoolToVisibility}}"
ToolTip="액션 태그·대상 컨트롤 등 조작했을 때 벌어지는 일">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="동작"/>
<TextBlock Text="{Binding BehaviorCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
ToolTip="액션 태그·대상 컨트롤 등 조작했을 때 벌어지는 일"/>
</UniformGrid>
</Border>