From 12ab759f3b6d96742ff1370a8f8149307a195ecb Mon Sep 17 00:00:00 2001 From: Anon Date: Sun, 3 May 2026 20:12:52 +0200 Subject: [PATCH] Fix TUI book controls Polish TUI book navigation and signed-book handling, update localized shortcut text, and document the final PageUp/PageDown interaction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- MinecraftClient/Commands/Book.cs | 23 ++- .../Translations/Translations.Designer.cs | 16 ++ .../Resources/Translations/Translations.resx | 12 ++ MinecraftClient/Tui/BookTuiHost.cs | 145 +++++++++++++++--- docs/guide/usage.md | 2 + 5 files changed, 168 insertions(+), 30 deletions(-) diff --git a/MinecraftClient/Commands/Book.cs b/MinecraftClient/Commands/Book.cs index 1362d227..e035f277 100644 --- a/MinecraftClient/Commands/Book.cs +++ b/MinecraftClient/Commands/Book.cs @@ -93,7 +93,7 @@ namespace MinecraftClient.Commands private int OpenEditor(CmdResult r) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out _)) + if (!EnsureWritable(r, handler, out _, Translations.cmd_book_cannot_edit_signed)) return -1; return BookTuiHost.TryOpen(handler, BookHand.Main, editable: true) @@ -104,7 +104,7 @@ namespace MinecraftClient.Commands private int WriteBook(CmdResult r, string text) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out _)) + if (!EnsureWritable(r, handler, out _, Translations.cmd_book_cannot_edit_signed)) return -1; IReadOnlyList pages = SplitPages(text); @@ -127,7 +127,7 @@ namespace MinecraftClient.Commands private int EditPage(CmdResult r, int page, string text) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out BookContent content)) + if (!EnsureWritable(r, handler, out BookContent content, Translations.cmd_book_cannot_edit_signed)) return -1; List pages = content.Pages.ToList(); @@ -146,7 +146,7 @@ namespace MinecraftClient.Commands private int InsertPage(CmdResult r, int page, string text) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out BookContent content)) + if (!EnsureWritable(r, handler, out BookContent content, Translations.cmd_book_cannot_edit_signed)) return -1; List pages = content.Pages.ToList(); @@ -165,7 +165,7 @@ namespace MinecraftClient.Commands private int DeletePage(CmdResult r, int page) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out BookContent content)) + if (!EnsureWritable(r, handler, out BookContent content, Translations.cmd_book_cannot_edit_signed)) return -1; List pages = content.Pages.ToList(); @@ -187,7 +187,7 @@ namespace MinecraftClient.Commands private int SignBook(CmdResult r, string title) { McClient handler = CmdResult.currentHandler!; - if (!EnsureWritable(r, handler, out BookContent content)) + if (!EnsureWritable(r, handler, out BookContent content, Translations.cmd_book_already_signed)) return -1; string normalizedTitle = title.Trim(); @@ -208,7 +208,7 @@ namespace MinecraftClient.Commands return false; } - private static bool EnsureWritable(CmdResult r, McClient handler, out BookContent content) + private static bool EnsureWritable(CmdResult r, McClient handler, out BookContent content, string signedBookMessage) { content = BookContent.EmptyWritable; if (!EnsureInventory(r, handler)) @@ -217,13 +217,20 @@ namespace MinecraftClient.Commands Item? item = handler.GetHeldBook(); if (!BookContentHelper.IsWritableBook(item)) { - r.SetAndReturn(CmdResult.Status.Fail, Translations.cmd_book_not_holding_writable); + r.SetAndReturn(CmdResult.Status.Fail, GetWritableBookFailureMessage(item, signedBookMessage)); return false; } return BookContentHelper.TryRead(item, out content); } + private static string GetWritableBookFailureMessage(Item? item, string signedBookMessage) + { + return BookContentHelper.TryRead(item, out BookContent content) && content.IsSigned + ? signedBookMessage + : Translations.cmd_book_not_holding_writable; + } + private static IReadOnlyList SplitPages(string text) { return BookContentHelper.NormalizePages(DecodeInlineText(text).Split(PageDelimiter)); diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index bccd0a57..dc76b6c4 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -7584,6 +7584,14 @@ namespace MinecraftClient { get { return ResourceManager.GetString("cmd.book.not_holding_writable", resourceCulture); } } + internal static string cmd_book_already_signed { + get { return ResourceManager.GetString("cmd.book.already_signed", resourceCulture); } + } + + internal static string cmd_book_cannot_edit_signed { + get { return ResourceManager.GetString("cmd.book.cannot_edit_signed", resourceCulture); } + } + internal static string cmd_book_tui_opened { get { return ResourceManager.GetString("cmd.book.tui_opened", resourceCulture); } } @@ -7696,5 +7704,13 @@ namespace MinecraftClient { get { return ResourceManager.GetString("tui.book.reading", resourceCulture); } } + internal static string tui_book_edit_shortcut_tip { + get { return ResourceManager.GetString("tui.book.edit_shortcut_tip", resourceCulture); } + } + + internal static string tui_book_page_shortcut_tip { + get { return ResourceManager.GetString("tui.book.page_shortcut_tip", resourceCulture); } + } + } } diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index aa167797..9d60b82d 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -2683,6 +2683,12 @@ see item details. You must hold a writable book in your main hand. + + Book already signed. + + + You cannot edit a signed book. + Book TUI opened. @@ -2767,4 +2773,10 @@ see item details. Reading book. + + PageUp/PageDown: previous/next page + + + PageUp/PageDown: previous/next page + diff --git a/MinecraftClient/Tui/BookTuiHost.cs b/MinecraftClient/Tui/BookTuiHost.cs index eac8c2b0..5d20b13f 100644 --- a/MinecraftClient/Tui/BookTuiHost.cs +++ b/MinecraftClient/Tui/BookTuiHost.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; @@ -58,14 +59,23 @@ internal sealed class BookView : UserControl private readonly List pages; private readonly TextBlock header; private readonly TextBlock status; + private readonly TextBlock shortcutTip; private readonly TextBox pageText; private readonly TextBox titleText; + private readonly Button previousButton; + private readonly Button nextButton; + private readonly Button insertButton; + private readonly Button deleteButton; + private readonly Button saveButton; + private readonly Button signButton; + private bool bookSigned; private int pageIndex; public BookView(McClient handler, BookContent content, bool editable) { this.handler = handler; this.editable = editable; + bookSigned = content.IsSigned; pages = content.Pages.ToList(); if (pages.Count == 0) pages.Add(string.Empty); @@ -87,11 +97,19 @@ internal sealed class BookView : UserControl TextWrapping = TextWrapping.Wrap }; + shortcutTip = new TextBlock + { + Foreground = Brushes.DarkGray, + Margin = new Thickness(1, 0), + Text = Translations.tui_book_page_shortcut_tip, + TextWrapping = TextWrapping.Wrap + }; + pageText = new TextBox { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, - IsReadOnly = !editable, + IsReadOnly = !CanEdit, Foreground = Brushes.White, Background = Brushes.Black, BorderBrush = Brushes.Gray, @@ -100,20 +118,27 @@ internal sealed class BookView : UserControl }; pageText.TextChanged += (_, _) => { - if (editable && pageIndex >= 0 && pageIndex < pages.Count) + if (CanEdit && pageIndex >= 0 && pageIndex < pages.Count) pages[pageIndex] = pageText.Text ?? string.Empty; }; titleText = new TextBox { Watermark = Translations.tui_book_title_watermark, - IsVisible = editable, + IsVisible = CanEdit, Foreground = Brushes.White, Background = Brushes.Black, BorderBrush = Brushes.Gray, Margin = new Thickness(1) }; + previousButton = Button(Translations.tui_book_prev, (_, _) => TryMovePage(-1)); + nextButton = Button(Translations.tui_book_next, (_, _) => TryMovePage(1)); + insertButton = Button(Translations.tui_book_insert, (_, _) => InsertPage(), editable); + deleteButton = Button(Translations.tui_book_delete, (_, _) => DeletePage(), editable); + saveButton = Button(Translations.tui_book_save, (_, _) => Save(), editable); + signButton = Button(Translations.tui_book_sign, (_, _) => Sign(), editable); + var controls = new StackPanel { Orientation = Orientation.Horizontal, @@ -121,12 +146,12 @@ internal sealed class BookView : UserControl Margin = new Thickness(1), Children = { - Button(Translations.tui_book_prev, (_, _) => MovePage(-1)), - Button(Translations.tui_book_next, (_, _) => MovePage(1)), - Button(Translations.tui_book_insert, (_, _) => InsertPage(), editable), - Button(Translations.tui_book_delete, (_, _) => DeletePage(), editable), - Button(Translations.tui_book_save, (_, _) => Save(), editable), - Button(Translations.tui_book_sign, (_, _) => Sign(), editable), + previousButton, + nextButton, + insertButton, + deleteButton, + saveButton, + signButton, Button(Translations.tui_book_close, (_, _) => Close()) } }; @@ -138,6 +163,7 @@ internal sealed class BookView : UserControl { DockTo(header, Dock.Top), DockTo(status, Dock.Bottom), + DockTo(shortcutTip, Dock.Bottom), DockTo(controls, Dock.Bottom), DockTo(titleText, Dock.Bottom), pageText @@ -145,26 +171,31 @@ internal sealed class BookView : UserControl }; Content = panel; + AttachedToVisualTree += (_, _) => + { + AddHandler(KeyDownEvent, OnTunnelKeyDown, RoutingStrategies.Tunnel, handledEventsToo: true); + FocusPageText(); + }; + DetachedFromVisualTree += (_, _) => RemoveHandler(KeyDownEvent, OnTunnelKeyDown); Refresh(); } - protected override void OnKeyDown(KeyEventArgs e) + private bool CanEdit => editable && !bookSigned; + + private void OnTunnelKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.PageUp) { - MovePage(-1); + TryMovePage(-1); e.Handled = true; return; } if (e.Key == Key.PageDown) { - MovePage(1); + TryMovePage(1); e.Handled = true; - return; } - - base.OnKeyDown(e); } private static Control DockTo(Control control, Dock dock) @@ -186,14 +217,22 @@ internal sealed class BookView : UserControl return button; } - private void MovePage(int delta) + private bool TryMovePage(int delta) { - pageIndex = Math.Clamp(pageIndex + delta, 0, pages.Count - 1); + int targetPageIndex = Math.Clamp(pageIndex + delta, 0, pages.Count - 1); + if (targetPageIndex == pageIndex) + return false; + + pageIndex = targetPageIndex; Refresh(); + return true; } private void InsertPage() { + if (!CanEdit) + return; + pages.Insert(pageIndex + 1, string.Empty); pageIndex++; Refresh(); @@ -201,6 +240,9 @@ internal sealed class BookView : UserControl private void DeletePage() { + if (!CanEdit) + return; + if (pages.Count == 1) pages[0] = string.Empty; else @@ -213,6 +255,14 @@ internal sealed class BookView : UserControl private void Save() { + if (bookSigned || IsHeldBookSigned()) + { + bookSigned = true; + status.Text = Translations.cmd_book_cannot_edit_signed; + RefreshEditability(); + return; + } + if (!Validate(out string error)) { status.Text = error; @@ -226,6 +276,14 @@ internal sealed class BookView : UserControl private void Sign() { + if (bookSigned || IsHeldBookSigned()) + { + bookSigned = true; + status.Text = Translations.cmd_book_already_signed; + RefreshEditability(); + return; + } + string title = (titleText.Text ?? string.Empty).Trim(); if (!Validate(out string error, title)) { @@ -233,8 +291,16 @@ internal sealed class BookView : UserControl return; } - status.Text = handler.SendBookEdit(pages, title) - ? Translations.tui_book_signed + if (handler.SendBookEdit(pages, title)) + { + bookSigned = true; + status.Text = Translations.tui_book_signed; + RefreshEditability(); + return; + } + + status.Text = IsHeldBookSigned() + ? Translations.cmd_book_already_signed : Translations.tui_book_save_failed; } @@ -274,9 +340,44 @@ internal sealed class BookView : UserControl private void Refresh() { - pageText.Text = pages[pageIndex]; + string currentPageText = pages[pageIndex]; + if (!string.Equals(pageText.Text, currentPageText, StringComparison.Ordinal)) + pageText.Text = currentPageText; + header.Text = string.Format(Translations.tui_book_page_header, pageIndex + 1, pages.Count); - status.Text = editable ? Translations.tui_book_editing : Translations.tui_book_reading; - pageText.Focus(); + status.Text = CanEdit ? Translations.tui_book_editing : Translations.tui_book_reading; + RefreshEditability(); + FocusPageText(); + } + + private void RefreshEditability() + { + previousButton.IsEnabled = pageIndex > 0; + nextButton.IsEnabled = pageIndex < pages.Count - 1; + insertButton.IsEnabled = CanEdit; + deleteButton.IsEnabled = CanEdit; + saveButton.IsEnabled = CanEdit; + signButton.IsEnabled = CanEdit; + pageText.IsReadOnly = !CanEdit; + titleText.IsEnabled = CanEdit; + titleText.IsVisible = CanEdit; + shortcutTip.Text = CanEdit + ? Translations.tui_book_edit_shortcut_tip + : Translations.tui_book_page_shortcut_tip; + } + + private void FocusPageText() + { + Dispatcher.UIThread.Post(() => + { + pageText.Focus(); + if (CanEdit) + pageText.CaretIndex = pageText.Text?.Length ?? 0; + }, DispatcherPriority.Input); + } + + private bool IsHeldBookSigned() + { + return BookContentHelper.TryRead(handler.GetHeldBook(BookHand.Main), out BookContent content) && content.IsSigned; } } diff --git a/docs/guide/usage.md b/docs/guide/usage.md index 6aa10b6a..1cc65d87 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -326,6 +326,8 @@ In scripts and remote control, no slash is needed to perform the command, eg. `q The interactive editor is only available in TUI mode. + In the TUI book view, `PageUp`/`PageDown` switches pages. + - **Examples:** Read the held book: