* feat(P2-007): migrate Magnifier adorner system to Avalonia (#20) Magnifier.cs: - System.Windows.Controls.Control → Avalonia TemplatedControl - DependencyProperty → StyledProperty<T> with AddClassHandler callbacks - Target property type UIElement → Control (Avalonia uses Control as the visual-tree base) - ZoomFactor / ZoomFactorOnMouseWheel: validation callback replaced with validate: parameter on Register (Avalonia 11 API) - OverrideMetadata for Width/Height → OverrideDefaultValue<T> - SizeChanged event → BoundsProperty.Changed class handler - IsInitialized guard → Bounds.Width/Height > 0 guard - Remove PART_VisualBrush TemplateBinding lookup: Magnifier now renders itself via Render(DrawingContext) instead of relying on a ControlTemplate in Resources.xaml (which is not yet migrated). Circle/Rectangle shapes are drawn directly via DrawingContext.DrawEllipse / DrawRectangle using a VisualBrush whose SourceControl points at the Target; SourceRect carries the viewport (equivalent to WPF VisualBrush.Viewbox / Absolute ViewboxUnits). Brush is rebuilt in RebuildBrush whenever Target changes. - Avalonia.Data using removed (unused after DependencyProperty removal) MagnifierAdorner.cs: - WPF Adorner subclass replaced by Canvas-based adorner control; the canvas is placed in the AdornerLayer via AdornerLayer.SetAdornment (called by MagnifierManager). - AddVisualChild(_magnifier) / ArrangeOverride replaced by Canvas.Children plus Canvas.SetLeft/SetTop for positioning. - InputManager.Current.PostProcessInput replaced by PointerMoved on the adorned element; position is obtained via e.GetPosition(this) relative to the adorner canvas. - VisualTreeHelper.GetOffset → Control.TranslatePoint to get the Target's offset within the adorned element's coordinate space. - Detach() method added to allow clean unsubscription on removal. MagnifierManager.cs: - DependencyObject with RegisterAttached → plain class with AvaloniaProperty.RegisterAttached; change handler wired via MagnifierProperty.Changed.Subscribe. - MouseLeftButtonDown/Up → PointerPressed/Released events. - MouseWheel → PointerWheelChanged; WPF Delta sign convention: Delta < 0 = scroll down = zoom out; Avalonia Delta.Y < 0 = scroll down — same directional mapping applied. - AdornerLayer.GetAdornerLayer + layer.Add → AdornerLayer.GetAdornerLayer + AdornerLayer.SetAdornment (Avalonia 11 API). - Visibility.Visible/Collapsed → IsVisible bool. ImagePopout.xaml: - AdonisWindow + WPF namespace → Avalonia Window. - MagnifierManager.Magnifier inline object-element syntax removed; Magnifier is now attached in code-behind (XAML object-valued attached property on a different element is not supported in Avalonia). - DockPanel given x:Name="RootPanel" for code-behind access. - UseLayoutRounding removed (Avalonia default). ImagePopout.xaml.cs: - Added : Window base class and using Avalonia.Markup.Xaml. - Magnifier attached in OnAttachedToVisualTree to avoid running before the adorner layer is available. Closes #20 * Add untracked file * fix(magnifier): address PR #74 review findings C1/C2 (Magnifier.cs) - BoundsProperty.Changed.AddClassHandler moved from instance ctor to static ctor; now also calls InvalidateVisual() (C1: was registering N handlers for N instances) - TargetProperty.Changed.AddClassHandler added to static ctor so RebuildBrush fires whenever Target is reassigned (C2 / PR-4) - Remove unused 'using Avalonia.Data' (m1 / PR-5) C3/M2 (MagnifierAdorner.cs) - Track _currentElementPosition (element-relative) alongside _currentPointerPosition (adorner-relative); both captured from the same PointerEventArgs so no PointToScreen/PointToClient round-trip is needed (C3) - CalculateViewBoxLocation now uses _currentElementPosition; the old PointToClient(PointToScreen(...)) call is removed - Subscribe to adornedElement.PointerPressed alongside PointerMoved; both route through a shared HandlePointerEvent() so the magnifier is pre-positioned before the first render frame (M2 / PR-3) - Detach() now unsubscribes both PointerPressed and PointerMoved (PR-2) - Remove unused 'using Avalonia.VisualTree' (PR-6) M1/PR-1/PR-2 (MagnifierManager.cs) - e.NewValue.GetValueOrDefault() replaces unsafe e.NewValue.Value access (PR-1) - ConditionalWeakTable<Control, MagnifierManager> lets OnMagnifierChanged detach the old manager when the attached property is changed or cleared - AttachToMagnifier subscribes element.DetachedFromVisualTree so the adorner is detached when the host element leaves the tree (M1 / PR-2) - New Detach() instance method: unsubscribes all element events, calls adorner.Detach(), hides and nulls the adorner ImagePopout.xaml.cs - Remove unused 'using Avalonia.Markup.Xaml' (PR-7) - Use compiler-generated RootPanel field directly instead of this.FindControl<DockPanel>("RootPanel") (m2) * fix(magnifier): address review findings from PR #74 C1: Stretch.Fill in RebuildBrush — restores 1/ZoomFactor magnification ratio; Stretch.None was painting source at 1:1 with no zoom effect. C2: Wire SetMagnifier in ImagePopout constructor, not OnAttachedToVisualTree. Window is the visual root so AttachedToVisualTree never fires on it; constructor wiring matches WPF parse-time property assignment timing. M1: Filter PointerPressed/Released to left button only via PointerUpdateKind, matching WPF MouseLeftButtonDown/MouseLeftButtonUp routing event semantics. M2: Remove dead OnApplyTemplate override; no ControlTheme is registered for Magnifier so the override could never be called. S1: Change base class from TemplatedControl to Control; declare Background, BorderBrush and BorderThickness as local StyledProperty fields. Makes the template-less rendering path explicit at the type-system level. Min1: Subscribe PointerMoved dynamically via OnPropertyChanged(IsVisibleProperty) so the handler is only live while the magnifier is visible, matching the WPF Loaded/Unloaded-gated InputManager.PostProcessInput subscription. Min2: Guard BoundsProperty.Changed with an oldBounds.Size == newBounds.Size short-circuit to skip redundant UpdateViewBox calls on position-only changes (e.g. PositionMagnifier calling Canvas.SetLeft/SetTop). Closes review findings from PR #74 / issue #20. * fix(magnifier): address two unresolved PR review comments MagnifierManager.OnElementDetached: call full Detach() + _managers.Remove() instead of only _adorner.Detach(). The partial cleanup left PointerPressed, PointerReleased, PointerWheelChanged and DetachedFromVisualTree subscriptions live on the element, causing stale handlers if the control re-entered the visual tree. Full teardown ensures a clean slate for any future re-attachment. Magnifier.UpdateViewBox: fall back to Width/Height styled properties (default 100px) when Bounds are still zero (pre-first-layout). This ensures ViewBox.Size is non-zero on the very first PointerPressed, so CalculateViewBoxLocation() correctly centers the viewport under the cursor before the initial measure pass has completed. * fix(magnifier): address final review findings Min1: Remove adorner from AdornerLayer on Detach() via SetAdornment(element, null) so the control is not left as an invisible orphan child in the layer for the lifetime of the host window. HideAdorner() call removed as redundant — layer removal is a stronger cleanup than a visibility toggle. S1: Remove redundant InvalidateVisual() call in BoundsProperty.Changed handler. UpdateViewBox() already calls InvalidateVisual() at its end; the direct call in the handler was scheduling the same dirty-flag set twice per size change. * fix(magnifier): address four unresolved PR review comments MagnifierAdorner ctor: set IsVisible=false so the first ShowAdorner() call triggers a false→true transition on IsVisibleProperty, firing OnPropertyChanged and subscribing PointerMoved. Previously, IsVisible defaulted to true, so the first ShowAdorner() was a no-op and PointerMoved was never subscribed until after the first hide/show cycle. MagnifierManager.Detach(): replace 'AdornerLayer.GetAdornerLayer + layer?.SetAdornment' with a direct 'AdornerLayer.SetAdornment(_element, null)' static call. In Avalonia 11 SetAdornment is static; calling it via an instance reference made the null-conditional '?.' meaningless and was inconsistent with VerifyAdornerLayer which correctly uses the static API. Add/remove are now symmetrical. ImagePopout.xaml: remove unused xmlns:controls namespace alias. The magnifier wiring was moved to code-behind; no XAML element in this file references the alias, eliminating a linter warning. |
||
|---|---|---|
| .github | ||
| .vscode | ||
| CUE4Parse@06fbf1aced | ||
| FModel | ||
| .editorconfig | ||
| .gitignore | ||
| .gitmodules | ||
| LICENSE | ||
| NOTICE | ||
| README.md | ||
FModel Linux — Unreal Engine Archives Explorer for Linux
This is an unofficial Linux port of FModel, originally created by Asval (4sval) and contributors. It is not affiliated with or endorsed by the upstream project or its maintainers. Please do not report Linux-port-specific issues upstream.
Description
FModel Linux is a Linux port of FModel — an archive explorer for Unreal Engine games. It uses CUE4Parse as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats, along with a comprehensive set of tools for previewing and converting game packages.
This fork replaces the Windows-only WPF UI stack with Avalonia UI and removes other Windows-specific dependencies to enable native Linux support. It is maintained by r6e.
Installation
Installation instructions for the Linux port are available in the project wiki (work in progress).
For the upstream Windows release, refer to the official FModel installation guide.
Supporting the Upstream Project
This fork does not accept donations. If you find FModel valuable, please consider supporting the original FModel project and its contributors.
License
FModel Linux is a derivative work licensed under GPL-3. The original FModel project is copyright © Asval and FModel contributors. Licenses of third-party libraries used are listed in NOTICE.