Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,6 @@ private static void ShowOperationSuccessNotification(AbstractOperation op)

if (OperatingSystem.IsMacOS() && MacOsNotificationBridge.ShowSuccess(op))
return;

if (TryGetMainWindow() is not { } mainWindow)
return;

mainWindow.ShowRuntimeNotification(
title,
message,
UniGetUI.Avalonia.Views.MainWindow.RuntimeNotificationLevel.Success);
}

private static void ShowOperationFailureNotification(AbstractOperation op)
Expand Down
16 changes: 11 additions & 5 deletions src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,27 @@ partial void OnBundlesBadgeVisibleChanged(bool value)
private bool _installedIsLoading;

// ─── Pane open/closed ─────────────────────────────────────────────────────
// Starts collapsed: the pane is a floating overlay in every size class (see MainWindow's
// adaptive logic), so there is no persistent open-on-launch state.
[ObservableProperty]
private bool isPaneOpen = !Settings.Get(Settings.K.CollapseNavMenuOnWideScreen);
private bool isPaneOpen;

// Only persist the open/closed choice as the "collapse on wide screen" preference
// while the pane is inline (Expanded mode). In the overlay modes used on smaller
// windows, opening/closing is transient and must not overwrite the saved preference.
// The MainWindow's adaptive logic keeps this in sync with the SplitView display mode.
public bool PersistPaneCollapsePreference { get; set; } = true;

partial void OnIsPaneOpenChanged(bool value)
{
Settings.Set(Settings.K.CollapseNavMenuOnWideScreen, !value);
OnPropertyChanged(nameof(PaneWidth));
if (PersistPaneCollapsePreference)
Settings.Set(Settings.K.CollapseNavMenuOnWideScreen, !value);
OnPropertyChanged(nameof(UpdatesBadgeExpandedVisible));
OnPropertyChanged(nameof(UpdatesBadgeCompactVisible));
OnPropertyChanged(nameof(BundlesBadgeExpandedVisible));
OnPropertyChanged(nameof(BundlesBadgeCompactVisible));
}

public double PaneWidth => IsPaneOpen ? 250 : 64;

public bool UpdatesBadgeExpandedVisible => UpdatesBadgeVisible && IsPaneOpen;
public bool UpdatesBadgeCompactVisible => UpdatesBadgeVisible && !IsPaneOpen;
public bool BundlesBadgeExpandedVisible => BundlesBadgeVisible && IsPaneOpen;
Expand Down
227 changes: 177 additions & 50 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml

Large diffs are not rendered by default.

37 changes: 30 additions & 7 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@ public MainWindow()
DataContext = new MainWindowViewModel();
InitializeComponent();
SetupTitleBar();
SetupResponsiveRail();

RestoreGeometry();

KeyDown += Window_KeyDown;
ViewModel.CurrentPageChanged += OnCurrentPageChanged;
// Title-bar back button: visible whenever there's somewhere to go back to (mirrors WinUI's TitleBar.IsBackButtonVisible).
ViewModel.CanGoBackChanged += (_, canGoBack) => BackButton.IsVisible = canGoBack;
ViewModel.PropertyChanged += OnViewModelPropertyChanged;
UpdateOperationsPanelRow();

Expand Down Expand Up @@ -216,6 +219,9 @@ private void Window_KeyDown(object? sender, KeyEventArgs e)

private void OnCurrentPageChanged(object? sender, PageType pageType)
{
// Like WinUI's NavigationView: picking a page collapses the sliding flyout back to the rail.
ViewModel.Sidebar.IsPaneOpen = false;

if (!_focusSidebarSelectionOnNextPageChange)
return;

Expand All @@ -227,6 +233,8 @@ private void OnCurrentPageChanged(object? sender, PageType pageType)
}, DispatcherPriority.Background);
}

private void BackButton_Click(object? sender, RoutedEventArgs e) => ViewModel.NavigateBack();

