fish-station/Content.Client/Paper/UI/PaperWindow.xaml.cs
Vigers Ray d6c0a011b7 Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/workflows/publish.yml
#	Content.Client/Clothing/ClientClothingSystem.cs
#	Content.Client/Clothing/Systems/ChameleonClothingSystem.cs
#	Content.Client/Fax/UI/FaxWindow.xaml
#	Content.Client/Ghost/GhostSystem.cs
#	Content.Client/Holiday/HolidaySystem.cs
#	Content.Client/Humanoid/HumanoidAppearanceSystem.cs
#	Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
#	Content.Client/Lobby/UI/LobbyGui.xaml
#	Content.Client/Lobby/UI/LobbyGui.xaml.cs
#	Content.Client/Mech/MechSystem.cs
#	Content.Client/Stealth/StealthSystem.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs
#	Content.Server/Bed/BedSystem.cs
#	Content.Server/Body/Systems/BodySystem.cs
#	Content.Server/Body/Systems/RespiratorSystem.cs
#	Content.Server/Cargo/Systems/CargoSystem.Orders.cs
#	Content.Server/Communications/CommunicationsConsoleComponent.cs
#	Content.Server/EntityEffects/Effects/AreaReactionEffect.cs
#	Content.Server/EntityEffects/Effects/MakeSentient.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs
#	Content.Server/EntityEffects/Effects/PlantMutateGases.cs
#	Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
#	Content.Server/GameTicking/GameTicker.Spawning.cs
#	Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Server/Power/EntitySystems/PowerNetSystem.cs
#	Content.Server/Preferences/Managers/ServerPreferencesManager.cs
#	Content.Server/RoundEnd/RoundEndSystem.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
#	Content.Server/Station/Systems/StationSpawningSystem.cs
#	Content.Server/VoiceMask/VoiceMaskSystem.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Shared/Bed/Components/HealOnBuckleComponent.cs
#	Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
#	Content.Shared/EntityEffects/Effects/ExplosionReactionEffect.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Preferences/Loadouts/RoleLoadout.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Wieldable/SharedWieldableSystem.cs
#	Resources/Audio/Machines/attributions.yml
#	Resources/Locale/en-US/_strings/atmos/plaque-component.ftl
#	Resources/Locale/en-US/_strings/chat/chat-channel.ftl
#	Resources/Locale/en-US/_strings/components/screen-component.ftl
#	Resources/Locale/en-US/_strings/inventory/components/slot-block-component.ftl
#	Resources/Locale/en-US/_strings/mass-media/news-discord.ftl
#	Resources/Locale/en-US/_strings/objectives/commands/stripall.ftl
#	Resources/Locale/en-US/_strings/salvage/job-board.ftl
#	Resources/Locale/en-US/_strings/ui/spray-painter-window.ftl
#	Resources/Locale/en-US/_strings/ui/turret-controls.ftl
#	Resources/Maps/_Sunrise/Station/box.yml
#	Resources/Prototypes/Access/misc.yml
#	Resources/Prototypes/Catalog/Fills/Boxes/emergency.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/service.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/suit_storage.yml
#	Resources/Prototypes/Entities/Clothing/Head/helmets.yml
#	Resources/Prototypes/Entities/Clothing/Masks/masks.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
#	Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
#	Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Player/dragon.yml
#	Resources/Prototypes/Entities/Mobs/Player/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Species/arachnid.yml
#	Resources/Prototypes/Entities/Mobs/Species/moth.yml
#	Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
#	Resources/Prototypes/LootTables/suspicion_loot_table.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/science.yml
#	Resources/Prototypes/Recipes/Lathes/medical.yml
#	Resources/Prototypes/SoundCollections/NukeMusic.yml
#	Resources/ServerInfo/Guidebook/Cargo/Salvage.xml
#	Resources/Textures/Clothing/Hands/Gloves/KnuckleDusters/brassknuckleduster.rsi/equipped-OUTERCLOTHING-reptilian.png
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/basic.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/alien3.png
#	Resources/Textures/Effects/speech.rsi/alienroyal3.png
#	Resources/Textures/Effects/speech.rsi/default3.png
#	Resources/Textures/Effects/speech.rsi/guardian3.png
#	Resources/Textures/Effects/speech.rsi/holo3.png
#	Resources/Textures/Effects/speech.rsi/lawyer3.png
#	Resources/Textures/Effects/speech.rsi/lizard3.png
#	Resources/Textures/Effects/speech.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/moth3.png
#	Resources/Textures/Effects/speech.rsi/robot3.png
#	Resources/Textures/Effects/speech.rsi/slime3.png
#	Resources/Textures/Effects/speech.rsi/spider3.png
#	Resources/Textures/Effects/speech.rsi/syndibot3.png
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_f.png
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_m.png
#	Resources/Textures/Objects/Misc/id_cards.rsi/meta.json
#	Resources/Textures/Structures/Doors/Airlocks/Glass/external.rsi/emergency_unlit.png
#	Resources/Textures/Structures/Piping/disposal.rsi/meta.json
#	Resources/Textures/Tiles/attributions.yml
#	Resources/Textures/Tiles/steel_dirty.png
#	Resources/migration.yml
#	runclient-Tools.bat
#	runclient.bat
2025-05-27 03:41:41 +03:00

362 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Numerics;
using Content.Client._Sunrise.UserInterface.RichText;
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),
typeof(CenterTag),
typeof(TextureTag) // Не дай бог игроки узнают что можно использовать данный ТЭГ для рофлов.
};
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);
// Then the footer
if (visuals.FooterImagePath is {} path)
{
FooterImage.TexturePath = path.ToString();
FooterImage.MinSize = FooterImage.TextureNormal?.Size ?? Vector2.Zero;
}
FooterImage.ModulateSelfOverride = visuals.FooterImageModulate;
FooterImage.Margin = new Thickness(visuals.FooterMargin.Left, visuals.FooterMargin.Top,
visuals.FooterMargin.Right, visuals.FooterMargin.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;
}
}
}
}