레이어 패널 3건 수정 — 목록에서 Ctrl+G, 스크롤바 잘림, 검색칩 이중 테두리

1) 목록에서 단축키가 안 먹었다
   CanvasKeyboardBehavior 는 캔버스에 붙어 있어 포커스가 레이어 목록에 있으면 이벤트가
   거기까지 가지 않는다. 여러 행을 골라 놓고 Ctrl+G 를 눌러도 아무 일이 없던 이유다.
   목록에 KeyDown 을 달아 그룹·삭제·복사·되돌리기를 처리한다. 방향키·Home/End·문자 입력은
   넘기지 않는다(목록 자체의 이동과 형식 검색이 우선). 이름 편집 중에는 통과시킨다.

2) 세로 스크롤바가 오른쪽이 잘려 보였다 — 앱 전체 공통 결함
   Thumb 스타일의 MinWidth/MinHeight 24 가 폭 9 짜리 막대 안에서 썸을 24 폭으로 강제해
   오른쪽이 통째로 잘렸다(둥근 모서리가 사라지고 수직으로 싹둑 잘린 막대). 가로 막대는
   아래쪽이 같은 식으로 잘렸다. 최소 '길이'는 MinThumbTrack.MinThumbLength=44 가 이미
   보장하므로 최소 '두께'는 지운다.
   화살표 글리프도 7 단위 + 스트로크 1.4 = 8.4 라 폭 9 에서 캡이 경계에 닿아 오른팔이
   잘렸다 — 막대 폭을 11 로 올려 양옆 여유를 준다.

3) 검색칩 안에 상자가 하나 더 그려졌다
   전역 TextBox 템플릿은 BorderThickness=1 이 하드코딩돼 있어 인스턴스에서 0 을 줘도
   무시된다. 칩 전용 ChipTextBox 스타일(테두리 없는 템플릿)을 만들고, 포커스 표시는
   칩 쪽 IsKeyboardFocusWithin 으로 옮겼다.

