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
23 changes: 23 additions & 0 deletions src/UniGetUI.Tui.Tests/TuiChromeCellsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NUnit.Framework;
using UniGetUI.Tui.Infrastructure;
using UniGetUI.Tui.Views.Controls;
using NAssert = NUnit.Framework.Assert;

namespace UniGetUI.Tui.Tests;

[TestFixture, NonParallelizable]
internal sealed class TuiChromeCellsTests : TuiE2ETestBase
{
[Test]
public async Task EmojiAreMeasuredTheWayTheConsoleDrawsThem()
{
await OnUi(() =>
{
NAssert.That(TuiChrome.Cells("⚙"), Is.EqualTo(2));
NAssert.That(TuiChrome.Cells("⚙ 设置"), Is.EqualTo(7));

(_, int variant) = TuiFunctionBar.Fit([], "INFO", ["⚙⚙⚙⚙", ""], 15);
NAssert.That(variant, Is.EqualTo(1));
});
}
}
16 changes: 16 additions & 0 deletions src/UniGetUI.Tui.Tests/TuiFunctionBarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ public void NarrowBarsDropWholeKeysByPriorityThenShortenTheNotification()
NAssert.That(keys, Is.EqualTo(new[] { 0, 1, 2, 3, 4 }));
NAssert.That(variant, Is.EqualTo(-1));
}

[Test]
public void DoubleWidthLabelsAreMeasuredInTerminalCells()
{
NAssert.That(TuiChrome.Cells("帮助"), Is.EqualTo(4));
NAssert.That(TuiChrome.Cells("Help"), Is.EqualTo(4));

TuiKeyHint[] keys = [new("F1", "帮助", 0), new("F5", "重新加载", 4)];
(List<int> shown, _) = TuiFunctionBar.Fit(keys, null, [""], 20);
NAssert.That(shown, Is.EqualTo(new[] { 0 }));
(shown, _) = TuiFunctionBar.Fit(keys, null, [""], 24);
NAssert.That(shown, Is.EqualTo(new[] { 0, 1 }));

(_, int variant) = TuiFunctionBar.Fit([], "INFO", ["已更新 3 个软件包", ""], 25);
NAssert.That(variant, Is.EqualTo(1));
}
}
40 changes: 40 additions & 0 deletions src/UniGetUI.Tui.Tests/TuiTabStripTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using NUnit.Framework;
using UniGetUI.Tui.Views;
using static UniGetUI.Tui.Views.MainWindow;
using NAssert = NUnit.Framework.Assert;

namespace UniGetUI.Tui.Tests;

[TestFixture]
internal sealed class TuiTabStripTests
{
private static readonly string[] Names = ["Discover", "Updates", "操作", "设置", "历史记录"];

private static string Label(int i, TabDensity density) => density switch
{
TabDensity.Full => $"{i + 1} {Names[i]}",
TabDensity.Short => $"{i + 1} {(Names[i].Length > 4 ? Names[i][..4] : Names[i])}",
TabDensity.Shorter => $"{i + 1} {(Names[i].Length > 3 ? Names[i][..3] : Names[i])}",
_ => $"{i + 1}",
};

[Test]
public void DoubleWidthTabLabelsAreMeasuredInTerminalCells()
{
NAssert.That(MainWindow.FitTabs(Names.Length, 0, Label, 45), Is.EqualTo((TabDensity.Shorter, TabDensity.Full)));
NAssert.That(MainWindow.FitTabs(Names.Length, 0, Label, 51), Is.EqualTo((TabDensity.Full, TabDensity.Full)));
}

[Test]
public void WhenNothingFitsTheSelectedTabDropsToItsNumberToo()
{
NAssert.That(MainWindow.FitTabs(Names.Length, 4, Label, 24), Is.EqualTo((TabDensity.NumberOnly, TabDensity.Full)));
NAssert.That(MainWindow.FitTabs(Names.Length, 4, Label, 20), Is.EqualTo((TabDensity.NumberOnly, TabDensity.NumberOnly)));
}

[Test]
public void AnUnmeasuredStripKeepsEveryTabFull()
{
NAssert.That(MainWindow.FitTabs(Names.Length, 0, Label, 0), Is.EqualTo((TabDensity.Full, TabDensity.Full)));
}
}
6 changes: 6 additions & 0 deletions src/UniGetUI.Tui/Infrastructure/TuiChrome.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Avalonia.Controls;
using UniGetUI.Core.Tools;
using UniGetUI.Tui.Views.Controls;
using Wcwidth;

namespace UniGetUI.Tui.Infrastructure;

Expand All @@ -17,4 +18,9 @@ public static TuiPanel PageTitle(string title, Control? summary = null)
/// <summary>Key hints with translated labels, most important first, for <see cref="ITuiPage.KeyHints"/>.</summary>
public static IReadOnlyList<TuiKeyHint> Hints(params (string Key, string Label)[] hints)
=> hints.Select((h, i) => new TuiKeyHint(h.Key, CoreTools.Translate(h.Label), i)).ToList();