private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(MainWindowViewModel.OperationsPanelExpanded)
Expand All @@ -240,10 +248,10 @@ or nameof(MainWindowViewModel.OperationsPanelVisible))
// user's chosen size across collapse/expand.
private void UpdateOperationsPanelRow()
{
if (MainContentGrid.RowDefinitions.Count < 3)
if (ContentRoot.RowDefinitions.Count < 3)
return;

RowDefinition row = MainContentGrid.RowDefinitions[2];
RowDefinition row = ContentRoot.RowDefinitions[2];
if (ViewModel.OperationsPanelVisible && ViewModel.OperationsPanelExpanded)
{
row.MinHeight = 80;
Expand All @@ -258,6 +266,21 @@ private void UpdateOperationsPanelRow()
}
}

// ─── Navigation rail (responsive) ─────────────────────────────────────────
// The always-visible icon rail shows on roomy windows and collapses below 800px so narrow
// windows give the content full width (the hamburger + sliding flyout still provide nav).
private void SetupResponsiveRail()
=> MainContentRoot.GetObservable(BoundsProperty)
.Subscribe(b =>
{
if (b.Width <= 0) return;
NavRail.IsVisible = b.Width >= 800;
});

// Light-dismiss: clicking outside the open flyout closes it (no darkening — the layer is transparent).
private void FlyoutDismiss_PointerPressed(object? sender, PointerPressedEventArgs e)
=> ViewModel.Sidebar.IsPaneOpen = false;