회귀: 테스트 124/124, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0,
종이 렌더 P062 바이트 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-12 18:03:19 +09:00
co-authored by Claude Opus 5
parent 76584e5004
commit c22d84fb61
3 changed files with 91 additions and 10 deletions
+36 -6
View File
@@ -143,6 +143,27 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<!--
칩 안에 얹는 입력 — 자기 테두리·채움을 그리지 않는다.
전역 TextBox 템플릿은 <c>BorderThickness="1"</c> 이 하드코딩돼 있어 인스턴스에서
BorderThickness=0 을 줘도 무시된다. 그대로 두면 검색칩 안에 <b>둥근 상자가 하나 더</b> 그려져
상자 속 상자가 된다. 포커스 표시는 칩 쪽에서 준다(IsKeyboardFocusWithin).
-->
<Style x:Key="ChipTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="MinHeight" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="0,0,6,0" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Framer 식 슬라이더(불투명도 등): 얇은 트랙 + 흰 원형 썸 --> <!-- Framer 식 슬라이더(불투명도 등): 얇은 트랙 + 흰 원형 썸 -->
<Style TargetType="Slider"> <Style TargetType="Slider">
<Setter Property="MinHeight" Value="20" /> <Setter Property="MinHeight" Value="20" />
@@ -1074,11 +1095,15 @@
<SolidColorBrush x:Key="ScrollBtnHover" Color="#22808080" /> <SolidColorBrush x:Key="ScrollBtnHover" Color="#22808080" />
<!--
스크롤 썸 — MinWidth/MinHeight 를 두지 않는다.
여기에 24 를 박으면 세로 막대(폭 9)에서 썸이 24 폭으로 강제돼 오른쪽이 통째로 잘린다
(둥근 모서리가 사라지고 수직으로 싹둑 잘린 막대가 된다. 가로 막대는 아래쪽이 같은 식으로 잘렸다).
최소 '길이'는 MinThumbTrack.MinThumbLength=44 가 이미 보장한다 — 최소 '두께'는 필요 없다.
-->
<Style x:Key="ScrollThumb" TargetType="Thumb"> <Style x:Key="ScrollThumb" TargetType="Thumb">
<Setter Property="OverridesDefaultStyle" Value="True" /> <Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="IsTabStop" Value="False" /> <Setter Property="IsTabStop" Value="False" />
<Setter Property="MinHeight" Value="24" />
<Setter Property="MinWidth" Value="24" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
@@ -1171,18 +1196,23 @@
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
<!--
폭 11 인 이유: 화살표 글리프가 7 단위 + StrokeThickness 1.4(양옆 0.7씩) = 8.4 를 차지한다.
9 로 두면 캡이 경계에 닿아 오른쪽 팔이 잘린 채로 그려진다(썸의 둥근 모서리도 같이 잘렸다).
11 이면 양옆 1.3 여유가 생겨 글리프와 썸이 온전히 들어간다.
-->
<Style TargetType="ScrollBar"> <Style TargetType="ScrollBar">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="SnapsToDevicePixels" Value="True" /> <Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Width" Value="9" /> <Setter Property="Width" Value="11" />
<Setter Property="MinWidth" Value="9" /> <Setter Property="MinWidth" Value="11" />
<Setter Property="Template" Value="{StaticResource VScrollBar}" /> <Setter Property="Template" Value="{StaticResource VScrollBar}" />
<Style.Triggers> <Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal"> <Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto" /> <Setter Property="Width" Value="Auto" />
<Setter Property="MinWidth" Value="0" /> <Setter Property="MinWidth" Value="0" />
<Setter Property="Height" Value="9" /> <Setter Property="Height" Value="11" />
<Setter Property="MinHeight" Value="9" /> <Setter Property="MinHeight" Value="11" />
<Setter Property="Template" Value="{StaticResource HScrollBar}" /> <Setter Property="Template" Value="{StaticResource HScrollBar}" />
</Trigger> </Trigger>
</Style.Triggers> </Style.Triggers>
+14 -4
View File
@@ -59,14 +59,23 @@
테두리가 없으면 칩 경계 대비가 정확히 1.00:1 — 입력란이 거기 있다는 것 자체가 안 보인다. 테두리가 없으면 칩 경계 대비가 정확히 1.00:1 — 입력란이 거기 있다는 것 자체가 안 보인다.
--> -->
<Border Height="32" CornerRadius="7" Margin="8,8,0,6" <Border Height="32" CornerRadius="7" Margin="8,8,0,6"
Background="{DynamicResource B.Input}" Background="{DynamicResource B.Input}" BorderThickness="1">
BorderThickness="1" BorderBrush="{DynamicResource B.InputBorder}"> <Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="{DynamicResource B.InputBorder}"/>
<Style.Triggers>
<!-- 포커스 표시는 칩이 준다 — 안쪽 입력이 자기 테두리를 그리면 상자 속 상자가 된다 -->
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel> <DockPanel>
<ContentControl DockPanel.Dock="Left" Margin="9,0,7,0" VerticalAlignment="Center" IsTabStop="False" <ContentControl DockPanel.Dock="Left" Margin="9,0,7,0" VerticalAlignment="Center" IsTabStop="False"
Content="{Binding Source=search, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/> Content="{Binding Source=search, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/>
<Grid> <Grid>
<TextBox x:Name="LayerSearchBox" MinHeight="0" Padding="0,0,6,0" BorderThickness="0" <TextBox x:Name="LayerSearchBox" Style="{StaticResource ChipTextBox}"
Background="Transparent" VerticalContentAlignment="Center"
Text="{Binding CurrentDesigner.LayerFilter, UpdateSourceTrigger=PropertyChanged}"/> Text="{Binding CurrentDesigner.LayerFilter, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="레이어 검색…" FontSize="12" Foreground="{DynamicResource B.Muted}" <TextBlock Text="레이어 검색…" FontSize="12" Foreground="{DynamicResource B.Muted}"
VerticalAlignment="Center" IsHitTestVisible="False" Margin="1,0,0,0"> VerticalAlignment="Center" IsHitTestVisible="False" Margin="1,0,0,0">
@@ -104,6 +113,7 @@
SelectionMode="Extended" Padding="0,6,0,0" SelectionMode="Extended" Padding="0,6,0,0"
MouseDoubleClick="OnLayerDoubleClick" MouseDoubleClick="OnLayerDoubleClick"
PreviewMouseLeftButtonDown="OnLayerRowPressed" PreviewMouseLeftButtonDown="OnLayerRowPressed"
KeyDown="OnLayerListKeyDown"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"> ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplateSelector> <ListBox.ItemTemplateSelector>
<ctl:LayerRowTemplateSelector> <ctl:LayerRowTemplateSelector>
@@ -75,6 +75,47 @@ public partial class LayerPanelView : System.Windows.Controls.UserControl
} }
} }
/// <summary>
/// 레이어 목록 단축키 — 그룹/삭제/복사/되돌리기.
///
/// 이 키들은 원래 <see cref="Behaviors.CanvasKeyboardBehavior"/> 가 처리하는데, 그 behavior 는
/// <b>캔버스에 붙어 있어서</b> 포커스가 목록에 있으면 이벤트가 거기까지 가지 않는다.
/// 목록에서 여러 행을 골라 놓고 Ctrl+G 를 눌렀을 때 아무 일도 안 일어나던 이유가 이것이다.
///
/// 방향키·Home/End·문자 타이핑은 <b>넘기지 않는다</b> — 목록 자체의 이동·형식 검색이 우선이다.
/// 그룹 이름을 편집 중일 때도 통과시킨다(입력 보호).
/// </summary>
private void OnLayerListKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (Designer is not { } designer
|| System.Windows.Input.Keyboard.FocusedElement
is System.Windows.Controls.Primitives.TextBoxBase or System.Windows.Controls.ComboBox)
{
return;
}
var ctrl = System.Windows.Input.Keyboard.Modifiers.HasFlag(System.Windows.Input.ModifierKeys.Control);
var shift = System.Windows.Input.Keyboard.Modifiers.HasFlag(System.Windows.Input.ModifierKeys.Shift);
var handled = true;
switch (e.Key)
{
case System.Windows.Input.Key.G when ctrl && shift: designer.UngroupSelection(); break;
case System.Windows.Input.Key.G when ctrl: designer.GroupSelection(); break;
case System.Windows.Input.Key.C when ctrl: designer.CopySelection(); break;
case System.Windows.Input.Key.X when ctrl: designer.CutSelection(); break;
case System.Windows.Input.Key.V when ctrl: designer.Paste(); break;
case System.Windows.Input.Key.D when ctrl: designer.CopySelection(); designer.Paste(); break;
case System.Windows.Input.Key.Z when ctrl: designer.Undo.Undo(); break;
case System.Windows.Input.Key.Y when ctrl: designer.Undo.Redo(); break;
case System.Windows.Input.Key.Delete: designer.DeleteSelection(); break;
case System.Windows.Input.Key.Escape: designer.Selection.Clear(); break;
default: handled = false; break;
}
e.Handled = handled;
}
/// <summary>페이지 헤더의 복제 버튼</summary> /// <summary>페이지 헤더의 복제 버튼</summary>
private void OnPageDuplicateClick(object sender, RoutedEventArgs e) private void OnPageDuplicateClick(object sender, RoutedEventArgs e)
{ {