XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Terraria;
using Terraria.Audio;
using Terraria.GameContent;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.UI;

namespace SoulHarvest.UI;

internal sealed class AltarEquipmentTreeViewport : UIElement
{
    private const float EdgeMargin = 96f;
    private const float DragThresholdScreenPixels = 5f;
    private readonly AltarEquipmentProgressionPanel owner;
    private readonly IReadOnlyList<AltarEquipmentTreeNode> nodes;
    private readonly float contentWidth;
    private readonly float contentHeight;
    private readonly ReaperTreeButton resetButton;
    private readonly AltarEquipmentTreeViewportOverlay overlay;
    private AltarEquipmentDragState dragState;
    private AltarEquipmentTreeNode? pressedNode;
    private AltarEquipmentTreeNode? hoveredNode;
    private Vector2 camera;
    private Vector2 dragStartMouse;
    private Vector2 dragStartCamera;
    private float layoutScale = 1f;
    private bool previousLeft;
    private bool previousMiddle;
    private bool cameraInitialized;

    internal float LayoutScale => layoutScale;
    internal AltarEquipmentTreeNode? HoveredNode => hoveredNode;
    internal AltarEquipmentTreeNode? SelectedNode { get; private set; }

    public AltarEquipmentTreeViewport(AltarEquipmentProgressionPanel owner, IReadOnlyList<AltarEquipmentTreeNode> nodes, float contentWidth, float contentHeight)
    {
        this.owner = owner;
        this.nodes = nodes;
        this.contentWidth = contentWidth;
        this.contentHeight = contentHeight;
        OverflowHidden = true;
        SetPadding(0f);
        AltarEquipmentTreeCanvas canvas = new(this, nodes, contentWidth, contentHeight) { IgnoresMouseInteraction = true };
        canvas.Width.Set(0f, 1f);
        canvas.Height.Set(0f, 1f);
        Append(canvas);
        overlay = new AltarEquipmentTreeViewportOverlay(this) { IgnoresMouseInteraction = true };
        overlay.Width.Set(0f, 1f);
        overlay.Height.Set(0f, 1f);
        Append(overlay);
        resetButton = new ReaperTreeButton(ResetCamera, new Color(45, 37, 38), new Color(99, 42, 47), new Color(207, 70, 77));
        Append(resetButton);
        CancelInteraction();
    }

    public void SetSelectedNode(AltarEquipmentTreeNode? node) => SelectedNode = node;

