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
4 changes: 4 additions & 0 deletions src/Languages/lang_en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"Navigation menu:": "Navigation menu:",
"Automatic": "Automatic",
"Docked open": "Docked open",
"Sliding overlay": "Sliding overlay",
"{0} of {1} operations completed": "{0} of {1} operations completed",
"{0} is now {1}": "{0} is now {1}",
"Enabled": "Enabled",
Expand Down
99 changes: 70 additions & 29 deletions src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,6 @@ public partial class SidebarViewModel : ViewModelBase
partial void OnUpdatesBadgeCountChanged(int value) =>
UpdatesBadgeVisible = value > 0;

partial void OnUpdatesBadgeVisibleChanged(bool value)
{
OnPropertyChanged(nameof(UpdatesBadgeExpandedVisible));
OnPropertyChanged(nameof(UpdatesBadgeCompactVisible));
}

partial void OnBundlesBadgeVisibleChanged(bool value)
{
OnPropertyChanged(nameof(BundlesBadgeExpandedVisible));
OnPropertyChanged(nameof(BundlesBadgeCompactVisible));
}

// ─── Loading indicators ───────────────────────────────────────────────────
[ObservableProperty]
private bool _discoverIsLoading;
Expand All @@ -47,31 +35,84 @@ 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.
// NavMenuMode: Automatic mirrors WinUI (dock ≥1600px, else rail+overlay), Docked = always
// inline, Overlay = always rail+overlay. IsPaneOpen = labeled pane showing, in any mode.
public enum NavMode { Automatic, Docked, Overlay }

private const double ExpandedThreshold = 1600; // WinUI ExpandedModeThresholdWidth
private const double CompactThreshold = 800; // WinUI CompactModeThresholdWidth

// Live window width, pushed by MainWindow's bounds observer.
[ObservableProperty]
private bool isPaneOpen;
private double _windowWidth = 1450;

[ObservableProperty]
private NavMode _mode = ParseMode(Settings.GetValue(Settings.K.NavMenuMode));

[ObservableProperty]
private bool _isPaneOpen;

public static NavMode ParseMode(string value) => value switch
{
"docked" => NavMode.Docked,
"overlay" => NavMode.Overlay,
_ => NavMode.Automatic,
};

// Whether the pane docks inline (vs. the rail + sliding overlay).
public bool Docked => Mode switch
{
NavMode.Docked => true,
NavMode.Overlay => false,
_ => WindowWidth >= ExpandedThreshold,
};

private bool RailAllowed => WindowWidth >= CompactThreshold;

// Icon rail vs docked labeled pane vs sliding overlay — mutually exclusive.
public bool NavDockVisible => Docked && IsPaneOpen;
public bool RailVisible => (Docked && !IsPaneOpen) || (!Docked && RailAllowed);
public bool OverlayActive => !Docked && IsPaneOpen;

private bool _wasDocked;

public SidebarViewModel()
{
_wasDocked = Docked;
_isPaneOpen = Docked && !Settings.Get(Settings.K.CollapseNavMenuOnWideScreen);
}

partial void OnWindowWidthChanged(double value) => ReconcileDock();

// 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 OnModeChanged(NavMode value) => ReconcileDock();

// On a docked-state flip, open the pane by default (unless collapsed on a wide screen), or
// collapse when leaving docked mode. Mirrors WinUI's startup IsPaneOpen logic.
private void ReconcileDock()
{
bool docked = Docked;
if (docked != _wasDocked)
{
_wasDocked = docked;
IsPaneOpen = docked && !Settings.Get(Settings.K.CollapseNavMenuOnWideScreen);
}
RaiseLayoutChanged();
}

partial void OnIsPaneOpenChanged(bool value)
{
if (PersistPaneCollapsePreference)
// Persist the collapse choice only while docked (WinUI persisted only at ≥1600px).
if (Docked)
Settings.Set(Settings.K.CollapseNavMenuOnWideScreen, !value);
OnPropertyChanged(nameof(UpdatesBadgeExpandedVisible));
OnPropertyChanged(nameof(UpdatesBadgeCompactVisible));
OnPropertyChanged(nameof(BundlesBadgeExpandedVisible));
OnPropertyChanged(nameof(BundlesBadgeCompactVisible));
RaiseLayoutChanged();
}

public bool UpdatesBadgeExpandedVisible => UpdatesBadgeVisible && IsPaneOpen;
public bool UpdatesBadgeCompactVisible => UpdatesBadgeVisible && !IsPaneOpen;
public bool BundlesBadgeExpandedVisible => BundlesBadgeVisible && IsPaneOpen;
public bool BundlesBadgeCompactVisible => BundlesBadgeVisible && !IsPaneOpen;
private void RaiseLayoutChanged()
{
OnPropertyChanged(nameof(NavDockVisible));
OnPropertyChanged(nameof(RailVisible));
OnPropertyChanged(nameof(OverlayActive));
}

// ─── Selected page ────────────────────────────────────────────────────────
[ObservableProperty]
Expand Down
30 changes: 26 additions & 4 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@
Background="{DynamicResource MicaPageBackground}"
ColumnDefinitions="Auto,*">

<!-- ── Icon rail (always visible ≥800px; collapsed below) ── -->
<!-- ── Icon rail (compact mode ≥800px, or docked-but-collapsed) ── -->
<ContentControl x:Name="NavRail"
Grid.Column="0"
Width="64"
ClipToBounds="True"
IsVisible="{Binding Sidebar.RailVisible}"
Content="{Binding Sidebar}"
automation:AutomationProperties.AccessibilityView="Control"
automation:AutomationProperties.Name="{t:Translate Navigation panel}"
Expand All @@ -79,6 +80,27 @@
</ContentControl.DataTemplates>
</ContentControl>

