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
5 changes: 4 additions & 1 deletion src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1974,5 +1974,8 @@
"Previous screenshot": "Previous screenshot",
"Next screenshot": "Next screenshot",
"Screenshot {0} of {1}": "Screenshot {0} of {1}",
"Screenshot {0} of {1}, current": "Screenshot {0} of {1}, current"
"Screenshot {0} of {1}, current": "Screenshot {0} of {1}, current",
"Navigation menu order": "Navigation menu order",
"Drag the pages in the navigation menu, or press {0} on a focused page, to reorder them. Keyboard shortcuts follow the same order.": "Drag the pages in the navigation menu, or press {0} on a focused page, to reorder them. Keyboard shortcuts follow the same order.",
"{0} moved to position {1} of {2}": "{0} moved to position {1} of {2}"
}
48 changes: 48 additions & 0 deletions src/UniGetUI.Avalonia/Infrastructure/NavigationPageOrder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using UniGetUI.Avalonia.Views;
using UniGetUI.Core.SettingsEngine;

namespace UniGetUI.Avalonia.Infrastructure;

internal static class NavigationPageOrder
{
public static PageType[] DefaultPages(bool catalogEnabled) => catalogEnabled
? [PageType.Catalog, PageType.Discover, PageType.Updates, PageType.Installed, PageType.Bundles, PageType.CatalogEditor]
: [PageType.Discover, PageType.Updates, PageType.Installed, PageType.Bundles];

public static PageType[] Get(bool catalogEnabled)
=> Resolve(Settings.GetValue(Settings.K.NavigationPageOrder), catalogEnabled);

public static void Save(IReadOnlyList<PageType> visiblePages)
=> Settings.SetValue(
Settings.K.NavigationPageOrder,
Serialize(Merge(Get(catalogEnabled: true), visiblePages)));

public static void Reset() => Settings.SetValue(Settings.K.NavigationPageOrder, "");

internal static PageType[] Resolve(string stored, bool catalogEnabled)
{
PageType[] defaults = DefaultPages(catalogEnabled);
PageType[] ordered = stored
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
.Select(name => Enum.TryParse(name, out PageType page) ? page : PageType.Null)
.Where(defaults.Contains)
.Distinct()
.ToArray();
return [.. ordered, .. defaults.Where(page => !ordered.Contains(page))];
}

internal static PageType[] Merge(PageType[] fullOrder, IReadOnlyList<PageType> visiblePages)
{
PageType[] merged = [.. fullOrder];
int next = 0;
for (int i = 0; i < merged.Length && next < visiblePages.Count; i++)
{
if (visiblePages.Contains(merged[i]))
merged[i] = visiblePages[next++];
}
return merged;
}

internal static string Serialize(PageType[] fullOrder)
=> fullOrder.SequenceEqual(DefaultPages(catalogEnabled: true)) ? "" : string.Join(',', fullOrder);
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private sealed record Entry(
new("Application theme:", ["appearance", "theme", "dark", "light", "mica"], typeof(Interface_P), "ThemeSelector"),
new("UniGetUI startup page:", ["startup page", "default page", "home page", "landing page"], typeof(Interface_P), "StartupPageSelector"),
new("Navigation menu:", ["sidebar", "nav menu", "navigation", "layout", "docked", "overlay"], typeof(Interface_P), "NavMenuModeSelector"),
new("Navigation menu order", ["sidebar", "nav menu", "navigation", "order", "reorder", "drag", "tabs", "pages", "shortcuts"], typeof(Interface_P), "NavigationOrderCard"),
new("Automatically switch to software rendering when Windows has no hardware GPU", ["rendering", "gpu", "software rendering"], typeof(Interface_P), "InterfaceRenderingCard"),
new("Close UniGetUI to the system tray", ["system tray", "background", "minimize to tray"], typeof(Interface_P), "DisableSystemTrayCard"),
new("Manage UniGetUI autostart behaviour", ["autostart", "run at login", "startup"], typeof(Interface_P), "EditAutostartSettings"),
Expand Down
16 changes: 6 additions & 10 deletions src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,16 @@
private void ToggleOperationsPanel() => OperationsPanelExpanded = !OperationsPanelExpanded;

[RelayCommand]
private void RetryFailedOperations() => AvaloniaOperationRegistry.RetryFailed();

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearSuccessfulOperations() => AvaloniaOperationRegistry.ClearSuccessful();

Check warning on line 171 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearFinishedOperations() => AvaloniaOperationRegistry.ClearFinished();

Check warning on line 174 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void CancelAllOperations() => AvaloniaOperationRegistry.CancelAll();

Check warning on line 177 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

// ─── Sidebar ─────────────────────────────────────────────────────────────
public SidebarViewModel Sidebar { get; } = new();
Expand Down Expand Up @@ -696,22 +696,18 @@
/// The navigation pages in sidebar order: Ctrl/Cmd+1, +2, … follow it (the More menu takes the
/// next number), and so does the previous/next page cycle.
/// </summary>
internal static PageType[] GetShortcutPages(bool catalogEnabled) => catalogEnabled
? [PageType.Catalog, PageType.Discover, PageType.Updates, PageType.Installed, PageType.Bundles,
PageType.CatalogEditor, PageType.Settings, PageType.Managers]
: [PageType.Discover, PageType.Updates, PageType.Installed, PageType.Bundles,
PageType.Settings, PageType.Managers];
internal static PageType[] GetShortcutPages(IEnumerable<PageType> navigationPages)
=> [.. navigationPages, PageType.Settings, PageType.Managers];

public PageType[] ShortcutPages => GetShortcutPages(Sidebar.SoftwareCatalogEnabled);
public PageType[] ShortcutPages => GetShortcutPages(Sidebar.NavigationPages);

internal static PageType GetNextPage(PageType type, bool catalogEnabled) => Step(type, catalogEnabled, +1);
internal static PageType GetNextPage(PageType type, PageType[] pages) => Step(type, pages, +1);

internal static PageType GetPreviousPage(PageType type, bool catalogEnabled) => Step(type, catalogEnabled, -1);
internal static PageType GetPreviousPage(PageType type, PageType[] pages) => Step(type, pages, -1);

// Pages outside the sidebar order (logs, help, …) step to Discover.
private static PageType Step(PageType type, bool catalogEnabled, int direction)
private static PageType Step(PageType type, PageType[] pages, int direction)
{
var pages = GetShortcutPages(catalogEnabled);
int index = Array.IndexOf(pages, type);
return index < 0 ? PageType.Discover : pages[(index + direction + pages.Length) % pages.Length];
}
Expand Down
16 changes: 16 additions & 0 deletions src/UniGetUI.Avalonia/ViewModels/SidebarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ public partial class SidebarViewModel : ViewModelBase
public bool HasUsableCatalog { get; } =
Settings.Get(Settings.K.EnableSoftwareCatalog) && SoftwareCatalog.HasUsableCatalog();

[ObservableProperty]
private PageType[] _navigationPages;

public void ReorderNavigationPages(IReadOnlyList<PageType> pages)
{
NavigationPageOrder.Save(pages);
NavigationPages = NavigationPageOrder.Get(SoftwareCatalogEnabled);
}

public void ResetNavigationPages()
{
NavigationPageOrder.Reset();
NavigationPages = NavigationPageOrder.Get(SoftwareCatalogEnabled);
}

// ─── Badge properties ─────────────────────────────────────────────────────
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(UpdatesBadgeText))]
Expand Down Expand Up @@ -89,6 +104,7 @@ public enum NavMode { Automatic, Docked, Overlay }

