diff --git a/Content.Client/Canvas/CanvasComponent.cs b/Content.Client/Canvas/CanvasComponent.cs new file mode 100644 index 00000000000..14d326f2016 --- /dev/null +++ b/Content.Client/Canvas/CanvasComponent.cs @@ -0,0 +1,20 @@ +using Content.Client.Canvas.Ui; +using Content.Shared.Canvas; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Color = Robust.Shared.Maths.Color; + +namespace Content.Client.Canvas +{ + [RegisterComponent] + public sealed partial class CanvasComponent : SharedCanvasComponent + { + [ViewVariables(VVAccess.ReadWrite)] + public bool UIUpdateNeeded; + } +} diff --git a/Content.Client/Canvas/CanvasSystem.cs b/Content.Client/Canvas/CanvasSystem.cs new file mode 100644 index 00000000000..98273e3725f --- /dev/null +++ b/Content.Client/Canvas/CanvasSystem.cs @@ -0,0 +1,226 @@ +using Content.Client.Items; +using Content.Client.Message; +using Content.Client.Stylesheets; +using Content.Shared.Canvas; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Localization; +using Robust.Shared.Timing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp; +using static Content.Shared.Canvas.SharedCanvasComponent; +using Color = Robust.Shared.Maths.Color; +using Robust.Client.Graphics; +using System.Globalization; +using System.Linq; + +namespace Content.Client.Canvas +{ + public sealed class CanvasSystem : SharedCanvasSystem + { + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnCanvasHandleState); + Subs.ItemStatus(ent => new StatusControl(ent)); + } + + private void OnCanvasHandleState(EntityUid uid, CanvasComponent component, ref ComponentHandleState args) + { + if (args.Current is not CanvasComponentState state) return; + + component.Color = state.Color; + component.SelectedState = state.State; + component.PaintingCode = state.PaintingCode; + component.Height = state.Height; + component.Width = state.Width; + component.Artist = state.Artist; + component.SizeMultiplier = state.SizeMultiplier; + component.Signature = state.Signature; + + component.UIUpdateNeeded = true; + + if (!string.IsNullOrEmpty(component.Artist)) + { + UpdateSprite(uid, component.PaintingCode, component.Height, component.Width, component.SizeMultiplier); + } + } + + public void UpdateSprite(EntityUid uid, string code, int height = 16, int width = 16, int sizeMultiplier = 2) + { + Logger.Info($"gerando arte system."); + if (string.IsNullOrEmpty(code)) + return; + // Update the sprite or visuals based on the artist + if (EntityManager.TryGetComponent(uid, out var sprite)) + { + // Change sprite texture based on artist name + var texture = GenerateArtistTexture(code, height, width, sizeMultiplier); // Implement this method + sprite.LayerSetTexture(0, texture); // Assuming layer 0; adjust as needed + } + } + + private Texture GenerateArtistTexture(string code, int height = 16, int width = 16, int sizeMultiplier = 2) + { + if (height > 32 || width > 32) + sizeMultiplier = 1; + var image = new Image(width * sizeMultiplier, height * sizeMultiplier); + + // Parse the code string into color segments + var colorSegments = code.Split(';', StringSplitOptions.RemoveEmptyEntries); + + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + // Calculate the index in the color segments array + int index = row * width + col; + + // Get the color, defaulting to white if the index is out of bounds + Color color = index < colorSegments.Length + ? ParseColor(colorSegments[index]) + : Color.White; + + // Fill the corresponding area in the image + for (int x = col * sizeMultiplier; x < (col + 1) * sizeMultiplier; x++) + { + for (int y = row * sizeMultiplier; y < (row + 1) * sizeMultiplier; y++) + { + image[x, y] = new Rgba32(color.R, color.G, color.B, color.A); + } + } + } + } + + // Convert the image to a texture + return Texture.LoadFromImage(image, "DynamicCanvas"); + } + + private Color ParseColor(string colorSegment) + { + colorSegment = new string(colorSegment + .Where(c => !char.IsWhiteSpace(c) && !char.IsControl(c)) + .ToArray()); + // Split the segment into individual color components + colorSegment = colorSegment.Replace('.', ','); + var values = colorSegment.Split('|'); + + // Validate the number of components + if (values.Length != 4) + { + Logger.ErrorS("canvas", $"Invalid color segment format: '{colorSegment}'"); + return Color.White; // Default to white on error + } + + // Convert components to float + if (!TryParseFloat(values[0], out float r) || + !TryParseFloat(values[1], out float g) || + !TryParseFloat(values[2], out float b) || + !TryParseFloat(values[3], out float a)) + { + Logger.ErrorS("canvas", $"Failed to parse color segment '{colorSegment}'"); + return Color.White; // Default to white on error + } + + // Return the parsed color + return new Color(r, g, b, a); + } + + + private bool TryParseFloat(string input, out float result) + { + result = 0.0f; + + if (string.IsNullOrEmpty(input)) + return false; + + // Replace ',' with '.' if necessary (manual handling of decimal separator) + input = input.Replace(',', '.'); + + // Validate and parse manually + bool isNegative = false; + int intPart = 0; + float fracPart = 0.0f; + float fracDivisor = 1.0f; + bool isFraction = false; + + for (int i = 0; i < input.Length; i++) + { + char c = input[i]; + + if (c == '-' && i == 0) + { + isNegative = true; + continue; + } + else if (c == '.' && !isFraction) + { + isFraction = true; + continue; + } + else if (c >= '0' && c <= '9') + { + if (isFraction) + { + fracDivisor *= 10; + fracPart += (c - '0') / fracDivisor; + } + else + { + intPart = intPart * 10 + (c - '0'); + } + } + else + { + return false; // Invalid character + } + } + + result = intPart + fracPart; + if (isNegative) + result = -result; + + // Clamp the value to ensure it is within the range [0.0, 1.0] + result = MathF.Max(0.0f, MathF.Min(1.0f, result)); + return true; + } + + private sealed class StatusControl : Control + { + private readonly CanvasComponent _parent; + private readonly RichTextLabel _label; + + public StatusControl(CanvasComponent parent) + { + _parent = parent; + _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; + AddChild(_label); + + parent.UIUpdateNeeded = true; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (!_parent.UIUpdateNeeded) + { + return; + } + + _parent.UIUpdateNeeded = false; + _label.SetMarkup(Robust.Shared.Localization.Loc.GetString("Canvas-drawing-label", + ("color", _parent.Color), + ("state", _parent.SelectedState), + ("paintingcode", _parent.PaintingCode), + ("height", _parent.Height), + ("width", _parent.Width), + ("artist", _parent.Artist))); + } + } + } +} diff --git a/Content.Client/Canvas/Ui/CanvasBoundUserInterface.cs b/Content.Client/Canvas/Ui/CanvasBoundUserInterface.cs new file mode 100644 index 00000000000..14b48d85fa4 --- /dev/null +++ b/Content.Client/Canvas/Ui/CanvasBoundUserInterface.cs @@ -0,0 +1,180 @@ +using Content.Client.UserInterface; +using Content.Shared.Canvas; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; +using static Content.Shared.Canvas.SharedCanvasComponent; +using static Robust.Client.UserInterface.Controls.MenuBar; +using System.ComponentModel; +using Color = Robust.Shared.Maths.Color; + +namespace Content.Client.Canvas.Ui +{ + public sealed class CanvasBoundUserInterface : BoundUserInterface + { + [Dependency] private readonly IPrototypeManager _protoManager = default!; + + [ViewVariables] + private CanvasWindow? _window; + + public CanvasBoundUserInterface(EntityUid owner, object uiKey) : base(owner, (Enum) uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = this.CreateWindow(); + _window.OnColorSelected += SelectColor; + _window.OnSelected += Select; + _window.OnFinalize += Finalize; + _window.OnSignature += Signature; + _window.OnResizeHeight += ResizeHeight; + _window.OnResizeWidth += ResizeWidth; + _window.OnClose += Close; + PopulateCanvas(Owner); + _window.OpenCentered(); + } + + private void PopulateCanvas(EntityUid uid) + { + var colors = new List + { + Color.Transparent, + Color.White, // Transparent / White + Color.Gray, // Darker White (lowercase) + Color.Red, + Color.DarkRed, // Darker Red (lowercase) + Color.Green, + Color.DarkGreen, // Darker Green (lowercase) + Color.Blue, + Color.DarkBlue, // Darker Blue (lowercase) + Color.Yellow, + Color.LightYellow, // Darker Yellow (lowercase) + Color.Cyan, + Color.DarkCyan, // Darker Cyan (lowercase) + Color.Magenta, + Color.DarkMagenta, // Darker Magenta (lowercase) + new Color(1.0f, 0.65f, 0.0f), // Orange + new Color(0.5f, 0.32f, 0.0f), // Darker Orange (lowercase) + new Color(0.75f, 0.0f, 0.75f), // Purple + new Color(0.37f, 0.0f, 0.37f), // Darker Purple (lowercase) + new Color(0.33f, 0.55f, 0.2f), // Teal + new Color(0.16f, 0.27f, 0.1f), // Darker Teal (lowercase) + new Color(0.6f, 0.3f, 0.1f), // Brown + new Color(0.3f, 0.15f, 0.05f), // Darker Brown (lowercase) + new Color(0.9f, 0.8f, 0.7f), // Beige + new Color(0.45f, 0.4f, 0.35f), // Darker Beige (lowercase) + Color.LightGray, + Color.DarkGray, // Darker Light Gray (lowercase) + Color.Gray, // Dark Gray + new Color(0.2f, 0.2f, 0.2f), // Darker Gray (lowercase) + new Color(0.5f, 0.5f, 1.0f), // Pastel Blue + new Color(0.25f, 0.25f, 0.5f), // Darker Pastel Blue (lowercase) + new Color(1.0f, 0.5f, 0.5f), // Pastel Pink + new Color(0.5f, 0.25f, 0.25f), // Darker Pastel Pink (lowercase) + new Color(0.0f, 0.5f, 0.5f), // Dark Cyan + new Color(0.0f, 0.25f, 0.25f), // Darker Dark Cyan (lowercase) + new Color(0.4f, 0.2f, 0.6f), // Deep Purple + new Color(0.2f, 0.1f, 0.3f), // Darker Deep Purple (lowercase) + Color.Black, // Darker Black (lowercase) + Color.Black, // Black + new Color(0.76f, 0.29f, 0.56f), // New Color 1 (Pinkish) + new Color(0.38f, 0.15f, 0.28f), // Darker New Color 1 (lowercase) + new Color(0.13f, 0.14f, 0.25f), // New Color 2 (Dark Blue) + new Color(0.06f, 0.07f, 0.13f), // Darker New Color 2 (lowercase) + new Color(0.73f, 0.47f, 0.25f), // New Color 3 (Brownish) + new Color(0.36f, 0.24f, 0.13f), // Darker New Color 3 (lowercase) + new Color(0.05f, 0.05f, 0.05f), // New Color 4 (Black) + new Color(0.02f, 0.02f, 0.02f), // Darker New Color 4 (lowercase) + new Color(0.48f, 0.26f, 0.22f), // New Color 5 (Muted Brown) + new Color(0.24f, 0.13f, 0.11f), // Darker New Color 5 (lowercase) + new Color(0.61f, 0.37f, 0.29f), // New Color 6 (Light Brown) + new Color(0.3f, 0.18f, 0.15f), // Darker New Color 6 (lowercase) + new Color(0.13f, 0.18f, 0.3f), // New Color 7 (Darker Blue) + new Color(0.06f, 0.09f, 0.15f), // Darker New Color 7 (lowercase) + new Color(0.53f, 0.26f, 0.18f), // New Color 8 (Orange-Brown) + new Color(0.27f, 0.13f, 0.09f), // Darker New Color 8 (lowercase) + new Color(1.0f, 0.35f, 0.38f), // New Color 9 (Red) + new Color(0.5f, 0.17f, 0.19f), // Darker New Color 9 (lowercase) + new Color(0.06f, 0.08f, 0.19f), // New Color 10 (Dark Purple) + new Color(0.03f, 0.04f, 0.09f) // Darker New Color 10 (lowercase) + }; + + + EntMan.TryGetComponent(Owner, out var canvasComponent); + if (canvasComponent == null || _window == null) + return; + + // Set properties from canvasComponent to the window + _window.SetPaintingCode(canvasComponent?.PaintingCode ?? string.Empty); + _window.SetHeight(canvasComponent?.Height ?? 16); + _window.SetWidth(canvasComponent?.Width ?? 16); + _window.SetSignature(canvasComponent?.Signature ?? string.Empty); + + + if (!string.IsNullOrEmpty(canvasComponent?.Artist)) + { + _window.SetArtist(canvasComponent.Artist); + } + _window?.PopulateColorSelector(colors); + _window?.PopulatePaintingGrid(); + } + + + public override void OnProtoReload(PrototypesReloadedEventArgs args) + { + base.OnProtoReload(args); + } + + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + base.ReceiveMessage(message); + + if (_window is null || message is not CanvasUsedMessage canvasMessage) + return; + + _window.AdvanceState(canvasMessage.DrawnDecal); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + + var castState = (CanvasBoundUserInterfaceState) state; + + _window?.UpdateState(castState); + } + + public void Select(string state) + { + SendMessage(new CanvasSelectMessage(state)); + } + + public void Finalize(string state) + { + SendMessage(new CanvasFinalizeMessage(state)); + } + public void Signature(string state) + { + SendMessage(new CanvasSignatureMessage(state)); + } + public void ResizeHeight(int height) + { + SendMessage(new CanvasHeightMessage(height)); + } + public void ResizeWidth(int width) + { + SendMessage(new CanvasWidthMessage(width)); + } + + public void SelectColor(Color color) + { + SendMessage(new CanvasColorMessage(color)); + } + } +} diff --git a/Content.Client/Canvas/Ui/CanvasWindow.xaml b/Content.Client/Canvas/Ui/CanvasWindow.xaml new file mode 100644 index 00000000000..12a7f1b55c8 --- /dev/null +++ b/Content.Client/Canvas/Ui/CanvasWindow.xaml @@ -0,0 +1,58 @@ + + + +