diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
index 9fe3310473..dd0b43daf5 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs
@@ -12,6 +12,7 @@
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.ViewModels;
using UniGetUI.Avalonia.Views.DialogPages;
+using UniGetUI.Core.Logging;
using UniGetUI.Core.Tools;
using UniGetUI.Interface.Telemetry;
using UniGetUI.PackageEngine.Enums;
@@ -297,106 +298,146 @@ private void AddInlineRow(StackPanel host, string label, string value)
private void AddInlineRow(StackPanel host, string label, Uri? url)
{
- var tb = new SelectableTextBlock { TextWrapping = TextWrapping.Wrap };
- var inlines = tb.Inlines ??= new InlineCollection();
- inlines.Add(new Run(label + ": ") { FontWeight = FontWeight.Bold });
-
if (url is null)
{
+ var tb = new SelectableTextBlock { TextWrapping = TextWrapping.Wrap };
+ var inlines = tb.Inlines ??= new InlineCollection();
+ inlines.Add(new Run(label + ": ") { FontWeight = FontWeight.Bold });
inlines.Add(new Run(_vm.LabelNotAvailable)
{
Foreground = NotAvailableBrush,
FontStyle = FontStyle.Italic,
});
+ host.Children.Add(tb);
+ return;
}
- else
- {
- inlines.Add(new Run(url.ToString())
- {
- Foreground = LinkBrush,
- TextDecorations = TextDecorations.Underline,
- });
- tb.Cursor = new Cursor(StandardCursorType.Hand);
- tb.PointerPressed += (_, e) =>
- {
- if (e.GetCurrentPoint(tb).Properties.IsLeftButtonPressed)
- OpenUrl(url.ToString());
- };
- }
- host.Children.Add(tb);
+
+ host.Children.Add(BuildLinkRow(BoldLabel(label + ": "), url.ToString()));
}
private void AddLicenseRow(StackPanel host)
{
- var tb = new SelectableTextBlock { TextWrapping = TextWrapping.Wrap };
- var inlines = tb.Inlines ??= new InlineCollection();
- inlines.Add(new Run(_vm.LabelLicense + ": ") { FontWeight = FontWeight.Bold });
-
bool hasName = !string.IsNullOrEmpty(_vm.LicenseName);
bool hasUrl = _vm.LicenseUrl is not null;
- if (hasName)
- inlines.Add(new Run(_vm.LicenseName!));
-
- if (hasUrl)
+ if (!hasUrl)
{
- if (hasName) inlines.Add(new Run(" "));
- var url = _vm.LicenseUrl!.ToString();
- inlines.Add(new Run(url)
- {
- Foreground = LinkBrush,
- TextDecorations = TextDecorations.Underline,
- });
- tb.Cursor = new Cursor(StandardCursorType.Hand);
- tb.PointerPressed += (_, e) =>
- {
- if (e.GetCurrentPoint(tb).Properties.IsLeftButtonPressed)
- OpenUrl(url);
- };
+ // No link: render as a plain selectable line with the usual cursor.
+ var tb = new SelectableTextBlock { TextWrapping = TextWrapping.Wrap };
+ var inlines = tb.Inlines ??= new InlineCollection();
+ inlines.Add(new Run(_vm.LabelLicense + ": ") { FontWeight = FontWeight.Bold });
+ if (hasName)
+ inlines.Add(new Run(_vm.LicenseName!));
+ else
+ inlines.Add(new Run(_vm.LabelNotAvailable)
+ {
+ Foreground = NotAvailableBrush,
+ FontStyle = FontStyle.Italic,
+ });
+ host.Children.Add(tb);
+ return;
}
- else if (!hasName)
+
+ var labelBlock = new SelectableTextBlock
{
- inlines.Add(new Run(_vm.LabelNotAvailable)
- {
- Foreground = NotAvailableBrush,
- FontStyle = FontStyle.Italic,
- });
- }
+ TextWrapping = TextWrapping.Wrap,
+ VerticalAlignment = VerticalAlignment.Top,
+ };
+ var labelInlines = labelBlock.Inlines ??= new InlineCollection();
+ labelInlines.Add(new Run(_vm.LabelLicense + ": ") { FontWeight = FontWeight.Bold });
+ if (hasName)
+ labelInlines.Add(new Run(_vm.LicenseName! + " "));
- host.Children.Add(tb);
+ host.Children.Add(BuildLinkRow(labelBlock, _vm.LicenseUrl!.ToString()));
}
private void AddDownloadRow(StackPanel host)
{
- var tb = new SelectableTextBlock { TextWrapping = TextWrapping.Wrap };
- var inlines = tb.Inlines ??= new InlineCollection();
-
if (_vm.CanDownloadInstaller && !_vm.IsLoading)
{
- inlines.Add(new Run(_vm.LabelDownloadInstaller)
- {
- Foreground = LinkBrush,
- TextDecorations = TextDecorations.Underline,
- FontWeight = FontWeight.SemiBold,
- });
+ var panel = new StackPanel { Orientation = Orientation.Horizontal };
+ var link = CreateLinkBlock(_vm.LabelDownloadInstaller, DownloadInstaller);
+ link.FontWeight = FontWeight.SemiBold;
+ panel.Children.Add(link);
if (!string.IsNullOrEmpty(_vm.InstallerSize))
- inlines.Add(new Run($" ({_vm.InstallerSize})"));
- tb.Cursor = new Cursor(StandardCursorType.Hand);
- tb.PointerPressed += (_, e) =>
- {
- if (e.GetCurrentPoint(tb).Properties.IsLeftButtonPressed)
- DownloadInstaller();
- };
+ panel.Children.Add(new SelectableTextBlock { Text = $" ({_vm.InstallerSize})" });
+ host.Children.Add(panel);
}
else
{
- inlines.Add(new Run(_vm.LabelInstallerNotAvailable)
+ host.Children.Add(new SelectableTextBlock
{
+ Text = _vm.LabelInstallerNotAvailable,
Foreground = NotAvailableBrush,
FontStyle = FontStyle.Italic,
});
}
- host.Children.Add(tb);
+ }
+
+ private static SelectableTextBlock BoldLabel(string text) => new()
+ {
+ Text = text,
+ FontWeight = FontWeight.Bold,
+ VerticalAlignment = VerticalAlignment.Top,
+ };
+
+ /// "Label: url" laid out so a long URL wraps under itself; only the URL is the link.
+ private Grid BuildLinkRow(Control label, string url)
+ {
+ var grid = new Grid
+ {
+ ColumnDefinitions =
+ [
+ new ColumnDefinition(GridLength.Auto),
+ new ColumnDefinition(new GridLength(1, GridUnitType.Star)),
+ ],
+ };
+ Grid.SetColumn(label, 0);
+ var link = CreateLinkBlock(url, () => OpenUrl(url));
+ link.TextWrapping = TextWrapping.Wrap;
+ Grid.SetColumn(link, 1);
+ grid.Children.Add(label);
+ grid.Children.Add(link);
+ return grid;
+ }
+
+ /// A selectable, copyable hyperlink. The hand cursor and activation cover only this text.
+ private SelectableTextBlock CreateLinkBlock(string text, Action activate)
+ {
+ var link = new SelectableTextBlock
+ {
+ Text = text,
+ Foreground = LinkBrush,
+ TextDecorations = TextDecorations.Underline,
+ Cursor = new Cursor(StandardCursorType.Hand),
+ };
+ AttachLinkActivation(link, activate);
+ return link;
+ }
+
+ /// Activate on a clean click, but let a press-and-drag run the text selection instead.
+ private static void AttachLinkActivation(Control link, Action activate)
+ {
+ Point press = default;
+ bool tracking = false;
+
+ link.AddHandler(InputElement.PointerPressedEvent, (_, e) =>
+ {
+ if (e.GetCurrentPoint(link).Properties.IsLeftButtonPressed)
+ {
+ tracking = true;
+ press = e.GetPosition(link);
+ }
+ }, RoutingStrategies.Bubble, handledEventsToo: true);
+
+ link.AddHandler(InputElement.PointerReleasedEvent, (_, e) =>
+ {
+ if (!tracking) return;
+ tracking = false;
+ var moved = e.GetPosition(link) - press;
+ if (Math.Abs(moved.X) <= 4 && Math.Abs(moved.Y) <= 4)
+ activate();
+ }, RoutingStrategies.Bubble, handledEventsToo: true);
}
private IBrush LinkBrush =>
@@ -408,8 +449,15 @@ private static void OpenUrl(string url)
{
if (string.IsNullOrEmpty(url) || !url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
return;
- try { Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); }
- catch { }
+ try
+ {
+ Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"Failed to open URL: {url}");
+ Logger.Error(ex);
+ }
}
private void DownloadInstaller()