* feat(images): Migrate BitmapImage/ImageSource to Avalonia.Media.Imaging.Bitmap [P2-011]
Replace WPF image types in the three affected ViewModels with their
Avalonia.Media.Imaging.Bitmap equivalents. Closes#24.
TabControlViewModel.cs (TabImage):
- Remove 'using System.Windows' and 'using System.Windows.Media.Imaging'
- Add 'using Avalonia.Media.Imaging'
- BitmapImage _image / public BitmapImage Image -> Bitmap
- Both SetImage overloads (SKBitmap and CTexture): replace
BeginInit/CacheOption/StreamSource/EndInit/Freeze pattern
with 'Image = new Bitmap(stream)'. Bitmap reads the stream
into an internal buffer at construction; disposing the
MemoryStream after is safe.
GameFileViewModel.cs:
- Remove 'using System.Windows', '.Windows.Media',
'.Windows.Media.Imaging'
- Add 'using Avalonia.Media.Imaging', 'using Avalonia.Threading'
- ImageSource _previewImage / public ImageSource PreviewImage -> Bitmap
- SetPreviewImage: replace WPF BitmapImage construction +
Application.Current.Dispatcher.InvokeAsync with
new Bitmap(stream) + Dispatcher.UIThread.Post. Bitmap is
immutable and not UI-thread-affine; constructing on the
background Thread and posting the property assignment is safe.
CustomDirectoriesViewModel.cs:
- Remove 'using System.Windows', '.Windows.Controls',
'.Windows.Media.Imaging'
- Add 'using Avalonia.Controls', 'using Avalonia.Layout',
'using Avalonia.Media.Imaging', 'using Avalonia.Platform'
- MenuItem/Image/Separator/Control -> Avalonia equivalents
(same class names, different namespaces)
- BitmapImage(new Uri('/FModel;component/Resources/xxx.png'))
-> new Bitmap(AssetLoader.Open(new Uri('avares://FModel/...')))
for the four directory-icon menu items
* fix(images): Address PR #79 review findings [P2-011]
FModel.csproj [C1]:
- Change <Resource> to <AvaloniaResource> for the four directory icon
PNGs (add_directory, delete, edit, go_to_directory). WPF <Resource>
items are not processed into Avalonia's avares resource manifest,
so AssetLoader.Open would have thrown at runtime on every app startup.
GameFileViewModel.cs [m1 + PR comment]:
- Remove redundant 'ms.Position = 0' in SetPreviewImage: MemoryStream
initializes to position 0, making the reset a no-op.
- Fix 'Resolved |= ~EResolveCompute.Preview' to 'Resolved &= ~...'.
The bitwise-NOT operator on an enum value produces a large negative
int, ORing in undefined bits; ORing never clears bits, so the intent
(undo the premature Preview flag set at the top of
ResolveByExtensionAsync) required &= ~, not |= ~.
CustomDirectoriesViewModel.cs [PR comment]:
- Extract private static LoadIcon(string filename) helper to wrap each
AssetLoader.Open call in a using, ensuring the stream returned by
AssetLoader.Open is disposed after Bitmap copies its data.
[m2] (Bitmap disposal in Image/PreviewImage setters) is intentionally
deferred: disposing the old Bitmap in the setter while the Avalonia
render thread may still hold it for the current frame risks a crash.
* fix(magnifier): Address review findings [M1, m1, m2, S1]
MagnifierManager.cs [M1]:
- Replace AdornerLayer.SetAdornment(element, adorner) / SetAdornment(element, null)
with AdornerLayer.SetAdornedElement + layer.Children.Add in VerifyAdornerLayer
and Parent-cast + Children.Remove in Detach(). SetAdornment is a single-slot
API that silently evicts any other adornment on the same element; the collection
API matches WPF's AdornerLayer.Add/Remove semantics and allows co-existing
adorners.
MagnifierManager.cs [S1]:
- Set e.Handled = true after a zoom change when the adorner is visible so that
a parent ScrollViewer does not also scroll while the user is zooming the
magnifier.
- Adjust the zoom-out floor from 0 to ZoomFactorOnMouseWheel now that the
ZoomFactor validator rejects values <= 0.
- Only call UpdateViewBox when the adorner is visible (no-op otherwise).
Magnifier.cs [m1]:
- Change ZoomFactor validate: v >= 0 -> v > 0. ZoomFactor = 0 produced a
zero-sized SourceRect on the VisualBrush which rendered undefined content.
The minimum meaningful zoom is any positive value.
MagnifierAdorner.cs [m2]:
- Return early from HandlePointerEvent when Bounds.Width and Bounds.Height are
both 0: the first PointerPressed arrives before the adorner canvas has been
through a layout pass, so GetPosition(this) returns meaningless coordinates.
The subsequent PointerMoved fires after layout and picks up correct values.
* Fix reviewer to stop it getting confused
* feat(#14): migrate App.xaml + App.xaml.cs to Avalonia bootstrap
- App.xaml: replace WPF/AdonisUI xmlns with Avalonia xmlns=https://github.com/avaloniaui; add FluentTheme; remove AdonisUI MergedDictionaries and BooleanToVisibilityConverter (Views/Resources/* will be re-added as they are migrated in later issues)
- App.xaml.cs: inherit Avalonia.Application; OnFrameworkInitializationCompleted replaces OnStartup; AppExit wired via IClassicDesktopStyleApplicationLifetime.Exit; AdonisUI MessageBox replaced with inline Avalonia Window dialog; P/Invokes guarded with OperatingSystem.IsWindows(); GetRegistryValue guarded for Linux (P3-006 will replace fully)
- Program.cs (new): Avalonia entry point with BuildAvaloniaApp + Main; AppDomain.UnhandledException registered to drive ShowErrorDialog
- AssemblyInfo.cs: remove WPF ThemeInfoAttribute (no Avalonia equivalent)
- FModel.csproj: StartupObject -> FModel.Program; add Serilog.Sinks.Console 6.1.1 (needed for AnsiConsoleTheme in debug logger); exclude Properties/Resources.resx + Designer.cs (WPF boilerplate, unreferenced)
Closes#14
* fix(#14): address code review findings in App bootstrap
[C1] Replace AppDomain.UnhandledException with Dispatcher.UIThread
.UnhandledExceptionFilter — fires on UI thread, allows Handled=true
[C2] Wrap dialog.Show() fallback in try/catch; return early if app is
shutting down to avoid silent crash
[M1] Add TODO(#15) comment for desktop.MainWindow assignment
[M2] Change Dispatcher.UIThread.Post → InvokeAsync; restructure
ShowErrorDialog to only use tcs.Task in the modeless path
[M3] Restore Environment.Exit(0) in AppExit to preserve original
termination behaviour until proper cancellation is in place
[m2] Add SolidColorBrush resources (AccentColorBrush, AlertColorBrush,
ErrorColorBrush) alongside Color resources in App.xaml
* fix(#14): address second-pass review findings
[m1/s1] Move Dispatcher.UIThread.UnhandledExceptionFilter subscription
to after Log.Logger is configured; Log.Error calls inside the
handler were no-ops when exceptions occurred during the ~80-line
init block (settings load, directory creation)
[m2] Attach .ContinueWith(OnlyOnFaulted) to the InvokeAsync task in
ShowErrorDialog so secondary exceptions in the error-handling
path (UserSettings.Delete, Restart) are logged rather than
silently swallowed as unobserved task faults
* Address review feedback