diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml index 6d44525f10..bce364e4f6 100644 --- a/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml +++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml @@ -25,7 +25,7 @@ - + @@ -61,8 +61,8 @@ - + diff --git a/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs b/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs index 0f85fd8ebe..7987e2a697 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs @@ -21,7 +21,7 @@ internal static class MicaWindowHelper private const int DWMWA_BORDER_COLOR = 34; private const int DWMWA_SYSTEMBACKDROP_TYPE = 38; private const int DWMWCP_ROUND = 2; - private const int DWMSBT_MAINWINDOW = 2; // Mica — same backdrop as the rest of the app, so menus match + private const int DWMSBT_TRANSIENTWINDOW = 3; // Acrylic — for transient surfaces (menus/flyouts); Mica won't paint on these private const int DWMWA_COLOR_DEFAULT = unchecked((int)0xFFFFFFFF); private static bool _acrylicPopupsHooked; @@ -69,15 +69,28 @@ public static void EnableAcrylicPopups() return; _acrylicPopupsHooked = true; - // Every flyout/menu/tooltip/combo popup is hosted in a PopupRoot; style it as it loads. + // In-app flyouts/menus/tooltips/combo popups are hosted in a PopupRoot; style each as it loads. Control.LoadedEvent.AddClassHandler((root, _) => ApplyAcrylicToPopup(root)); + + // The system-tray context menu is NOT a PopupRoot — Avalonia hosts it in its own Window + // (Avalonia.Win32.TrayIconImpl.TrayPopupRoot), so it misses the handler above and would + // render with no backdrop (transparent over the desktop). Catch it by type name and apply + // the same acrylic treatment. The other Windows (MainWindow/dialogs) are handled via Apply(). + Control.LoadedEvent.AddClassHandler((win, _) => + { + if (win.GetType().Name == "TrayPopupRoot") + ApplyAcrylicToPopup(win); + }); } - private static void ApplyAcrylicToPopup(PopupRoot root) + private static void ApplyAcrylicToPopup(TopLevel root) { - // Transparent surface so the DWM acrylic shows; the presenter backgrounds are also - // transparent (Styles.WindowsMica) so only the acrylic + menu items are painted. - root.TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; + // Request acrylic (not Transparent): the Transparent level makes a layered window, and DWM + // system backdrops never paint on those — so the popup ended up fully see-through with only + // the presenter tint, unreadable when shown over the desktop (e.g. the tray menu). AcrylicBlur + // gives a composited window DWM can actually fill. This path only runs when Mica is enabled + // (Win11 + transparency effects), so acrylic is always available here. + root.TransparencyLevelHint = new[] { WindowTransparencyLevel.AcrylicBlur, WindowTransparencyLevel.Blur }; root.Background = Brushes.Transparent; if (root.TryGetPlatformHandle()?.Handle is not { } handle || handle == 0) @@ -85,7 +98,7 @@ private static void ApplyAcrylicToPopup(PopupRoot root) int corner = DWMWCP_ROUND; NativeMethods.DwmSetWindowAttribute(handle, DWMWA_WINDOW_CORNER_PREFERENCE, ref corner, sizeof(int)); - int backdrop = DWMSBT_MAINWINDOW; + int backdrop = DWMSBT_TRANSIENTWINDOW; NativeMethods.DwmSetWindowAttribute(handle, DWMWA_SYSTEMBACKDROP_TYPE, ref backdrop, sizeof(int)); } diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs index 310e46ea43..c5b106bdea 100644 --- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs @@ -126,6 +126,7 @@ partial void OnIsFilterPaneOpenChanged(bool value) public readonly bool MegaQueryBoxEnabled; public readonly bool DisableFilterOnQueryChange; public readonly bool DisableReload; + public readonly bool LoadsOnStart; public readonly bool RoleIsUpdateLike; public bool SimilarSearchEnabled { get; private set; } public readonly string NoPackagesText; @@ -206,6 +207,7 @@ public PackagesPageViewModel(PackagesPageData data) DisableFilterOnQueryChange = data.DisableFilterOnQueryChange; MegaQueryBoxEnabled = data.MegaQueryBlockEnabled; DisableReload = data.DisableReload; + LoadsOnStart = !data.DisableAutomaticPackageLoadOnStart; _showLastCheckedTime = data.ShowLastLoadTime; NoPackagesText = data.NoPackages_BackgroundText; NoMatchesText = data.NoMatches_BackgroundText; @@ -516,7 +518,14 @@ public void FilterPackages(bool fromQuery = false) UpdateSubtitle(); PackageCountUpdated?.Invoke(); - if (FilteredPackages.Count == 0) + bool loadingOrPending = Loader.IsLoading || (LoadsOnStart && !Loader.IsLoaded); + + if (loadingOrPending && FilteredPackages.Count == 0) + { + BackgroundText = _stillLoadingSubtitle; + BackgroundTextVisible = true; + } + else if (FilteredPackages.Count == 0) { BackgroundText = string.IsNullOrWhiteSpace(query) ? NoPackagesText : NoMatchesText; BackgroundTextVisible = !MegaQueryBoxEnabled || !string.IsNullOrWhiteSpace(query); @@ -533,7 +542,6 @@ public async Task LoadPackages(ReloadReason reason = ReloadReason.External) if (!Loader.IsLoading && (!Loader.IsLoaded || reason is ReloadReason.External or ReloadReason.Manual or ReloadReason.Automated)) { - Loader.ClearPackages(emitFinishSignal: false); await Loader.ReloadPackages(); } } @@ -827,7 +835,7 @@ public void UpdatePackageCount() // ─── Subtitle ───────────────────────────────────────────────────────────── public void UpdateSubtitle() { - if (Loader.IsLoading) + if (Loader.IsLoading || (LoadsOnStart && !Loader.IsLoaded)) { Subtitle = _stillLoadingSubtitle; return; diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml index fa60f3d359..32f70d8a04 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml @@ -362,6 +362,8 @@ FontSize="15" CornerRadius="0" BorderThickness="0" + VerticalContentAlignment="Center" + Padding="8,0,4,0" Background="Transparent" PlaceholderText="{Binding GlobalSearchPlaceholder}" IsEnabled="{Binding GlobalSearchEnabled}" diff --git a/src/UniGetUI.PackageEngine.PackageLoader/AbstractPackageLoader.cs b/src/UniGetUI.PackageEngine.PackageLoader/AbstractPackageLoader.cs index ad85de9a73..be5139222b 100644 --- a/src/UniGetUI.PackageEngine.PackageLoader/AbstractPackageLoader.cs +++ b/src/UniGetUI.PackageEngine.PackageLoader/AbstractPackageLoader.cs @@ -144,12 +144,17 @@ public virtual async Task ReloadPackages() return; } - ClearPackages(emitFinishSignal: false); LoadOperationIdentifier = new Random().Next(); int current_identifier = LoadOperationIdentifier; IsLoading = true; StartedLoading?.Invoke(this, EventArgs.Empty); + // Clear packages only after signaling the load started, so the UI shows the + // loading state instead of briefly flashing the "no packages found" message. + PackageReference.Clear(); + IsLoaded = false; + InvokePackagesChangedEvent(false, [], []); + if (REQUIRES_INTERNET) { await CoreTools.WaitForInternetConnection(); diff --git a/src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml.cs b/src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml.cs index df6e550490..a4443af0af 100644 --- a/src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml.cs +++ b/src/UniGetUI/Pages/SoftwarePages/AbstractPackagesPage.xaml.cs @@ -697,7 +697,6 @@ protected async Task LoadPackages(ReloadReason reason) ) ) { - Loader.ClearPackages(emitFinishSignal: false); await Loader.ReloadPackages(); } }