From 176c869bb9dcb3b5d0f5db983547a0eeabf5a844 Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Sat, 25 Jul 2026 12:20:40 +0000
Subject: [PATCH 01/26] Migrate legacy dialogs to WinUI-style immersive
overlays
---
.../Assets/Styles/Styles.Common.axaml | 52 ++-
.../Assets/Styles/Styles.WindowsMica.axaml | 4 +-
.../Infrastructure/MicaWindowHelper.cs | 44 ++-
.../Infrastructure/TrayService.cs | 2 +-
.../UninstallConfirmationDialog.cs | 11 +-
.../ManageIgnoredUpdatesViewModel.cs | 2 -
.../Pages/SettingsPages/BackupViewModel.cs | 8 +-
.../ViewModels/SidebarViewModel.cs | 2 +-
.../SoftwarePages/PackagesPageViewModel.cs | 9 +-
.../Views/Controls/InfoBar.axaml.cs | 33 +-
.../Views/DialogPages/AboutWindow.axaml | 16 +-
.../Views/DialogPages/AboutWindow.axaml.cs | 3 +-
.../BundleSecurityReportDialog.axaml | 14 +-
.../BundleSecurityReportDialog.axaml.cs | 3 +-
.../Views/DialogPages/CrashReportWindow.axaml | 16 +-
.../DialogPages/CrashReportWindow.axaml.cs | 3 +-
.../DiscardBundleChangesDialog.axaml | 24 +-
.../DiscardBundleChangesDialog.axaml.cs | 3 +-
.../Views/DialogPages/ImmersiveDialog.cs | 71 +++++
.../DialogPages/InstallOptionsWindow.axaml | 35 +-
.../DialogPages/InstallOptionsWindow.axaml.cs | 15 +-
.../IntegrityViolationDialog.axaml | 26 +-
.../IntegrityViolationDialog.axaml.cs | 3 +-
.../ManageDesktopShortcutsWindow.axaml | 34 +-
.../ManageDesktopShortcutsWindow.axaml.cs | 3 +-
.../ManageIgnoredUpdatesWindow.axaml | 36 +--
.../ManageIgnoredUpdatesWindow.axaml.cs | 67 +---
.../DialogPages/MissingDependencyDialog.axaml | 27 +-
.../MissingDependencyDialog.axaml.cs | 4 +-
.../DialogPages/OperationFailedDialog.axaml | 16 +-
.../OperationFailedDialog.axaml.cs | 3 +-
.../DialogPages/OperationOutputWindow.axaml | 14 +-
.../OperationOutputWindow.axaml.cs | 3 +-
.../DialogPages/PackageDetailsWindow.axaml | 25 +-
.../DialogPages/PackageDetailsWindow.axaml.cs | 3 +-
.../DialogPages/ResetSettingsDialog.axaml | 24 +-
.../DialogPages/ResetSettingsDialog.axaml.cs | 3 +-
.../Views/DialogPages/SimpleErrorDialog.axaml | 27 +-
.../DialogPages/SimpleErrorDialog.axaml.cs | 4 +-
.../Views/DialogPages/TelemetryDialog.axaml | 29 +-
.../DialogPages/TelemetryDialog.axaml.cs | 3 +-
src/UniGetUI.Avalonia/Views/MainWindow.axaml | 122 +++++--
.../Views/MainWindow.axaml.cs | 300 ++++++++++++++++--
src/UniGetUI.Avalonia/Views/SidebarView.axaml | 3 +-
.../Views/SidebarView.axaml.cs | 17 +-
.../SoftwarePages/AbstractPackagesPage.axaml | 1 +
.../AbstractPackagesPage.axaml.cs | 2 +-
47 files changed, 768 insertions(+), 401 deletions(-)
create mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
index 1582038c82..33791054d0 100644
--- a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
@@ -33,6 +33,10 @@
+
+
+
+
@@ -64,6 +68,8 @@
+
+
@@ -76,7 +82,7 @@
-
+
@@ -115,6 +121,10 @@
+
+
+
+
@@ -146,6 +156,8 @@
+
+
@@ -187,6 +199,44 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml
index 073cc8779e..6e6fd9d9be 100644
--- a/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.WindowsMica.axaml
@@ -30,7 +30,7 @@
-
+
@@ -71,7 +71,7 @@
stays readably dark even when the popup floats over bright content (e.g. the release-notes
WebView) — a lighter tint let the white page wash the menu out (#5007) -->
-
+
diff --git a/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs b/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs
index 17b65453a5..5574de6671 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/MicaWindowHelper.cs
@@ -5,6 +5,7 @@
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Platform;
+using Avalonia.VisualTree;
using UniGetUI.Avalonia.Assets.Styles;
namespace UniGetUI.Avalonia.Infrastructure;
@@ -48,22 +49,22 @@ public static void Apply(Window window)
};
}
- // Gives flyouts / menus / combo dropdowns / tooltips a native Win11 acrylic backdrop:
- // the popup window is made transparent and DWM paints the (blurred, theme-adaptive)
- // acrylic material behind it. Registered once at startup when Mica is enabled.
+ // Applies the Windows 11 transient-surface treatment to popup hosts. Menus use an
+ // opaque WinUI surface; combo dropdowns, tooltips, and ordinary flyouts use acrylic.
+ // Registered once at startup when Mica is enabled.
public static void EnableAcrylicPopups()
{
if (!IsMicaEnabled() || _acrylicPopupsHooked)
return;
_acrylicPopupsHooked = true;
- // In-app flyouts/menus/tooltips/combo popups are hosted in a PopupRoot; style each as it loads.
+ // In-app menus, flyouts, tooltips, and combo popups are hosted in a PopupRoot.
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().
+ // render with no surface over the desktop. Catch it by type name and apply the opaque
+ // menu treatment. The other Windows (MainWindow/dialogs) are handled via Apply().
Control.LoadedEvent.AddClassHandler((win, _) =>
{
if (win.GetType().Name == "TrayPopupRoot")
@@ -76,10 +77,28 @@ private static void ApplyAcrylicToPopup(TopLevel root)
if (!IsMicaEnabled())
return;
+ // Menus use WinUI's opaque flyout surface. Other transient controls (combo boxes,
+ // tooltips and ordinary flyouts) retain acrylic.
+ bool isMenu = root.GetType().Name == "TrayPopupRoot"
+ || root.GetVisualDescendants()
+ .Any(control => control is MenuFlyoutPresenter or ContextMenu);
+ if (isMenu)
+ {
+ root.TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
+ if (root.TryFindResource("MenuSurfaceBrush", root.ActualThemeVariant, out object? resource)
+ && resource is IBrush brush)
+ {
+ root.Background = brush;
+ }
+
+ ApplyRoundedCorners(root);
+ return;
+ }
+
// 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
+ // the presenter tint and make its contents unreadable. 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;
@@ -93,6 +112,15 @@ private static void ApplyAcrylicToPopup(TopLevel root)
NativeMethods.DwmSetWindowAttribute(handle, DWMWA_SYSTEMBACKDROP_TYPE, ref backdrop, sizeof(int));
}
+ private static void ApplyRoundedCorners(TopLevel root)
+ {
+ if (root.TryGetPlatformHandle()?.Handle is not { } handle || handle == 0)
+ return;
+
+ int corner = DWMWCP_ROUND;
+ NativeMethods.DwmSetWindowAttribute(handle, DWMWA_WINDOW_CORNER_PREFERENCE, ref corner, sizeof(int));
+ }
+
///
/// True when the native Mica look should be used: Windows 11+, "Transparency effects" on,
/// GPU-accelerated (software rendering can't be transparent, so Mica shows as black — #5111),
diff --git a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
index c1311c2f12..c0ae4c04b8 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
@@ -136,7 +136,7 @@ private static NativeMenu BuildMenu(MainWindow owner)
menu.Add(new NativeMenuItemSeparator());
menu.Add(new NativeMenuItem(
- CoreTools.Translate("UniGetUI Version {0} by Devolutions", CoreData.VersionName))
+ CoreTools.Translate("UniGetUI Version {0}", CoreData.VersionName))
{
IsEnabled = false,
});
diff --git a/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs b/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
index d414993d3c..47d89a17ea 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
@@ -22,15 +22,10 @@ public static async Task ConfirmAsync(Window owner, IReadOnlyList 1,
- ShowInTaskbar = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ MaxWidth = packages.Count == 1 ? 520 : 560,
+ MaxHeight = packages.Count == 1 ? 220 : 380,
Title = CoreTools.Translate("Are you sure?"),
};
diff --git a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
index 78ac769cc0..2157b83974 100644
--- a/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/DialogPages/ManageIgnoredUpdatesViewModel.cs
@@ -11,8 +11,6 @@ namespace UniGetUI.Avalonia.ViewModels;
public partial class ManageIgnoredUpdatesViewModel : ObservableObject
{
- public event EventHandler? CloseRequested;
-
public string Title { get; } = CoreTools.Translate("Manage ignored updates");
public string Description { get; } = CoreTools.Translate("The packages listed here won't be taken in account when checking for updates. Double-click them or click the button on their right to stop ignoring their updates.");
public string ResetLabel { get; } = CoreTools.Translate("Reset list");
diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
index d2a79a2fa2..939549c82c 100644
--- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/BackupViewModel.cs
@@ -347,13 +347,11 @@ private static async Task RestoreFromCloud()
Width = 360,
};
- var dialog = new Window
+ var dialog = new UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
Title = CoreTools.Translate("Select backup"),
- Width = 440,
- Height = 160,
- CanResize = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ MaxWidth = 440,
+ MaxHeight = 160,
Content = new StackPanel
{
Margin = new Thickness(20),
diff --git a/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
index 69727307b7..80bc3b497b 100644
--- a/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
@@ -122,7 +122,7 @@ private void RaiseLayoutChanged()
public event EventHandler? NavigationRequested;
public string VersionLabel { get; } =
- CoreTools.Translate("UniGetUI Version {0} by Devolutions", CoreData.VersionName);
+ CoreTools.Translate("UniGetUI Version {0}", CoreData.VersionName);
[RelayCommand]
public void RequestNavigation(string? pageName)
diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
index 20a8d09fc4..3ce07b6a99 100644
--- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
+++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs
@@ -366,13 +366,10 @@ public async Task ShowInfoDialog(Window owner, string title, string message)
{
object? bgResource = null;
Application.Current?.Resources.TryGetResource("AppWindowBackground", Application.Current.ActualThemeVariant, out bgResource);
- var dialog = new Window
+ var dialog = new UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
- Width = 460,
- Height = 180,
- CanResize = false,
- ShowInTaskbar = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ MaxWidth = 460,
+ MaxHeight = 180,
Title = title,
Background = bgResource as IBrush,
};
diff --git a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
index 9191e6dab5..1058a46f97 100644
--- a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
@@ -26,6 +26,8 @@ public InfoBar()
}
private InfoBarViewModel? _vm;
+ private IDisposable? _severityStripBinding;
+ private IDisposable? _severityIconBinding;
private void OnDataContextChanged(object? sender, EventArgs e)
{
@@ -53,17 +55,19 @@ private void ApplySeverity(InfoBarSeverity severity)
BodyBorder.Classes.Set("severity-warning", severity == InfoBarSeverity.Warning);
BodyBorder.Classes.Set("severity-info", severity == InfoBarSeverity.Informational);
- // Strip colour (solid, not theme-sensitive)
- var stripColor = severity switch
+ _severityStripBinding?.Dispose();
+ _severityIconBinding?.Dispose();
+ _severityStripBinding = null;
+ _severityIconBinding = null;
+
+ Color? stripColor = severity switch
{
InfoBarSeverity.Warning => Color.Parse("#F7A800"),
InfoBarSeverity.Error => Color.Parse("#C42B1C"),
InfoBarSeverity.Success => Color.Parse("#107C10"),
- _ => Color.Parse("#0078D4"),
+ _ => null,
};
- SeverityStrip.Background = new SolidColorBrush(stripColor);
- // Icon (shared SVG asset, tinted with the severity colour)
SeverityIcon.Path = severity switch
{
InfoBarSeverity.Warning => WarningIcon,
@@ -71,6 +75,23 @@ private void ApplySeverity(InfoBarSeverity severity)
InfoBarSeverity.Success => SuccessIcon,
_ => InfoIcon,
};
- SeverityIcon.Foreground = new SolidColorBrush(stripColor);
+
+ if (stripColor is { } color)
+ {
+ var brush = new SolidColorBrush(color);
+ SeverityStrip.Background = brush;
+ SeverityIcon.Foreground = brush;
+ }
+ else
+ {
+ SeverityStrip.ClearValue(Border.BackgroundProperty);
+ SeverityIcon.ClearValue(ForegroundProperty);
+ _severityStripBinding = SeverityStrip.Bind(
+ Border.BackgroundProperty,
+ this.GetResourceObservable("SystemAccentColor"));
+ _severityIconBinding = SeverityIcon.Bind(
+ ForegroundProperty,
+ this.GetResourceObservable("SystemAccentColor"));
+ }
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml
index 8e048a709b..03516c8311 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml
@@ -1,15 +1,15 @@
-
+ MaxWidth="700"
+ MinWidth="500"
+ MaxHeight="600"
+ MinHeight="400"
+ Background="{DynamicResource AppDialogBackground}">
@@ -31,4 +31,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml.cs
index 267b4388e5..aea2a62f97 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/AboutWindow.axaml.cs
@@ -3,12 +3,11 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class AboutWindow : Window
+public partial class AboutWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public AboutWindow()
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
}
protected override void OnOpened(EventArgs e)
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml
index 14b8221374..cf78b64f84 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml
@@ -1,17 +1,15 @@
-
+ Background="{DynamicResource AppDialogBackground}">
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml.cs
index f04a91f32c..5c01bdfc2e 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/BundleSecurityReportDialog.axaml.cs
@@ -4,12 +4,11 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class BundleSecurityReportDialog : Window
+public partial class BundleSecurityReportDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public BundleSecurityReportDialog(BundleReport report)
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
var sb = new System.Text.StringBuilder();
foreach (var (pkgId, entries) in report.Contents)
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml
index 7993b0f2b9..e29a1e5bfb 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml
@@ -1,14 +1,14 @@
-
+ MaxWidth="800"
+ MinWidth="500"
+ MaxHeight="580"
+ MinHeight="380"
+ Background="{DynamicResource AppDialogBackground}">
@@ -66,4 +66,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml.cs
index 0be7336d52..732df5e28c 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/CrashReportWindow.axaml.cs
@@ -8,7 +8,7 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-internal sealed partial class CrashReportWindow : Window
+internal sealed partial class CrashReportWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
private readonly string _crashReport;
@@ -16,7 +16,6 @@ public CrashReportWindow(string crashReport)
{
_crashReport = crashReport;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
CrashReportText.Text = crashReport;
DontSendButton.Content = CoreTools.Translate("Don't Send");
SendButton.Content = CoreTools.Translate("Send Report");
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
index 81e8286486..8648171cf3 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
@@ -1,33 +1,25 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
-
@@ -44,4 +36,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
index d156fdca58..1f1e1cc868 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
@@ -3,14 +3,13 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class DiscardBundleChangesDialog : Window
+public partial class DiscardBundleChangesDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public bool Confirmed { get; private set; }
public DiscardBundleChangesDialog()
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
CancelButton.Click += (_, _) => Close();
DiscardButton.Click += (_, _) => { Confirmed = true; Close(); };
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
new file mode 100644
index 0000000000..c18adf3df1
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
@@ -0,0 +1,71 @@
+using System.ComponentModel;
+using Avalonia;
+using Avalonia.Controls;
+using UniGetUI.Core.Logging;
+
+namespace UniGetUI.Avalonia.Views.DialogPages;
+
+///
+/// Base class for modal content hosted inside .
+/// It exposes dialog-oriented show and close operations while the host owns presentation,
+/// focus, animation, and stacking.
+///
+public class ImmersiveDialog : ContentControl
+{
+ public static readonly StyledProperty TitleProperty =
+ AvaloniaProperty.Register(nameof(Title));
+ public static readonly StyledProperty IsCloseButtonVisibleProperty =
+ AvaloniaProperty.Register(nameof(IsCloseButtonVisible), true);
+
+ internal event EventHandler? CloseRequested;
+ public event EventHandler? Closing;
+
+ public string? Title
+ {
+ get => GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ public bool IsCloseButtonVisible
+ {
+ get => GetValue(IsCloseButtonVisibleProperty);
+ set => SetValue(IsCloseButtonVisibleProperty, value);
+ }
+
+ public Task ShowDialog(Window owner)
+ {
+ MainWindow host = owner as MainWindow
+ ?? throw new ArgumentException(
+ "An immersive dialog must be owned by the main window.",
+ nameof(owner));
+ return host.ShowImmersiveDialogAsync(this);
+ }
+
+ public void Show(Window owner) => _ = ShowAndLogFailureAsync(owner);
+
+ private async Task ShowAndLogFailureAsync(Window owner)
+ {
+ try
+ {
+ await ShowDialog(owner);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex);
+ }
+ }
+
+ public void Close()
+ {
+ var args = new CancelEventArgs();
+ Closing?.Invoke(this, args);
+ if (!args.Cancel)
+ CloseRequested?.Invoke(this, EventArgs.Empty);
+ }
+
+ internal void NotifyOpened() => OnOpened(EventArgs.Empty);
+ internal void NotifyClosed() => OnClosed(EventArgs.Empty);
+
+ protected virtual void OnOpened(EventArgs e) { }
+ protected virtual void OnClosed(EventArgs e) { }
+}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
index 3e3f7fc38c..8b14b9e3c2 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml
@@ -1,39 +1,22 @@
-
+ Title="{Binding DialogTitle}">
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
@@ -59,4 +42,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
index 082979e708..fc5fa38945 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsWindow.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia.Controls;
-using Avalonia.Input;
using Avalonia.Threading;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.PackageEngine.Enums;
@@ -8,7 +7,7 @@
namespace UniGetUI.Avalonia.Views;
-public partial class InstallOptionsWindow : Window
+public partial class InstallOptionsWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public bool ShouldProceedWithOperation =>
((InstallOptionsViewModel)DataContext!).ShouldProceedWithOperation;
@@ -19,11 +18,6 @@ public InstallOptionsWindow(IPackage package, OperationType operation, InstallOp
DataContext = vm;
InitializeComponent();
- // Drop the OS title-bar strip but keep the system min/close buttons, extending the
- // client area into the title-bar region (matches the Manage-ignored-updates window).
- ExtendClientAreaToDecorationsHint = true;
- ExtendClientAreaTitleBarHeightHint = -1;
-
vm.CloseRequested += (_, _) => Close();
}
@@ -33,11 +27,4 @@ protected override void OnOpened(EventArgs e)
Dispatcher.UIThread.Post(OptionsControl.FocusProfileSelector, DispatcherPriority.Background);
}
- // The client area is extended over the title bar, so provide window dragging from the
- // transparent strip at the top.
- private void TitleBar_PointerPressed(object? sender, PointerPressedEventArgs e)
- {
- if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
- BeginMoveDrag(e);
- }
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml
index 4bb3f5e327..f6868596e5 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml
@@ -1,28 +1,20 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
-
+
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml.cs
index 2a7c309af3..54dc3ad8c4 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/IntegrityViolationDialog.axaml.cs
@@ -3,12 +3,11 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class IntegrityViolationDialog : Window
+public partial class IntegrityViolationDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public IntegrityViolationDialog()
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
CloseButton.Click += (_, _) => Close();
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml
index 931825ab91..4c3bba20dd 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml
@@ -1,42 +1,34 @@
-
-
+
-
+
-
-
-
-
-
+
-
-
+
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml.cs
index c8b7a68b22..f71aa6af43 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ManageDesktopShortcutsWindow.axaml.cs
@@ -5,14 +5,13 @@
namespace UniGetUI.Avalonia.Views;
-public partial class ManageDesktopShortcutsWindow : Window
+public partial class ManageDesktopShortcutsWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public ManageDesktopShortcutsWindow(System.Collections.Generic.IReadOnlyList? shortcuts = null)
{
var vm = new ManageDesktopShortcutsViewModel(shortcuts);
DataContext = vm;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
vm.CloseRequested += (_, _) => Close();
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml
index 2b8204bc6b..8a5b371b01 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml
@@ -1,20 +1,21 @@
-
-
-
+ Title="{Binding Title}"
+ MaxWidth="850"
+ MinWidth="520"
+ MaxHeight="500"
+ MinHeight="280"
+ HorizontalContentAlignment="Stretch"
+ VerticalContentAlignment="Stretch">
+
+
-
+
+ Margin="20"
+ RowSpacing="12">
-
+
-
-
@@ -170,4 +170,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml.cs
index bbac588cbc..1bd870f349 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ManageIgnoredUpdatesWindow.axaml.cs
@@ -1,76 +1,39 @@
using Avalonia.Controls;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using UniGetUI.Avalonia.ViewModels;
-using UniGetUI.Core.Logging;
-using UniGetUI.Core.SettingsEngine;
namespace UniGetUI.Avalonia.Views;
-public partial class ManageIgnoredUpdatesWindow : Window
+public partial class ManageIgnoredUpdatesWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
- public ManageIgnoredUpdatesWindow(Window? owner = null)
+ public ManageIgnoredUpdatesWindow()
{
var vm = new ManageIgnoredUpdatesViewModel();
DataContext = vm;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
- // Set the size before the window opens so CenterOwner positions it correctly.
- ApplyInitialSize(owner);
-
- vm.CloseRequested += (_, _) => Close();
+ KeyDown += OnDialogKeyDown;
+ AttachedToVisualTree += (_, _) => Dispatcher.UIThread.Post(FocusInitialControl,
+ DispatcherPriority.Background);
}
- // Restore the user's last size, falling back to the XAML default, and never open larger
- // than the owner window (recovers the adaptive sizing of the pre-Avalonia overlay).
- private void ApplyInitialSize(Window? owner)
+ private void FocusInitialControl()
{
- double width = Width;
- double height = Height;
-
- string saved = Settings.GetValue(Settings.K.IgnoredUpdatesWindowSize);
- if (!string.IsNullOrEmpty(saved))
- {
- string[] parts = saved.Split(',');
- if (parts.Length == 2 && int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
- {
- width = w;
- height = h;
- }
- }
-
- if (owner is not null)
- {
- width = Math.Min(width, owner.Width - 40);
- height = Math.Min(height, owner.Height - 40);
- }
-
- Width = Math.Max(MinWidth, width);
- Height = Math.Max(MinHeight, height);
+ if (((ManageIgnoredUpdatesViewModel)DataContext!).HasEntries)
+ IgnoredUpdatesGrid.Focus();
+ else
+ ResetButton.Focus();
}
- protected override void OnClosing(WindowClosingEventArgs e)
+ private void OnDialogKeyDown(object? sender, KeyEventArgs e)
{
- // Persist only the normal (non-maximized) size so the dialog reopens as the user left it.
- if (WindowState == WindowState.Normal)
+ if (e.Key == Key.Escape)
{
- try { Settings.SetValue(Settings.K.IgnoredUpdatesWindowSize, $"{(int)Width},{(int)Height}"); }
- catch (Exception ex) { Logger.Error(ex); }
+ Close();
+ e.Handled = true;
}
- base.OnClosing(e);
- }
-
- protected override void OnOpened(EventArgs e)
- {
- base.OnOpened(e);
- Dispatcher.UIThread.Post(() =>
- {
- if (((ManageIgnoredUpdatesViewModel)DataContext!).HasEntries)
- IgnoredUpdatesGrid.Focus();
- else
- ResetButton.Focus();
- }, DispatcherPriority.Background);
}
private void ResetYes_Click(object? sender, RoutedEventArgs e)
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml
index fbf0e559ea..dcd67bf4cf 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml
@@ -1,28 +1,19 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
-
+
@@ -47,7 +38,7 @@
IsVisible="False"/>
-
@@ -61,4 +52,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml.cs
index 0e36090543..26e8bf528a 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/MissingDependencyDialog.axaml.cs
@@ -10,7 +10,7 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class MissingDependencyDialog : Window
+public partial class MissingDependencyDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
private readonly ManagerDependency _dep;
private readonly int _current;
@@ -26,7 +26,6 @@ public MissingDependencyDialog(ManagerDependency dep, int current, int total)
_total = total;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
bool notFirstTime =
Settings.GetDictionaryItem(Settings.K.DependencyManagement, dep.Name)
@@ -36,7 +35,6 @@ public MissingDependencyDialog(ManagerDependency dep, int current, int total)
Title = CoreTools.Translate("Missing dependency")
+ (total > 1 ? $" ({current}/{total})" : "");
- TitleBlock.Text = Title;
DescBlock.Text = CoreTools.Translate(
"UniGetUI requires {0} to operate, but it was not found on your system.", dep.Name);
InfoBlock.Text = CoreTools.Translate(
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml
index 4d349edc9a..af6f1eb7fb 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml
@@ -1,14 +1,14 @@
-
+ MaxWidth="800"
+ MinWidth="500"
+ MaxHeight="550"
+ MinHeight="300"
+ Background="{DynamicResource AppDialogBackground}">
@@ -43,4 +43,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml.cs
index eabc4d64cf..b012b766cc 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationFailedDialog.axaml.cs
@@ -12,7 +12,7 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class OperationFailedDialog : Window
+public partial class OperationFailedDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
private readonly AbstractOperation _operation;
@@ -20,7 +20,6 @@ public OperationFailedDialog(AbstractOperation operation)
{
_operation = operation;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
Title = operation.Metadata.FailureMessage;
HeaderContent.Text =
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
index e77bcc6c72..4f7399e96e 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml
@@ -1,16 +1,16 @@
-
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
index 3c7ef42105..41d063d257 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/OperationOutputWindow.axaml.cs
@@ -7,14 +7,13 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class OperationOutputWindow : Window
+public partial class OperationOutputWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public OperationOutputWindow(AbstractOperation operation)
{
var vm = new OperationOutputViewModel(operation);
DataContext = vm;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
OutputText.SetLines(vm.OutputLines);
vm.OutputLines.CollectionChanged += OnOutputLinesChanged;
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
index cde1c13c26..740c25cdd0 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
@@ -1,20 +1,20 @@
-
+ Title="{t:Translate Package details}">
-
+
@@ -97,7 +97,7 @@
-
+
@@ -255,9 +255,10 @@
x:Name="ScreenshotsBorder"
Height="320">
-
-
+
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
index c12270c430..b044de66b6 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
@@ -24,7 +24,7 @@
namespace UniGetUI.Avalonia.Views;
-public partial class PackageDetailsWindow : Window
+public partial class PackageDetailsWindow : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
private const double WideThreshold = 950;
private const string ContributeUrl = "https://github.com/Devolutions/UniGetUI";
@@ -44,7 +44,6 @@ public PackageDetailsWindow(IPackage package, OperationType operation)
_vm = new PackageDetailsViewModel(package, operation);
DataContext = _vm;
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
// Honor the OS "reduce motion" preference: drop the screenshot slide animation.
if (MotionPreference.ReducedMotion)
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
index 6b83d6531c..16b5baae40 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
@@ -1,33 +1,25 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
-
@@ -44,4 +36,4 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
index b39b6e49c2..1d38565837 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
@@ -3,14 +3,13 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class ResetSettingsDialog : Window
+public partial class ResetSettingsDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public bool Confirmed { get; private set; }
public ResetSettingsDialog()
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
CancelButton.Click += (_, _) => Close();
ResetButton.Click += (_, _) => { Confirmed = true; Close(); };
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml
index 284f1146a6..e4da756aee 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml
@@ -1,34 +1,25 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml.cs
index 9ff1e79adc..8518c8e6e3 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/SimpleErrorDialog.axaml.cs
@@ -3,14 +3,12 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class SimpleErrorDialog : Window
+public partial class SimpleErrorDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public SimpleErrorDialog(string title, string message)
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
Title = title;
- TitleBlock.Text = title;
MessageBlock.Text = message;
OkButton.Click += (_, _) => Close();
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
index af167392a5..9a85f9369b 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
@@ -1,30 +1,23 @@
-
+
+ Background="{DynamicResource AppDialogBackground}">
-
-
-
-
+
@@ -40,7 +33,7 @@
-
-
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
index 0aa027331e..c30a2f656e 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
@@ -4,14 +4,13 @@
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class TelemetryDialog : Window
+public partial class TelemetryDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
{
public bool? Result { get; private set; }
public TelemetryDialog()
{
InitializeComponent();
- UniGetUI.Avalonia.Infrastructure.MicaWindowHelper.Apply(this);
Body2.Text = CoreTools.Translate("No personal information is collected nor sent, and the collected data is anonimized, so it can't be back-tracked to you.");
diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
index 57630932e8..b3638440b5 100644
--- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
@@ -50,6 +50,33 @@
+
+
+
+
+
+
+
+
@@ -611,66 +638,115 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
_badgeSubscriptions = new();
+ private readonly SemaphoreSlim _modalTransitionSemaphore = new(1, 1);
+ private readonly List _modalStack = new();
+ private readonly Dictionary _modalFocusHistory = new();
+ private readonly List _webViewsHiddenForModal = new();
+ private IDisposable? _modalTitleSubscription;
+ private Control? _focusBeforeModal;
public enum RuntimeNotificationLevel
{
@@ -149,6 +158,7 @@ public MainWindow()
UpdateOperationsPanelRow();
Resized += (_, _) => _ = SaveGeometryAsync();
+ ModalLayer.SizeChanged += (_, _) => UpdateModalSurfaceSize();
PositionChanged += (_, _) => _ = SaveGeometryAsync();
this.GetObservable(WindowStateProperty).SubscribeValue(state => { UpdateOnScreenState(); _ = SaveGeometryAsync(); });
this.GetObservable(IsVisibleProperty).SubscribeValue(_ => UpdateOnScreenState());
@@ -237,6 +247,16 @@ private void ReleaseWindowResources()
private void Window_KeyDown(object? sender, KeyEventArgs e)
{
+ // A WinUI ContentDialog is a true modal scope: background navigation and package-page
+ // shortcuts must not run while keyboard focus is inside the dialog.
+ if (ModalLayer.IsVisible)
+ {
+ if (e.Key == Key.Escape)
+ (ModalContent.Content as ImmersiveDialog)?.Close();
+ e.Handled = true;
+ return;
+ }
+
bool isCtrl = e.KeyModifiers.HasFlag(KeyModifiers.Control);
bool isShift = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
@@ -450,26 +470,13 @@ private void SetupTitleBarFocusOpacity()
HamburgerPanel.Opacity = active ? 1.0 : 0.6;
WindowButtons.Opacity = active ? 1.0 : 0.55;
- // Match WinUI's inactive Mica appearance without invoking the permanent, global
- // NotifyMicaUnavailable() fallback used when composition actually fails (#5111).
- // A partial (not full) overlay so the inactive window still keeps some of the Mica
- // tint instead of flattening to solid grey. Skip it while a modal dialog is open: the
- // window deactivates but shouldn't grey out behind its own dialog. Modeless owned
- // windows (operation output/failure) don't count — the main window should still grey
- // when the user switches to another application.
+ // WinUI replaces inactive Mica with SolidBackgroundFillColorBase rather than leaving
+ // a partially wallpaper-tinted backdrop. AppWindowBackground is that token
+ // (#F3F3F3 light / #202020 dark), so make the fallback fully opaque while inactive.
InactiveMicaFallback.Opacity =
- MicaWindowHelper.IsMicaEnabled() && !active && !HasOpenModalDialog() ? 0.4 : 0.0;
+ MicaWindowHelper.IsMicaEnabled() && !active ? 1.0 : 0.0;
});
- // True when a window opened modally (ShowDialog) over this one is currently open.
- private bool HasOpenModalDialog()
- {
- foreach (var owned in OwnedWindows)
- if (owned.IsDialog)
- return true;
- return false;
- }
-
private void SetupTitleBar()
{
if (OperatingSystem.IsMacOS())
@@ -892,7 +899,7 @@ private void UpdateMaximizeButtonState(bool isMaximized)
{
MaximizeIcon.Data = Geometry.Parse(
isMaximized
- ? "M2,0 H10 V8 H2 Z M0,2 H8 V10 H0 Z"
+ ? "M3,0 H10 V7 H8 V2 H3 Z M0,3 H7 V10 H0 Z"
: "M0,0 H10 V10 H0 Z");
ToolTip.SetTip(
MaximizeButton,
@@ -903,7 +910,7 @@ private void UpdateMaximizeButtonState(bool isMaximized)
// button's bounds — the area for which we claim HTMAXBUTTON so Snap Layouts can attach.
private bool HitTestMaximizeButton(nint lParam)
{
- if (!MaximizeButton.IsVisible)
+ if (!MaximizeButton.IsVisible || !MaximizeButton.IsEnabled)
return false;
try
{
@@ -931,7 +938,7 @@ private void SetMaximizeButtonVisual(bool hover, bool pressed)
if (key is not null && this.TryFindResource(key, ActualThemeVariant, out var res) && res is IBrush brush)
MaximizeButton.Background = brush;
else
- MaximizeButton.Background = Brushes.Transparent;
+ MaximizeButton.ClearValue(BackgroundProperty);
}
private static void TrackNonClientMouseLeave(nint hWnd)
@@ -1446,6 +1453,259 @@ public void OpenManagerSettings(IPackageManager? manager = null) =>
ViewModel.OpenManagerSettings(manager);
public void ShowHelp(string uriAttachment = "") => ViewModel.ShowHelp(uriAttachment);
+ ///
+ /// Shows the ignored-updates editor as a modal surface inside this window. Keeping the
+ /// surface in the owner's visual tree makes it resize with the app and avoids a second
+ /// task-switcher/window-management target.
+ ///
+ public async Task ShowManageIgnoredUpdatesAsync()
+ {
+ var dialog = new ManageIgnoredUpdatesWindow();
+ await ShowImmersiveDialogAsync(dialog);
+ }
+
+ public async Task ShowImmersiveDialogAsync(ImmersiveDialog dialog)
+ {
+ var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ void CloseDialog(object? sender, EventArgs args) => completion.TrySetResult();
+ bool opened = false;
+ bool added = false;
+ dialog.CloseRequested += CloseDialog;
+
+ try
+ {
+ await _modalTransitionSemaphore.WaitAsync();
+ try
+ {
+ if (_modalStack.Contains(dialog))
+ throw new InvalidOperationException("The immersive dialog is already open.");
+
+ if (_modalStack.Count == 0)
+ {
+ _focusBeforeModal = FocusManager?.GetFocusedElement() as Control;
+ HideNativeWebViewsForModal();
+ // Block pointer input without putting every underlying control into Avalonia's
+ // disabled visual state. The modal layer owns focus while it is visible.
+ MainContentRoot.IsHitTestVisible = false;
+ TitleBarGrid.IsHitTestVisible = false;
+ }
+ else
+ {
+ _modalFocusHistory[_modalStack[^1]] =
+ FocusManager?.GetFocusedElement() as Control;
+ }
+
+ ModalContent.Content = null;
+ _modalStack.Add(dialog);
+ added = true;
+ PresentModal(dialog);
+ dialog.NotifyOpened();
+ opened = true;
+ await AnimateModalAsync(opening: true);
+ FocusModalIfNeeded(dialog);
+ }
+ finally
+ {
+ _modalTransitionSemaphore.Release();
+ }
+
+ await completion.Task;
+ }
+ finally
+ {
+ dialog.CloseRequested -= CloseDialog;
+ await _modalTransitionSemaphore.WaitAsync();
+ try
+ {
+ int index = added ? _modalStack.IndexOf(dialog) : -1;
+ bool isTopmost = index >= 0 && index == _modalStack.Count - 1;
+ if (isTopmost)
+ {
+ try
+ {
+ await AnimateModalAsync(opening: false);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex);
+ }
+ ModalContent.Content = null;
+ }
+
+ if (index >= 0)
+ _modalStack.RemoveAt(index);
+ _modalFocusHistory.Remove(dialog);
+
+ if (opened)
+ {
+ try
+ {
+ dialog.NotifyClosed();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex);
+ }
+ }
+
+ if (_modalStack.Count > 0)
+ {
+ PresentModal(_modalStack[^1]);
+ ModalLayer.Opacity = 1;
+ ModalSurface.Opacity = 1;
+ ModalSurface.RenderTransform = null;
+ RestoreModalFocus(_modalStack[^1]);
+ }
+ else
+ {
+ _modalTitleSubscription?.Dispose();
+ _modalTitleSubscription = null;
+ ModalLayer.IsVisible = false;
+ ModalContent.Content = null;
+ ModalTitle.Text = "";
+ MainContentRoot.IsHitTestVisible = true;
+ TitleBarGrid.IsHitTestVisible = true;
+ RestoreNativeWebViewsAfterModal();
+ Dispatcher.UIThread.Post(
+ () => _focusBeforeModal?.Focus(),
+ DispatcherPriority.Background);
+ _focusBeforeModal = null;
+ }
+ }
+ finally
+ {
+ _modalTransitionSemaphore.Release();
+ }
+ }
+ }
+
+ private void PresentModal(ImmersiveDialog dialog)
+ {
+ _modalTitleSubscription?.Dispose();
+ _modalTitleSubscription = dialog.GetObservable(ImmersiveDialog.TitleProperty)
+ .SubscribeValue(title => ModalTitle.Text = title ?? "");
+ ModalCloseButton.IsVisible = dialog.IsCloseButtonVisible;
+ ModalContent.Content = dialog;
+ ModalLayer.IsVisible = true;
+ UpdateModalSurfaceSize();
+ }
+
+ private void HideNativeWebViewsForModal()
+ {
+ _webViewsHiddenForModal.Clear();
+ foreach (UniGetUiWebView webView in MainContentRoot.GetVisualDescendants().OfType())
+ {
+ if (!webView.IsVisible)
+ continue;
+
+ _webViewsHiddenForModal.Add(webView);
+ webView.IsVisible = false;
+ }
+ }
+
+ private void RestoreNativeWebViewsAfterModal()
+ {
+ foreach (UniGetUiWebView webView in _webViewsHiddenForModal)
+ webView.IsVisible = true;
+ _webViewsHiddenForModal.Clear();
+ }
+
+ private void FocusModalIfNeeded(ImmersiveDialog dialog)
+ {
+ Dispatcher.UIThread.Post(
+ () =>
+ {
+ if (ReferenceEquals(ModalContent.Content, dialog) && !dialog.IsKeyboardFocusWithin)
+ ModalCloseButton.Focus();
+ },
+ DispatcherPriority.Background);
+ }
+
+ private void RestoreModalFocus(ImmersiveDialog dialog)
+ {
+ _modalFocusHistory.Remove(dialog, out Control? previousFocus);
+ Dispatcher.UIThread.Post(
+ () =>
+ {
+ if (!ReferenceEquals(ModalContent.Content, dialog))
+ return;
+ if (previousFocus?.Focus() != true && !dialog.IsKeyboardFocusWithin)
+ ModalCloseButton.Focus();
+ },
+ DispatcherPriority.Background);
+ }
+
+ private void ModalCloseButton_Click(object? sender, RoutedEventArgs e) =>
+ (ModalContent.Content as ImmersiveDialog)?.Close();
+
+ private void UpdateModalSurfaceSize()
+ {
+ if (ModalContent.Content is not ImmersiveDialog dialog)
+ return;
+
+ const double maximumSurfaceWidth = 1100;
+ const double maximumSurfaceHeight = 900;
+ double layerWidth = ModalLayer.Bounds.Width > 0 ? ModalLayer.Bounds.Width : Bounds.Width;
+ double layerHeight = ModalLayer.Bounds.Height > 0 ? ModalLayer.Bounds.Height : Bounds.Height;
+ double availableWidth = Math.Max(0, layerWidth - 96);
+ double availableHeight = Math.Max(0, layerHeight - 96);
+
+ ModalSurface.MaxWidth = Math.Min(maximumSurfaceWidth, availableWidth);
+ ModalSurface.MaxHeight = Math.Min(maximumSurfaceHeight, availableHeight);
+ ModalSurface.Width = double.IsFinite(dialog.MaxWidth)
+ ? Math.Min(dialog.MaxWidth, ModalSurface.MaxWidth)
+ : double.NaN;
+ ModalSurface.Height = double.IsFinite(dialog.MaxHeight)
+ ? Math.Min(dialog.MaxHeight, ModalSurface.MaxHeight)
+ : double.NaN;
+ }
+
+ private async Task AnimateModalAsync(bool opening)
+ {
+ if (MotionPreference.ReducedMotion)
+ {
+ ModalLayer.Opacity = opening ? 1 : 0;
+ ModalSurface.Opacity = opening ? 1 : 0;
+ ModalSurface.RenderTransform = null;
+ return;
+ }
+
+ var scale = new ScaleTransform();
+ ModalSurface.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
+ ModalSurface.RenderTransform = scale;
+
+ double fromOpacity = opening ? 0 : 1;
+ double toOpacity = opening ? 1 : 0;
+ double fromScale = opening ? 1.06 : 1;
+ double toScale = opening ? 1 : 1.06;
+ var duration = TimeSpan.FromMilliseconds(opening ? 180 : 120);
+ long started = Stopwatch.GetTimestamp();
+ while (true)
+ {
+ double progress = Math.Clamp(
+ Stopwatch.GetElapsedTime(started).TotalMilliseconds / duration.TotalMilliseconds,
+ 0,
+ 1);
+ double eased = 1 - Math.Pow(1 - progress, 3);
+ double opacity = fromOpacity + ((toOpacity - fromOpacity) * eased);
+ double currentScale = fromScale + ((toScale - fromScale) * eased);
+
+ ModalLayer.Opacity = opacity;
+ ModalSurface.Opacity = opacity;
+ scale.ScaleX = currentScale;
+ scale.ScaleY = currentScale;
+
+ if (progress >= 1)
+ break;
+ await Task.Delay(16);
+ }
+
+ ModalLayer.Opacity = toOpacity;
+ ModalSurface.Opacity = toOpacity;
+ scale.ScaleX = toScale;
+ scale.ScaleY = toScale;
+ }
+
///
/// Focuses the global search box and optionally pre-fills a character typed
/// while the package list had focus (type-to-search).
diff --git a/src/UniGetUI.Avalonia/Views/SidebarView.axaml b/src/UniGetUI.Avalonia/Views/SidebarView.axaml
index 61e47a35be..9d7e488ce5 100644
--- a/src/UniGetUI.Avalonia/Views/SidebarView.axaml
+++ b/src/UniGetUI.Avalonia/Views/SidebarView.axaml
@@ -184,8 +184,7 @@
-
+
diff --git a/src/UniGetUI.Avalonia/Views/SidebarView.axaml.cs b/src/UniGetUI.Avalonia/Views/SidebarView.axaml.cs
index 5bdf240b8e..7a1878bad6 100644
--- a/src/UniGetUI.Avalonia/Views/SidebarView.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/SidebarView.axaml.cs
@@ -13,6 +13,7 @@ namespace UniGetUI.Avalonia.Views;
public partial class SidebarView : BaseView
{
private bool _lastNavItemSelectionWasAuto;
+ private bool _isMoreFlyoutOpen;
///
/// Whether the nav item text labels are shown. False renders an icon-only rail; true renders the
@@ -31,6 +32,15 @@ public bool ShowLabels
public SidebarView()
{
InitializeComponent();
+ if (FlyoutBase.GetAttachedFlyout(MoreNavBtn) is { } moreFlyout)
+ {
+ moreFlyout.Opened += (_, _) => _isMoreFlyoutOpen = true;
+ moreFlyout.Closed += (_, _) =>
+ {
+ _isMoreFlyoutOpen = false;
+ SyncListBoxSelection(ViewModel?.SelectedPageType ?? PageType.Null);
+ };
+ }
}
protected override void OnDataContextChanged(EventArgs e)
@@ -50,6 +60,9 @@ protected override void OnDataContextChanged(EventArgs e)
private void SyncListBoxSelection(PageType page)
{
+ if (_isMoreFlyoutOpen)
+ return;
+
// Selection lives in two ListBoxes (main + footer); only one may hold a selection at a time.
_lastNavItemSelectionWasAuto = true;
NavListBox.SelectedItem = page switch
@@ -82,8 +95,8 @@ private void HandleNavSelectionChanged(object? selectedItem)
if (tag == "More")
{
- // Not a page: re-sync selection so "More" doesn't stay highlighted, then open its menu.
- SyncListBoxSelection(ViewModel?.SelectedPageType ?? PageType.Null);
+ // Keep the item selected until the menu closes so its accent pill remains anchored.
+ _isMoreFlyoutOpen = true;
FlyoutBase.ShowAttachedFlyout(item);
return;
}
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
index 849b4a0b68..01aba0051d 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
@@ -29,6 +29,7 @@
+ The accent pill is a single moving overlay owned by SidebarView. The content presenter is
+ named ContentHost (not PART_ContentPresenter) so the base theme's accent-fill setters don't
+ apply. -->
@@ -291,10 +280,6 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -104,6 +123,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 0ece76f53c46eeaef957092932bb25b6b2212f41 Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Tue, 28 Jul 2026 16:35:09 +0000
Subject: [PATCH 14/26] revert tray dual-line experiment
---
src/UniGetUI.Avalonia/Infrastructure/TrayService.cs | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
index 1d8c1152ee..c1311c2f12 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/TrayService.cs
@@ -135,12 +135,8 @@ private static NativeMenu BuildMenu(MainWindow owner)
menu.Add(installed);
menu.Add(new NativeMenuItemSeparator());
- menu.Add(new NativeMenuItem(CoreTools.Translate("UniGetUI by Devolutions"))
- {
- IsEnabled = false,
- });
menu.Add(new NativeMenuItem(
- $"{CoreTools.Translate("Version")} {CoreData.VersionName}")
+ CoreTools.Translate("UniGetUI Version {0} by Devolutions", CoreData.VersionName))
{
IsEnabled = false,
});
From 7ff836e1869311f2064a9cce7768d620e7099ebd Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Tue, 28 Jul 2026 16:38:13 +0000
Subject: [PATCH 15/26] switch close glyph to path-rendering for thickness
consistency
---
src/UniGetUI.Avalonia/Views/MainWindow.axaml | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
index 119206f204..24c33d5a73 100644
--- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
@@ -688,10 +688,15 @@
automation:AutomationProperties.Name="{t:Translate Close}"
ToolTip.Tip="{t:Translate Close}"
Click="CloseButton_Click">
-
+
From 62a525b435a1bc22ede2cf410395fe2274d6896e Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Tue, 28 Jul 2026 16:42:55 +0000
Subject: [PATCH 16/26] update package-manager status indicators to add icons
for parity with previous UI
---
.../SettingsPages/ManagersHomepage.axaml.cs | 67 ++++++++++++++++---
1 file changed, 57 insertions(+), 10 deletions(-)
diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
index 5e903b8d23..b99e443dbf 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
using Avalonia.Layout;
using Avalonia.Media;
using UniGetUI.Avalonia.Infrastructure;
@@ -10,6 +11,7 @@
using UniGetUI.PackageEngine;
using UniGetUI.PackageEngine.Interfaces;
using CoreSettings = UniGetUI.Core.SettingsEngine.Settings;
+using AvaloniaPath = Avalonia.Controls.Shapes.Path;
namespace UniGetUI.Avalonia.Views.Pages.SettingsPages;
@@ -22,7 +24,8 @@ public event EventHandler? RestartRequired { add { } remove { } }
public event EventHandler? NavigationRequested { add { } remove { } }
public event EventHandler? ManagerNavigationRequested;
- private readonly List<(ToggleSwitch Toggle, IPackageManager Manager, Border Badge, TextBlock BadgeText)> _rows = [];
+ private readonly List<(ToggleSwitch Toggle, IPackageManager Manager, Border Badge,
+ Ellipse BadgeIcon, AvaloniaPath BadgeGlyph, TextBlock BadgeText)> _rows = [];
private bool _isLoadingToggles;
public ManagersHomepage()
@@ -49,13 +52,46 @@ public ManagersHomepage()
FontSize = 12,
FontWeight = FontWeight.SemiBold,
VerticalAlignment = VerticalAlignment.Center,
+ Foreground = LookupBrush("TextFillColorPrimaryBrush"),
};
AutomationProperties.SetAccessibilityView(badgeText, AccessibilityView.Raw);
+
+ var badgeIcon = new Ellipse
+ {
+ Width = 12,
+ Height = 12,
+ };
+ var badgeGlyph = new AvaloniaPath
+ {
+ Width = 7,
+ Height = 7,
+ StrokeThickness = 1.5,
+ StrokeLineCap = PenLineCap.Round,
+ StrokeJoin = PenLineJoin.Round,
+ Stretch = Stretch.Uniform,
+ };
+ var iconHost = new Grid
+ {
+ Width = 12,
+ Height = 12,
+ VerticalAlignment = VerticalAlignment.Center,
+ Children = { badgeIcon, badgeGlyph },
+ };
+ AutomationProperties.SetAccessibilityView(iconHost, AccessibilityView.Raw);
+
+ var badgeContent = new StackPanel
+ {
+ Orientation = Orientation.Horizontal,
+ Spacing = 5,
+ VerticalAlignment = VerticalAlignment.Center,
+ Children = { iconHost, badgeText },
+ };
var badge = new Border
{
CornerRadius = new CornerRadius(4),
Padding = new Thickness(6, 3, 6, 3),
- Child = badgeText,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ Child = badgeContent,
};
AutomationProperties.SetAccessibilityView(badge, AccessibilityView.Raw);
@@ -72,14 +108,14 @@ public ManagersHomepage()
_isLoadingToggles = true;
toggle.IsChecked = manager.IsEnabled();
_isLoadingToggles = false;
- ApplyStatusBadge(manager, toggle, badge, badgeText);
+ ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
};
toggle.IsCheckedChanged += async (_, _) =>
{
if (_isLoadingToggles) return;
CoreSettings.SetDictionaryItem(CoreSettings.K.DisabledManagers, manager.Name, toggle.IsChecked != true);
await Task.Run(manager.Initialize);
- ApplyStatusBadge(manager, toggle, badge, badgeText);
+ ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
AccessibilityAnnouncementService.AnnounceToggle(manager.DisplayName, toggle.IsChecked == true);
};
@@ -108,7 +144,7 @@ public ManagersHomepage()
btn.Click += (_, _) => ManagerNavigationRequested?.Invoke(this, capturedManager);
ManagersPanel.Children.Add(btn);
- _rows.Add((toggle, manager, badge, badgeText));
+ _rows.Add((toggle, manager, badge, badgeIcon, badgeGlyph, badgeText));
}
}
@@ -116,15 +152,21 @@ public ManagersHomepage()
public void RefreshToggles()
{
_isLoadingToggles = true;
- foreach (var (toggle, manager, badge, badgeText) in _rows)
+ foreach (var (toggle, manager, badge, badgeIcon, badgeGlyph, badgeText) in _rows)
{
toggle.IsChecked = manager.IsEnabled();
- ApplyStatusBadge(manager, toggle, badge, badgeText);
+ ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
}
_isLoadingToggles = false;
}
- private void ApplyStatusBadge(IPackageManager manager, ToggleSwitch toggle, Border badge, TextBlock text)
+ private void ApplyStatusBadge(
+ IPackageManager manager,
+ ToggleSwitch toggle,
+ Border badge,
+ Ellipse icon,
+ AvaloniaPath glyph,
+ TextBlock text)
{
string bgKey, fgKey, label;
if (!manager.IsEnabled())
@@ -132,21 +174,26 @@ private void ApplyStatusBadge(IPackageManager manager, ToggleSwitch toggle, Bord
bgKey = "WarningBannerBackground";
fgKey = "StatusWarningForeground";
label = CoreTools.Translate("Disabled");
+ glyph.Data = Geometry.Parse("M3.5,1 V4.2 M3.5,6 V6.1");
}
else if (manager.Status.Found)
{
bgKey = "StatusSuccessBackground";
fgKey = "StatusSuccessForeground";
label = CoreTools.Translate("Ready");
+ glyph.Data = Geometry.Parse("M1,3.6 L2.8,5.4 L6,1.5");
}
else
{
bgKey = "StatusErrorBackground";
fgKey = "StatusErrorForeground";
label = CoreTools.Translate("Not found");
+ glyph.Data = Geometry.Parse("M1.4,1.4 L5.6,5.6 M5.6,1.4 L1.4,5.6");
}
- badge.Background = LookupBrush(bgKey);
- text.Foreground = LookupBrush(fgKey);
+ IBrush background = LookupBrush(bgKey);
+ badge.Background = background;
+ icon.Fill = LookupBrush(fgKey);
+ glyph.Stroke = background;
text.Text = label;
// Bake state into Name so VoiceOver always announces it on macOS
AutomationProperties.SetName(toggle, $"{manager.DisplayName}, {label}");
From 5d8786d9f79ab6eee6c2867ee339d49d2c392178 Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Tue, 28 Jul 2026 16:50:34 +0000
Subject: [PATCH 17/26] combobox styling update
---
.../Assets/Styles/Styles.Common.axaml | 18 ++++++++++++++++++
.../SoftwarePages/AbstractPackagesPage.axaml | 4 ++--
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
index 3d9efba61e..fddb4dbd05 100644
--- a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
@@ -67,6 +67,15 @@
+
+
+
+
+
+
+
+
+
@@ -176,6 +185,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
index 40351d06a5..691cd1f874 100644
--- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
+++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml
@@ -922,9 +922,9 @@