<!-- ── Docked labeled pane (Expanded ≥1600px, or NavMenuMode=Docked): sits inline, reflows content. ── -->
<ContentControl x:Name="NavDock"
Grid.Column="0"
Width="250"
ClipToBounds="True"
IsVisible="{Binding Sidebar.NavDockVisible}"
BorderBrush="{DynamicResource AppBorderBrush}"
BorderThickness="0,0,1,0"
Content="{Binding Sidebar}"
automation:AutomationProperties.AccessibilityView="Control"
automation:AutomationProperties.Name="{t:Translate Navigation panel}"
automation:AutomationProperties.LandmarkType="{x:Static peers:AutomationLandmarkType.Navigation}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:SidebarViewModel">
<views:SidebarView ShowLabels="True"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>

<!-- ── Content: banners + page + operations ── -->
<Grid x:Name="ContentRoot" Grid.Column="1" RowDefinitions="*,4,Auto">

Expand Down Expand Up @@ -338,7 +360,7 @@
Grid.Column="0" Grid.ColumnSpan="2"
Background="Transparent"
ZIndex="9"
IsVisible="{Binding Sidebar.IsPaneOpen}"
IsVisible="{Binding Sidebar.OverlayActive}"
automation:AutomationProperties.AccessibilityView="Raw"
PointerPressed="FlyoutDismiss_PointerPressed"/>

Expand All @@ -350,8 +372,8 @@
VerticalAlignment="Stretch"
ZIndex="10"
Background="{DynamicResource AppWindowBackground}"
IsHitTestVisible="{Binding Sidebar.IsPaneOpen}"
Classes.open="{Binding Sidebar.IsPaneOpen}">
IsHitTestVisible="{Binding Sidebar.OverlayActive}"
Classes.open="{Binding Sidebar.OverlayActive}">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.18">
Expand Down
16 changes: 11 additions & 5 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,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;
// A docked pane stays put (it's a persistent inline pane, not a transient overlay).
if (!ViewModel.Sidebar.Docked)
ViewModel.Sidebar.IsPaneOpen = false;

if (!_focusSidebarSelectionOnNextPageChange)
return;
Expand Down Expand Up @@ -305,17 +307,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).
// ─── Navigation rail / docked pane (responsive) ────────────────────────────
// Feed the live window width to the sidebar, which resolves the layout mode and drives
// the NavRail / NavDock / overlay visibility bindings.
private void SetupResponsiveRail()
=> MainContentRoot.GetObservable(BoundsProperty)
.SubscribeValue(b =>
{
if (b.Width <= 0) return;
NavRail.IsVisible = b.Width >= 800;
ViewModel.Sidebar.WindowWidth = b.Width;
});

// Re-reads the NavMenuMode setting so the layout switch applies live from the settings page.
public void RefreshNavigationMode()
=> ViewModel.Sidebar.Mode = SidebarViewModel.ParseMode(Settings.GetValue(Settings.K.NavMenuMode));

// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
CornerRadius="8,8,0,0"/>

<settings:ComboboxCard x:Name="StartupPageSelector"
CornerRadius="0,0,8,8"
CornerRadius="0"
BorderThickness="1,0,1,1"/>

<settings:ComboboxCard x:Name="NavMenuModeSelector"
CornerRadius="0,0,8,8"
BorderThickness="1,0,1,1"/>

<Border Height="16"/>

<settings:TranslatedTextBlock Text="Rendering"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,13 @@ public Interface_P()
StartupPageSelector.SettingName = CoreSettings.K.StartupPage;
StartupPageSelector.Text = CoreTools.Translate("UniGetUI startup page:");
StartupPageSelector.ShowAddedItems();

NavMenuModeSelector.AddItem(CoreTools.Translate("Automatic"), "auto");
NavMenuModeSelector.AddItem(CoreTools.Translate("Docked open"), "docked");
NavMenuModeSelector.AddItem(CoreTools.Translate("Sliding overlay"), "overlay");
NavMenuModeSelector.SettingName = CoreSettings.K.NavMenuMode;
NavMenuModeSelector.Text = CoreTools.Translate("Navigation menu:");
NavMenuModeSelector.ShowAddedItems();
NavMenuModeSelector.ValueChanged += (_, _) => MainWindow.Instance?.RefreshNavigationMode();
}
}
2 changes: 2 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public enum K
AlreadyWarnedAboutChocolateyMigration,
ShownTelemetryBanner,
CollapseNavMenuOnWideScreen,
NavMenuMode,
EnablePackageBackup_LOCAL,
EnablePackageBackup_CLOUD,
ChangeBackupOutputDirectory,
Expand Down Expand Up @@ -147,6 +148,7 @@ public static string ResolveKey(K key)
K.AlreadyWarnedAboutChocolateyMigration => "AlreadyWarnedAboutChocolateyMigration",
K.ShownTelemetryBanner => "ShownTelemetryBanner",
K.CollapseNavMenuOnWideScreen => "CollapseNavMenuOnWideScreen",
K.NavMenuMode => "NavMenuMode",
K.EnablePackageBackup_LOCAL => "EnablePackageBackup",
K.EnablePackageBackup_CLOUD => "EnablePackageBackup_CLOUD",
K.ChangeBackupOutputDirectory => "ChangeBackupOutputDirectory",
Expand Down
Loading