mirror of
https://github.com/4sval/FModel.git
synced 2026-03-24 18:54:42 -05:00
40d8b73ec5
1691 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
40d8b73ec5
|
fix: address remaining WPF→Avalonia gaps for phase 2 completion (#85)
* fix: address remaining WPF→Avalonia gaps for phase 2 completion Resolves r6e/FModel-Linux#84 - Convert Colors.xaml & Icons.xaml to Avalonia xmlns - Migrate CommitControl to Avalonia (replace DataTriggers with IsVisible) - Migrate SearchTextBox to Avalonia (StyledProperty, native Watermark) - Migrate TreeViewItemBehavior to Avalonia attached properties - Replace ListCollectionView with DataGridCollectionView in 8 ViewModels - Add Framework/CompositeCollection as WPF CompositeCollection replacement - Remove AdonisUI.Controls refs from LoadCommand & AddEditDirectoryCommand - Replace SystemParameters with Avalonia screen API in CUE4ParseViewModel - Rewrite Resources.xaml for Avalonia Fluent theme (~2150→290 lines) - Extend CommitMessageConverter with HasDescription parameter support * fix: address review findings for PR #85 - C1: Fix ToolTip syntax (TextBlock.ToolTip → ToolTip.Tip) in tree view - C2: Add Fill binding to folder icons via ConverterParameter=Brush - C3: Convert AddEditDirectoryCommand.ShowDialog() to async with owner - M1: Convert keyed Style → ControlTheme + Theme= in all consumers - M2: Fix CommitControl badge overlap (Latest/Current mutual exclusivity) Add FallbackValue=False for null-safe Asset.IsLatest binding - M3: Add TODO(P4-004) markers for OpenFileDialog/SaveFileDialog - M4: Implement IDisposable on CompositeCollection; dispose on replace - PR: Add UrlToBitmapConverter for HTTP avatar image loading - PR: Fix ContextMenu DataContext in GameFilesTabControl ContentTemplate Restore 'Disable Alpha Channel' toggle (NoAlpha binding) - PR: Replace ElementName=TabControlName with direct DataContext bindings - PR: TreeViewItemBehavior uses GetObservable(IsSelectedProperty) with proper IDisposable subscription cleanup - S1: Clarify UpdateViewModel grouping TODO (non-DataGrid limitation) - S2: Cache compiled Regex in SearchViewModel.ItemFilter - m3: Implement TabItemFillSpace with HorizontalAlignment=Stretch Also updates FolderToGeometryConverter to support ConverterParameter for explicit brush/geometry selection. * fix: address review #2 findings on PR #85 - M1/m1/m2: UrlToBitmapConverter — sync download with ConcurrentDictionary URL cache, 4-space indent, doc comment noting async limitation - M2: Remove duplicate IsVisible attribute on Latest badge Border (MultiBinding property element takes precedence; attribute was dead code) - m3: Add TimeSpan.FromSeconds(1) timeout to user-input Regex construction - PR: ZIndex → Panel.ZIndex in SearchTextBox.xaml (Avalonia attached prop) - PR: ShadowEffect comment updated to reflect key removal (no consumers) - PR: CustomSeparator Tag TextBlock hidden when empty via StringConverters.IsNotNullOrEmpty (eliminates unwanted 10px margin) - PR: CustomVerticalSeparator BasedOn changed from default Separator to CustomSeparator (preserves tagged template inheritance from WPF) - PR: AddEditDirectoryCommand — early return when owner is null instead of silently skipping dialog - PR: CompositeCollection.Count — skip non-ICollection sources instead of O(n) enumeration fallback * Fix Avalonia parity gaps in updates, styles, and dialogs * fix: address parity review follow-ups and unresolved PR comments * Fix remaining PR review thread regressions * Address review findings: thread safety, Linux guards, indentation, and cleanup - [C1] Fix LoadCoAuthors thread race by snapshotting Commits before Task.Run - [M1] Remove IsAsync=True (WPF-only) from Avalonia bindings - [M2] Add OperatingSystem.IsWindows() guard for Win32 OpenFileDialog - [M3] Add null-safety for Application.Current in PlayPause converter - [N1] Add BrushTransition to HighlightedCheckBox for animation parity - [N2] Dispose TreeViewItem subscriptions on DetachedFromVisualTree - [N3] Fix SelectionMode converter: only Multiple uses multi-select - [S1] Simplify AddEditDirectoryCommand owner-null to early return - [S3] Remove dead WPF trigger comments from UpdateView.xaml - [S4] Add 10s timeout to UrlToBitmapConverter HttpClient - PR: Reformat UrlToBitmapConverter to 4-space indentation - PR: Catch RegexMatchTimeoutException in SearchViewModel filter * Fix review round 2: ActualWidth→Bounds, IsAsync removal, FindAncestor xmlns, misc Critical: - C1: Replace ActualHeight/ActualWidth with Bounds.Height/Bounds.Width in UpdateView, MainWindow, SettingsView, DirectorySelector (Avalonia has no ActualWidth/ActualHeight properties) - C2: Remove remaining IsAsync=True from SearchView.xaml and AudioPlayer.xaml (WPF-only, no-op in Avalonia) Major: - M1: Add xmlns:views for FModel.Views and replace dotted sub-namespace local:Views.SettingsView with views:SettingsView in all 23 FindAncestor bindings (avoids ambiguous XAML type resolution) - M2: CompositeCollection.Count now skips non-ICollection sources instead of enumerating them (O(1) for current callers) Minor: - N1: Add Application.Current null guard in FolderToGeometryConverter (matches PlaybackStateToPlayPauseConverter pattern) Suggestions: - S1: UrlToBitmapConverter.LoadAsync now cleans up _inFlight on all exit paths (defense-in-depth for exceptional failures) - S2: Add comment in CommitMessageConverter documenting interaction with LoadCoAuthors co-author cleanup * fix: merge resource dicts, regex cache, handler cleanup, null-init - Merge Colors.xaml, Icons.xaml, Resources.xaml into App.xaml so StaticResource/DynamicResource lookups resolve at runtime - Cache invalid regex state in SearchViewModel to avoid repeated recompilation on every filter invocation - Detach DetachedFromVisualTree handler in TreeViewItemBehavior when IsBroughtIntoViewWhenSelected is set to false - Fix LoadingModeToSelectionModeConverter doc to match behavior - Initialize Commit/Author backing fields with null! to satisfy non-nullable contract before JSON deserialization - Add Debug.Fail in CompositeCollection.Count for non-ICollection sources to surface unexpected usage in development * fix: load missing resource dictionaries, remove dead style refs, fix using - App.xaml: add ResourceInclude for FileContextMenu, FolderContextMenu, and TiledExplorer/Resources.xaml so StaticResource lookups resolve at runtime (C1 fix) - TiledExplorer/Resources.xaml: remove ContextMenu Setters from both TiledExplorer and AssetsListBox styles; Avalonia does not support x:Shared="False", so the shared instance would break when two ListBoxes reference the same ContextMenu. ListBoxItemBehavior already handles assignment programmatically via TryFindResource on right-click. - SettingsView.xaml: remove 11 references to undefined TextBoxDefaultStyle; HotkeyTextBox inherits FluentTheme TextBox styling by default (C2 fix) - LoadingModeToSelectionModeConverter.cs: add missing 'using FModel' for ELoadingMode resolution (M1 fix) * fix: convert Resources.xaml to Styles root, restore toggle button states - Resources.xaml: change root from <ResourceDictionary> to <Styles> so StyleInclude in App.xaml correctly loads an IStyle-implementing element. All keyed resources (ControlThemes, converters) moved into <Styles.Resources>; selector-based styles (audio controls, FoldingMargin) are direct <Styles> children. - AssetsExplorerToggleButtonStyle: restore IsChecked-dependent behavior using Avalonia selectors. :checked shows FolderIconAlt + accent background, :unchecked shows AssetIcon + default background. Tooltip shows hotkey. - MainWindow.xaml: add Classes="AssetsExplorerToggle" to the toggle button so the selector-based checked/unchecked styles match. * Fix Round 7 review findings: ControlTheme, ToolTip.Tip, dead code - C1: Convert keyed Styles to ControlTheme in TiledExplorer/Resources.xaml, update Style= to Theme= in MainWindow.xaml, convert CustomRichTextBox to auto-applying Style Selector in Resources.xaml - M1: Fix ToolTip= to ToolTip.Tip= across SearchView, SettingsView, AudioPlayer, and DirectorySelector (22 occurrences) - m1: Remove duplicate _inFlight.TryRemove in UrlToBitmapConverter.cs - m2: Remove dead Result property from CustomDir.xaml.cs - S2: Remove unused InvertBooleanConverter from Resources.xaml * Fix PR review findings: image column collapse, thread safety - Add HasImageToColumnSpanConverter so AvalonEditor spans all columns when HasImage is false, preventing unused blank space from the image column width (Resources.xaml GameFilesTabControl ContentTemplate) - Marshal LoadAssets and LoadCoAuthors collection/property mutations to Dispatcher.UIThread.InvokeAsync for explicit thread safety * fix: reformat HasImageToColumnSpanConverter to 4-space indentation * fix: map All/AllButNew/AllButModified/AllButPatched to multi-select LoadingModeToSelectionModeConverter only mapped Multiple to SelectionMode.Multiple. The original WPF DataTriggers also enabled Extended selection for All, AllButNew, AllButModified, and AllButPatched modes. Add those mappings to restore parity. |
||
|
|
c5bc12e618
|
fix: address remaining WPF→Avalonia migration gaps (issue #82) (#83)
* fix: address remaining WPF→Avalonia migration gaps (issue #82) Critical fixes: - C1: RatioToGridLengthConverter — add missing using Avalonia.Controls - C2/C3: ApplicationViewModel — remove AdonisUI MessageBox, async ShowDialog, IClassicDesktopStyleApplicationLifetime.Shutdown() - C4: ImageCommand — Avalonia Window, BitmapInterpolationMode, PixelSize - C5: TabCommand/CopyCommand — Avalonia async clipboard with error logging - C6: ImGuiController — DPI via screen.PixelDensity - C7: ClipboardExtensions — Avalonia DataObject with PNG bytes, InvokeAsync Major fixes: - M1: Timeclock — full StyledProperty rewrite, UI-thread-safe event handlers - M2: DictionaryEditor/EndpointEditor — Avalonia Window + AvaloniaEdit - M3: CommitDownloaderControl — Avalonia UserControl + StyledProperty - M4: FileButton2/FolderButton2/FolderButton3 — Avalonia XAML, restore NumTextures badge and colored separator in FileButton2 - M5: Breadcrumb — Avalonia XAML + pointer events - M6: CUE4ParseViewModel — Helper.CloseWindow<Window> - M7: App.xaml.cs — e.RequestCatch replacing e.Handled - M8: FileContextMenu — Avalonia namespace + IsVisible Minor/other fixes: - Enable <Nullable> in csproj - OnTagDataTemplateSelector stub + SettingsView code-behind template selection - TiledExplorer/Resources.xaml — full Avalonia rewrite (style selectors, WrapPanel, IDataTemplate, attached behaviors, converter-based empty state) - ListBoxItemBehavior — Avalonia AttachedProperty with 3 behaviors - TypeDataTemplateSelector — IDataTemplate with FolderContextMenu attachment - SmoothScroll — Avalonia AttachedProperty + PointerWheelChanged - Remove dead code-behind from Resources.xaml.cs - Remove unsupported IsAsync=True from bindings - Remove duplicate CornerRadius property from Timeclock - New converters: AssetExtensionToIconConverter, IntGreaterThanZeroConverter, IsNullToBoolConverter, ItemsSourceEmptyToBoolConverter - FolderToGeometryConverter — add bool return for IsVisible bindings Closes #82 * fix: address PR #83 review comments - RestartWithWarning: restore user-visible Avalonia dialog before restart (matches original WPF MessageBox.Show behavior) - UpdateProvider: add null guards for nullable AesManager/CUE4Parse - FolderContextMenu: add .ContinueWith error logging on clipboard write - SettingsView: replace throwing FindResource with TryFindResource - SmoothScroll: cache ScrollViewer in ConditionalWeakTable to avoid per-event visual tree walks - ListBoxItemBehavior: replace throwing FindResource with TryFindResource; move e.Handled inside success block so it's only set when menu opens * fix: address correctness review and second round PR comments - C1: Fix FolderButton3 LinearGradientBrush StartPoint/EndPoint to use relative percentage format (0%,0%/0%,100%) instead of absolute pixels - M1: TypeDataTemplateSelector resolves FolderContextMenu per-control via AttachedToVisualTree + TryFindResource instead of shared Application lookup - M2: Replace 19 unresolvable SystemColors.ControlTextBrushKey references with #DAE5F2 in FileContextMenu.xaml and FolderContextMenu.xaml - M4: RestartWithWarningAsync non-modal fallback awaits dialog.Closed via TaskCompletionSource before calling Restart() - mn1: EndpointEditor adds _isInitialized flag to suppress TextChanged during construction (restores WPF IsLoaded guard behavior) - S1: Timeclock unsubscribes from Source events in OnDetachedFromVisualTree Second round PR comments: - Fix SmoothScroll ConditionalWeakTable.AddOrUpdate → Add (API doesn't exist) - AvoidEmptyGameDirectoryAsync falls back to MainWindow when owner is null - Remove unused fileName parameter from ClipboardExtensions.SetImage * Address PR review findings and reviewer comments Review findings addressed: - [M1] Wire 5 Settings dialog handlers (OpenCustomVersions, OpenOptions, OpenMapStructTypes, OpenAesEndpoint, OpenMappingEndpoint) to actual DictionaryEditor/EndpointEditor modal calls instead of no-op stubs - [M2] Add TODO(P3-perf) comment documenting WrapPanel virtualization tradeoff - [m1] Add template fallback in SettingsView to clear ContentTemplate on failed resource lookup - [m2] Improve clipboard image format parity by adding PNG and Bitmap formats - [S1] Replace hardcoded #DAE5F2 icon fills with DynamicResource SystemColors.ControlTextBrushKey in both context menus - [S2] Simplify TypeDataTemplateSelector by removing redundant global guard PR reviewer comments addressed: - EndpointEditor: Fix OnTextChanged signature (EventArgs -> TextChangedEventArgs) - FolderContextMenu: Fix FindAncestor to use GetVisualAncestors() instead of unreliable Parent cast chain - CommitDownloaderControl: Fix button Width binding (.Bounds.Height -> .Height) to avoid zero during initial layout - ApplicationViewModel: Make RestartWithWarningAsync okButton a direct reference instead of brittle Children[1] index cast * Address review findings and PR comments (round 2) Major fixes: - M1: ImGuiController now probes Linux font directories (DejaVu Sans, Liberation Sans, Noto Sans) instead of hardcoding Windows paths - M2: ClipboardExtensions.SetImage wraps MemoryStream/Bitmap in using statements to prevent native resource leaks - M3: ImageCommand divides PixelSize by DPI scale for correct HiDPI window sizing Minor fixes: - m1: Document Timeclock threading contract for CalculateTime - m2: CommitDownloaderControl uses fixed 32x32 button size instead of Bounds-based binding that yields 0 during initial layout - m3: Resources.xaml reformatted to consistent indentation - m4: FolderToGeometryConverter adds parentheses for operator precedence Suggestions applied: - S1: Extract ClipboardExtensions.SetText helper; update CopyCommand, TabCommand, and FolderContextMenu to use it consistently PR comments addressed: - FileContextMenu.xaml: Replace fragile $parent[Window].DataContext with Opened handler that resolves DataContext from PlacementTarget visual tree (new FileContextMenu.xaml.cs code-behind) - RestartWithWarningAsync: Accept optional Window owner parameter so callers (SettingsView) can pass their window for proper modal parenting - ClipboardExtensions bitmap leak: Fixed (same as M2) No new build errors introduced (78 pre-existing in unmigrated files). * fix: address second review findings (M1, M2, m1, m2, S1) and TitleExtra null guard - Remove [AggressiveInlining] from ResolveFontPaths and cache result in static Lazy<> (M1 + S1) - Remove using disposal on Bitmap/MemoryStream in SetImage to prevent ObjectDisposedException on X11 deferred clipboard reads (M2) - CommitDownloader button now binds Height to sibling grid, restoring parent-relative sizing with MinWidth/MinHeight fallback (m1) - Add Log.Warning when FileContextMenu cannot find Window ancestor (m2) - Guard TitleExtra against null CurrentDir (unresolved PR comment) * fix: address third review findings and 3 unresolved PR comments - ClipboardExtensions.SetImage: dispose MemoryStream after Bitmap decoding (Bitmap copies pixel data during construction); keep Bitmap alive for X11/Wayland deferred clipboard rendering - MenuCommand: add null guards for CUE4Parse before dereferencing in Directory_Backup, Directory_ArchivesInfo, Views_3dViewer, and ToolBox_Collapse_All cases - CommitDownloaderControl: revert to deterministic Width=32 Height=32 to avoid Bounds.Height instability during initial layout * fix: breadcrumb button filter, one-shot context menu, nullable guards - Breadcrumb: filter PointerReleased by left button only (M2) - TypeDataTemplateSelector: one-shot AttachedToVisualTree handler (m1) - DictionaryEditor: initialize properties to empty defaults, null-coalesce DeserializeObject results (PR comment) - Resources.xaml: document SelectionMode mapping (S1) * fix: CurrentDir NRE guard, discard ExtractAsync, clean up null checks - ApplicationViewModel: null-safe CurrentDir?.Equals() in AvoidEmptyGameDirectoryAsync (M1) - ListBoxItemBehavior: explicit discard on fire-and-forget ExtractAsync (S1/PR) - SettingsView: remove redundant null checks on DictionaryEditor properties (m1) * fix: add Cancel click handler to DictionaryEditor Avalonia's IsCancel="True" maps Escape to the button's Click event but does not auto-close the window like WPF does. Without a handler, the Cancel button and Escape key left the dialog stuck. |
||
|
|
c8a8b79fd9
|
feat(taskbar): Remove WPF TaskbarItemInfo dead code [P2-015] (#81)
* feat(taskbar): Remove WPF TaskbarItemInfo dead code [P2-015]
- Delete StatusToTaskbarStateConverter.cs: converter was already migrated
to Avalonia IValueConverter returning null (no-op stub), and was not
referenced by any XAML or C# consumer
- Remove empty InitTaskbarInfo() stub and its [SupportedOSPlatform("windows")]
guard from MainWindow.xaml.cs: no taskbar progress implementation exists;
accepting silent omission per the issue acceptance criteria
- Remove now-unused 'using System.Runtime.Versioning' from MainWindow.xaml.cs
No System.Windows.Shell references remain anywhere in the codebase.
App does not crash on Linux. Taskbar progress is silently omitted.
Closes #27
* Update outdated comment
|
||
|
|
88d78a025c
|
feat(resources): Replace pack:// URIs with avares:// AssetLoader [P2-013] (#80)
* feat(resources): Replace pack:// URIs with avares:// AssetLoader [P2-013] - Typefaces.cs: Replace pack:// Uri field with avares:// and use AssetLoader.Open - UCreator.cs: Replace Application.GetResourceStream for T_Placeholder_Item_Image.png - BaseIcon.cs: Replace Application.GetResourceStream for T-Icon-Pets/Quests-64.png - BaseTandem.cs: Replace Application.GetResourceStream for npcleftside.png - Snooper.cs: Replace Application.GetResourceStream for engine.png; remove unused System.Windows.Forms using - FModel.csproj: Change 6 <Resource> entries to <AvaloniaResource> so avares:// can locate them Replace using System.Windows with using Avalonia.Platform in all affected files. Closes #22 * fixup! feat(resources): Replace pack:// URIs with avares:// AssetLoader [P2-013] - Dispose AssetLoader streams with 'using var' in Typefaces, UCreator, BaseIcon - Extract stream + intermediate SKBitmap locals in BaseTandem.DrawBackground so all three (stream, decoded, resized) are deterministically disposed (fixes M1 + S2) - Migrate Texture.cs (missed call site): ProcessPixels used Application.GetResourceStream for all Snooper icon textures (materialicon, square/square_off, cube/cube_off, light/light_off, checker, pointlight, spotlight, skybox faces, UI timeline icons) - Update FModel.csproj: change 25 additional <Resource> entries to <AvaloniaResource> covering all resources loaded by Texture.ProcessPixels via AssetLoader * fixup! feat(resources): Replace pack:// URIs with avares:// AssetLoader [P2-013] Tighten scope of bgStream/decoded/resized in BaseTandem.DrawBackground: use brace-scoped using() so all three disposables are freed immediately after DrawBitmap returns, rather than surviving the rest of the method. |
||
|
|
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 |
||
|
|
4da1449515
|
feat(dispatcher): Replace Application.Current.Dispatcher with Avalonia UIThread [P2-010] (#78)
* feat(dispatcher): replace Application.Current.Dispatcher with Avalonia UIThread
Migrate all Application.Current.Dispatcher.Invoke() and BeginInvoke()
calls to Avalonia.Threading.Dispatcher.UIThread equivalents across the
three remaining affected files. CustomRichTextBox.cs was already migrated.
AudioPlayerViewModel.cs (7 calls):
- Remove 'using System.Windows;' (only used for Application)
- Add 'using Avalonia.Threading;'
- Replace all Application.Current.Dispatcher.Invoke(() => {...}) with
Dispatcher.UIThread.Post(() => {...}) in Load(), AddToPlaylist(x2),
Remove(), Replace(), SavePlaylist(), Dispose().
Post() is appropriate because all callers are fire-and-forget
cross-thread UI collection mutations.
CUE4ParseViewModel.cs (3 calls):
- Remove 'using Application = System.Windows.Application;'
- Add 'using Avalonia.Threading;'
- SnooperViewer getter: extract local function MakeSnooper() and use
Dispatcher.UIThread.CheckAccess() ? MakeSnooper() :
Dispatcher.UIThread.InvokeAsync(MakeSnooper).GetAwaiter().GetResult()
Avoids deadlock when accessed from UI thread (MenuCommand), while
still dispatching correctly when called from background thread.
- FindReferences(): Dispatcher.UIThread.Post(delegate {...})
- Audio player window: Dispatcher.UIThread.Post(delegate {...}) and
remove the stale TODO comment (Post already solves the thread-lifetime
issue the comment described).
Snooper.cs (1 call):
- Keep 'using Application = System.Windows.Application;' — still
needed for Application.GetResourceStream in LoadWindowIcon().
- Add 'using Avalonia.Threading;'
- Run(): Dispatcher.UIThread.Post() so the background thread is not
blocked for the lifetime of the game loop window.
Closes #25
* fix(dispatcher): address review findings and complete migration
Fix [C1] — Load() wrapped in Post() silently broke every Load→Play call:
All callers of Load() are already on the UI thread (ICommand.Execute,
key handlers, or inside an enclosing Post lambda). Wrapping Load() in
Post() deferred it past the immediately-following Play() call, so Play()
always found _soundOut == null. Fix: remove the Post wrapper and run
Load() synchronously. The WPF Dispatcher.Invoke was a no-op re-entry
from the dispatcher thread anyway; Post() does not have that property.
Fix [M1] — Dispose() as Post() risked leaked resources and a timer race:
(a) _sourceTimer ticks every 10 ms on a threadpool thread. With deferred
disposal, there was a window many ticks wide where _waveSource was being
disposed concurrently with a tick accessing it (use-after-dispose).
(b) If the Avalonia event loop shut down before processing the queued
Post (e.g. last window closing), _waveSource/_soundOut would never be
disposed. Fix: stop the timer synchronously with Change(Infinite,Infinite)
before touching any audio resources; then dispose inline (Dispose() is
always called from OnClosing, which fires on the UI thread).
Fix [M2] — Snooper.Run() was posting the blocking GLFW game loop to the
Avalonia UI thread, freezing the entire UI for the lifetime of the 3D
viewer. The Win32 shared message-pump trick that made this work on WPF
does not apply to Avalonia on Linux. Fix: post only IsVisible=true to
the UI thread (GLFW requires glfwShowWindow on the main/creating thread),
set GLFW.SetWindowShouldClose (thread-safe per GLFW docs) from the
calling thread, then start base.Run() on a new IsBackground thread named
'Snooper-GameLoop'. OpenTK transfers the GL context to that thread via
Context.MakeCurrent() at the start of Run().
Fix [m1] — SnooperViewer getter had no lock: two concurrent background
threads could both see _snooper==null, both InvokeAsync MakeSnooper,
and the second call would overwrite and leak the first Snooper. Fix:
volatile field + double-checked lock (_snooperLock) around the slow
path. The CheckAccess() fast path (UI thread caller) never touches the
lock so there is no deadlock.
Expand scope to all remaining Application.Current.Dispatcher usages
(addresses reviewer comment on PR #78):
- BackupManagerViewModel.cs (1 usage → Post)
- AssetsFolderViewModel.cs (CheckAccess+Invoke pattern → CheckAccess+
InvokeAsync.GetAwaiter().GetResult(); BulkPopulate → Post)
- TabControlViewModel.cs (8 usages → Invoke for synchronous ops)
- GameDirectoryViewModel.cs (1 usage → Post)
- Renderer.cs (3 usages → Invoke)
- EndpointEditor.xaml.cs (1 usage → Post)
GameFileViewModel.cs is intentionally excluded: its Dispatcher usage is
inseparable from System.Windows.Media.ImageSource/BitmapImage types
that require a separate migration ticket.
* fix(snooper): dispatch IsVisible to UI thread from game loop thread
After [M2] moved base.Run() off the UI thread to Snooper-GameLoop,
all game loop callbacks — OnClosing, key/button handlers, animation
freeze points — fire on that background thread, making every IsVisible
assignment a thread safety violation (glfwShowWindow and glfwHideWindow
must be called from the GLFW main thread, which is the Avalonia UI
thread that created the window).
GLFW.SetWindowShouldClose remains inline in both WindowShouldClose and
WindowShouldFreeze: it is explicitly documented as callable from any
thread.
IsVisible is now assigned inside Dispatcher.UIThread.Post() in both
methods. Post (not Invoke) is correct: no caller checks or depends on
the visibility change completing synchronously.
|
||
|
|
58e5e3c74b
|
feat(aed): Replace ICSharpCode.AvalonEdit with AvaloniaEdit [P2-008] (#77)
* feat(aed): migrate ICSharpCode.AvalonEdit → AvaloniaEdit across all views
Replace all ICSharpCode.AvalonEdit.* references with AvaloniaEdit.*
throughout the codebase. This makes the text editor components
cross-platform by using the Avalonia port of the editor.
Files migrated (namespace-only change):
- Extensions/AvalonExtensions.cs
- Extensions/StringExtensions.cs
- ViewModels/TabControlViewModel.cs
- Views/Resources/Controls/EndpointEditor.xaml.cs
- Views/Resources/Controls/DictionaryEditor.xaml.cs
- Views/Resources/Controls/Aed/HexColorElementGenerator.cs
- Views/Resources/Controls/Aed/GamePathElementGenerator.cs
- Views/Resources/Controls/Aed/JumpElementGenerator.cs
- Views/Resources/Controls/Aed/BraceFoldingStrategy.cs
Files migrated (full API migration):
- Views/Resources/Controls/Aed/HexColorVisualLineText.cs
- Add Avalonia.Media.TextFormatting using for TextRun
- CreateTextRun: use base.CreateTextRun() + SetForegroundBrush()
- Remove OnQueryCursor (no Avalonia equivalent)
- Views/Resources/Controls/Aed/GamePathVisualLineText.cs
- Same CreateTextRun pattern
- OnMouseDown → OnPointerPressed(PointerPressedEventArgs)
- Keyboard.Modifiers → e.KeyModifiers parameter
- Views/Resources/Controls/Aed/JumpVisualLineText.cs
- Same pattern as GamePathVisualLineText
- Views/Resources/Controls/PropertiesPopout.xaml.cs
- WPF ToolTip → Avalonia ToolTip.SetTip/SetIsOpen
- MouseEventArgs → PointerEventArgs
- PointerWheelEventArgs (was PointerWheelChangedEventArgs)
- OnPreviewMouseWheel: e.Delta.Y * 2.4
- Views/Resources/Controls/AvalonEditor.xaml
- Namespaces: WPF → Avalonia + AvaloniaEdit
- DataTrigger/ZIndex → IsVisible binding
- AdonisUI watermark → built-in TextBox Watermark property
- AdonisUI brush refs → ThemeForegroundBrush
- ToolTip.Tip attribute syntax
- Remove XAML-bound events (wired in constructor)
- Views/Resources/Controls/AvalonEditor.xaml.cs
- Remove CommandBindings (WPF-only RoutedUICommand)
- Wire events in constructor: PointerHover, PointerHoverStopped,
PointerWheelChangedEvent (tunnel), PointerReleased
- Key.System → Key.Left/Right + Alt modifier check
- OnPointerWheelChanged with PointerWheelEventArgs
Closes #28
* fix(aed): address review findings and PR comments
Critical (C1):
- Migrate PropertiesPopout.xaml from WPF/AdonisUI to Avalonia Window.
Root element: AdonisWindow → Window; namespaces updated; AdonisUI
brushes/watermark/events removed; Width set to 800 (was SystemParameters
screen-relative which has no Avalonia equivalent).
Major (M1):
- Wire PointerHover, PointerHoverStopped and tunnelling
PointerWheelChangedEvent in PropertiesPopout constructor, since
these events live on TextArea.TextView and cannot be bound in XAML
as TextEditor event attributes.
Minor (Min1):
- Rename OnPreviewKeyDown → OnKeyDown in both AvalonEditor.xaml and
.cs; the event is Avalonia KeyDown (bubble), not a WPF tunnel event.
PR comments:
- Fix TabItem ambiguity in AvalonEditor.xaml.cs: add using alias
VmTabItem = FModel.ViewModels.TabItem and update all casts (was
ambiguous with Avalonia.Controls.TabItem).
- Fix FModel.ViewModels.TabItem fully-qualified in PropertiesPopout
constructor parameter (ViewModels.TabItem did not resolve in context).
- Brush leak in VisualLineText.CreateTextRun: add explicit else-branch
TextRunProperties.SetForegroundBrush(null) so the separator token
always resets to the default, preventing colour leakage if the
shared TextRunProperties object is reused across invocations.
- Cache tooltip Border/TextBlock in both AvalonEditor and
PropertiesPopout; update Background/Foreground/Text on each hover
rather than allocating new controls each time.
Suggestion (S2):
- Remove no-op BraceFoldingStrategy constructor eager UpdateFoldings
call (result was discarded; JsonFoldingStrategies.UpdateFoldings
performs the actual FoldingManager installation).
* fix(aed): mark Ctrl+Scroll event handled to prevent scroll-through
Set e.Handled = true in OnPointerWheelChanged in both AvalonEditor
and PropertiesPopout so that Ctrl+Wheel resizes the font only, without
also scrolling the document content.
* fix(aed): remove unused TextEditor parameter from BraceFoldingStrategy
The constructor no longer uses its parameter after the eager
UpdateFoldings call was removed. Drop the parameter entirely and
update the single call site in JsonFoldingStrategies.
|
||
|
|
fdc4b05b2c
|
feat(aup): migrate SpectrumAnalyzer and Timeline to Avalonia (#76)
* feat(aup): migrate SpectrumAnalyzer and Timeline to Avalonia — closes #21 * fix(aup): address all review findings and PR comments for Timeline and SpectrumAnalyzer Timeline.cs: - C1/PR#3: Wire ProgressLineBrushProperty to _progressLine.BorderBrush; add BorderThickness(0,0,1,0) to reproduce WPF right-edge cursor line - M1: Restructure constructor to two-row Grid (Row0=20px ticks, Row1=* progress) with _positionLine spanning both rows — matches WPF PART_Timeline template - M2: Fix default brush values to match WPF Resources.xaml style setters (TickBrush=#7F848E, TimeBrush=#DAE5F2, ProgressLineBrush=Brown, ProgressBrush=LinearGradientBrush #45B649→#DCE35B) - Min1: Fix OnSourceEvent signature — remove erroneous nullable SourceEventArgs? - PR#4: Add PositionProperty.Changed handler to keep _progressLine.Width in sync whenever Position is set by any means (not only via SourcePropertyChangedEvent) - S1: Set ClipToBounds=true on root Grid and _lengthGrid SpectrumAnalyzer.cs: - Min2: Add FrequencyBarBorderThicknessProperty.Changed handler → CreateBars() - Min3/PR#2: Fix double CreateBars() on out-of-range FrequencyBarCount by using early returns instead of fall-through after coerce assignment - PR#1: Guard UpdateSpectrum when _bars is empty; trigger CreateBars() from OnSourceEvent(Loading) so bars exist before first FftData event arrives * fix(aup): second-pass review fixes for Timeline and SpectrumAnalyzer Timeline.cs: - M2: Reorder root.Children so _positionLine is added last, ensuring the mouse-cursor indicator renders on top of the advancing _progressLine fill - Min1: Remove _bottomBorder shared field; instantiate Border inline inside UpdateTimeline() like all other tick elements, eliminating shared-state risk - S1: Wrap TickBrush/TimeBrush AddClassHandler callbacks in Dispatcher.UIThread.Post to be consistent with BoundsProperty.Changed SpectrumAnalyzer.cs: - M1: Reset _bars to Array.Empty<Border>() inside SilenceBars() so the UpdateSpectrum _bars.Length==0 guard correctly blocks stale writes after bars are cleared - Min2: Add AddClassHandler for FrequencyBarBrush, FrequencyBarBorderBrush, and FrequencyBarCornerRadius → CreateBars() to keep runtime changes in sync * fix(aup): third-pass review fixes for Timeline and SpectrumAnalyzer SpectrumAnalyzer.cs: - M1/PR#5: Snapshot _bars into a local 'bars' at the top of UpdateSpectrum so the dispatched UI-thread closure and all loop bounds use the same consistent array reference; prevents IndexOutOfRangeException when SilenceBars() or a FrequencyBarCount change replaces _bars between the guard check and the Background-priority closure - M2: Remove Dispatcher.UIThread.Invoke() wrapper around SpectrumScalingStrategy read — Avalonia 11 StyledProperty value reads are thread-safe for value types; eliminates synchronous audio-thread→UI-thread roundtrip (deadlock risk + perf) Timeline.cs: - M3: Extend BoundsProperty.Changed handler to also recompute _progressLine.Width using the new Bounds.Width so the progress indicator stays proportionally correct after window resize - PR#6: Clamp seek ratio to [0.0, 1.0] before passing to Source.SkipTo() to guard against pointer positions marginally outside the control bounds |
||
|
|
81cb705290
|
Fix formatting (#75)
* 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. * Re-add missing files |
||
|
|
c33d5f58f2
|
[P2-007] Migrate Magnifier adorner system to Avalonia (#74)
* 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. |
||
|
|
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. |
||
|
|
bc74c35dc1
|
feat(P2-005): migrate CustomRichTextBox (FLogger) and CustomScrollViewer to Avalonia (#72)
* feat(P2-005): migrate CustomRichTextBox (FLogger) and CustomScrollViewer to Avalonia (#18) CustomRichTextBox: - Replace WPF RichTextBox / FlowDocument / Hyperlink with AvaloniaEdit.TextEditor - FLogger converted to static class; removes ITextFormatter / BrushConverter / _previous offset - Per-segment colouring implemented via LogColorizer : DocumentColorizingTransformer - LogSegment record tracks offset, length, IBrush, and optional link URL - Append/Text/Link methods marshal to Dispatcher.UIThread.InvokeAsync (DispatcherPriority.Background) - Link click handler stubs TODO(P4-001) (OS file manager integration) - GetBrush() caches parsed Color.Parse(hexString) -> SolidColorBrush per hex string CustomScrollViewer: - Replace WPF DependencyProperty / FrameworkPropertyMetadata with Avalonia AttachedProperty<double> - Changed from ScrollViewer subclass to static helper class - VerticalOffsetProperty uses AvaloniaProperty.RegisterAttached + Changed.AddClassHandler - Offset set via ScrollViewer.Offset.WithY(); two-way sync via ScrollChanged event - Formatter-clean GetVerticalOffset/SetVerticalOffset accessors added Package updates (FModel.csproj): - Replace deprecated AvaloniaEdit 0.10.12 with Avalonia.AvaloniaEdit 11.3.0 (old package unlisted on NuGet; new namespace is AvaloniaEdit.* not ICSharpCode.AvalonEdit.*) - Serilog 4.3.0 -> 4.3.1 (patch) Closes #18 * fix(P2-005): address post-review findings in CustomRichTextBox and CustomScrollViewer CustomScrollViewer: - Register VerticalOffsetProperty with BindingMode.TwoWay so bare {Binding ScrollPosition} in XAML preserves tab scroll-position across tab switches (matches WPF BindsTwoWayByDefault) - Replace null! with null in _subscribed.Add (no nullable context; operator was semantic no-op) CustomRichTextBox: - Move ScrollToEnd() call to end of Append dispatch lambda so it fires once per log event rather than once per Text()/Link() segment (n calls per exception → 1 call per Append) - Replace dead guard Document.LineCount > 0 with Document.TextLength > 0 in ScrollToEnd() (TextDocument always has LineCount >= 1; the old guard was never false) * fix(P2-005): fix CustomScrollViewer subscription and CWT null marker - Move ScrollChanged subscription before the epsilon guard so viewers starting at offset 0.0 still subscribe on their first property set; previously the early return prevented subscription entirely in the common initial case where bound value == viewer.Offset.Y == 0.0 - Replace null with a static readonly _marker object as the ConditionalWeakTable value; null is technically accepted on .NET 8 but a non-null sentinel is clearer and avoids platform ambiguity |
||
|
|
c6d7a1b148
|
Re-add missing files | ||
|
|
1506c71665
|
feat(P2-004): migrate all 26 IValueConverter/IMultiValueConverter implementations to Avalonia (#71)
* feat(P2-004): migrate all 19 converters from WPF to Avalonia (#17) Migrates every converter in FModel/Views/Resources/Converters/ off System.Windows.Data / System.Windows.Markup / System.Windows.Shell and onto Avalonia.Data.Converters equivalents. Changes by converter: Simple using-swap only (IValueConverter interface unchanged): AnyItemMeetsConditionConverter, CommitMessageConverter, DateTimeToDateConverter, FolderToSeparatorTagConverter, GameFileMeetsConditionConverter, InvertBooleanConverter, IsNullToBoolReversedConverter, RelativeDateTimeConverter, StringToGameConverter, TrimRightToLeftConverter BoolToRenderModeConverter: System.Windows.Media.BitmapScalingMode → Avalonia.Media.Imaging.BitmapInterpolationMode (NearestNeighbor → None, Linear → HighQuality); typed helper method return type updated accordingly. BorderThicknessToStrokeThicknessConverter: System.Windows.Thickness → Avalonia.Thickness (same properties). InvertBoolToVisibilityConverter: Visibility.Visible/Collapsed/Hidden → bool; Avalonia uses IsVisible (bool). Hidden concept does not exist on Avalonia — both Collapsed and Hidden map to false. RatioConverter: Removed MarkupExtension base class (PresentationFramework — Windows-only). Added Instance singleton; use {x:Static converters:RatioConverter.Instance} in XAML. RatioToGridLengthConverter: System.Windows.GridLength/GridUnitType → Avalonia.GridLength/GridUnitType. IMultiValueConverter signature: object[] → IList<object?>, ConvertBack removed. StatusToTaskbarStateConverter: Stubbed out — System.Windows.Shell.TaskbarItemProgressState has no cross-platform Avalonia equivalent. Returns null; tracked by TODO(P2-015). MarkupExtension removed; added Instance singleton. TabSizeConverter: System.Windows.Controls.TabControl → Avalonia.Controls.TabControl. ActualWidth → Bounds.Width; Items.Count → ItemCount. IMultiValueConverter: object[] → IList<object?>, ConvertBack removed. FileToGeometryConverter: System.Windows.Media.{Geometry,Brush} → Avalonia.Media.{Geometry,IBrush}. Application.Current.FindResource → Application.Current.TryGetResource(key, null, out var r). IMultiValueConverter: object[] → IList<object?>, ConvertBack removed. FolderToGeometryConverter: Same Geometry/IBrush/TryGetResource changes as FileToGeometryConverter. Already migrated (no changes): BoolToToggleConverter, CaseInsensitiveStringEqualsConverter, EndpointToTypeConverter, EnumToStringConverter, IsNotZeroConverter, MultiParameterConverter, SizeToStringConverter Build: 1266 errors baseline → 1174 after this commit (92 fewer). Zero errors introduced in Converters/. * fix(P2-004): address PR #71 review findings M1 (PR comment): FolderToGeometryConverter — remove duplicate if condition (copy-paste artifact: identical guard appeared twice consecutively) M2 (PR comment): TabSizeConverter — two fixes - divisor guard: return 0 when ItemCount == 0 to prevent divide-by-zero - double.Parse now uses CultureInfo.InvariantCulture (XAML ConverterParameter values are always invariant-culture strings; avoids FormatException on locales that use comma as decimal separator) PR comment: BoolToRenderModeConverter — typed helper Convert(bool) now implements the mapping directly instead of delegating to the interface method with null culture; eliminates fragile null-culture call chain m2: StatusToTaskbarStateConverter — reverted from IMultiValueConverter back to IValueConverter to match the original WPF single-value binding contract (EStatusKind → TaskbarItemProgressState was always a single-value binding); also removed unused System.Collections.Generic using m1: Aligned all ten 'simple-swap' IValueConverter implementations to use Avalonia-idiomatic nullable signatures (object? value, object? parameter, object? return) matching the Avalonia interface definition; previously these retained the non-nullable WPF-era signatures which were inconsistent with the nine converters already updated in the same commit S1: Renamed InvertBoolToVisibilityConverter class → InvertBoolConverter to reflect that it returns bool (for IsVisible) not WPF Visibility; file rename is deferred pending terminal access |
||
|
|
24c6a68cce
|
[P2-003] Migrate all 10 dialog Windows from WPF/AdonisUI to Avalonia (#70)
* feat(P2-003): migrate all 10 dialog Windows from WPF/AdonisUI to Avalonia Resolves #16 [P2-003] Migrate all 11 XAML view windows to Avalonia Window. Views migrated (XAML + code-behind): - About: root Window, AdonisUI namespace/styles removed - AesManager: root Window, BoolToVisibility→IsVisible, ListView→DataGrid - AudioPlayer: root Window, CSCore TODO stubs, OpenFileDialog TODO stub - BackupManager: root Window, IsNotZeroConverter for IsEnabled binding - CustomDir: root Window, AdonisUI removals - DirectorySelector: root Window, AdonisUI removals, Visibility→IsVisible - ImageMerger: root Window, BitmapImage→Avalonia Bitmap, WPF dialogs→TODO stubs - SearchView: root Window, ListView+GridView→DataGrid x2, ContextMenus - SettingsView: root Window, DataTriggers→IsVisible/HyperlinkButton/converters, SelectedItemChanged→SelectionChanged, VistaFolderBrowserDialog→TODO stub - UpdateView: root Window, adonisControls:SplitButton→SplitButton+MenuFlyout Support: - FModel.csproj: add Avalonia.Controls.DataGrid v11.3.12 - Add IsNotZeroConverter (Avalonia.Data.Converters IValueConverter) * chore(P2-003): apply post-push refinements to migrated view files * fix: address review findings from Avalonia migration code review Critical: - MainWindow.xaml.cs: unsubscribe all PropertyChanged handlers in OnClosing to prevent memory leak and use-after-free on restart (C2) - MainWindow.xaml.cs: remove ConfigureAwait(false) from async void OnLoaded — continuation must stay on Avalonia UI thread (C1) Major: - MainWindow.xaml: replace WPF KeyboardNavigation.TabNavigation with Avalonia IsTabStop="False" on the feature-preview CheckBox (M3) - MainWindow.xaml.cs: guard PixelDensity against zero in OnLoaded (m2) - MainWindow.xaml.cs: add _syncingSelection re-entrancy guard to SyncSelection to prevent infinite SelectionChanged event loop (m4) - App.xaml.cs: add try/catch inside ShowErrorDialog async lambda to prevent silent exception swallowing; add TaskScheduler.UnobservedTaskException handler in OnFrameworkInitializationCompleted (M1, S1) - App.xaml.cs: remove redundant Environment.Exit(0) from AppExit handler (m1) - Helper.cs: null-guard GetWindow<T> return value; add comment re: Show() race (S2) Views (review fixes): - SettingsView.xaml.cs: replace TryBrowse/OnBrowseMappings stubs with real StorageProvider implementations (OpenFolderPickerAsync, OpenFilePickerAsync) - ImageMerger.xaml.cs: fix async void + MemoryStream disposal in OnExport - UpdateView.xaml: fix Grid nesting of ItemsControl + empty-state TextBlock; add IsNotZeroConverter-based empty-state visibility - CustomDir.xaml/cs, DirectorySelector.xaml/cs: cancel button handlers - Converters: migrate System.Windows.Data IValueConverter usings to Avalonia.Data.Converters for BoolToToggleConverter, EndpointToTypeConverter, EnumToStringConverter, IsNotZeroConverter, SizeToStringConverter * fix: address second-round review findings C1 - SettingsView.xaml.cs: replace WPF editor ShowDialog() calls with async void stubs + TODO(P2-016) comments; DictionaryEditor and EndpointEditor are not yet migrated to Avalonia so these used the WPF synchronous ShowDialog() which is unavailable and returns the wrong type C2 - UpdateView.xaml: add TODO noting CommitsView is still a WPF System.Windows.Data.ListCollectionView; once UpdateViewModel is migrated the Count binding should be replaced with a dedicated HasNoCommits bool property for reliable INPC notification M1+S1 - Helper.cs: null-guard GetOpenedWindow result in OpenWindow<T> to prevent NRE if the window deregisters between IsWindowOpen and the dereference M2 - MainWindow.xaml.cs: add TODO tech-debt comment on the double UpdateStatusLabel dispatch when Kind changes; note consolidation into a single UpdateStatusUI() for a later pass M3 - ImageMerger.xaml.cs: replace ContinueWith(..., FromCurrentSync- Context()) with try/finally after await Task.Run; eliminates the synchronization-context capture hazard when called via ConfigureAwait m1 - App.xaml.cs: UnobservedTaskException now calls ShowErrorDialog for unexpected faults; silently absorbs OperationCanceledException; SetObserved() moved before the early-return guard m2 - IsNotZeroConverter.cs: fix 2-space indentation to 4-space to match rest of codebase m3 - EndpointToTypeConverter.cs: replace misleading NotImplemented- Exception with ArgumentException carrying a descriptive message * fix: address third-round review findings - App.xaml.cs: check all InnerExceptions for OperationCanceledException, not just InnerException[0], so a mixed AggregateException is never silently absorbed (m1) - Helper.cs: add Dispatcher.UIThread.RunJobs() between Show() and the window-registry lookup in GetWindow<T> to close the registration race on first open (S1); also add missing System.Collections.Generic using that caused a pre-existing IEnumerable<> CS0246 error - SettingsView.xaml.cs: clarify EndpointEditor TODO comments — original WPF code used ShowDialog() (modal), so migration target is ShowDialog(this), not Show() (m2) * fix: add missing System.Linq using in App.xaml.cs InnerExceptions.All() is a LINQ extension method. The project has no implicit usings, so the explicit using was required. The build check after the previous commit only grepped for Helper.cs errors, so the CS1061 on App.xaml.cs was not caught until the follow-up review. * fix: address PR reviewer findings across six view files C1 - AudioPlayer.xaml: replace undeclared adonisUi:Brushes.ForegroundBrush DynamicResource (compile error) with Fill="White", consistent with all other icon paths in the file. C2 - SettingsView.xaml.cs: fix OnBrowseMappings storing into non-existent UserSettings.Default.MappingsPath; now sets _applicationView.SettingsView.MappingEndpoint.FilePath which is the property the XAML TextBox is bound to (compile error + silent data loss). C3 - UpdateView.xaml: remove WPF-only ItemsControl.GroupStyle / GroupStyle / GroupItem / HeaderStringFormat block (XAML parse failure on Avalonia). Date grouping will be re-added alongside the CommitsView ViewModel migration (see existing TODO). Also fix FallbackValue=False → 0 so IsNotZeroConverter receives the expected int type when the binding target can't be resolved. M1 - ImageMerger.xaml.cs (UpButton case): remove spurious SelectedItems.Add(indices) call which was adding the raw int[] to the selected-items collection. Items are already marked IsSelected=true inside the move loop; the Add call was a no-op at best and a runtime inconsistency at worst. M2 - DirectorySelector.xaml: remove self-referential Width={Binding ActualWidth, ElementName=OkGuysButWhoFuckingAsked} (control binding its own width to itself — produces binding warnings and has no useful effect; layout is determined by content/padding). m1 - SearchView.xaml.cs: restore WPF DataTrigger behavior — subscribe to PropertyChanged on each SearchViewModel and update the corresponding TextBox.Watermark when HasRegexEnabled toggles, giving 'Write your regex pattern...' vs 'Write your pattern...' hint text. * fix: unsubscribe PropertyChanged handlers in SearchView on close SearchVm and RefVm are application-lifetime singletons. Subscribing anonymous lambdas capturing 'this' (the window's TextBox fields) meant each closed-and-reopened SearchView was kept permanently alive by the singletons' PropertyChanged invocation lists. Fix: promote the handlers to named local functions, cache the VM references at construction time, and unsubscribe both handlers in the Closed event so the window becomes eligible for GC immediately after it is closed. Also adds the required 'using System.ComponentModel' for the PropertyChangedEventArgs parameter type. * fix: migrate WPF converters to Avalonia.Data.Converters interfaces CaseInsensitiveStringEqualsConverter: replace System.Windows.Data.IValueConverter with Avalonia.Data.Converters.IValueConverter. Avalonia cannot call WPF converter implementations, so any SettingsView.xaml binding using this converter would fail silently at runtime. Also null-guards value/parameter before calling .ToString() to avoid NREs on binding fallback values. MultiParameterConverter: replace System.Windows.Data.IMultiValueConverter with Avalonia.Data.Converters.IMultiValueConverter. The Avalonia interface takes IList<object?> rather than object[], so update the signature accordingly. Return values.ToArray() to preserve the same object[] snapshot that the WPF values.Clone() call produced, keeping CommandParameter multi-binding callers in SearchView.xaml working. Avalonia's IMultiValueConverter has no ConvertBack, so that method is removed. |
||
|
|
7e66c44f90
|
chore: update identity, branding, and CI for FModel Linux fork (#69)
* chore: update identity, branding, and CI for FModel Linux fork
- README.md: complete rewrite with fork identity disclaimer, upstream
attribution, updated links, and removal of sponsorship section
- NOTICE: add upstream FModel GPL-3 attribution; update library list
(add Avalonia/AvaloniaEdit/Svg.Skia/Twizzle.ImGui-Bundle.NET, remove
AdonisUI/AutoUpdater.NET/CSCore/ImGui.NET/Ookii.Dialogs.Wpf)
- Constants.cs: update ISSUE_LINK and GH_REPO to r6e/FModel-Linux;
clear DISCORD_LINK pending app registration
- FModel.csproj: add opt-in USE_FMODEL_API build flag
(-p:UseFModelApi=true) for upstream api.fmodel.app integration
- FModelApiEndpoint.cs: gate all api.fmodel.app calls behind
#if USE_FMODEL_API; rewrite CheckForUpdatesAsync to use GitHub
Releases API and return GitHubRelease? for caller-side notification
- GitHubApiEndpoint.cs: add GetLatestReleaseAsync() for /releases/latest
- GitHubResponse.cs: add TagName/HtmlUrl/Body to GitHubRelease; replace
AutoUpdater.DownloadUpdate with Process.Start(UseShellExecute=true)
- MainWindow.xaml.cs: surface update availability via FLogger [WRN] entry
at startup when a newer GitHub release is found
- DiscordService.cs: disable RPC (empty APP_ID, early-return guard)
pending Discord application registration for the fork
- About.xaml: version label reads 'FModel Linux {0}'; dual credit lines
for Asval (original) and Rob Trame (Linux port)
- AboutViewModel.cs: update description, contributors, and references
list to reflect current Avalonia-based dependency set
- .github/workflows: target ubuntu-latest, linux-x64, net8.0; remove
fmodel.app deploy steps; rename artifact to FModel-Linux.zip
- .github/ISSUE_TEMPLATE: update assignee and remove Discord link
- .github/FUNDING.yml: note donation link points to upstream project
* Fix whitespace issues
* chore: address PR review feedback
- NOTICE: remove orphaned MS-PL and BSD 3-Clause license texts; the
index already lists only MIT and Apache 2.0, and no current dependency
uses either of those licenses
- GitHubResponse.cs: guard against null Asset/BrowserDownloadUrl in
Download(); log a warning via Serilog instead of NullReferenceException
or re-throwing InvalidOperationException
- MainWindow.xaml.cs: run update check in a background Task.Run to
avoid blocking startup; post FLogger notification back on
Dispatcher.UIThread; swallow exceptions so a failed check never
impacts startup
- FModelApiEndpoint.cs: log a warning when the current application
version string cannot be parsed, matching the existing behaviour for
an unparseable latest-release tag
- .github/workflows: add `permissions: contents: write` to both
main.yml and qa.yml (GitHub Advanced Security finding)
* chore: address second round of PR review feedback
- NOTICE: add missing direct NuGet dependencies —
EpicManifestParser.ZlibngDotNetDecompressor, Serilog.Sinks.Console,
Serilog.Sinks.File, and SkiaSharp.HarfBuzz — each with their
respective copyright notice and license (MIT / Apache 2.0)
- FModelApiEndpoint.cs: remove now-unused _applicationView property
(CheckForUpdatesAsync was refactored to use GitHubApi directly;
_applicationView is dead code outside of #if USE_FMODEL_API blocks)
- DiscordService.cs: fix DiscordRpcClient being constructed at
field-init time with an empty APP_ID string; make _client nullable
and only instantiate when APP_ID is non-empty; update all call sites
to use null-safe pattern matching (_client is not { IsInitialized:
true }) so callers are safe without runtime client construction
* chore: address third round of PR review feedback
- MenuCommand.cs: guard Help_Discord action against empty DISCORD_LINK;
Process.Start with an empty FileName throws at runtime, so the action
is now a no-op when no Discord server URL is configured
- qa.yml: zip the full publish output directory (./FModel/bin/Publish/)
rather than just the single executable; --no-self-contained publishes
companion files (*.deps.json, *.runtimeconfig.json) that are required
at runtime alongside the PublishSingleFile output
- main.yml: same fix — zip ./FModel/bin/Publish/ so the release artifact
includes all required publish outputs, not just the executable
* Update FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
||
|
|
3afea6c9fd
|
feat(#15): migrate MainWindow.xaml + MainWindow.xaml.cs to Avalonia Window (#68)
* feat(#15): migrate MainWindow.xaml + MainWindow.xaml.cs to Avalonia Window - Replace AdonisWindow root with Avalonia Window, remove all adonisUi xmlns - Remove TaskbarItemInfo XAML block (guarded stub in code-behind) - Replace StatusBar/StatusBarItem + WPF Style.Triggers with Border+DockPanel - Replace Border.Triggers Storyboard with Avalonia Transitions + :pointerover - Replace ToggleButton WPF trigger style with Avalonia selector style - Rewrite MainWindow.xaml.cs: remove WPF CommandBindings/RoutedCommand, update event handler signatures, add PropertyChanged-based status bar color updates and window title updates (replacing WPF DataTrigger logic) - Add OnLoaded screen sizing via Screens.Primary.WorkingArea - Add [SupportedOSPlatform(windows)] InitTaskbarInfo stub - Helper.cs: replace Application.Current.Windows with IClassicDesktopStyleApplicationLifetime.Windows, remove Activate() - App.xaml.cs: wire desktop.MainWindow = new MainWindow() * fix(#15): remove double UpdateStatusLabel invocation per SetStatus call OnStatusKindChanged Kind branch now only calls UpdateStatusBarColor(). UpdateStatusLabel() is driven exclusively by the Label branch, which always fires immediately after Kind during SetStatus() because distinct enum states always produce distinct label strings — so SetProperty on Label always raises PropertyChanged, guaranteeing the Label branch runs. This eliminates the redundant double-invoke without any behavioral change. * Address review feedback |
||
|
|
4d82992237
|
phase2: migrate App.xaml + App.xaml.cs to Avalonia bootstrap (#14) (#67)
* 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 |
||
|
|
21183927e8
|
chore: point CUE4Parse submodule at r6e/CUE4Parse-Linux (#66)
Update .gitmodules URL from FabianFG/CUE4Parse to r6e/CUE4Parse-Linux and pin to commit 06fbf1ac which adds Linux-compatible libdetex.so and libtegra_swizzle_x64.so alongside the Windows DLLs, with RID-aware loading in DetexHelper and PlatformDeswizzlers. Closes #10 |
||
|
|
51b05e2ffa
|
chore: remove Autoupdater.NET.Official (WPF-only auto-updater) (#65)
Autoupdater.NET.Official requires WPF for its built-in download dialog and cannot be used on Linux. Cross-platform replacement is tracked in #51 (P4-005). Closes #9 in r6e/FModel-Linux |
||
|
|
72cb906da6
|
chore: remove VirtualizingWrapPanel (WPF-only panel) (#64)
VirtualizingWrapPanel is a WPF-only third-party panel control. Replacement with Avalonia's built-in WrapPanel or a community virtualized panel is tracked in Phase 2 UI migration work. Closes #6 in r6e/FModel-Linux |
||
|
|
23960d1266
|
chore: remove CSCore (Windows-only audio engine) (#63)
CSCore uses WASAPI, DirectSound and MediaFoundation APIs that are unavailable on Linux. Call-site replacement with a cross-platform audio backend (OpenTK.Audio.OpenAL) is tracked in #40 (P3-013). Closes #13 in r6e/FModel-Linux |
||
|
|
77431d8c1b
|
chore: remove Ookii.Dialogs.Wpf (WPF-only folder dialog) (#62)
Ookii.Dialogs.Wpf depends on PresentationFramework and cannot be restored on Linux. Call-site replacement with Avalonia StorageProvider is tracked in #48 (P4-004). Closes #11 in r6e/FModel-Linux |
||
|
|
471bb22b13
|
chore: replace AvalonEdit with AvaloniaEdit (#61)
- Remove AvalonEdit 6.3.1.120 (WPF-only text editor) - Add AvaloniaEdit 0.10.12 (Avalonia port, API-compatible) Existing .xshd syntax definition files (Changelog.xshd, Cpp.xshd, Ini.xshd, etc.) are format-compatible with AvaloniaEdit and require no changes at this stage. Closes #12 in r6e/FModel-Linux |
||
|
|
903aad35c9
|
[P1-002] Replace AdonisUI with Avalonia UI packages (#60)
* chore: replace AdonisUI with Avalonia UI packages - Remove AdonisUI 1.17.1 (WPF-only) - Remove AdonisUI.ClassicTheme 1.17.1 (WPF-only) - Add Avalonia 11.3.12 (core framework) - Add Avalonia.Desktop 11.3.12 (desktop host) - Add Avalonia.Themes.Fluent 11.3.12 (cross-platform theme) Closes #8 in r6e/FModel-Linux * chore: add Avalonia.Fonts.Inter for consistent Linux font rendering Without this package, Avalonia falls back to the system fontconfig font on Linux, which varies by distro and can produce missing glyphs on minimal CI runners. Inter gives a consistent cross-platform baseline. |
||
|
|
a8966ae9c1
|
chore: migrate TFM from net8.0-windows to net8.0 (#59)
- Change TargetFramework from net8.0-windows to net8.0 - Remove UseWPF (not used by Avalonia) - Change OutputType from WinExe to Exe (cross-platform) - Remove hardcoded RuntimeIdentifier win-x64 (supply at publish time) Closes #7 in r6e/FModel-Linux |
||
|
|
ec0bb2b43b
|
Finish setting up env | ||
|
|
62a0d846f9
|
Set up dev env | ||
|
|
c9542f1a91 |
Aion 2, Crystal of Atlan, Fate Trigger, WuWa and The First Descendant updates
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
d2fe259adc |
bump c4p + a few settings changes + fix #647
Some checks failed
FModel QA Builder / build (push) Has been cancelled
export raw data is now disabled by default (see settings) |
||
|
|
fd4eb0a418
|
Borderlands 3 audio (#649)
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
7fab3e2ead |
Titan Quest 2 update, Windrose support
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
83b5330d1e |
Fix issue with animation RateScale
Co-authored-by: Asval <asval.contactme@gmail.com> |
||
|
|
500fa59f85
|
Option for bulk audio conversion (#646)
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
1c48a27f8e |
InitOodle update, High on Life 2 and Borderlands 4 Audio support
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
6b3a4ae785
|
Borderlands 4 - GbxAudio support (#645)
Some checks are pending
FModel QA Builder / build (push) Waiting to run
|
||
|
|
40ed646dcd
|
Fix HTML tags in some Quests DisplayName. (#640)
Some checks failed
FModel QA Builder / build (push) Has been cancelled
* Fix HTML tags in some Quests DisplayName. * New Backups & Hotfixes API * fix backups being null --------- Co-authored-by: GhostScissors <79089473+GhostScissors@users.noreply.github.com> |
||
|
|
bd2e924925
|
Merge pull request #643 from Krowe-moh/patch-1
Some checks are pending
FModel QA Builder / build (push) Waiting to run
|
||
|
|
dee4ecab9c
|
Prevents ImGui assert from AddFontFromFileTTF | ||
|
|
e47b3ac6bb
|
WWise refactor and Wuthering Waves, Neverness To Everness update
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
87fe6ed09d | bump cue4parse | ||
|
|
508dcb4c7a | Wwise media assets + .adx fallback | ||
|
|
40996973eb |
Code Vein 2, Infinity Nikki update
Some checks failed
FModel QA Builder / build (push) Has been cancelled
Ashes of Creation dbc extraction |
||
|
|
14e05da2e0
|
fixed line endings to lf to match editorconfig
Some checks failed
FModel QA Builder / build (push) Has been cancelled
|
||
|
|
892dd8cdef
|
latest oodle dl | ||
|
|
1e2b1b1e3e
|
support for .UONDEMANDTOC files | ||
|
|
ef906084fb |
Bump CUE4Parse
I forgot |
||
|
|
669b07f807
|
Overall improvements (#633) | ||
|
|
31d426f795
|
Merge pull request #635 from LeleDerGrasshalmi/fix-bp-decompiling-disabled
Allow "decompile bp" for other bp types |
||
|
|
667e5ef7b9
|
Fix "decompile bp" being disabled (typo) |