public SidebarViewModel()
{
_navigationPages = NavigationPageOrder.Get(SoftwareCatalogEnabled);
_wasDocked = Docked;
_isPaneOpen = Docked && !Settings.Get(Settings.K.CollapseNavMenuOnWideScreen);
}
Expand Down
5 changes: 3 additions & 2 deletions src/UniGetUI.Avalonia/Views/Controls/SvgIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Styling;

Expand Down Expand Up @@ -179,8 +180,8 @@ private static ParsedSvg ParseSvg(string uri)
return new ParsedSvg(geometries, viewBoxWidth, viewBoxHeight);
}

private static readonly IBrush _darkFg = new SolidColorBrush(Color.Parse("#E8E8E8"));
private static readonly IBrush _lightFg = new SolidColorBrush(Color.Parse("#1E1E1E"));
private static readonly IBrush _darkFg = new ImmutableSolidColorBrush(Color.Parse("#E8E8E8"));
private static readonly IBrush _lightFg = new ImmutableSolidColorBrush(Color.Parse("#1E1E1E"));

private static IBrush LookupThemeForeground() =>
Application.Current?.ActualThemeVariant == ThemeVariant.Dark
Expand Down
44 changes: 37 additions & 7 deletions src/UniGetUI.Avalonia/Views/MainWindow.MacMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Avalonia.Input;
using Avalonia.VisualTree;
using AvaloniaEdit;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.Core.Tools;

namespace UniGetUI.Avalonia.Views;
Expand All @@ -19,6 +20,9 @@ public partial class MainWindow
private TextBox? _menuTextBox;
private TextEditor? _menuEditor;

private NativeMenu? _macViewMenu;
private NativeMenuItem[] _macPageItems = [];

private void SetupMacMenuBar()
{
if (!OperatingSystem.IsMacOS())
Expand Down Expand Up @@ -60,14 +64,9 @@ private void SetupMacMenuBar()
new NativeMenuItemSeparator(),
Action(CoreTools.Translate("Find"), TriggerPageSearch, Key.F, enabled: () => PageShortcutListener is not null));

