diff --git a/Services/DialogService.cs b/Services/DialogService.cs index a62b060..7fb7719 100644 --- a/Services/DialogService.cs +++ b/Services/DialogService.cs @@ -19,6 +19,14 @@ public class DialogService : IDialogService { private readonly Func _xamlRootFactory; + // Wrap and cap plain messages so short notices do not expand to the dialog's max width. + private static TextBlock CreateMessageText(string message) => new() + { + Text = message, + TextWrapping = TextWrapping.Wrap, + MaxWidth = 280, + }; + public DialogService(Func xamlRootFactory) { _xamlRootFactory = xamlRootFactory; @@ -655,20 +663,11 @@ void UpdateLanAddressText() public async Task ShowConfirmationAsync(string title, string message, string? confirmText = null, string? cancelText = null, bool isDanger = false) { - confirmText ??= L.Dialog_OK; - cancelText ??= L.Dialog_Cancel; - var content = new TextBlock - { - Text = message, - TextWrapping = TextWrapping.Wrap, - MaxWidth = 280 - }; - var dialog = CreateDialog(); dialog.Title = title; - dialog.Content = content; - dialog.PrimaryButtonText = confirmText; - dialog.CloseButtonText = cancelText; + dialog.Content = CreateMessageText(message); + dialog.PrimaryButtonText = confirmText ?? L.Dialog_OK; + dialog.CloseButtonText = cancelText ?? L.Dialog_Cancel; dialog.DefaultButton = isDanger ? ContentDialogButton.None : ContentDialogButton.Primary; if (isDanger && Application.Current.Resources.TryGetValue("DangerAccentButtonStyle", out var style) && @@ -728,7 +727,7 @@ public async Task ShowErrorAsync(string title, string message, XamlRoot? xamlRoo { var dialog = CreateDialog(xamlRoot); dialog.Title = title; - dialog.Content = message; + dialog.Content = CreateMessageText(message); dialog.CloseButtonText = L.Dialog_OK; await dialog.ShowAsync(); } @@ -1005,6 +1004,19 @@ public async Task ShowShareLinkDialogAsync(string serverName, string link) // ── App update confirm ──────────────────────────────────────────────── + /// + /// Content width of the update dialog's notes list, inside ContentDialog's own padding. + /// Sized so a typical changelog line still fits on one row; longer ones wrap onto the + /// hanging indent that sets up, which is why it can stay + /// well below what the longest line would need. It also has to cover the bullet column + /// and , neither of which holds text, so trimming it back + /// toward the bare text width costs a good dozen characters a line. + /// + private const double UpdateContentWidth = 392; + + /// Right gutter kept clear inside the notes list for the overlay scrollbar. + private const double ScrollBarGutter = 14; + public async Task ShowUpdateConfirmDialogAsync( Version newVersion, IReadOnlyList notes) { @@ -1021,7 +1033,7 @@ public async Task ShowUpdateConfirmDialogAsync( // ContentDialog clips tall content instead of letting the notes list scroll. // Fixed width keeps the dialog compact — without it the longest note line // stretches it toward ContentDialog's max width. - var root = new Grid { Width = 380 }; + var root = new Grid { Width = UpdateContentWidth }; root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); // notes header root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); // notes list @@ -1038,17 +1050,25 @@ public async Task ShowUpdateConfirmDialogAsync( Grid.SetRow(notesHeader, 0); root.Children.Add(notesHeader); - var list = new StackPanel { Spacing = 4 }; + var list = new StackPanel { Spacing = 6 }; foreach (var line in notes) list.Children.Add(BuildNoteLine(line)); var scroller = new ScrollViewer { Content = list, - MaxHeight = 220, + MaxHeight = 240, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollMode = ScrollMode.Disabled, HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled, + // WinUI scrollbars are overlays: they paint on top of the content rather + // than taking layout space, so without this gutter the expanded mouse + // scrollbar covers the last dozen pixels of every note line. Whether it + // overlays at all depends on the "always show scrollbars" accessibility + // setting, so reserving the space is the only way to get the same result + // under both. The gutter is unconditional because whether the list will + // overflow is not known until after layout. + Padding = new Thickness(0, 0, ScrollBarGutter, 0), }; Grid.SetRow(scroller, 1); root.Children.Add(scroller); @@ -1072,7 +1092,8 @@ private static Grid BuildNoteLine(string text) var bullet = new TextBlock { Text = "•", - FontSize = 13, + FontSize = 14, + LineHeight = 20, Opacity = 0.65, VerticalAlignment = VerticalAlignment.Top, }; @@ -1080,7 +1101,10 @@ private static Grid BuildNoteLine(string text) var body = new TextBlock { Text = text, - FontSize = 13, + FontSize = 14, + // Default leading is tight for CJK once a line wraps; 20 keeps a wrapped note + // readable without spacing the list out. + LineHeight = 20, TextWrapping = TextWrapping.Wrap, }; Grid.SetColumn(body, 1);