Files
wwdpublic/Content.Client/_White/Book/UI/BookWindow.xaml.cs
Yarik 7b502a53fc Biggest books update (#772)
* Большое обновление книжек

* Remove dotnet tool cache artifacts

* Remove dotnet tool cache artifacts 2

* All comments on English

* Add all validation

* All localization, code reduction, named bookmarks system

* Add "try catch" because coderabbitai asked

* Add base check content length

* Fix "\\n"

* the "BookRandomStory" system has been fix and translate

* little fix "BookRandomStory" system

* 2 little fix "BookRandomStory" system

* 3 little fix "BookRandomStory" system

* 4 little fix "BookRandomStory" system

* 5 little fix "BookRandomStory" system

* Improved handling of UTF-8 character truncation

* 2 Improved validation and handling of UTF-8 character truncation

* Mini-up for author_books

* add new author book for botany

* Smaller long of text on one page

* translation of the new janitor's book

* Spelling lesson

* Make TODO notes

* Translate TODO notes...

* little fix "Check the status of the dialog before using it."

* DONE: Create new pages to transfer text, not replace old ones

* DONE: Make it visible when the text limit per page is exceeded

* DONE: Make it possible to delete unnecessary pages

* Fall protection on incorrect markings

* fix ".ftl"

* Text length limit for SplitContentIntoPages

* change limits

* Add sound

* Add placeholder to loadouts and new symbol for CreateNewPage

* Apply some suggestions from code review

All changes, except moving files into White folders

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* Apply some suggestions from code review 2

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* Transfering into "_White" folder

* Remove dublicate code

* Added the necessary code

* Add method

* BookSystem has been transfered into "Shared" folder

* Add attributions for ".ogg"

* changes for tools have been canceled

* Apply some suggestions from code review 3

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* content have been transfered into "_White" folder 2

* Little fix locales

* Apply some suggestions from code review 4

Add comments in "Resources/Prototypes/Entities/Objects/Misc/books_author.yml"

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* content have been transfered into "_White" folder and translate

* English spelling lesson

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* Add BookRandomeStorySystem

* Apply some suggestions from code review 5

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* rolling back some changes from "code review 5"

* Spelling lesson 2

* tweak BookSystem. (Apply suggestion from code review)

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* Little fix bookmark-default-title

* Apply some suggestions from code review 6

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* fix code review changes and transfer "BookRandomStorySystem.cs" into "Content.System"

* Half of good locales

* full good locales of RandomStorySystem

* Spelling lesson 3

* Removed unnecessary StoryGen file

* Fixed bookmarks pos after del any page

* Transd files into _White folder

* Fixed the constant activity of the add page button

* Correction at the request of coderabbitai

* Transfered at the request of code review

* Transfered at the request of code review 2

* Fix transfer

* Apply suggestions from code review 7

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>

* Apply suggestions from code review 8

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Apply and fix all suggestions from code review of file "BookWindow.xaml.cs"

* Fix formated

* Apply some suggestions from code review 9

maybe last...

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add try catch on texture loading

* Add Dispose method

* Correct display of blank pages

* Little fix eng locale

* Unnecessary code removed

* A Little Defense Against NRE/KeyNotFound.

* Highlighting the current page in the bookmarks drop-down list.

* Added fallback for the save button when there is no hotkey.

* Correct unsubscribing from events.

* Little fix save button

* Little fix formated

* Locales transfered fix

---------

Co-authored-by: Spatison <137375981+Spatison@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-30 17:35:36 +03:00

458 lines
16 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared._White.Book;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
namespace Content.Client._White.Book.UI;
[GenerateTypedNameReferences]
public sealed partial class BookWindow : FancyWindow
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private List<string> _pages = new();
private int _currentPageIndex = 0;
private Dictionary<int, string> _bookmarks = new();
private int _maxBookmarks;
private DialogWindow? _bookmarkDialog;
private int _maxCharactersPerPage;
private int _maxPages;
private Label? _characterCountLabel;
private ISawmill _sawmill = default!;
private Action? _bookmarkDialogOnClose;
private void OnPrevPressed(BaseButton.ButtonEventArgs _) => NavigateToPage(_currentPageIndex - 1);
private void OnNextPressed(BaseButton.ButtonEventArgs _) => NavigateToPage(_currentPageIndex + 1);
private void OnAddPagePressed(BaseButton.ButtonEventArgs _) => OnAddPage?.Invoke();
private void OnSavePressed(BaseButton.ButtonEventArgs _) => SaveCurrentPage();
private void OnDeletePressed(BaseButton.ButtonEventArgs _) => DeleteCurrentPage();
private void OnToggleBookmarkPressed(BaseButton.ButtonEventArgs _) => ToggleBookmarkForCurrentPage();
private void OnBookmarksTogglePressed(BaseButton.ButtonEventArgs _) => ToggleBookmarksVisibility();
public event Action<int>? OnPageChanged;
public event Action<string>? OnTextSaved;
public event Action? OnAddPage;
public event Action<int, string>? OnBookmarkAdded;
public event Action<int>? OnBookmarkRemoved;
public event Action<int>? OnPageDeleted;
public BookWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = _logManager.GetSawmill("book.ui");
var resCache = IoCManager.Resolve<IResourceCache>();
try
{
var customTexture = resCache.GetResource<TextureResource>("/Textures/_White/Interface/Book/rounded_book_background.png");
var paperSheetTexture = resCache.GetResource<TextureResource>("/Textures/_White/Interface/Book/paper_sheet_curled_edges.png");
var paperBackground = new StyleBoxTexture
{
Texture = customTexture,
Mode = StyleBoxTexture.StretchMode.Stretch
};
var paperSheetBackground = new StyleBoxTexture
{
Texture = paperSheetTexture,
Mode = StyleBoxTexture.StretchMode.Stretch,
Modulate = Color.FromHex("#f8f8f8") // Paper color
};
paperBackground.SetPatchMargin(StyleBox.Margin.All, 24.0f);
paperSheetBackground.SetPatchMargin(StyleBox.Margin.All, 20.0f);
WindowsPanelBackground.PanelOverride = paperBackground;
WindowsPanelBackground.ModulateSelfOverride = Color.FromHex("#636363ff"); // Book color
PaperBackground.Margin = new Thickness(0, 8, 5, 0);
PaperBackground.MinSize = new Vector2(400, 500);
PaperBackground.PanelOverride = paperSheetBackground;
}
catch (Exception ex)
{
// Fallback to default styling if texture loading fails
_sawmill.Error($"Failed to load book textures: {ex}");
}
WindowsHeadingBackground.Visible = false;
var floatingCloseButton = new PanelContainer
{
StyleClasses = { "AngleRect", },
VerticalAlignment = VAlignment.Top,
HorizontalAlignment = HAlignment.Right,
Margin = new Thickness(0, -50, 10, 0),
SetSize = new Vector2(42, 42),
VerticalExpand = false,
HorizontalExpand = false
};
CloseButton.Parent?.RemoveChild(CloseButton);
floatingCloseButton.AddChild(CloseButton);
AddChild(floatingCloseButton);
PrevPageButton.OnPressed += OnPrevPressed;
NextPageButton.OnPressed += OnNextPressed;
AddPageButton.OnPressed += OnAddPagePressed;
Input.OnKeyBindDown += args =>
{
if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
{
SaveCurrentPage();
args.Handle();
}
};
SaveButton.OnPressed += OnSavePressed;
var key = _inputManager.GetKeyFunctionButtonString(EngineKeyFunctions.MultilineTextSubmit);
SaveButton.Text = Loc.GetString(
string.IsNullOrEmpty(key)
? "book-ui-save-button-no-keybind"
: "book-ui-save-button",
("keybind", key)
);
PageNumberInput.OnTextChanged += args =>
{
const int maxLength = 3;
if (args.Text.Length > maxLength)
PageNumberInput.Text = args.Text[..maxLength];
var newText = string.Concat(args.Text.Where(char.IsDigit));
if (newText != args.Text)
PageNumberInput.Text = newText;
};
PageNumberInput.OnTextEntered += OnPageNumberEntered;
PageNumberInput.OnFocusExit += OnPageNumberEntered;
InitializeBookmarks();
Input.OnTextChanged += _ => UpdateCharacterCount();
DeletePageButton.OnPressed += OnDeletePressed;
}
public void ShowBook(BookBoundUserInterfaceState state, bool isEditing)
{
_pages = state.Pages;
_bookmarks = state.Bookmarks;
_maxBookmarks = state.MaxBookmarks;
_maxCharactersPerPage = state.MaxCharactersPerPage;
_maxPages = state.MaxPages;
_currentPageIndex = Math.Max(0, Math.Min(state.CurrentPage, _pages.Count - 1));
UpdatePageDisplay();
UpdateNavigationButtons();
SetEditMode(isEditing);
UpdateBookmarksDisplay();
}
private void SetEditMode(bool isEditing)
{
InputContainer.Visible = isEditing;
EditButtons.Visible = isEditing;
PageContainer.Visible = !isEditing;
if (!isEditing)
return;
if (_currentPageIndex >= 0 && _currentPageIndex < _pages.Count && _pages.Count > 0)
{
Input.TextRope = new Rope.Leaf(_pages[_currentPageIndex]);
Input.CursorPosition = new TextEdit.CursorPos(Rope.Collapse(Input.TextRope).Length, TextEdit.LineBreakBias.Top);
}
else
{
Input.TextRope = new Rope.Leaf("");
Input.CursorPosition = new TextEdit.CursorPos(0, TextEdit.LineBreakBias.Top);
}
UpdateCharacterCount();
}
private void SaveCurrentPage()
{
OnTextSaved?.Invoke(Rope.Collapse(Input.TextRope));
}
private void UpdatePageDisplay()
{
PageContainer.RemoveAllChildren();
if (_pages.Count == 0)
{
var blankPageLabel = new RichTextLabel();
blankPageLabel.SetMessage(Loc.GetString("paper-ui-blank-page-message"), null, Color.Gray);
PageContainer.AddChild(blankPageLabel);
}
else if (_currentPageIndex < _pages.Count)
{
var pageText = _pages[_currentPageIndex];
if (string.IsNullOrWhiteSpace(pageText))
{
var blankPageLabel = new RichTextLabel();
blankPageLabel.SetMessage(Loc.GetString("paper-ui-blank-page-message"), null, Color.Gray);
PageContainer.AddChild(blankPageLabel);
}
else
{
var label = new RichTextLabel();
var formattedMessage = new FormattedMessage();
formattedMessage.PushColor(Color.Black);
if (!formattedMessage.TryAddMarkup(pageText, out _))
formattedMessage.AddText(pageText);
formattedMessage.Pop();
label.SetMessage(formattedMessage);
PageContainer.AddChild(label);
}
}
PageInfo.Text = Loc.GetString("book-page-info", ("total", _pages.Count));
}
private void UpdatePageNumberDisplay()
{
PageNumberInput.Text = Math.Min(_pages.Count, _currentPageIndex + 1).ToString();
}
private void NavigateToPage(int pageIndex)
{
if (pageIndex >= 0 && pageIndex < _pages.Count)
{
_currentPageIndex = pageIndex;
OnPageChanged?.Invoke(pageIndex);
UpdatePageDisplay();
UpdateNavigationButtons();
UpdateBookmarksDisplay();
if (InputContainer.Visible)
SetEditMode(true);
}
else
{
UpdatePageNumberDisplay();
}
}
private void ToggleBookmarksVisibility()
{
BookmarksPanel.Visible = !BookmarksPanel.Visible;
BookmarksToggleButton.Text = BookmarksPanel.Visible ? Loc.GetString("book-bookmarks-hide") : Loc.GetString("book-bookmarks-show");
}
private void ToggleBookmarkForCurrentPage()
{
if (_currentPageIndex < 0 || _currentPageIndex >= _pages.Count)
return;
if (_bookmarks.Remove(_currentPageIndex))
{
OnBookmarkRemoved?.Invoke(_currentPageIndex);
}
else if (_bookmarks.Count < _maxBookmarks)
{
var defaultTitle = Loc.GetString("book-bookmark-default-title", ("page", _currentPageIndex + 1));
ShowBookmarkDialog(defaultTitle);
}
UpdateBookmarksDisplay();
}
private void UpdateNavigationButtons()
{
if (_pages == null)
{
PrevPageButton.Disabled = true;
NextPageButton.Disabled = true;
DeletePageButton.Disabled = true;
AddPageButton.Disabled = true;
return;
}
PrevPageButton.Disabled = _currentPageIndex <= 0;
NextPageButton.Disabled = _currentPageIndex >= _pages.Count - 1;
DeletePageButton.Disabled = _pages.Count <= 1;
AddPageButton.Disabled = _pages.Count >= _maxPages;
UpdatePageNumberDisplay();
}
private void OnPageNumberEntered(LineEdit.LineEditEventArgs args)
{
if (int.TryParse(args.Text, out var pageNumber) && pageNumber > 0)
{
NavigateToPage(pageNumber - 1);
}
else
{
UpdatePageNumberDisplay();
}
}
private void InitializeBookmarks()
{
ToggleBookmarkButton.OnPressed += OnToggleBookmarkPressed;
BookmarkDropdown.OnItemSelected += OnBookmarkSelected;
BookmarksToggleButton.OnPressed += OnBookmarksTogglePressed;
}
private void ShowBookmarkDialog(string defaultTitle)
{
if (_bookmarkDialog != null)
{
if (_bookmarkDialog.IsOpen)
{
_bookmarkDialog.MoveToFront();
return;
}
_bookmarkDialog = null;
}
var field = "title";
var prompt = Loc.GetString("book-bookmark-title-prompt");
var entry = new QuickDialogEntry(field, QuickDialogEntryType.ShortText, prompt, defaultTitle);
var entries = new List<QuickDialogEntry> { entry, };
_bookmarkDialog = new DialogWindow(Loc.GetString("book-bookmark-title-dialog"), entries);
_bookmarkDialog.OnConfirmed += responses =>
{
if (!responses.TryGetValue(field, out var title))
title = defaultTitle;
title = title.Trim();
if (string.IsNullOrEmpty(title))
title = defaultTitle;
const int maxLength = 50;
if (title.Length > maxLength)
title = title[..maxLength];
_bookmarks[_currentPageIndex] = title;
OnBookmarkAdded?.Invoke(_currentPageIndex, title);
UpdateBookmarksDisplay();
};
_bookmarkDialogOnClose ??= () => _bookmarkDialog = null;
_bookmarkDialog.OnClose += _bookmarkDialogOnClose;
}
private void OnBookmarkSelected(OptionButton.ItemSelectedEventArgs args)
{
switch (args.Id)
{
case -1:
return;
case var pageIndex:
NavigateToPage(pageIndex);
break;
}
}
private void UpdateBookmarksDisplay()
{
BookmarkDropdown.Clear();
if (_bookmarks.Count == 0)
{
BookmarkDropdown.Disabled = true;
BookmarkCountLabel.Text = Loc.GetString("book-no-bookmarks");
}
else
{
BookmarkDropdown.Disabled = false;
BookmarkDropdown.AddItem(Loc.GetString("book-select-bookmark"), -1);
var sortedBookmarks = _bookmarks.OrderBy(kvp => kvp.Key);
foreach (var bookmark in sortedBookmarks)
{
var displayText = bookmark.Value;
BookmarkDropdown.AddItem(displayText, bookmark.Key);
}
BookmarkCountLabel.Text = Loc.GetString("book-bookmarks-count", ("count", _bookmarks.Count), ("max", _maxBookmarks));
if (_bookmarks.ContainsKey(_currentPageIndex))
BookmarkDropdown.SelectId(_currentPageIndex);
else
BookmarkDropdown.SelectId(-1);
}
if (_bookmarks.ContainsKey(_currentPageIndex))
{
ToggleBookmarkButton.Text = "-";
ToggleBookmarkButton.Disabled = false;
}
else if (_bookmarks.Count >= _maxBookmarks)
{
ToggleBookmarkButton.Text = "+";
ToggleBookmarkButton.Disabled = true;
}
else
{
ToggleBookmarkButton.Text = "+";
ToggleBookmarkButton.Disabled = false;
}
}
private void UpdateCharacterCount()
{
if (CharacterCountLabel == null || !InputContainer.Visible)
return;
var currentLength = Rope.Collapse(Input.TextRope).Length;
var isOverLimit = currentLength > _maxCharactersPerPage;
CharacterCountLabel.Text = Loc.GetString("book-character-count",
("current", currentLength),
("max", _maxCharactersPerPage));
if (isOverLimit)
{
CharacterCountLabel.StyleClasses.Clear();
CharacterCountLabel.StyleClasses.Add("LabelDanger");
}
else
{
CharacterCountLabel.StyleClasses.Clear();
CharacterCountLabel.StyleClasses.Add("LabelSubText");
}
}
private void DeleteCurrentPage()
{
if (_pages.Count <= 1 || _currentPageIndex < 0 || _currentPageIndex >= _pages.Count)
return;
OnPageDeleted?.Invoke(_currentPageIndex);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
PrevPageButton.OnPressed -= OnPrevPressed;
NextPageButton.OnPressed -= OnNextPressed;
AddPageButton.OnPressed -= OnAddPagePressed;
SaveButton.OnPressed -= OnSavePressed;
DeletePageButton.OnPressed -= OnDeletePressed;
PageNumberInput.OnTextEntered -= OnPageNumberEntered;
PageNumberInput.OnFocusExit -= OnPageNumberEntered;
ToggleBookmarkButton.OnPressed -= OnToggleBookmarkPressed;
BookmarkDropdown.OnItemSelected -= OnBookmarkSelected;
BookmarksToggleButton.OnPressed -= OnBookmarksTogglePressed;
if (_bookmarkDialog != null)
{
if (_bookmarkDialogOnClose != null)
_bookmarkDialog.OnClose -= _bookmarkDialogOnClose;
_bookmarkDialog = null;
_bookmarkDialogOnClose = null;
}
}
}