public static int Cells(string text)
=> Application.Current is null
? text.EnumerateRunes().Sum(r => Math.Max(0, UnicodeCalculator.GetWidth(r)))
: Consolonia.Controls.ControlUtils.MeasureText(text);
}
6 changes: 3 additions & 3 deletions src/UniGetUI.Tui/Views/Controls/TuiFunctionBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ internal static IReadOnlyList<string> NotificationVariants(string title, string
/// </summary>
internal static (List<int> Keys, int Variant) Fit(IReadOnlyList<TuiKeyHint> keys, string? badge, IReadOnlyList<string> variants, double width)
{
int NotificationWidth(int variant) => Gap + badge!.Length + 2 + (variants[variant].Length > 0 ? variants[variant].Length + 2 : 0);
int NotificationWidth(int variant) => Gap + TuiChrome.Cells(badge!) + 2 + (variants[variant].Length > 0 ? TuiChrome.Cells(variants[variant]) + 2 : 0);

var byPriority = Enumerable.Range(0, keys.Count).OrderBy(i => keys[i].Priority).ToList();
var shown = new List<int>();
Expand Down Expand Up @@ -152,13 +152,13 @@ void Place(IEnumerable<int> candidates)
return (shown, chosen);
}

private static int CellWidth(TuiKeyHint hint) => hint.Key.Length + 2 + 1 + hint.Label.Length;
private static int CellWidth(TuiKeyHint hint) => TuiChrome.Cells(hint.Key) + 2 + 1 + TuiChrome.Cells(hint.Label);

private void Layout()
{
double width = Bounds.Width - Padding.Left - Padding.Right;
if (width <= 0) return;
double badges = _badgeTexts.Sum(b => b.Length + 2) + Math.Max(0, _badgeTexts.Count - 1) + (_badgeTexts.Count > 0 ? 1 : 0);
double badges = _badgeTexts.Sum(b => TuiChrome.Cells(b) + 2) + Math.Max(0, _badgeTexts.Count - 1) + (_badgeTexts.Count > 0 ? 1 : 0);
IReadOnlyList<string> variants = NotificationVariants(_title, _message);
string? badge = _notificationActive ? _notificationBadge.Text?.Trim() : null;
(List<int> keys, int variant) = Fit(_hints, badge, variants, width - badges);
Expand Down
24 changes: 15 additions & 9 deletions src/UniGetUI.Tui/Views/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ private Control GetPage(string id)
/// <summary>The pages, each with a numbered tab (Help and About are dialogs, opened from the Help menu and F1).</summary>
private static readonly string[] TabPages = TuiPageIds.All.Where(id => id is not (TuiPageIds.Help or TuiPageIds.About)).ToArray();

private enum TabDensity
internal enum TabDensity
{
Full,
Short,
Expand Down Expand Up @@ -342,18 +342,24 @@ private static string TabLabel(string id, int index, TabDensity density)
};
}

internal static (TabDensity Others, TabDensity Active) FitTabs(int count, int active, Func<int, TabDensity, string> label, double available)
{
foreach (TabDensity candidate in Enum.GetValues<TabDensity>())
{
int width = Enumerable.Range(0, count).Sum(i => TuiChrome.Cells(label(i, i == active ? TabDensity.Full : candidate)) + 2);
if (available <= 0 || width <= available) return (candidate, TabDensity.Full);
}

return (TabDensity.NumberOnly, TabDensity.NumberOnly);
}

private void RefreshTabs()
{
List<(string Id, int Index)> tabs = TabPages.Select((id, i) => (id, i)).ToList();

double available = (_tabStrip.Parent as Control)?.Bounds.Width ?? 0;
TabDensity density = TabDensity.Full;
foreach (TabDensity candidate in Enum.GetValues<TabDensity>())
{
density = candidate;
int width = tabs.Sum(t => TabLabel(t.Id, t.Index, t.Id == _currentPageId ? TabDensity.Full : candidate).Length + 2);
if (available <= 0 || width <= available) break;
}
int current = tabs.FindIndex(t => t.Id == _currentPageId);
(TabDensity density, TabDensity activeDensity) = FitTabs(tabs.Count, current, (i, d) => TabLabel(tabs[i].Id, tabs[i].Index, d), available);

while (_tabStrip.Children.Count > tabs.Count) _tabStrip.Children.RemoveAt(_tabStrip.Children.Count - 1);
for (int i = 0; i < tabs.Count; i++)
Expand All @@ -377,7 +383,7 @@ private void RefreshTabs()
var text = (TextBlock)border.Child!;
border.Tag = id;
border.Background = active ? TuiPalette.Focus : Brushes.Transparent;
text.Text = " " + TabLabel(id, index, active ? TabDensity.Full : density) + " ";
text.Text = " " + TabLabel(id, index, active ? activeDensity : density) + " ";
text.Foreground = active ? TuiPalette.FocusText : TuiPalette.TextMuted;
text.FontWeight = active ? FontWeight.Bold : FontWeight.Normal;
}
Expand Down
Loading