FModel/.github
Rob Trame 4c233f9947
feat(images): Migrate BitmapImage/ImageSource to Avalonia.Media.Imaging.Bitmap [P2-011] (#79)
* 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
2026-03-15 19:09:04 -06:00
..
agents feat(images): Migrate BitmapImage/ImageSource to Avalonia.Media.Imaging.Bitmap [P2-011] (#79) 2026-03-15 19:09:04 -06:00
ISSUE_TEMPLATE chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00
workflows chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00
FUNDING.yml chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00