private void SetupTitleBar()
{
if (OperatingSystem.IsMacOS())
Expand All @@ -278,8 +301,8 @@ private void SetupTitleBar()
{
TitleBarGrid.ClearValue(HeightProperty);
TitleBarGrid.Height = 44;
MainContentGrid.ClearValue(MarginProperty);
MainContentGrid.Margin = new Thickness(0, 44, 0, 0);
MainContentRoot.ClearValue(MarginProperty);
MainContentRoot.Margin = new Thickness(0, 44, 0, 0);
HamburgerPanel.Margin = new Thickness(10, 0, 8, 0);
}
else
Expand All @@ -288,7 +311,7 @@ private void SetupTitleBar()
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) },
});
MainContentGrid.Bind(MarginProperty, new Binding("WindowDecorationMargin")
MainContentRoot.Bind(MarginProperty, new Binding("WindowDecorationMargin")
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(Window) },
});
Expand All @@ -311,7 +334,7 @@ private void SetupTitleBar()
TitleBarGrid.Height = 44;
HamburgerPanel.Margin = new Thickness(10, 0, 8, 0);
WindowButtons.IsVisible = true;
MainContentGrid.Margin = new Thickness(0, 44, 0, 0);
MainContentRoot.Margin = new Thickness(0, 44, 0, 0);
this.GetObservable(WindowStateProperty).Subscribe(state =>
{
UpdateMaximizeButtonState(state == WindowState.Maximized);
Expand All @@ -328,7 +351,7 @@ private void SetupTitleBar()
TitleBarGrid.Height = 44;
HamburgerPanel.Margin = new Thickness(10, 0, 8, 0);
WindowButtons.IsVisible = !useNativeDecorations;
MainContentGrid.Margin = new Thickness(0, 44, 0, 0);
MainContentRoot.Margin = new Thickness(0, 44, 0, 0);
// Keep maximize icon in sync with window state
this.GetObservable(WindowStateProperty).Subscribe(state =>
{
Expand Down
63 changes: 25 additions & 38 deletions src/UniGetUI.Avalonia/Views/SidebarView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
xmlns:automation="clr-namespace:Avalonia.Automation;assembly=Avalonia.Controls"
xmlns:vm="using:UniGetUI.Avalonia.ViewModels"
xmlns:controls="using:UniGetUI.Avalonia.Views.Controls"
xmlns:local="using:UniGetUI.Avalonia.Views"
xmlns:t="using:UniGetUI.Avalonia.MarkupExtensions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="SidebarRoot"
x:DataType="vm:SidebarViewModel"
MinWidth="64">

Expand Down Expand Up @@ -36,13 +38,16 @@
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
<ProgressBar IsIndeterminate="True"
IsVisible="{Binding DiscoverIsLoading}"
Height="2"
Width="32"
MinWidth="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="8,0"/>
Margin="4,0,0,2"/>
</Panel>
</ListBoxItem>

Expand All @@ -58,26 +63,15 @@
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
<!-- Updates badge (expanded) -->
<Border Background="{DynamicResource SystemAccentColor}"
CornerRadius="10"
Padding="6,1"
IsVisible="{Binding UpdatesBadgeExpandedVisible}"
VerticalAlignment="Center">
<TextBlock Text="{Binding UpdatesBadgeCount}"
FontSize="11"
FontWeight="Bold"
Foreground="White"/>
</Border>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
<!-- Updates badge (compact overlay) -->
<!-- Update count bubble (InfoBadge-style), anchored to the icon's top-right in both rail and flyout -->
<Border Background="{DynamicResource SystemAccentColor}"
CornerRadius="8"
Padding="3,1"
MinWidth="16"
Height="16"
IsVisible="{Binding UpdatesBadgeCompactVisible}"
IsVisible="{Binding UpdatesBadgeVisible}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="26,3,0,0">
Expand All @@ -91,8 +85,11 @@
<ProgressBar IsIndeterminate="True"
IsVisible="{Binding UpdatesIsLoading}"
Height="2"
Width="32"
MinWidth="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="8,0"/>
Margin="4,0,0,2"/>
</Panel>
</ListBoxItem>

Expand All @@ -108,13 +105,16 @@
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
<ProgressBar IsIndeterminate="True"
IsVisible="{Binding InstalledIsLoading}"
Height="2"
Width="32"
MinWidth="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="8,0"/>
Margin="4,0,0,2"/>
</Panel>
</ListBoxItem>

Expand All @@ -130,27 +130,14 @@
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
<!-- Unsaved changes badge (expanded) -->
<Border Background="{DynamicResource SystemFillColorCautionBrush}"
CornerRadius="10"
Width="16"
Height="16"
IsVisible="{Binding BundlesBadgeExpandedVisible}"
VerticalAlignment="Center">
<TextBlock Text="!"
FontSize="10"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
<!-- Unsaved changes badge (compact overlay dot) -->
<!-- Unsaved changes dot, anchored to the icon's top-right in both rail and flyout -->
<Border Background="{DynamicResource SystemFillColorCautionBrush}"
CornerRadius="5"
Width="10"
Height="10"
IsVisible="{Binding BundlesBadgeCompactVisible}"
IsVisible="{Binding BundlesBadgeVisible}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="28,5,0,0"/>
Expand All @@ -177,7 +164,7 @@
<StackPanel Orientation="Horizontal" Spacing="12">
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/settings.svg" Width="24" Height="24" VerticalAlignment="Center"/>
<TextBlock Text="{t:Translate Settings}" FontSize="16" VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
</Button>

Expand All @@ -195,7 +182,7 @@
<StackPanel Orientation="Horizontal" Spacing="12">
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/clipboard_list.svg" Width="24" Height="24" VerticalAlignment="Center"/>
<TextBlock Text="{t:Translate Package Managers}" FontSize="16" VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
</Button>

Expand All @@ -211,7 +198,7 @@
<TextBlock Text="···" FontSize="20" Width="24" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"
automation:AutomationProperties.AccessibilityView="Raw"/>
<TextBlock Text="{t:Translate More}" FontSize="16" VerticalAlignment="Center"
IsVisible="{Binding IsPaneOpen}"/>
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
</StackPanel>
<Button.Flyout>
<MenuFlyout Placement="RightEdgeAlignedTop">
Expand Down
15 changes: 15 additions & 0 deletions src/UniGetUI.Avalonia/Views/SidebarView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using UniGetUI.Avalonia.ViewModels;
Expand All @@ -8,6 +9,20 @@ public partial class SidebarView : BaseView<SidebarViewModel>
{
private bool _lastNavItemSelectionWasAuto;

/// <summary>
/// Whether the nav item text labels are shown. False renders an icon-only rail; true renders the
/// full labeled pane. Decoupled from the view-model's pane state so the same view can be used both
/// as the always-visible rail and as the sliding flyout simultaneously.
/// </summary>
public static readonly StyledProperty<bool> ShowLabelsProperty =
AvaloniaProperty.Register<SidebarView, bool>(nameof(ShowLabels), defaultValue: true);

public bool ShowLabels
{
get => GetValue(ShowLabelsProperty);
set => SetValue(ShowLabelsProperty, value);
}

public SidebarView()
{
InitializeComponent();
Expand Down
Loading
Loading