// Settings is reached from the application menu (⌘,), so the View menu skips it.
NativeMenuItemBase[] pageItems = ViewModel.ShortcutPages
.Select((page, index) => (page, key: Key.D1 + index))
.Where(p => p.page != PageType.Settings)
.Select(p => (NativeMenuItemBase)Item(PageMenuTitle(p.page), navigate, p.page.ToString(), p.key))
.ToArray();
_macPageItems = CreateMacPageItems();
var view = Submenu(CoreTools.Translate("View"), [
.. pageItems,
.. _macPageItems,
new NativeMenuItemSeparator(),
Item(CoreTools.Translate("Toggle navigation panel"), ViewModel.ToggleSidebarCommand, null, Key.S, KeyModifiers.Control),
new NativeMenuItemSeparator(),
Expand All @@ -90,10 +89,41 @@ private void SetupMacMenuBar()
Item(CoreTools.Translate("Package Manager logs"), navigate, "ManagerLog"),
Item(CoreTools.Translate("Operation history"), navigate, "OperationHistory"));

_macViewMenu = view.Menu;
sidebar.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(SidebarViewModel.NavigationPages))
RefreshMacViewMenu();
};
NativeMenu.SetMenu(this, new NativeMenu { Items = { edit, view, window, help } });
UpdateMacMenuState();
}

// Settings is reached from the application menu (⌘,), so the View menu skips it.
private NativeMenuItem[] CreateMacPageItems()
=> ViewModel.ShortcutPages
.Select((page, index) => (page, key: Key.D1 + index))
.Where(p => p.page != PageType.Settings)
.Select(p => Item(PageMenuTitle(p.page), ViewModel.Sidebar.RequestNavigationCommand, p.page.ToString(), p.key))
.ToArray();

private void RefreshMacViewMenu()
{
if (_macViewMenu is null)
return;

foreach (var item in _macPageItems)
{
_macViewMenu.Items.Remove(item);
_macMenuStates.RemoveAll(state => state.Item == item);
}

_macPageItems = CreateMacPageItems();
for (int i = 0; i < _macPageItems.Length; i++)
_macViewMenu.Items.Insert(i, _macPageItems[i]);
UpdateMacMenuState();
}

// States are computed as a menu shows. For ⌘ shortcuts a stale disabled item lets the key reach the
// focused control, and a click re-checks the live state, so the outcome is right either way.
private void RefreshOnDemand(NativeMenu menu)
Expand Down
6 changes: 4 additions & 2 deletions src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ private void Window_KeyDownTunnel(object? sender, KeyEventArgs e)

_focusSidebarSelectionOnNextPageChange = true;
ViewModel.NavigateTo(e.KeyModifiers.HasFlag(KeyModifiers.Shift)
? MainWindowViewModel.GetPreviousPage(ViewModel.CurrentPage_t, SoftwareCatalogEnabled)
: MainWindowViewModel.GetNextPage(ViewModel.CurrentPage_t, SoftwareCatalogEnabled));
? MainWindowViewModel.GetPreviousPage(ViewModel.CurrentPage_t, ViewModel.ShortcutPages)
: MainWindowViewModel.GetNextPage(ViewModel.CurrentPage_t, ViewModel.ShortcutPages));
e.Handled = true;
}

Expand Down Expand Up @@ -565,6 +565,8 @@ private void SetupResponsiveRail()
public void RefreshNavigationMode()
=> ViewModel.Sidebar.Mode = SidebarViewModel.ParseMode(Settings.GetValue(Settings.K.NavMenuMode));

public void ResetNavigationOrder() => ViewModel.Sidebar.ResetNavigationPages();

// Let the splitter hover surface replace the separator visually without changing its layout thickness.
private void OperationsSplitter_PointerEntered(object? sender, PointerEventArgs e)
=> OperationsPanelBorder.Classes.Set("splitter-active", true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@

<Border Height="16"/>

<settings:ButtonCard x:Name="NavigationOrderCard"
CornerRadius="8"
Text="{t:Translate Navigation menu order}"
ButtonText="{t:Translate Reset}"/>

<Border Height="16"/>

<settings:TranslatedTextBlock Text="Rendering"
FontSize="14"
FontWeight="SemiBold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,10 @@ public Interface_P()
NavMenuModeSelector.Text = CoreTools.Translate("Navigation menu:");
NavMenuModeSelector.ShowAddedItems();
NavMenuModeSelector.ValueChanged += (_, _) => MainWindow.Instance?.RefreshNavigationMode();

NavigationOrderCard.Description = CoreTools.Translate(
"Drag the pages in the navigation menu, or press {0} on a focused page, to reorder them. Keyboard shortcuts follow the same order.",
OperatingSystem.IsMacOS() ? "Option+Up/Down" : "Alt+Up/Down");
NavigationOrderCard.Click += (_, _) => MainWindow.Instance?.ResetNavigationOrder();
}
}
6 changes: 6 additions & 0 deletions src/UniGetUI.Avalonia/Views/SidebarView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
automation:AutomationProperties.Name="{t:Translate Navigation}"
Padding="8,10,8,0">

<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

<!-- Software Catalog: first when enabled, as the curated starting point -->
<ListBoxItem x:Name="CatalogNavBtn" Tag="Catalog" Classes="nav-item" Padding="4,2" CornerRadius="6"
automation:AutomationProperties.Name="{t:Translate Software Catalog}"
Expand Down
Loading
Loading