Bring WinUI app to feature/visual parity with WPF, fix drag-and-drop and branding assets

- Restyle Fluent controls (buttons, fields, dialogs, ListView) to match the
  app's navy dark/light palette and consistent corner radii/sizing
- Fix ContentDialog (Settings, exit-confirm, error) styling by binding
  PrimaryButtonStyle/CloseButtonStyle explicitly via new DialogHelper,
  since ContentDialog ignores implicit Button styles and forces accent
  color onto whichever button is DefaultButton
- Remove MicaBackdrop and theme the title bar directly so it no longer
  shows a gray tint mismatched with the app's navy background
- Replace placeholder Assets/*.png (unused VS template art) with the real
  AmiReel logo at all required tile/splash/store sizes
- Set explicit default window size (1280x860, matching the old WPF app)
  with DPI-aware centering and a minimum size, instead of sizing to content
- Fix drag-and-drop silently failing: InputList_Drop needs a
  DragOperationDeferral before the first await or the DataView is torn
  down before GetStorageItemsAsync completes
- Add Preview player path setting (was present in WPF, missing in WinUI)
  and error handling for preview playback
- Accept all ffmpeg-readable video formats (mp4, mov, mkv, webm, ...) for
  file picker and drag-and-drop, not just .avi
- Add exit-confirmation dialog to WinUI, mirroring the WPF app

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 13:53:55 +02:00
parent ab46a16726
commit 3e881b79b8
21 changed files with 405 additions and 74 deletions
+66 -24
View File
@@ -33,6 +33,7 @@ public sealed partial class MainPage : Page
EndCardBox.Text = _loadedSettings.EndCardPath;
FfmpegPathBox.Text = _loadedSettings.FfmpegPath;
FfprobePathBox.Text = _loadedSettings.FfprobePath;
PreviewPlayerPathBox.Text = _loadedSettings.PreviewPlayerPath;
OutputNameBox.Text = "amigadb_intro";
TrimBox.Text = _loadedSettings.TrimStart;
FadeBox.Text = _loadedSettings.FadeSeconds;
@@ -47,7 +48,8 @@ public sealed partial class MainPage : Page
private async void AddInputs_Click(object sender, RoutedEventArgs e)
{
FileOpenPicker picker = CreateFileOpenPicker();
picker.FileTypeFilter.Add(".avi");
foreach (string extension in SupportedVideoFormats.Extensions)
picker.FileTypeFilter.Add(extension);
picker.SuggestedStartLocation = PickerLocationId.VideosLibrary;
var files = await picker.PickMultipleFilesAsync();
if (files is null) return;
@@ -69,12 +71,20 @@ public sealed partial class MainPage : Page
{
if (!e.DataView.Contains(StandardDataFormats.StorageItems)) return;
IReadOnlyList<IStorageItem> items = await e.DataView.GetStorageItemsAsync();
foreach (string path in items.OfType<StorageFile>()
.Select(file => file.Path)
.Where(path => string.Equals(Path.GetExtension(path), ".avi", StringComparison.OrdinalIgnoreCase))
.OrderBy(NaturalKey))
if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
DragOperationDeferral deferral = e.GetDeferral();
try
{
IReadOnlyList<IStorageItem> items = await e.DataView.GetStorageItemsAsync();
foreach (string path in items.OfType<StorageFile>()
.Select(file => file.Path)
.Where(SupportedVideoFormats.IsSupported)
.OrderBy(NaturalKey))
if (!_inputs.Contains(path, StringComparer.OrdinalIgnoreCase)) _inputs.Add(path);
}
finally
{
deferral.Complete();
}
}
private async void BrowseEndCard_Click(object sender, RoutedEventArgs e)
@@ -116,6 +126,15 @@ public sealed partial class MainPage : Page
FfprobePathBox.Text = file.Path;
}
private async void BrowsePreviewPlayer_Click(object sender, RoutedEventArgs e)
{
FileOpenPicker picker = CreateFileOpenPicker();
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync();
if (file is not null)
PreviewPlayerPathBox.Text = file.Path;
}
private async void OpenSettings_Click(object sender, RoutedEventArgs e)
{
SettingsDialog.XamlRoot = XamlRoot;
@@ -171,16 +190,16 @@ public sealed partial class MainPage : Page
Process.Start(new ProcessStartInfo("explorer.exe", OutputFolderBox.Text) { UseShellExecute = true });
}
private void PreviewSource_Click(object sender, RoutedEventArgs e)
private async void PreviewSource_Click(object sender, RoutedEventArgs e)
{
if (InputList.SelectedItem is string path)
OpenPreview(path);
await OpenPreviewAsync(path);
}
private void PreviewRendered_Click(object sender, RoutedEventArgs e)
private async void PreviewRendered_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(_lastRenderedFile) && File.Exists(_lastRenderedFile))
OpenPreview(_lastRenderedFile);
await OpenPreviewAsync(_lastRenderedFile);
}
private void ThemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -241,12 +260,39 @@ public sealed partial class MainPage : Page
StatusText.Text = value.Message;
}
private void OpenPreview(string mediaPath)
private async Task OpenPreviewAsync(string mediaPath)
{
if (!File.Exists(mediaPath))
{
await ShowMessageAsync("Preview unavailable", "The selected media file was not found.");
return;
}
Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
string playerPath = PreviewPlayerPathBox.Text.Trim();
try
{
if (!string.IsNullOrWhiteSpace(playerPath))
{
if (!File.Exists(playerPath))
throw new FileNotFoundException("Configured preview player was not found.", playerPath);
ProcessStartInfo customPlayer = new()
{
FileName = playerPath,
UseShellExecute = false
};
customPlayer.ArgumentList.Add(mediaPath);
Process.Start(customPlayer);
return;
}
Process.Start(new ProcessStartInfo(mediaPath) { UseShellExecute = true });
}
catch (Exception exception)
{
await ShowMessageAsync("Preview failed", exception.Message);
}
}
private void AppendLog(string line)
@@ -285,7 +331,7 @@ public sealed partial class MainPage : Page
EndCardBox.Text.Trim(),
FfmpegPathBox.Text.Trim(),
FfprobePathBox.Text.Trim(),
_loadedSettings.PreviewPlayerPath,
PreviewPlayerPathBox.Text.Trim(),
SelectedTheme(),
SelectedEncoder(),
TrimBox.Text.Trim(),
@@ -334,9 +380,8 @@ public sealed partial class MainPage : Page
private void ApplyTheme(string theme)
{
RequestedTheme = string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase)
? ElementTheme.Light
: ElementTheme.Dark;
bool isDark = !string.Equals(theme, "Light", StringComparison.OrdinalIgnoreCase);
App.MainWindowInstance?.ApplyTheme(isDark);
}
private FileOpenPicker CreateFileOpenPicker()
@@ -365,13 +410,10 @@ public sealed partial class MainPage : Page
private async Task ShowMessageAsync(string title, string message)
{
ContentDialog dialog = new()
{
Title = title,
Content = message,
CloseButtonText = "OK",
XamlRoot = XamlRoot
};
ContentDialog dialog = DialogHelper.CreateStyled(XamlRoot, ActualTheme);
dialog.Title = title;
dialog.Content = message;
dialog.CloseButtonText = "OK";
await dialog.ShowAsync();
}
}