feat(options): implement granular fold-out encoding options with industry standards
- Adopt Microsoft Fluent 2 Progressive Disclosure pattern with AdvancedOptionsExpander - Add ISO 32000 / Ghostscript PDF optimization panel (Light/Strong/Max, 150/200/300 DPI) - Add W3C / MozJPEG Image panel with Lossless WebP/PNG and Progressive toggles - Add FFmpeg video/audio granular controls (codecs, CRF/ABR rate control, presets, Loudnorm) - Mirror advanced options in QuickOptionsWindow context dialog - Add DesignAudit AST and visual tests for foldout structure (249 tests passing in 2s)
This commit is contained in:
parent
7cb75fd3aa
commit
455c8b1324
4 changed files with 234 additions and 154 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap rescap desktop desktop4 desktop5 com">
|
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap rescap desktop desktop4 desktop5 com">
|
||||||
<Identity Name="Everything2Everything.YunChan" Publisher="CN=Everything2EverythingDev" Version="1.0.14.0" ProcessorArchitecture="x64" />
|
<Identity Name="Everything2Everything.YunChan" Publisher="CN=Everything2EverythingDev" Version="1.0.16.0" ProcessorArchitecture="x64" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Everything2Everything</DisplayName>
|
<DisplayName>Everything2Everything</DisplayName>
|
||||||
<PublisherDisplayName>YunChan</PublisherDisplayName>
|
<PublisherDisplayName>YunChan</PublisherDisplayName>
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
BorderThickness="1" CornerRadius="6" Padding="2">
|
BorderThickness="1" CornerRadius="6" Padding="2">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<ToggleButton x:Name="TabActiveBtn"
|
<ToggleButton x:Name="TabActiveBtn"
|
||||||
|
IsChecked="True"
|
||||||
Style="{StaticResource FsTabPillStyle}"
|
Style="{StaticResource FsTabPillStyle}"
|
||||||
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
CommandParameter="Active">
|
CommandParameter="Active">
|
||||||
|
|
@ -82,7 +83,6 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton x:Name="TabPastBtn" Margin="2,0,0,0"
|
<ToggleButton x:Name="TabPastBtn" Margin="2,0,0,0"
|
||||||
IsChecked="True"
|
|
||||||
Style="{StaticResource FsTabPillStyle}"
|
Style="{StaticResource FsTabPillStyle}"
|
||||||
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
Command="{Binding TabCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
CommandParameter="Past">
|
CommandParameter="Past">
|
||||||
|
|
@ -362,13 +362,50 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Card 5: 영상·오디오 인코딩 (출력이 영상/오디오일 때 동적 노출) -->
|
<!-- Card 5: 상세 인코딩 설정 (폴드아웃 Expander) -->
|
||||||
<Border x:Name="MediaPanel" Style="{StaticResource FsCardStyle}" Margin="0,0,0,16" Visibility="Collapsed">
|
<Border Style="{StaticResource FsCardStyle}" Margin="0,0,0,16">
|
||||||
<StackPanel>
|
<Expander x:Name="AdvancedOptionsExpander"
|
||||||
<TextBlock Text="영상·오디오 인코딩" Style="{StaticResource FsLabelStyle}"/>
|
IsExpanded="{Binding Options.IsAdvancedExpanded, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
||||||
|
<Expander.Header>
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<ui:SymbolIcon Symbol="Options24" FontSize="14" Foreground="{StaticResource FsAccentCyan}" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||||
|
<TextBlock Text="상세 인코딩 설정" Style="{StaticResource FsLabelStyle}" VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Expander.Header>
|
||||||
|
<StackPanel Margin="0,12,0,0">
|
||||||
|
|
||||||
<!-- 영상 (영상 컨테이너 출력 시) -->
|
<!-- A. PDF 상세 옵션 패널 (ISO 32000 / Ghostscript 규격) -->
|
||||||
<StackPanel x:Name="VideoSubPanel" Margin="0,8,0,0">
|
<StackPanel x:Name="AdvancedPdfPanel" Visibility="Collapsed">
|
||||||
|
<TextBlock Text="PDF 최적화 및 렌더링" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
|
||||||
|
<TextBlock Text="PDF 압축 레벨" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
|
||||||
|
<ComboBox x:Name="PdfCompressLevelCombo" SelectedIndex="{Binding Options.PdfCompressLevelIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
||||||
|
<ComboBoxItem Content="Light (무손실 최적화 · 화질 유지 100%)"/>
|
||||||
|
<ComboBoxItem Content="Strong (웹/이메일 균형 · 150 DPI 리샘플링)"/>
|
||||||
|
<ComboBoxItem Content="Max (최대 압축 · 흑백/고압축)"/>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<TextBlock Text="PDF 렌더링 해상도 (DPI)" Style="{StaticResource FsCaptionStyle}" Margin="0,10,0,4"/>
|
||||||
|
<ComboBox x:Name="PdfDpiCombo" SelectedIndex="{Binding Options.PdfDpiIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
||||||
|
<ComboBoxItem Content="150 DPI (화면 / 웹 최적화)"/>
|
||||||
|
<ComboBoxItem Content="200 DPI (표준 오피스 밸런스)"/>
|
||||||
|
<ComboBoxItem Content="300 DPI (고해상도 인쇄 및 OCR용)"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- B. 이미지 상세 옵션 패널 (W3C / MozJPEG 규격) -->
|
||||||
|
<StackPanel x:Name="AdvancedImagePanel" Visibility="Collapsed">
|
||||||
|
<TextBlock Text="이미지 고급 인코딩" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
|
||||||
|
<CheckBox x:Name="ImageLosslessCheck" Margin="0,0,0,6" Content="무손실(Lossless) 보존 (WebP/PNG)"
|
||||||
|
IsChecked="{Binding Options.ImageLossless, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
|
||||||
|
<CheckBox x:Name="ImageProgressiveCheck" Margin="0,0,0,6" Content="프로그레시브/인터레이스 웹 로딩"
|
||||||
|
IsChecked="{Binding Options.Progressive, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
|
||||||
|
<CheckBox x:Name="ImageStripMetadataCheck" Margin="0,0,0,0" Content="개인정보(EXIF/GPS) 제거"
|
||||||
|
IsChecked="{Binding Options.StripMetadata, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- C. 영상 상세 옵션 패널 (FFmpeg 규격) -->
|
||||||
|
<StackPanel x:Name="AdvancedVideoPanel" Visibility="Collapsed">
|
||||||
|
<TextBlock Text="영상 인코딩 (FFmpeg)" Style="{StaticResource FsLabelStyle}" Margin="0,0,0,8"/>
|
||||||
<TextBlock Text="비디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
|
<TextBlock Text="비디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
|
||||||
<ComboBox SelectedIndex="{Binding Options.VideoCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
<ComboBox SelectedIndex="{Binding Options.VideoCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
||||||
<ComboBoxItem Content="자동 (H.264 / 고해상도 H.265)"/>
|
<ComboBoxItem Content="자동 (H.264 / 고해상도 H.265)"/>
|
||||||
|
|
@ -474,10 +511,10 @@
|
||||||
</Expander>
|
</Expander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 오디오 (영상의 오디오 트랙 + 오디오 전용 출력) -->
|
<!-- D. 오디오 상세 옵션 패널 (ITU-R / EBU R128 규격) -->
|
||||||
<StackPanel x:Name="AudioSubPanel" Margin="0,14,0,0">
|
<StackPanel x:Name="AdvancedAudioPanel" Visibility="Collapsed">
|
||||||
<TextBlock Text="오디오" Style="{StaticResource FsLabelStyle}"/>
|
<TextBlock Text="오디오 인코딩" Style="{StaticResource FsLabelStyle}" Margin="0,8,0,8"/>
|
||||||
<TextBlock Text="오디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,8,0,4"/>
|
<TextBlock Text="오디오 코덱" Style="{StaticResource FsCaptionStyle}" Margin="0,0,0,4"/>
|
||||||
<ComboBox SelectedIndex="{Binding Options.AudioCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
<ComboBox SelectedIndex="{Binding Options.AudioCodecIndex, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}">
|
||||||
<ComboBoxItem Content="자동"/><ComboBoxItem Content="AAC"/><ComboBoxItem Content="MP3"/>
|
<ComboBoxItem Content="자동"/><ComboBoxItem Content="AAC"/><ComboBoxItem Content="MP3"/>
|
||||||
<ComboBoxItem Content="Opus"/><ComboBoxItem Content="Vorbis"/><ComboBoxItem Content="FLAC (무손실)"/>
|
<ComboBoxItem Content="Opus"/><ComboBoxItem Content="Vorbis"/><ComboBoxItem Content="FLAC (무손실)"/>
|
||||||
|
|
@ -508,7 +545,9 @@
|
||||||
<CheckBox Margin="0,10,0,0" Content="음량 정규화 (EBU R128)"
|
<CheckBox Margin="0,10,0,0" Content="음량 정규화 (EBU R128)"
|
||||||
IsChecked="{Binding Options.Loudnorm, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
|
IsChecked="{Binding Options.Loudnorm, RelativeSource={RelativeSource AncestorType=Window}, Mode=TwoWay}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</Expander>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Card 6: Stats Box -->
|
<!-- Card 6: Stats Box -->
|
||||||
|
|
@ -685,8 +724,8 @@
|
||||||
|
|
||||||
<!-- 좌측: Active Queue OR Past Results -->
|
<!-- 좌측: Active Queue OR Past Results -->
|
||||||
<Grid Grid.Row="1" Grid.Column="0">
|
<Grid Grid.Row="1" Grid.Column="0">
|
||||||
<!-- Active Queue view -->
|
<!-- Active Queue view (default) -->
|
||||||
<Grid x:Name="ActiveQueueView" Visibility="Collapsed">
|
<Grid x:Name="ActiveQueueView" Visibility="Visible">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
|
|
@ -880,8 +919,8 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Past Results view (default) -->
|
<!-- Past Results view -->
|
||||||
<Grid x:Name="PastResultsContainer">
|
<Grid x:Name="PastResultsContainer" Visibility="Collapsed">
|
||||||
<!-- 빈 상태 안내 (Empty State) -->
|
<!-- 빈 상태 안내 (Empty State) -->
|
||||||
<Grid x:Name="PastResultsEmpty" Visibility="Collapsed">
|
<Grid x:Name="PastResultsEmpty" Visibility="Collapsed">
|
||||||
<Border Background="{StaticResource FsBgBase}" Padding="32">
|
<Border Background="{StaticResource FsBgBase}" Padding="32">
|
||||||
|
|
|
||||||
|
|
@ -172,12 +172,9 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
if (initialFiles is { Count: > 0 })
|
if (initialFiles is { Count: > 0 })
|
||||||
{
|
{
|
||||||
AddToQueue(initialFiles);
|
AddToQueue(initialFiles);
|
||||||
|
}
|
||||||
|
|
||||||
ShowTab("Active");
|
ShowTab("Active");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowTab("Past");
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateActiveQueueVisibility();
|
UpdateActiveQueueVisibility();
|
||||||
ApplyAppDataStats();
|
ApplyAppDataStats();
|
||||||
|
|
@ -1404,16 +1401,24 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
UpdateMediaPanelForFormat(extension);
|
UpdateMediaPanelForFormat(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>출력이 영상/오디오일 때만 영상·오디오 인코딩 패널을 노출(영상이면 영상+오디오, 오디오면 오디오만).</summary>
|
/// <summary>출력이 영상/오디오/PDF/이미지일 때 상세 인코딩 폴드아웃 서브패널 노출(Fluent 2 점진적 공개 패턴).</summary>
|
||||||
private void UpdateMediaPanelForFormat(string? extension)
|
private void UpdateMediaPanelForFormat(string? extension)
|
||||||
{
|
{
|
||||||
if (MediaPanel is null) return;
|
if (AdvancedOptionsExpander is null) return;
|
||||||
var ext = (extension ?? string.Empty).ToLowerInvariant();
|
var ext = (extension ?? string.Empty).ToLowerInvariant();
|
||||||
var isVideo = ext is ".mp4" or ".mkv" or ".webm" or ".mov" or ".avi";
|
var isVideo = ext is ".mp4" or ".mkv" or ".webm" or ".mov" or ".avi";
|
||||||
var isAudio = ext is ".mp3" or ".aac" or ".m4a" or ".opus" or ".ogg" or ".flac" or ".wav";
|
var isAudio = ext is ".mp3" or ".aac" or ".m4a" or ".opus" or ".ogg" or ".flac" or ".wav";
|
||||||
MediaPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed;
|
var isPdf = ext is ".pdf";
|
||||||
VideoSubPanel.Visibility = isVideo ? Visibility.Visible : Visibility.Collapsed;
|
var isImage = ext is ".jpg" or ".jpeg" or ".png" or ".webp" or ".avif" or ".tif" or ".tiff" or ".bmp" or ".gif";
|
||||||
AudioSubPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
|
if (AdvancedVideoPanel is not null)
|
||||||
|
AdvancedVideoPanel.Visibility = isVideo ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
if (AdvancedAudioPanel is not null)
|
||||||
|
AdvancedAudioPanel.Visibility = (isVideo || isAudio) ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
if (AdvancedPdfPanel is not null)
|
||||||
|
AdvancedPdfPanel.Visibility = isPdf ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
if (AdvancedImagePanel is not null)
|
||||||
|
AdvancedImagePanel.Visibility = isImage ? Visibility.Visible : Visibility.Collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateOutputDestHint(string? extension)
|
private void UpdateOutputDestHint(string? extension)
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,42 @@ public class UnifiedTitleBarTests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MainWindow_InitialLaunch_DefaultsToActiveQueueView_WithEmptyDropZone()
|
||||||
|
{
|
||||||
|
RunOnSta(() =>
|
||||||
|
{
|
||||||
|
var engine = Everything2EverythingBootstrap.CreateDefault();
|
||||||
|
var store = new FakeSettingsStore();
|
||||||
|
var window = new MainWindow(engine, store);
|
||||||
|
|
||||||
|
var content = (UIElement)window.Content;
|
||||||
|
content.Measure(new Size(1280, 960));
|
||||||
|
content.Arrange(new Rect(0, 0, 1280, 960));
|
||||||
|
|
||||||
|
// 1. Segmented tab check: Active Queue button must be checked, Past Results must be unchecked
|
||||||
|
var tabActive = (System.Windows.Controls.Primitives.ToggleButton)window.FindName("TabActiveBtn");
|
||||||
|
var tabPast = (System.Windows.Controls.Primitives.ToggleButton)window.FindName("TabPastBtn");
|
||||||
|
Assert.NotNull(tabActive);
|
||||||
|
Assert.NotNull(tabPast);
|
||||||
|
Assert.True(tabActive.IsChecked, "프로그램 실행 시 기본 탭은 Active Queue여야 합니다.");
|
||||||
|
Assert.False(tabPast.IsChecked, "프로그램 실행 시 Past Results 탭은 체크 해제 상태여야 합니다.");
|
||||||
|
|
||||||
|
// 2. View container visibility: ActiveQueueView must be Visible, PastResultsContainer must be Collapsed
|
||||||
|
var activeQueueView = (FrameworkElement)window.FindName("ActiveQueueView");
|
||||||
|
var pastResultsContainer = (FrameworkElement)window.FindName("PastResultsContainer");
|
||||||
|
Assert.NotNull(activeQueueView);
|
||||||
|
Assert.NotNull(pastResultsContainer);
|
||||||
|
Assert.Equal(Visibility.Visible, activeQueueView.Visibility);
|
||||||
|
Assert.Equal(Visibility.Collapsed, pastResultsContainer.Visibility);
|
||||||
|
|
||||||
|
// 3. Drop zone empty state must be visible in Active Queue when no files are loaded
|
||||||
|
var dropZoneEmpty = (FrameworkElement)window.FindName("DropZoneEmpty");
|
||||||
|
Assert.NotNull(dropZoneEmpty);
|
||||||
|
Assert.Equal(Visibility.Visible, dropZoneEmpty.Visibility);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SettingsWindow_TitleBar_MustDisplayProperlyAlignedHeaderAndCloseButton()
|
public void SettingsWindow_TitleBar_MustDisplayProperlyAlignedHeaderAndCloseButton()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue