6
StarHorizon_Public/Content.Client/_NF/Research/UI/FancyResearchConsoleMenu.xaml.cs
2026-01-24 12:49:55 +03:00

352 lines
11 KiB
C#

using Content.Client.Parallax;
using Content.Client.Research;
using Content.Client.UserInterface.Controls;
using Content.Shared._NF.Research;
using Content.Shared.Access.Systems;
using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Numerics;
namespace Content.Client._NF.Research.UI;
[GenerateTypedNameReferences]
public sealed partial class FancyResearchConsoleMenu : FancyWindow
{
public Action<string>? OnTechnologyCardPressed;
public Action? OnServerButtonPressed;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
private readonly ResearchSystem _research;
private readonly SpriteSystem _sprite;
private readonly AccessReaderSystem _accessReader;
private ParallaxControl _parallaxControl;
/// <summary>
/// The parallax prototype to use for the background. Configurable.
/// </summary>
public string ParallaxPrototype { get; set; } = "Default";
/// <summary>
/// Updates the parallax background to use a different prototype
/// </summary>
/// <param name="parallaxPrototype">The new parallax prototype to use</param>
public void SetParallaxPrototype(string parallaxPrototype)
{
ParallaxPrototype = parallaxPrototype;
if (_parallaxControl != null)
{
_parallaxControl.ParallaxPrototype = parallaxPrototype;
}
}
/// <summary>
/// Console entity
/// </summary>
public EntityUid Entity;
/// <summary>
/// Currently selected tech
/// Exists for better UI refreshing
/// </summary>
public ProtoId<TechnologyPrototype>? CurrentTech;
/// <summary>
/// All technologies and their availability
/// </summary>
public Dictionary<string, ResearchAvailability> List = new();
/// <summary>
/// Cached research points
/// </summary>
public int Points = 0;
/// <summary>
/// Is tech currently being dragged
/// </summary>
private bool _draggin;
/// <summary>
/// the distance between elements on the grid.
/// </summary>
private const int GridSize = 90;
/// <summary>
/// technology cards size.
/// </summary>
private const int CardSize = 64;
/// <summary>
/// the origin point of the grid.
/// </summary>
private static readonly Vector2i DefaultPosition = Vector2i.Zero;
private Box2i _bounds = new(DefaultPosition, DefaultPosition);
public FancyResearchConsoleMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_research = _entity.System<ResearchSystem>();
_sprite = _entity.System<SpriteSystem>();
_accessReader = _entity.System<AccessReaderSystem>();
// Set up scroll container properties
TechScrollContainer.ScrollSpeedX = 100;
TechScrollContainer.ScrollSpeedY = 100;
TechScrollContainer.HScrollEnabled = false;
TechScrollContainer.VScrollEnabled = true;
// Frontier: Initialize parallax background
_parallaxControl = new ParallaxControl
{
ParallaxPrototype = ParallaxPrototype,
HorizontalExpand = true,
VerticalExpand = true,
};
// Add the parallax control to the ResearchesContainer at the beginning (bottom layer)
ResearchesContainer.AddChild(_parallaxControl);
// Set the proper rendering order by adjusting positions in the parent's child list
// Controls with a higher position value are drawn on top (foreground)
// Make sure the parallax is at the bottom of the z-order (drawn first)
_parallaxControl.SetPositionInParent(0);
// The drag container should be in the middle
TechScrollContainer.SetPositionInParent(1);
// Apply scrollbar styling
ApplyScrollbarStyling();
// Set up event handlers
ServerButton.OnPressed += _ => OnServerButtonPressed?.Invoke();
DragContainer.OnKeyBindDown += OnKeybindDown;
DragContainer.OnKeyBindUp += OnKeybindUp;
RecenterButton.OnPressed += _ => Recenter();
// Empty initialization
UpdatePanels(List);
}
/// <summary>
/// Apply scrollbar styling using centralized colors and utilities
/// </summary>
private void ApplyScrollbarStyling()
{
var scrollBarNormal = ResearchUIHelpers.CreateScrollbarStyleBox("normal");
var scrollBarHovered = ResearchUIHelpers.CreateScrollbarStyleBox("hovered");
var scrollBarGrabbed = ResearchUIHelpers.CreateScrollbarStyleBox("grabbed");
}
public void SetEntity(EntityUid entity)
=> Entity = entity;
public void UpdatePanels(Dictionary<string, ResearchAvailability> dict)
{
// Clear existing items
DragContainer.RemoveAllChildren();
List = dict;
var bounds = new Box2i();
var boundsSet = false;
// Calculate bounds
foreach (var tech in List)
{
var proto = _prototype.Index<TechnologyPrototype>(tech.Key);
var position = DefaultPosition + (GridSize * proto.Position.X, GridSize * proto.Position.Y);
if (!boundsSet)
{
bounds.BottomLeft = position;
bounds.TopRight = position;
boundsSet = true;
}
else
{
bounds.Left = int.Min(position.X, bounds.Left);
bounds.Bottom = int.Min(position.Y, bounds.Bottom);
bounds.Right = int.Max(position.X, bounds.Right);
bounds.Top = int.Max(position.Y, bounds.Top);
}
}
if (boundsSet)
{
_bounds = bounds;
// Set container size with padding
var padding = 200;
var totalWidth = _bounds.Width + CardSize + padding;
var totalHeight = _bounds.Height + CardSize + padding;
DragContainer.SetWidth = Math.Max(totalWidth, 1000);
DragContainer.SetHeight = Math.Max(totalHeight, 1000);
}
// Add tech items
foreach (var tech in List)
{
var proto = _prototype.Index<TechnologyPrototype>(tech.Key);
var control = new FancyResearchConsoleItem(proto, _sprite, tech.Value);
DragContainer.AddChild(control);
// Position the tech item
var leftPadding = 20;
var topPadding = 20;
var uiPosition = new Vector2(
proto.Position.X * GridSize - _bounds.Left + leftPadding,
proto.Position.Y * GridSize - _bounds.Bottom + topPadding
);
LayoutContainer.SetPosition(control, uiPosition);
control.SelectAction += SelectTech;
control.IsSelected = tech.Key == CurrentTech;
}
}
public void UpdateInformationPanel(int points)
{
Points = points;
var amountMsg = new FormattedMessage();
amountMsg.AddMarkupOrThrow(Loc.GetString("research-console-menu-research-points-text",
("points", points)));
ResearchAmountLabel.SetMessage(amountMsg);
if (!_entity.TryGetComponent(Entity, out TechnologyDatabaseComponent? database))
return;
TierDisplayContainer.RemoveAllChildren();
foreach (var disciplineId in database.SupportedDisciplines)
{
var discipline = _prototype.Index<TechDisciplinePrototype>(disciplineId);
var tier = _research.GetTierCompletionPercentage(database, discipline, _prototype);
var texture = new TextureRect
{
TextureScale = new Vector2(2, 2),
VerticalAlignment = VAlignment.Center
};
var label = new RichTextLabel();
texture.Texture = _sprite.Frame0(discipline.Icon);
label.SetMessage(Loc.GetString("research-console-tier-percentage", ("perc", tier)));
var control = new BoxContainer
{
Children =
{
texture,
label,
new Control
{
MinWidth = 10
}
}
};
TierDisplayContainer.AddChild(control);
}
}
#region Drag handle
protected override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (!_draggin)
return;
// Adjust scroll position with drag
var scrollSpeed = 2.0f;
TechScrollContainer.VScrollTarget -= args.Relative.Y * scrollSpeed;
}
/// <summary>
/// Raised when LMB is pressed at <see cref="DragContainer"/>
/// </summary>
private void OnKeybindDown(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.Use)
_draggin = true;
}
/// <summary>
/// Raised when LMB is unpressed at <see cref="DragContainer"/>
/// </summary>
private void OnKeybindUp(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.Use)
_draggin = false;
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
=> _draggin ? DragMode.None : base.GetDragModeFor(relativeMousePos);
#endregion
/// <summary>
/// Selects a tech prototype and opens info panel
/// </summary>
/// <param name="proto">Tech proto</param>
/// <param name="availability">Tech availability</param>
public void SelectTech(TechnologyPrototype proto, ResearchAvailability availability)
{
InfoContainer.RemoveAllChildren();
if (!_player.LocalEntity.HasValue)
return;
// Update selection
CurrentTech = proto.ID;
// Update visual selection state
foreach (var child in DragContainer.Children)
{
if (child is FancyResearchConsoleItem techItem)
{
techItem.IsSelected = techItem.Prototype.ID == CurrentTech;
}
}
// Create and add info panel
var control = new FancyTechnologyInfoPanel(proto, _accessReader.IsAllowed(_player.LocalEntity.Value, Entity), availability, _sprite);
control.BuyAction += args => OnTechnologyCardPressed?.Invoke(args.ID);
InfoContainer.AddChild(control);
}
public void Recenter()
{
// Reset scroll position
TechScrollContainer.VScrollTarget = 0;
}
public override void Close()
{
base.Close();
DragContainer.RemoveAllChildren();
InfoContainer.RemoveAllChildren();
}
private sealed partial class DisciplineButton(TechDisciplinePrototype proto) : Button
{
public TechDisciplinePrototype Proto = proto;
}
protected override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
}
}