초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너. 기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49. 이 커밋에 함께 포함된 자격증명 분리: - appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환 - 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외, csproj Debug 조건부 복사라 Release 산출물에 실리지 않음) - ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경, 미치환 플레이스홀더는 '미설정'으로 간주 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vmc="clr-namespace:SheetMe.Designer.ViewModels.Controls">
|
||||
|
||||
<!-- 타입별 렌더 템플릿 — 캔버스/미리보기/인쇄가 공유하는 단일 원천.
|
||||
레거시 WinForms 렌더 모양의 시각 근사. 콘텐츠는 히트테스트 제외(입력은 월드 파이프라인 단일 수신). -->
|
||||
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
|
||||
|
||||
<!-- 라벨 -->
|
||||
<DataTemplate DataType="{x:Type vmc:LabelViewModel}">
|
||||
<Grid Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<TextBlock Text="{Binding Text}"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" FontStyle="{Binding FontStyle}"
|
||||
Foreground="{Binding Foreground}"
|
||||
HorizontalAlignment="{Binding TextAlignment}"
|
||||
VerticalAlignment="Top"
|
||||
TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 텍스트박스 -->
|
||||
<DataTemplate DataType="{x:Type vmc:TextBoxViewModel}">
|
||||
<Border Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="BorderBrush" Value="#7F9DB9"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowBorder}" Value="False">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<TextBlock Text="{Binding Text}" Margin="2,1"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" FontStyle="{Binding FontStyle}"
|
||||
Foreground="{Binding Foreground}"
|
||||
HorizontalAlignment="{Binding TextAlignment}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="NoWrap"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Multiline}" Value="True">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 마스크 입력 -->
|
||||
<DataTemplate DataType="{x:Type vmc:MaskedTextBoxViewModel}">
|
||||
<Border Background="{Binding Background}" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
|
||||
<TextBlock Margin="2,1" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
Foreground="{Binding Foreground}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding Text}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Text}" Value="">
|
||||
<Setter Property="Text" Value="{Binding Mask}"/>
|
||||
<Setter Property="Opacity" Value="0.45"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 체크박스 -->
|
||||
<DataTemplate DataType="{x:Type vmc:CheckBoxViewModel}">
|
||||
<StackPanel Orientation="Horizontal" Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<Border Width="13" Height="13" Background="White" BorderBrush="#5A5A5A" BorderThickness="1"
|
||||
VerticalAlignment="Center" Margin="0,0,4,0">
|
||||
<Path Data="M1,6 L5,10 L11,2" Stroke="#1E5AA0" StrokeThickness="2"
|
||||
Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisibility}}"/>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 라디오버튼 -->
|
||||
<DataTemplate DataType="{x:Type vmc:RadioButtonViewModel}">
|
||||
<StackPanel Orientation="Horizontal" Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<Grid Width="13" Height="13" VerticalAlignment="Center" Margin="0,0,4,0">
|
||||
<Ellipse Fill="White" Stroke="#5A5A5A" StrokeThickness="1"/>
|
||||
<Ellipse Width="6" Height="6" Fill="#1E5AA0"
|
||||
Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisibility}}"/>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 콤보박스 -->
|
||||
<DataTemplate DataType="{x:Type vmc:ComboBoxViewModel}">
|
||||
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Right" Width="17" Background="#E1E1E1">
|
||||
<Path Data="M0,0 L8,0 L4,5 Z" Fill="#5A5A5A"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding DisplayText}" Margin="3,1" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
Foreground="{Binding Foreground}" TextTrimming="CharacterEllipsis"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 리스트박스 -->
|
||||
<DataTemplate DataType="{x:Type vmc:ListBoxViewModel}">
|
||||
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
|
||||
<ItemsControl ItemsSource="{Binding Items}" Margin="2">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" FontSize="12"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 체크리스트 -->
|
||||
<DataTemplate DataType="{x:Type vmc:CheckListViewModel}">
|
||||
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
|
||||
<ItemsControl ItemsSource="{Binding Items}" Margin="2">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Width="11" Height="11" Background="White" BorderBrush="#5A5A5A"
|
||||
BorderThickness="1" Margin="0,1,4,1"/>
|
||||
<TextBlock Text="{Binding}" FontSize="12"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 날짜선택 -->
|
||||
<DataTemplate DataType="{x:Type vmc:DateTimePickerViewModel}">
|
||||
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Right" Width="17" Background="#E1E1E1">
|
||||
<Path Data="M0,0 L8,0 L4,5 Z" Fill="#5A5A5A"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding DisplayText}" Margin="3,1" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
Foreground="{Binding Foreground}" TextTrimming="CharacterEllipsis"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 패널 (컨테이너 — 자식 재귀 렌더) -->
|
||||
<DataTemplate DataType="{x:Type vmc:PanelViewModel}">
|
||||
<Grid Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<Border BorderBrush="#A0A0A0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowBorder}" Value="True">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<ItemsControl ItemsSource="{Binding Children}">
|
||||
<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}"/>
|
||||
<Setter Property="Width" Value="{Binding Width}"/>
|
||||
<Setter Property="Height" Value="{Binding Height}"/>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 그룹박스 (컨테이너) -->
|
||||
<DataTemplate DataType="{x:Type vmc:GroupBoxViewModel}">
|
||||
<Grid Background="{Binding Background}" IsHitTestVisible="False">
|
||||
<Border BorderBrush="#B5B5B5" BorderThickness="1" CornerRadius="3" Margin="0,7,0,0"/>
|
||||
<TextBlock Text="{Binding Text}" Margin="8,0,0,0" Padding="3,0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
Background="{Binding Background}"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
|
||||
<ItemsControl ItemsSource="{Binding Children}">
|
||||
<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}"/>
|
||||
<Setter Property="Width" Value="{Binding Width}"/>
|
||||
<Setter Property="Height" Value="{Binding Height}"/>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 선 -->
|
||||
<DataTemplate DataType="{x:Type vmc:LineViewModel}">
|
||||
<Rectangle Fill="{Binding LineBrush}" IsHitTestVisible="False"/>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 이미지 -->
|
||||
<DataTemplate DataType="{x:Type vmc:PictureBoxViewModel}">
|
||||
<Border Background="#F5F7FA" BorderBrush="#C8D0DA" BorderThickness="1" IsHitTestVisible="False">
|
||||
<TextBlock Text="{Binding Hint}" FontSize="10" Foreground="#8090A0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 계산박스 -->
|
||||
<DataTemplate DataType="{x:Type vmc:CalcBoxViewModel}">
|
||||
<Border Background="#FFFDF0" BorderBrush="#C9B458" BorderThickness="1" IsHitTestVisible="False">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Left" Text="ƒ" Margin="3,0,2,0" Foreground="#A08A2A"
|
||||
FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
Foreground="{Binding Foreground}"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 버튼 (레거시 MButton — 표준 버튼 근사) -->
|
||||
<DataTemplate DataType="{x:Type vmc:ButtonViewModel}">
|
||||
<Border CornerRadius="3" BorderBrush="#8A9AAB" BorderThickness="1" IsHitTestVisible="False">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||
<GradientStop Color="#FDFDFD" Offset="0"/>
|
||||
<GradientStop Color="#E8ECF0" Offset="0.5"/>
|
||||
<GradientStop Color="#D8DEE6" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<Grid>
|
||||
<TextBlock Text="{Binding Text}"
|
||||
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
|
||||
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Text="{Binding ActionHint}" FontSize="8" Foreground="#7A8AA0"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,3,1"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 데이터소스 (레거시 MDataTable — 디자인 전용 배지, 런타임/인쇄 비가시) -->
|
||||
<DataTemplate DataType="{x:Type vmc:DataTableViewModel}">
|
||||
<Border Background="#EAF2FB" BorderBrush="#5B8DEF" BorderThickness="1" CornerRadius="4"
|
||||
IsHitTestVisible="False" ToolTip="{Binding QuerySummary}">
|
||||
<Grid>
|
||||
<!-- DB 원통 아이콘 -->
|
||||
<Path Fill="#5B8DEF" Stretch="Uniform" Margin="6"
|
||||
Data="M4,3 C4,1.9 7.6,1 12,1 C16.4,1 20,1.9 20,3 L20,17 C20,18.1 16.4,19 12,19 C7.6,19 4,18.1 4,17 Z M4,3 C4,4.1 7.6,5 12,5 C16.4,5 20,4.1 20,3 M4,8 C4,9.1 7.6,10 12,10 C16.4,10 20,9.1 20,8 M4,13 C4,14.1 7.6,15 12,15 C16.4,15 20,14.1 20,13"/>
|
||||
<TextBlock Text="{Binding Id}" FontSize="7" Foreground="#3E6BC4"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,1"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 표 (레거시 Spread — E_SpdMst 격자 렌더, 읽기 전용) -->
|
||||
<DataTemplate DataType="{x:Type vmc:SpreadViewModel}">
|
||||
<Border BorderBrush="#8A9AAB" BorderThickness="1" Background="White" IsHitTestVisible="False">
|
||||
<Grid>
|
||||
<!-- 격자선 -->
|
||||
<ItemsControl ItemsSource="{Binding GridLines}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}"
|
||||
Stroke="#C6CDD6" StrokeThickness="1"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- 셀 텍스트(스팬 반영) -->
|
||||
<ItemsControl ItemsSource="{Binding CellViews}">
|
||||
<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 Width="{Binding W}" Height="{Binding H}">
|
||||
<TextBlock Text="{Binding Text}"
|
||||
FontFamily="{Binding DataContext.FontFamily, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
FontSize="{Binding DataContext.FontSize, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" TextWrapping="Wrap"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- 격자 정보 없음(파일 열기 등) 안내 -->
|
||||
<TextBlock Text="표 (Spread — DB에서 열면 격자 표시)" FontSize="10" Foreground="#8090A0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding HasGrid}" Value="False">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- 미지원 컨트롤 자리표시 (빗금 + 원본 타입명) -->
|
||||
<DataTemplate DataType="{x:Type vmc:PlaceholderViewModel}">
|
||||
<Border BorderBrush="#D08080" BorderThickness="1" IsHitTestVisible="False">
|
||||
<Border.Background>
|
||||
<DrawingBrush TileMode="Tile" Viewport="0,0,8,8" ViewportUnits="Absolute">
|
||||
<DrawingBrush.Drawing>
|
||||
<GeometryDrawing Geometry="M0,8 L8,0">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#30D08080" Thickness="1"/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</Border.Background>
|
||||
<TextBlock Text="{Binding LegacyClassName}" FontSize="10" Foreground="#B06060"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,183 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SheetMe.Designer.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Lucide(ISC) 아이콘을 WPF 로 렌더 — SVG(24x24, stroke 2, round) inner 마크업을
|
||||
/// Geometry 로 파싱해 Viewbox+Path 로 그린다. 외부 패키지 불필요.
|
||||
/// </summary>
|
||||
public static class LucideIcons
|
||||
{
|
||||
private static readonly Dictionary<string, string> Data = new(StringComparer.Ordinal)
|
||||
{
|
||||
["file-text"] = """<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" />""",
|
||||
["layout-dashboard"] = """<rect width="7" height="9" x="3" y="3" rx="1" /> <rect width="7" height="5" x="14" y="3" rx="1" /> <rect width="7" height="9" x="14" y="12" rx="1" /> <rect width="7" height="5" x="3" y="16" rx="1" />""",
|
||||
["pencil-ruler"] = """<path d="M13 7 8.7 2.7a2.41 2.41 0 0 0-3.4 0L2.7 5.3a2.41 2.41 0 0 0 0 3.4L7 13" /> <path d="m8 6 2-2" /> <path d="m18 16 2-2" /> <path d="m17 11 4.3 4.3c.94.94.94 2.46 0 3.4l-2.6 2.6c-.94.94-2.46.94-3.4 0L11 17" /> <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" />""",
|
||||
["settings"] = """<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" />""",
|
||||
["eye"] = """<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" /> <circle cx="12" cy="12" r="3" />""",
|
||||
["save"] = """<path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" /> <path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" /> <path d="M7 3v4a1 1 0 0 0 1 1h7" />""",
|
||||
["folder-open"] = """<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" />""",
|
||||
["plus"] = """<path d="M5 12h14" /> <path d="M12 5v14" />""",
|
||||
["trash-2"] = """<path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />""",
|
||||
["link"] = """<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />""",
|
||||
["sigma"] = """<path d="M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2" />""",
|
||||
["table"] = """<path d="M12 3v18" /> <rect width="18" height="18" x="3" y="3" rx="2" /> <path d="M3 9h18" /> <path d="M3 15h18" />""",
|
||||
["printer"] = """<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" /> <path d="M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" /> <rect x="6" y="14" width="12" height="8" rx="1" />""",
|
||||
["pen-line"] = """<path d="M13 21h8" /> <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />""",
|
||||
["building-2"] = """<path d="M10 12h4" /> <path d="M10 8h4" /> <path d="M14 21v-3a2 2 0 0 0-4 0v3" /> <path d="M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2" /> <path d="M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16" />""",
|
||||
["package"] = """<path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" /> <path d="M12 22V12" /> <polyline points="3.29 7 12 12 20.71 7" /> <path d="m7.5 4.27 9 5.15" />""",
|
||||
["key-round"] = """<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" /> <circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />""",
|
||||
["scroll-text"] = """<path d="M15 12h-5" /> <path d="M15 8h-5" /> <path d="M19 17V5a2 2 0 0 0-2-2H4" /> <path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3" />""",
|
||||
["search"] = """<path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" />""",
|
||||
["circle-check"] = """<circle cx="12" cy="12" r="10" /> <path d="m9 12 2 2 4-4" />""",
|
||||
["type"] = """<path d="M12 4v16" /> <path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" /> <path d="M9 20h6" />""",
|
||||
["text-cursor-input"] = """<path d="M5 4h1a3 3 0 0 1 3 3 3 3 0 0 1 3-3h1" /> <path d="M13 20h-1a3 3 0 0 1-3-3 3 3 0 0 1-3 3H5" /> <path d="M5 16H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1" /> <path d="M13 8h7a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-7" /> <path d="M9 7v10" />""",
|
||||
["calendar"] = """<path d="M8 2v4" /> <path d="M16 2v4" /> <rect width="18" height="18" x="3" y="4" rx="2" /> <path d="M3 10h18" />""",
|
||||
["list"] = """<path d="M3 5h.01" /> <path d="M3 12h.01" /> <path d="M3 19h.01" /> <path d="M8 5h13" /> <path d="M8 12h13" /> <path d="M8 19h13" />""",
|
||||
["square-check"] = """<rect width="18" height="18" x="3" y="3" rx="2" /> <path d="m9 12 2 2 4-4" />""",
|
||||
["circle-dot"] = """<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="1" />""",
|
||||
["heading"] = """<path d="M6 12h12" /> <path d="M6 20V4" /> <path d="M18 20V4" />""",
|
||||
["check"] = """<path d="M20 6 9 17l-5-5" />""",
|
||||
["chevron-right"] = """<path d="m9 18 6-6-6-6" />""",
|
||||
["chevron-left"] = """<path d="m15 18-6-6 6-6" />""",
|
||||
["chevron-down"] = """<path d="m6 9 6 6 6-6" />""",
|
||||
["layout-template"] = """<rect width="18" height="7" x="3" y="3" rx="1" /> <rect width="9" height="7" x="3" y="14" rx="1" /> <rect width="5" height="7" x="16" y="14" rx="1" />""",
|
||||
["download"] = """<path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" />""",
|
||||
["pencil"] = """<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" />""",
|
||||
["folder"] = """<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />""",
|
||||
["minus"] = """<path d="M5 12h14" />""",
|
||||
["image"] = """<rect width="18" height="18" x="3" y="3" rx="2" ry="2" /> <circle cx="9" cy="9" r="2" /> <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />""",
|
||||
["mouse-pointer-click"] = """<path d="M14 4.1 12 6" /> <path d="m5.1 8-2.9-.8" /> <path d="m6 12-1.9 2" /> <path d="M7.2 2.2 8 5.1" /> <path d="M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z" />""",
|
||||
["list-checks"] = """<path d="m3 17 2 2 4-4" /> <path d="m3 7 2 2 4-4" /> <path d="M13 6h8" /> <path d="M13 12h8" /> <path d="M13 18h8" />""",
|
||||
["box"] = """<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" /> <path d="m3.3 7 8.7 5 8.7-5" /> <path d="M12 22V12" />""",
|
||||
["align-left"] = """<line x1="21" x2="3" y1="6" y2="6" /> <line x1="15" x2="3" y1="12" y2="12" /> <line x1="17" x2="3" y1="18" y2="18" />""",
|
||||
["align-center"] = """<line x1="21" x2="3" y1="6" y2="6" /> <line x1="17" x2="7" y1="12" y2="12" /> <line x1="19" x2="5" y1="18" y2="18" />""",
|
||||
["align-right"] = """<line x1="21" x2="3" y1="6" y2="6" /> <line x1="21" x2="9" y1="12" y2="12" /> <line x1="21" x2="7" y1="18" y2="18" />""",
|
||||
["bold"] = """<path d="M14 12a4 4 0 0 0 0-8H6v8" /> <path d="M15 20a4 4 0 0 0 0-8H6v8Z" />""",
|
||||
["italic"] = """<line x1="19" x2="10" y1="4" y2="4" /> <line x1="14" x2="5" y1="20" y2="20" /> <line x1="15" x2="9" y1="4" y2="20" />""",
|
||||
["underline"] = """<path d="M6 4v6a6 6 0 0 0 12 0V4" /> <line x1="4" x2="20" y1="20" y2="20" />""",
|
||||
["zoom-in"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="11" x2="11" y1="8" y2="14" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
||||
["zoom-out"] = """<circle cx="11" cy="11" r="8" /> <line x1="21" x2="16.65" y1="21" y2="16.65" /> <line x1="8" x2="14" y1="11" y2="11" />""",
|
||||
["maximize"] = """<path d="M8 3H5a2 2 0 0 0-2 2v3" /> <path d="M21 8V5a2 2 0 0 0-2-2h-3" /> <path d="M3 16v3a2 2 0 0 0 2 2h3" /> <path d="M16 21h3a2 2 0 0 0 2-2v-3" />""",
|
||||
["eye-off"] = """<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /> <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /> <path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /> <path d="m2 2 20 20" />""",
|
||||
["copy"] = """<rect width="14" height="14" x="8" y="8" rx="2" ry="2" /> <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />""",
|
||||
["clipboard"] = """<rect width="8" height="4" x="8" y="2" rx="1" ry="1" /> <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />""",
|
||||
["undo-2"] = """<path d="M9 14 4 9l5-5" /> <path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5 5.5 5.5 0 0 1-5.5 5.5H11" />""",
|
||||
["redo-2"] = """<path d="m15 14 5-5-5-5" /> <path d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5 5.5 5.5 0 0 0 9.5 20H13" />""",
|
||||
["sun"] = """<circle cx="12" cy="12" r="4" /> <path d="M12 2v2" /> <path d="M12 20v2" /> <path d="m4.93 4.93 1.41 1.41" /> <path d="m17.66 17.66 1.41 1.41" /> <path d="M2 12h2" /> <path d="M20 12h2" /> <path d="m6.34 17.66-1.41 1.41" /> <path d="m19.07 4.93-1.41 1.41" />""",
|
||||
["moon"] = """<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />""",
|
||||
["move-horizontal"] = """<polyline points="18 8 22 12 18 16" /> <polyline points="6 8 2 12 6 16" /> <line x1="2" x2="22" y1="12" y2="12" />""",
|
||||
["move-vertical"] = """<polyline points="8 18 12 22 16 18" /> <polyline points="8 6 12 2 16 6" /> <line x1="12" x2="12" y1="2" y2="22" />""",
|
||||
["align-obj-top"] = """<line x1="3" x2="21" y1="4" y2="4" /> <rect x="6" y="7" width="4" height="9" rx="1" /> <rect x="14" y="7" width="4" height="13" rx="1" />""",
|
||||
["align-obj-middle"] = """<line x1="3" x2="21" y1="12" y2="12" /> <rect x="6" y="4" width="4" height="6" rx="1" /> <rect x="14" y="14" width="4" height="6" rx="1" />""",
|
||||
["align-obj-bottom"] = """<line x1="3" x2="21" y1="20" y2="20" /> <rect x="6" y="8" width="4" height="9" rx="1" /> <rect x="14" y="4" width="4" height="13" rx="1" />""",
|
||||
["distribute-h"] = """<rect x="3" y="7" width="3.5" height="10" rx="1" /> <rect x="10.25" y="7" width="3.5" height="10" rx="1" /> <rect x="17.5" y="7" width="3.5" height="10" rx="1" />""",
|
||||
["distribute-v"] = """<rect x="7" y="3" width="10" height="3.5" rx="1" /> <rect x="7" y="10.25" width="10" height="3.5" rx="1" /> <rect x="7" y="17.5" width="10" height="3.5" rx="1" />""",
|
||||
["square"] = """<rect width="18" height="18" x="3" y="3" rx="2" />""",
|
||||
["lock"] = """<rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" />""",
|
||||
["unlock"] = """<rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 9.9-1" />""",
|
||||
["group"] = """<path d="M3 7V5a2 2 0 0 1 2-2h2" /> <path d="M17 3h2a2 2 0 0 1 2 2v2" /> <path d="M21 17v2a2 2 0 0 1-2 2h-2" /> <path d="M7 21H5a2 2 0 0 1-2-2v-2" /> <rect width="7" height="5" x="7" y="7" rx="1" /> <rect width="7" height="5" x="10" y="12" rx="1" />""",
|
||||
["ungroup"] = """<rect width="8" height="6" x="5" y="4" rx="1" /> <rect width="8" height="6" x="11" y="14" rx="1" />""",
|
||||
["align-obj-left"] = """<line x1="4" x2="4" y1="3" y2="21" /> <rect x="7" y="6" width="9" height="4" rx="1" /> <rect x="7" y="14" width="13" height="4" rx="1" />""",
|
||||
["align-obj-center-h"] = """<line x1="12" x2="12" y1="3" y2="21" /> <rect x="6" y="6" width="12" height="4" rx="1" /> <rect x="3" y="14" width="18" height="4" rx="1" />""",
|
||||
["align-obj-right"] = """<line x1="20" x2="20" y1="3" y2="21" /> <rect x="11" y="6" width="9" height="4" rx="1" /> <rect x="7" y="14" width="13" height="4" rx="1" />""",
|
||||
["droplet"] = """<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z" />""",
|
||||
["rotate-cw"] = """<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" />""",
|
||||
["frame"] = """<line x1="22" x2="2" y1="6" y2="6" /> <line x1="22" x2="2" y1="18" y2="18" /> <line x1="6" x2="6" y1="2" y2="22" /> <line x1="18" x2="18" y1="2" y2="22" />""",
|
||||
["filter"] = """<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />""",
|
||||
["filter-x"] = """<path d="M13.013 3H2l8 9.46V19l4 2v-8.54l.9-1.055" /> <path d="m22 3-5 5" /> <path d="m17 3 5 5" />""",
|
||||
["user"] = """<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" />""",
|
||||
["play"] = """<polygon points="6 3 20 12 6 21 6 3" />""",
|
||||
["menu"] = """<line x1="4" x2="20" y1="12" y2="12" /> <line x1="4" x2="20" y1="6" y2="6" /> <line x1="4" x2="20" y1="18" y2="18" />""",
|
||||
["hand"] = """<path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" /> <path d="M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" /> <path d="M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" /> <path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" />""",
|
||||
["layout-grid"] = """<rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" />""",
|
||||
};
|
||||
|
||||
/// <summary>아이콘 + 텍스트 가로 묶음 (버튼/네비 Content 용).</summary>
|
||||
public static StackPanel Label(string icon, string text, double iconSize = 15, Brush? color = null)
|
||||
{
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
|
||||
sp.Children.Add(Icon(icon, iconSize, color));
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
var tb = new TextBlock { Text = text, Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center };
|
||||
if (color is not null) tb.Foreground = color; // 아이콘과 같은 색을 글자에도 적용(다크 배경서 글자 안 묻히게)
|
||||
sp.Children.Add(tb);
|
||||
}
|
||||
return sp;
|
||||
}
|
||||
|
||||
public static FrameworkElement Icon(string name, double size = 16, Brush? color = null)
|
||||
{
|
||||
color ??= Brushes.Black;
|
||||
var canvas = new Canvas { Width = 24, Height = 24 };
|
||||
if (Data.TryGetValue(name, out var markup))
|
||||
{
|
||||
var (stroke, fill) = Parse(markup);
|
||||
if (stroke.Children.Count > 0)
|
||||
canvas.Children.Add(new Path { Data = stroke, Stroke = color, StrokeThickness = 2, StrokeStartLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round, StrokeLineJoin = PenLineJoin.Round });
|
||||
if (fill.Children.Count > 0)
|
||||
canvas.Children.Add(new Path { Data = fill, Fill = color });
|
||||
}
|
||||
return new Viewbox { Width = size, Height = size, Child = canvas, Stretch = Stretch.Uniform, SnapsToDevicePixels = true };
|
||||
}
|
||||
|
||||
private static (GeometryGroup Stroke, GeometryGroup Fill) Parse(string markup)
|
||||
{
|
||||
var stroke = new GeometryGroup();
|
||||
var fill = new GeometryGroup();
|
||||
foreach (Match m in Regex.Matches(markup, @"<(path|rect|circle|line|polyline|polygon)\b([^>]*?)/?>", RegexOptions.Singleline))
|
||||
{
|
||||
var tag = m.Groups[1].Value;
|
||||
var attrs = m.Groups[2].Value;
|
||||
var isFill = string.Equals(Attr(attrs, "fill"), "currentColor", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Geometry? g = tag switch
|
||||
{
|
||||
"path" => SafeParse(Attr(attrs, "d")),
|
||||
"rect" => new RectangleGeometry(new Rect(D(attrs, "x"), D(attrs, "y"), D(attrs, "width"), D(attrs, "height")), D(attrs, "rx"), D(attrs, "rx")),
|
||||
"circle" => new EllipseGeometry(new Point(D(attrs, "cx"), D(attrs, "cy")), D(attrs, "r"), D(attrs, "r")),
|
||||
"line" => new LineGeometry(new Point(D(attrs, "x1"), D(attrs, "y1")), new Point(D(attrs, "x2"), D(attrs, "y2"))),
|
||||
"polyline" => PolyGeo(Attr(attrs, "points"), false),
|
||||
"polygon" => PolyGeo(Attr(attrs, "points"), true),
|
||||
_ => null,
|
||||
};
|
||||
if (g is not null) (isFill ? fill : stroke).Children.Add(g);
|
||||
}
|
||||
return (stroke, fill);
|
||||
}
|
||||
|
||||
private static string Attr(string attrs, string name)
|
||||
{
|
||||
var m = Regex.Match(attrs, name + @"\s*=\s*""([^""]*)""");
|
||||
return m.Success ? m.Groups[1].Value : string.Empty;
|
||||
}
|
||||
|
||||
private static double D(string attrs, string name)
|
||||
=> double.TryParse(Attr(attrs, name), NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0;
|
||||
|
||||
private static Geometry? SafeParse(string d)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(d)) return null;
|
||||
try { return Geometry.Parse(d); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private static Geometry? PolyGeo(string points, bool closed)
|
||||
{
|
||||
var nums = Regex.Split(points.Trim(), @"[\s,]+").Where(s => s.Length > 0)
|
||||
.Select(s => double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0).ToList();
|
||||
if (nums.Count < 4) return null;
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i + 1 < nums.Count; i += 2)
|
||||
sb.Append(i == 0 ? "M" : " L").Append(' ').Append(nums[i].ToString(CultureInfo.InvariantCulture)).Append(',').Append(nums[i + 1].ToString(CultureInfo.InvariantCulture));
|
||||
if (closed) sb.Append(" Z");
|
||||
return SafeParse(sb.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SheetMe.Designer.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 팔레트 타입 → (Lucide 아이콘, 카테고리) 매핑 — [200]SheetMe 팔레트 타일 룩 이식.
|
||||
/// Core(ControlRegistry)는 UI 무관하게 유지하고 표시 계층인 여기서만 매핑한다.
|
||||
/// </summary>
|
||||
public static class PaletteIconCatalog
|
||||
{
|
||||
#region Member Fields
|
||||
private static readonly Dictionary<string, (string Icon, string Category)> Map = new(StringComparer.Ordinal)
|
||||
{
|
||||
["Label"] = ("type", "표시"),
|
||||
["PictureBox"] = ("image", "표시"),
|
||||
["Line"] = ("minus", "표시"),
|
||||
["TextBox"] = ("text-cursor-input", "입력"),
|
||||
["MaskedTextBox"] = ("text-cursor-input", "입력"),
|
||||
["ComboBox"] = ("chevron-down", "입력"),
|
||||
["ListBox"] = ("list", "입력"),
|
||||
["CheckList"] = ("list-checks", "입력"),
|
||||
["DateTimePicker"] = ("calendar", "입력"),
|
||||
["CalcBox"] = ("sigma", "입력"),
|
||||
["CheckBox"] = ("square-check", "선택"),
|
||||
["RadioButton"] = ("circle-dot", "선택"),
|
||||
["Panel"] = ("box", "컨테이너"),
|
||||
["GroupBox"] = ("package", "컨테이너"),
|
||||
["Button"] = ("mouse-pointer-click", "동작/데이터"),
|
||||
["DataTable"] = ("table", "동작/데이터"),
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>타입의 Lucide 아이콘명(미지정 타입은 square)</summary>
|
||||
public static string IconOf(string type) => Map.TryGetValue(type, out var entry) ? entry.Icon : "square";
|
||||
|
||||
/// <summary>타입의 팔레트 카테고리(그룹 헤더)</summary>
|
||||
public static string CategoryOf(string type) => Map.TryGetValue(type, out var entry) ? entry.Category : "기타";
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>팔레트 타입명 → Lucide 아이콘 비주얼(FrameworkElement) 컨버터. parameter=크기(기본 18)</summary>
|
||||
public sealed class TypeToIconConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string type)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var size = parameter is string s && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 18;
|
||||
var brush = System.Windows.Application.Current.TryFindResource("B.Muted") as Brush ?? Brushes.Gray;
|
||||
return LucideIcons.Icon(PaletteIconCatalog.IconOf(type), size, brush);
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>팔레트 타입명 → 카테고리명 컨버터(CollectionViewSource 그룹핑용)</summary>
|
||||
public sealed class TypeToCategoryConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is string type ? PaletteIconCatalog.CategoryOf(type) : "기타";
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>불리언 반전 컨버터(선택 도구 토글 = !IsHandTool 표시용)</summary>
|
||||
public sealed class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is not true;
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is not true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lucide 아이콘명 → 비주얼 컨버터(문서 탭 file-text, 플로팅 바 layout-grid 등 고정 아이콘용).
|
||||
/// Binding Source 에 아이콘명 문자열을 넣어 사용 — 항목마다 새 비주얼이 생성되어 공유 부모 충돌이 없다.
|
||||
/// parameter=크기(기본 14).
|
||||
/// </summary>
|
||||
public sealed class IconNameToVisualConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var size = parameter is string s && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 14;
|
||||
var brush = System.Windows.Application.Current.TryFindResource("B.Muted") as Brush ?? Brushes.Gray;
|
||||
return LucideIcons.Icon(name, size, brush);
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
Reference in New Issue
Block a user