FModel/FModel/FModel.csproj
Rob Trame 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.
2026-03-16 14:17:00 -06:00

313 lines
14 KiB
XML

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.4.0</Version>
<AssemblyVersion>4.4.4.0</AssemblyVersion>
<FileVersion>4.4.4.0</FileVersion>
<IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<StartupObject>FModel.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<!-- Properties/Resources.resx is a WPF boilerplate stub; excluded from non-WPF builds -->
<EmbeddedResource Remove="Properties\Resources.resx" />
<Compile Remove="Properties\Resources.Designer.cs" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PublishSingleFile>true</PublishSingleFile>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(UseFModelApi)' == 'true'">
<!-- Build with -p:UseFModelApi=true to enable upstream fmodel.app API integration -->
<DefineConstants>$(DefineConstants);USE_FMODEL_API</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\android.png" />
<None Remove="Resources\apple.png" />
<None Remove="Resources\battlebreakers.png" />
<None Remove="Resources\blueprint.png" />
<None Remove="Resources\borderlands.png" />
<None Remove="Resources\empty_folder.png" />
<None Remove="Resources\engine.png" />
<None Remove="Resources\fallenorder.png" />
<None Remove="Resources\FModel.ico" />
<None Remove="Resources\folder.png" />
<None Remove="Resources\label.png" />
<None Remove="Resources\fortnite.png" />
<None Remove="Resources\fortnitebr.png" />
<None Remove="Resources\gear.png" />
<None Remove="Resources\localization.png" />
<None Remove="Resources\materialicon.png" />
<None Remove="Resources\square.png" />
<None Remove="Resources\square_off.png" />
<None Remove="Resources\cube.png" />
<None Remove="Resources\cube_off.png" />
<None Remove="Resources\light.png" />
<None Remove="Resources\light_off.png" />
<None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" />
<None Remove="Resources\sound.png" />
<None Remove="Resources\creative.png" />
<None Remove="Resources\spellbreak.png" />
<None Remove="Resources\texture.png" />
<None Remove="Resources\thecycle.png" />
<None Remove="Resources\ui.png" />
<None Remove="Resources\valorant.png" />
<None Remove="Resources\weapon.png" />
<None Remove="Resources\windows.png" />
<None Remove="Resources\cinematics.png" />
<None Remove="Resources\archive.png" />
<None Remove="Resources\archive_enabled.png" />
<None Remove="Resources\archive_disabled.png" />
<None Remove="Resources\unknown_asset.png" />
<None Remove="Resources\asset.png" />
<None Remove="Resources\asset_ini.png" />
<None Remove="Resources\asset_psd.png" />
<None Remove="Resources\asset_png.png" />
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Verse.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<None Remove="Resources\T-Icon-Pets-64.png" />
<None Remove="Resources\T-Icon-Quests-64.png" />
<None Remove="Resources\Default.png" />
<None Remove="Resources\NoBackground.png" />
<None Remove="Resources\NoText.png" />
<None Remove="Resources\Flat.png" />
<None Remove="Resources\Cataba.png" />
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
<None Remove="Resources\add_directory.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\spline.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\spline.vert" />
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />
<EmbeddedResource Include="Resources\grid.frag" />
<EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="Resources\skybox.frag" />
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\outline.frag" />
<EmbeddedResource Include="Resources\outline.vert" />
<EmbeddedResource Include="Resources\picking.frag" />
<EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia"
Version="11.3.12" />
<PackageReference Include="Avalonia.Controls.DataGrid"
Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop"
Version="11.3.12" />
<PackageReference Include="Avalonia.Themes.Fluent"
Version="11.3.12" />
<PackageReference Include="Avalonia.Fonts.Inter"
Version="11.3.12" />
<PackageReference Include="Avalonia.AvaloniaEdit"
Version="11.3.0" />
<PackageReference Include="DiscordRichPresence"
Version="1.6.1.70" />
<PackageReference Include="EpicManifestParser"
Version="2.4.1" />
<PackageReference Include="EpicManifestParser.ZlibngDotNetDecompressor"
Version="1.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams"
Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json"
Version="13.0.4" />
<PackageReference Include="NVorbis"
Version="0.10.5" />
<PackageReference Include="OpenTK"
Version="4.9.4" />
<PackageReference Include="RestSharp"
Version="113.0.0" />
<PackageReference Include="Serilog"
Version="4.3.1" />
<PackageReference Include="Serilog.Sinks.Console"
Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File"
Version="7.0.0" />
<PackageReference Include="SixLabors.ImageSharp"
Version="3.1.12" />
<PackageReference Include="SkiaSharp.HarfBuzz"
Version="2.88.9" />
<PackageReference Include="Svg.Skia"
Version="3.2.1" />
<PackageReference Include="Twizzle.ImGui-Bundle.NET"
Version="1.91.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\android.png" />
<Resource Include="Resources\apple.png" />
<Resource Include="Resources\battlebreakers.png" />
<Resource Include="Resources\blueprint.png" />
<Resource Include="Resources\borderlands.png" />
<Resource Include="Resources\fallenorder.png" />
<Resource Include="Resources\FModel.ico" />
<Resource Include="Resources\folder.png" />
<Resource Include="Resources\label.png" />
<Resource Include="Resources\fortnite.png" />
<Resource Include="Resources\fortnitebr.png" />
<Resource Include="Resources\empty_folder.png" />
<AvaloniaResource Include="Resources\engine.png" />
<Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" />
<AvaloniaResource Include="Resources\materialicon.png" />
<AvaloniaResource Include="Resources\square.png" />
<AvaloniaResource Include="Resources\square_off.png" />
<AvaloniaResource Include="Resources\cube.png" />
<AvaloniaResource Include="Resources\cube_off.png" />
<AvaloniaResource Include="Resources\light.png" />
<AvaloniaResource Include="Resources\light_off.png" />
<Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" />
<Resource Include="Resources\spellbreak.png" />
<Resource Include="Resources\sound.png" />
<Resource Include="Resources\creative.png" />
<Resource Include="Resources\texture.png" />
<Resource Include="Resources\thecycle.png" />
<Resource Include="Resources\valorant.png" />
<Resource Include="Resources\ui.png" />
<Resource Include="Resources\weapon.png" />
<Resource Include="Resources\windows.png" />
<Resource Include="Resources\cinematics.png" />
<Resource Include="Resources\archive.png" />
<Resource Include="Resources\archive_enabled.png" />
<Resource Include="Resources\archive_disabled.png" />
<Resource Include="Resources\unknown_asset.png" />
<Resource Include="Resources\asset.png" />
<Resource Include="Resources\asset_ini.png" />
<Resource Include="Resources\asset_psd.png" />
<Resource Include="Resources\asset_png.png" />
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\stateofdecay2.png" />
<AvaloniaResource Include="Resources\T_Placeholder_Item_Image.png" />
<AvaloniaResource Include="Resources\checker.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<AvaloniaResource Include="Resources\T-Icon-Pets-64.png" />
<AvaloniaResource Include="Resources\T-Icon-Quests-64.png" />
<Resource Include="Resources\Default.png" />
<Resource Include="Resources\NoBackground.png" />
<Resource Include="Resources\NoText.png" />
<Resource Include="Resources\Flat.png" />
<Resource Include="Resources\Cataba.png" />
<AvaloniaResource Include="Resources\BurbankBigCondensed-Bold.ttf" />
<AvaloniaResource Include="Resources\add_directory.png" />
<AvaloniaResource Include="Resources\delete.png" />
<AvaloniaResource Include="Resources\edit.png" />
<AvaloniaResource Include="Resources\go_to_directory.png" />
<AvaloniaResource Include="Resources\npcleftside.png" />
<AvaloniaResource Include="Resources\nx.png" />
<AvaloniaResource Include="Resources\ny.png" />
<AvaloniaResource Include="Resources\nz.png" />
<AvaloniaResource Include="Resources\px.png" />
<AvaloniaResource Include="Resources\py.png" />
<AvaloniaResource Include="Resources\pz.png" />
<AvaloniaResource Include="Resources\pointlight.png" />
<AvaloniaResource Include="Resources\spotlight.png" />
<AvaloniaResource Include="Resources\link_on.png" />
<AvaloniaResource Include="Resources\link_off.png" />
<AvaloniaResource Include="Resources\link_has.png" />
<AvaloniaResource Include="Resources\tl_play.png" />
<AvaloniaResource Include="Resources\tl_pause.png" />
<AvaloniaResource Include="Resources\tl_rewind.png" />
<AvaloniaResource Include="Resources\tl_forward.png" />
<AvaloniaResource Include="Resources\tl_previous.png" />
<AvaloniaResource Include="Resources\tl_next.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>