Unreal Engine Archives Explorer
Go to file
Rob Trame 2081c8af7d
feat(P2-006): migrate HotkeyTextBox, FilterableComboBox, and Hotkey to Avalonia (#73)
* feat(P2-006): migrate HotkeyTextBox, FilterableComboBox, and Hotkey to Avalonia (#19)

Hotkey.cs (Framework):
- Replace System.Windows.Input (Key, ModifierKeys, Keyboard.Modifiers)
  with Avalonia.Input (Key, KeyModifiers)
- ModifierKeys.Windows → KeyModifiers.Meta
- IsTriggered(Key) → IsTriggered(Key, KeyModifiers): receives explicit
  modifiers from the caller's KeyEventArgs instead of the WPF global
  Keyboard.Modifiers static (which does not exist in Avalonia)

UserSettings.cs (Settings):
- Replace using System.Windows / System.Windows.Input with
  Avalonia.Controls / Avalonia.Input
- ModifierKeys.Control → KeyModifiers.Control on the three default
  hotkey values (AssetAddTab, AssetRemoveTab, AddAudio)

HotkeyTextBox.cs:
- WPF TextBox / DependencyProperty / FrameworkPropertyMetadata replaced
  by Avalonia TextBox / StyledProperty<Hotkey> with BindingMode.TwoWay
- Changed property-changed hook to AddClassHandler static ctor pattern
- OnPreviewKeyDown → OnKeyDown; Keyboard.Modifiers → e.KeyModifiers
- Key.System / e.SystemKey handling removed (not applicable on Linux
  X11/Wayland; Alt key arrives directly via e.Key in Avalonia)
- Key.OemClear removed (not in Avalonia.Input.Key enum)
- Key.DeadCharProcessed removed (not in Avalonia.Input.Key enum)
- IsReadOnlyCaretVisible / IsUndoEnabled removed (no Avalonia equivalent)
- ContextMenu set to null instead of collapsed (no default ContextMenu)

FilterableComboBox.cs:
- WPF ComboBox / DependencyProperty / CollectionViewSource replaced by
  Avalonia ComboBox / StyledProperty / manual filter-list pattern
- CollectionViewSource.GetDefaultView().Filter → store original
  IEnumerable, rebuild filtered List<object> in ApplyFilter() on each
  text change; _isUpdatingItems flag prevents recursion
- OnPreviewKeyDown → OnKeyDown
- OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs) →
  OnLostFocus + TopLevel.FocusManager check to avoid false commits when
  focus moves between ComboBox and its own TextBox
- GetTemplateChild in Loaded handler → OnApplyTemplate NameScope.Find
- TextBoxBaseUserChangeTracker: PreviewTextInput→TextInput,
  PreviewKeyDown→KeyDown, PreviewKeyUp→KeyUp; SelectionLength replaced
  with SelectionEnd - SelectionStart (Avalonia TextBox API)
- StaysOpenOnEdit / IsTextSearchEnabled removed (not in Avalonia ComboBox)

MainWindow.xaml.cs:
- Update 7 IsTriggered(e.Key) call sites to IsTriggered(e.Key, e.KeyModifiers)

Closes #19

* fix(P2-006): address #19 review findings and PR comments

Hotkey.cs:
- [M1] IsTriggered: modifiers.HasFlag(Modifiers) → modifiers == Modifiers
  (HasFlag(None) is always true, so any key combo with no modifiers
  requirement would incorrectly fire for every key press with modifiers)
- [S1] Add [JsonConverter(typeof(StringEnumConverter))] on Key and
  Modifiers properties so they serialise as strings rather than integers;
  Avalonia.Input.Key integer values differ from System.Windows.Input.Key
  values (e.g. Key.A: WPF=44, Avalonia=65), so integer serialisation
  would load wrong keys from existing AppSettings.json on first launch

HotkeyTextBox.cs:
- [C1]/PR-1 Move e.Handled = true into capturing cases only (Delete/Back/
  Escape clears hotkey; default captures new hotkey). Modifier-only keys,
  Tab/Enter/Space without modifiers now propagate unhandled so Tab focus
  navigation works correctly. (OnPreviewKeyDown does not exist as a
  virtual override in Avalonia TextBox; OnKeyDown is correct.)
- [m1] Remove dead HasKeyChar method (never called)

FilterableComboBox.cs:
- [M2] OnLostFocus: replace reference equality to _editableTextBox with
  IsVisualAncestorOf(focused) (covers all template parts including the
  dropdown popup, not just the TextBox part)
- [M3] AttachSourceFilter: reset _currentFilter = string.Empty before
  ApplyFilter() so a stale filter is not applied to a new source
- [m2]/PR-6 Remove dead FilterItem method
- [m3] FreezeTextBoxState: wrap action() in try/finally so _textBoxFrozen
  is always reset even if action() throws
- PR-3 Subscribe to INotifyCollectionChanged on the new _originalSource
  in AttachSourceFilter; unsubscribe from the old one in OnPropertyChanged
  before replacing _originalSource. This keeps the filtered ItemsSource
  live when items are added/removed in the bound collection (e.g.
  GameSelectorViewModel.DetectedDirectories).
- PR-4 Normalize tb.Text to string.Empty in OnUserTextChanged before
  Length/Substring to avoid NullReferenceException on empty TextBox
- PR-5 Use string.Contains(filter, StringComparison.OrdinalIgnoreCase)
  in ApplyFilter instead of ToLower().Contains(); removes per-item
  allocations and makes matching locale-independent
- Store _currentFilter without lower-casing (OrdinalIgnoreCase does not
  need pre-normalised input)

AudioPlayer.xaml.cs:
- PR-2 Update 4× IsTriggered(e.Key) → IsTriggered(e.Key, e.KeyModifiers)
  (missed call sites from original migration; old 1-arg overload removed)

* fix(P2-006): address second-pass #19 review findings

FilterableComboBox.cs:
- [M1] Add OnDetachedFromVisualTree: unsubscribe from
  INotifyCollectionChanged.CollectionChanged when the control is
  removed from the visual tree. Without this, a long-lived source
  collection (e.g. GameSelectorViewModel.DetectedDirectories) would
  hold a strong reference to the FilterableComboBox instance via the
  event delegate, preventing GC of the control after removal.
- [m2] Remove dead/inverted _lastText == currentText clause from
  TextBoxUserChangeTracker.TextChanged handler. TextChanged never fires
  with equal text in Avalonia, so the clause never executed; removing
  it eliminates confusing dead code (the equality check was logically
  inverted relative to its intended meaning).

Hotkey.cs:
- [S1] Add comment documenting StringEnumConverter round-trip behaviour
  for [Flags] KeyModifiers values (Newtonsoft serialises them as
  comma-separated strings, e.g. "Control, Shift", and round-trips
  correctly through the same format).

* fix(P2-006): address third-pass #19 review findings

FilterableComboBox.cs:
- [m1] Remove dead _lastText field and its two assignment sites
  (constructor init + TextChanged update). The field became unreachable
  after the [m2] fix removed the only read site (_lastText == text
  predicate). No behavioral change.

Hotkey.cs:
- Fix typo in comment: "combinationsthrough" → "combinations through"

* fix(P2-006): use SetCurrentValue in ApplyFilter to preserve XAML binding

FilterableComboBox.cs:
- ApplyFilter previously called ItemsSource = ... which invokes SetValue
  at LocalValue priority, clearing any XAML binding on ItemsSource. If
  the bound ViewModel property is later updated (e.g. the VM replaces
  DetectedDirectories), the severed binding means OnPropertyChanged
  never fires and the new source is never captured.
- Switch to SetCurrentValue(ItemsSourceProperty, filtered) which writes
  the filtered list at LocalValue priority without removing the binding,
  so ViewModel-driven ItemsSource replacements continue to flow through
  normally after filtering has been applied.
2026-03-14 17:32:55 -06:00
.github chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00
.vscode Set up dev env 2026-03-11 15:51:34 -06:00
CUE4Parse@06fbf1aced chore: point CUE4Parse submodule at r6e/CUE4Parse-Linux (#66) 2026-03-11 19:49:17 -06:00
FModel feat(P2-006): migrate HotkeyTextBox, FilterableComboBox, and Hotkey to Avalonia (#73) 2026-03-14 17:32:55 -06:00
.editorconfig chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00
.gitignore Finish setting up env 2026-03-11 17:28:01 -06:00
.gitmodules chore: point CUE4Parse submodule at r6e/CUE4Parse-Linux (#66) 2026-03-11 19:49:17 -06:00
LICENSE Re-add GPL-3 license 2021-05-22 16:33:08 -04:00
NOTICE chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00
README.md chore: update identity, branding, and CI for FModel Linux fork (#69) 2026-03-12 11:13:00 -06:00

FModel Linux — Unreal Engine Archives Explorer for Linux

CI Status Latest


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.