| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- using System.Numerics;
- using Content.Shared.Paper;
- using Robust.Client.AutoGenerated;
- using Robust.Client.Graphics;
- using Robust.Client.Input;
- using Robust.Client.ResourceManagement;
- using Robust.Client.UserInterface.CustomControls;
- using Robust.Client.UserInterface.Controls;
- using Robust.Client.UserInterface.XAML;
- using Robust.Shared.Utility;
- using Robust.Client.UserInterface.RichText;
- using Content.Client.UserInterface.RichText;
- using Robust.Shared.Input;
- namespace Content.Client.Paper.UI
- {
- [GenerateTypedNameReferences]
- public sealed partial class PaperWindow : BaseWindow
- {
- [Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly IResourceCache _resCache = default!;
- private static Color DefaultTextColor = new(25, 25, 25);
- // <summary>
- // Size of resize handles around the paper
- private const int DRAG_MARGIN_SIZE = 16;
- // We keep a reference to the paper content texture that we create
- // so that we can modify it later.
- private StyleBoxTexture _paperContentTex = new();
- // The number of lines that the content image represents.
- // See PaperVisualsComponent.ContentImageNumLines.
- private float _paperContentLineScale = 1.0f;
- // If paper limits the size in one or both axes, it'll affect whether
- // we're able to resize this UI or not. Default to everything enabled:
- private DragMode _allowedResizeModes = ~DragMode.None;
- private readonly Type[] _allowedTags = new Type[] {
- typeof(BoldItalicTag),
- typeof(BoldTag),
- typeof(BulletTag),
- typeof(ColorTag),
- typeof(HeadingTag),
- typeof(ItalicTag),
- typeof(MonoTag)
- };
- public event Action<string>? OnSaved;
- private int _MaxInputLength = -1;
- public int MaxInputLength
- {
- get
- {
- return _MaxInputLength;
- }
- set
- {
- _MaxInputLength = value;
- UpdateFillState();
- }
- }
- public PaperWindow()
- {
- IoCManager.InjectDependencies(this);
- RobustXamlLoader.Load(this);
- // We can't configure the RichTextLabel contents from xaml, so do it here:
- BlankPaperIndicator.SetMessage(Loc.GetString("paper-ui-blank-page-message"), null, DefaultTextColor);
- // Hook up the close button:
- CloseButton.OnPressed += _ => Close();
- Input.OnKeyBindDown += args => // Solution while TextEdit don't have events
- {
- if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
- {
- // SaveButton is disabled when we hit the max input limit. Just check
- // that flag instead of trying to calculate the input length again
- if (!SaveButton.Disabled)
- {
- RunOnSaved();
- args.Handle();
- }
- }
- };
- Input.OnTextChanged += args =>
- {
- UpdateFillState();
- };
- SaveButton.OnPressed += _ =>
- {
- RunOnSaved();
- };
- SaveButton.Text = Loc.GetString("paper-ui-save-button",
- ("keybind", _inputManager.GetKeyFunctionButtonString(EngineKeyFunctions.MultilineTextSubmit)));
- }
- /// <summary>
- /// Initialize this UI according to <code>visuals</code> Initializes
- /// textures, recalculates sizes, and applies some layout rules.
- /// </summary>
- public void InitVisuals(EntityUid entity, PaperVisualsComponent visuals)
- {
- // Randomize the placement of any stamps based on the entity UID
- // so that there's some variety in different papers.
- StampDisplay.PlacementSeed = (int)entity;
- // Initialize the background:
- PaperBackground.ModulateSelfOverride = visuals.BackgroundModulate;
- var backgroundImage = visuals.BackgroundImagePath != null? _resCache.GetResource<TextureResource>(visuals.BackgroundImagePath) : null;
- if (backgroundImage != null)
- {
- var backgroundImageMode = visuals.BackgroundImageTile ? StyleBoxTexture.StretchMode.Tile : StyleBoxTexture.StretchMode.Stretch;
- var backgroundPatchMargin = visuals.BackgroundPatchMargin;
- PaperBackground.PanelOverride = new StyleBoxTexture
- {
- Texture = backgroundImage,
- TextureScale = visuals.BackgroundScale,
- Mode = backgroundImageMode,
- PatchMarginLeft = backgroundPatchMargin.Left,
- PatchMarginBottom = backgroundPatchMargin.Bottom,
- PatchMarginRight = backgroundPatchMargin.Right,
- PatchMarginTop = backgroundPatchMargin.Top
- };
- }
- else
- {
- PaperBackground.PanelOverride = null;
- }
- // Then the header:
- if (visuals.HeaderImagePath != null)
- {
- HeaderImage.TexturePath = visuals.HeaderImagePath;
- HeaderImage.MinSize = HeaderImage.TextureNormal?.Size ?? Vector2.Zero;
- }
- HeaderImage.ModulateSelfOverride = visuals.HeaderImageModulate;
- HeaderImage.Margin = new Thickness(visuals.HeaderMargin.Left, visuals.HeaderMargin.Top,
- visuals.HeaderMargin.Right, visuals.HeaderMargin.Bottom);
- PaperContent.ModulateSelfOverride = visuals.ContentImageModulate;
- WrittenTextLabel.ModulateSelfOverride = visuals.FontAccentColor;
- FillStatus.ModulateSelfOverride = visuals.FontAccentColor;
- var contentImage = visuals.ContentImagePath != null ? _resCache.GetResource<TextureResource>(visuals.ContentImagePath) : null;
- if (contentImage != null)
- {
- // Setup the paper content texture, but keep a reference to it, as we can't set
- // some font-related properties here. We'll fix those up later, in Draw()
- _paperContentTex = new StyleBoxTexture
- {
- Texture = contentImage,
- Mode = StyleBoxTexture.StretchMode.Tile,
- };
- PaperContent.PanelOverride = _paperContentTex;
- _paperContentLineScale = visuals.ContentImageNumLines;
- }
- PaperContent.Margin = new Thickness(
- visuals.ContentMargin.Left, visuals.ContentMargin.Top,
- visuals.ContentMargin.Right, visuals.ContentMargin.Bottom);
- if (visuals.MaxWritableArea != null)
- {
- var a = (Vector2)visuals.MaxWritableArea;
- // Paper has requested that this has a maximum area that you can write on.
- // So, we'll make the window non-resizable and fix the size of the content.
- // Ideally, would like to be able to allow resizing only one direction.
- ScrollingContents.MinSize = Vector2.Zero;
- ScrollingContents.MinSize = a;
- if (a.X > 0.0f)
- {
- ScrollingContents.MaxWidth = a.X;
- _allowedResizeModes &= ~(DragMode.Left | DragMode.Right);
- // Since this dimension has been specified by the user, we
- // need to undo the SetSize which was configured in the xaml.
- // Controls use NaNs to indicate unset for this value.
- // This is leaky - there should be a method for this
- SetWidth = float.NaN;
- }
- if (a.Y > 0.0f)
- {
- ScrollingContents.MaxHeight = a.Y;
- _allowedResizeModes &= ~(DragMode.Top | DragMode.Bottom);
- SetHeight = float.NaN;
- }
- }
- }
- /// <summary>
- /// Control interface. We'll mostly rely on the children to do the drawing
- /// but in order to get lines on paper to match up with the rich text labels,
- /// we need to do a small calculation to sync them up.
- /// </summary>
- protected override void Draw(DrawingHandleScreen handle)
- {
- // Now do the deferred setup of the written area. At the point
- // that InitVisuals runs, the label hasn't had it's style initialized
- // so we need to get some info out now:
- if (WrittenTextLabel.TryGetStyleProperty<Font>("font", out var font))
- {
- float fontLineHeight = font.GetLineHeight(1.0f);
- // This positions the texture so the font baseline is on the bottom:
- _paperContentTex.ExpandMarginTop = font.GetDescent(UIScale);
- // And this scales the texture so that it's a single text line:
- var scaleY = (_paperContentLineScale * fontLineHeight) / _paperContentTex.Texture?.Height ?? fontLineHeight;
- _paperContentTex.TextureScale = new Vector2(1, scaleY);
- // Now, we might need to add some padding to the text to ensure
- // that, even if a header is specified, the text will line up with
- // where the content image expects the font to be rendered (i.e.,
- // adjusting the height of the header image shouldn't cause the
- // text to be offset from a line)
- {
- var headerHeight = HeaderImage.Size.Y + HeaderImage.Margin.Top + HeaderImage.Margin.Bottom;
- var headerInLines = headerHeight / (fontLineHeight * _paperContentLineScale);
- var paddingRequiredInLines = (float)Math.Ceiling(headerInLines) - headerInLines;
- var verticalMargin = fontLineHeight * paddingRequiredInLines * _paperContentLineScale;
- TextAlignmentPadding.Margin = new Thickness(0.0f, verticalMargin, 0.0f, 0.0f);
- }
- }
- base.Draw(handle);
- }
- /// <summary>
- /// Initialize the paper contents, i.e. the text typed by the
- /// user and any stamps that have peen put on the page.
- /// </summary>
- public void Populate(PaperComponent.PaperBoundUserInterfaceState state)
- {
- bool isEditing = state.Mode == PaperComponent.PaperAction.Write;
- bool wasEditing = InputContainer.Visible;
- InputContainer.Visible = isEditing;
- EditButtons.Visible = isEditing;
- var msg = new FormattedMessage();
- msg.AddMarkupPermissive(state.Text);
- // For premade documents, we want to be able to edit them rather than
- // replace them.
- var shouldCopyText = 0 == Input.TextLength && 0 != state.Text.Length;
- if (!wasEditing || shouldCopyText)
- {
- // We can get repeated messages with state.Mode == Write if another
- // player opens the UI for reading. In this case, don't update the
- // text input, as this player is currently writing new text and we
- // don't want to lose any text they already input.
- Input.TextRope = Rope.Leaf.Empty;
- Input.CursorPosition = new TextEdit.CursorPos();
- Input.InsertAtCursor(state.Text);
- }
- for (var i = 0; i <= state.StampedBy.Count * 3 + 1; i++)
- {
- msg.AddMarkupPermissive("\r\n");
- }
- WrittenTextLabel.SetMessage(msg, _allowedTags, DefaultTextColor);
- WrittenTextLabel.Visible = !isEditing && state.Text.Length > 0;
- BlankPaperIndicator.Visible = !isEditing && state.Text.Length == 0;
- StampDisplay.RemoveAllChildren();
- StampDisplay.RemoveStamps();
- foreach(var stamper in state.StampedBy)
- {
- StampDisplay.AddStamp(new StampWidget{ StampInfo = stamper });
- }
- }
- /// <summary>
- /// BaseWindow interface. Allow users to drag UI around by grabbing
- /// anywhere on the page (like FancyWindow) but try to calculate
- /// reasonable dragging bounds because this UI can have round corners,
- /// and it can be hard to judge where to click to resize.
- /// </summary>
- protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
- {
- var mode = DragMode.None;
- // Be quite generous with resize margins:
- if (relativeMousePos.Y < DRAG_MARGIN_SIZE)
- {
- mode |= DragMode.Top;
- }
- else if (relativeMousePos.Y > Size.Y - DRAG_MARGIN_SIZE)
- {
- mode |= DragMode.Bottom;
- }
- if (relativeMousePos.X < DRAG_MARGIN_SIZE)
- {
- mode |= DragMode.Left;
- }
- else if (relativeMousePos.X > Size.X - DRAG_MARGIN_SIZE)
- {
- mode |= DragMode.Right;
- }
- if((mode & _allowedResizeModes) == DragMode.None)
- {
- return DragMode.Move;
- }
- return mode & _allowedResizeModes;
- }
- private void RunOnSaved()
- {
- // Prevent further saving while text processing still in
- SaveButton.Disabled = true;
- OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
- }
- private void UpdateFillState()
- {
- if (MaxInputLength != -1)
- {
- var inputLength = Input.TextLength;
- FillStatus.Text = Loc.GetString("paper-ui-fill-level",
- ("currentLength", inputLength),
- ("maxLength", MaxInputLength));
- // Disable the save button if we've gone over the limit
- SaveButton.Disabled = inputLength > MaxInputLength;
- }
- else
- {
- FillStatus.Text = "";
- SaveButton.Disabled = false;
- }
- }
- }
- }
|