    public void ApplyLayoutScale(float scale)
    {
        layoutScale = Math.Clamp(scale, 0.25f, 1f);
        resetButton.Left.Set(-91f * layoutScale, 1f);
        resetButton.Top.Set(8f * layoutScale, 0f);
        resetButton.Width.Set(82f * layoutScale, 0f);
        resetButton.Height.Set(27f * layoutScale, 0f);
        resetButton.ApplyLayoutScale(layoutScale, 0.52f);
        resetButton.SetText(Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResetView"));
    }

    public override void Recalculate()
    {
        base.Recalculate();
        EnsureCameraIsValid();
    }

    public void EnsureCameraIsValid()
    {
        CalculatedStyle bounds = GetDimensions();
        if (bounds.Width <= 1f || bounds.Height <= 1f)
            return;
        if (!cameraInitialized)
            ResetCamera(playSound: false);
        else
            camera = ClampCamera(camera);
    }

    public void CancelInteraction()
    {
        dragState = AltarEquipmentDragState.None;
        pressedNode = null;
        hoveredNode = null;
        previousLeft = Main.mouseLeft;
        previousMiddle = Main.mouseMiddle;
    }

    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
        resetButton.SetText(Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResetView"));
        Vector2 mouse = Main.MouseScreen;
        bool inside = ContainsPoint(mouse);
        bool left = Main.mouseLeft;
        bool middle = Main.mouseMiddle;
        bool leftPressed = left && !previousLeft;
        bool leftReleased = !left && previousLeft;
        bool middlePressed = middle && !previousMiddle;
        bool fixedOverlayHovered = IsFixedOverlayHovered(mouse);

        if (inside || dragState != AltarEquipmentDragState.None)
            Main.LocalPlayer.mouseInterface = true;
        hoveredNode = inside
            && !fixedOverlayHovered
            && dragState is AltarEquipmentDragState.None or AltarEquipmentDragState.PendingLeft
                ? HitTest(mouse)
                : null;
        if (middlePressed && inside && !fixedOverlayHovered)
        {
            dragState = AltarEquipmentDragState.DraggingMiddle;
            pressedNode = null;
            dragStartMouse = mouse;
            dragStartCamera = camera;
        }
        else if (leftPressed && inside && dragState == AltarEquipmentDragState.None && !fixedOverlayHovered)
        {
            dragState = AltarEquipmentDragState.PendingLeft;
            pressedNode = HitTest(mouse);
            dragStartMouse = mouse;
            dragStartCamera = camera;
        }
        if (dragState == AltarEquipmentDragState.PendingLeft && left
            && Vector2.DistanceSquared(mouse, dragStartMouse) >= DragThresholdScreenPixels * DragThresholdScreenPixels)
        {
            dragState = AltarEquipmentDragState.DraggingLeft;
        }
        if (dragState == AltarEquipmentDragState.DraggingLeft)
        {
            if (left)
                camera = ClampCamera(dragStartCamera + (mouse - dragStartMouse) / layoutScale);
            else
                EndDrag();
        }
        else if (dragState == AltarEquipmentDragState.DraggingMiddle)
        {
            if (middle)
                camera = ClampCamera(dragStartCamera + (mouse - dragStartMouse) / layoutScale);
            else
                EndDrag();
        }
        else if (dragState == AltarEquipmentDragState.PendingLeft && leftReleased)
        {
            AltarEquipmentTreeNode? released = inside ? HitTest(mouse) : null;
            bool stayedWithinClickThreshold = Vector2.DistanceSquared(mouse, dragStartMouse)
                < DragThresholdScreenPixels * DragThresholdScreenPixels;
            if (stayedWithinClickThreshold
                && !fixedOverlayHovered
                && pressedNode is not null
                && ReferenceEquals(pressedNode, released))
            {
                SelectedNode = pressedNode;
                owner.SelectNode(pressedNode);
            }
            EndDrag();
        }
        previousLeft = left;
        previousMiddle = middle;
    }

    protected override void DrawSelf(SpriteBatch spriteBatch)
    {
        Rectangle bounds = GetDimensions().ToRectangle();
        Texture2D pixel = TextureAssets.MagicPixel.Value;
        spriteBatch.Draw(pixel, bounds, new Color(8, 9, 11) * 0.995f);
    }

    internal Vector2 DesignToScreen(Vector2 design) => GetDimensions().Position() + (design + camera) * layoutScale;

    internal Rectangle GetHintStripBounds()
    {
        Rectangle bounds = GetDimensions().ToRectangle();
        return new Rectangle(
            bounds.X + Math.Max(2, (int)(8f * layoutScale)),
            bounds.Bottom - Math.Max(8, (int)(28f * layoutScale)),
            Math.Max(1, bounds.Width - Math.Max(4, (int)(16f * layoutScale))),
            Math.Max(6, (int)(20f * layoutScale)));
    }

    private bool IsFixedOverlayHovered(Vector2 mouse)
    {
        return resetButton.ContainsPoint(mouse) || GetHintStripBounds().Contains(mouse.ToPoint());
    }

    private AltarEquipmentTreeNode? HitTest(Vector2 mouse)
    {
        if (!ContainsPoint(mouse))
            return null;
        Vector2 design = (mouse - GetDimensions().Position()) / layoutScale - camera;
        for (int index = nodes.Count - 1; index >= 0; index--)
        {
            AltarEquipmentTreeNode node = nodes[index];
            float radius = node.Radius + 7f;
            if (Vector2.DistanceSquared(design, node.Position) <= radius * radius)
                return node;
        }
        return null;
    }

    private void ResetCamera() => ResetCamera(playSound: true);

    private void ResetCamera(bool playSound)
    {
        CalculatedStyle bounds = GetDimensions();
        if (bounds.Width <= 1f || bounds.Height <= 1f)
            return;
        Vector2 viewportSize = new(bounds.Width / layoutScale, bounds.Height / layoutScale);
        Vector2 focus = new(contentWidth * 0.5f, Math.Min(contentHeight * 0.5f, 420f));
        camera = ClampCamera(viewportSize * 0.5f - focus);
        cameraInitialized = true;
        EndDrag();
        if (playSound)
            SoundEngine.PlaySound(SoundID.MenuTick);
    }

    private Vector2 ClampCamera(Vector2 value)
    {
        CalculatedStyle bounds = GetDimensions();
        Vector2 viewportSize = new(Math.Max(1f, bounds.Width / layoutScale), Math.Max(1f, bounds.Height / layoutScale));
        float minX = viewportSize.X - EdgeMargin - contentWidth;
        float maxX = EdgeMargin;
        float minY = viewportSize.Y - EdgeMargin - contentHeight;
        float maxY = EdgeMargin;
        value.X = minX > maxX ? (viewportSize.X - contentWidth) * 0.5f : Math.Clamp(value.X, minX, maxX);
        value.Y = minY > maxY ? (viewportSize.Y - contentHeight) * 0.5f : Math.Clamp(value.Y, minY, maxY);
        return value;
    }

    private void EndDrag()
    {
        dragState = AltarEquipmentDragState.None;
        pressedNode = null;
    }
}