Files
HSUI/Interface/DraggableHudElement.cs
2026-02-28 15:09:42 -05:00

310 lines
10 KiB
C#

using Dalamud.Logging;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface
{
public delegate void DraggableHudElementSelectHandler(DraggableHudElement element);
public class DraggableHudElement : HudElement
{
public DraggableHudElement(MovablePluginConfigObject config, string? displayName = null) : base(config)
{
_displayName = displayName ?? ID;
}
public event DraggableHudElementSelectHandler? SelectEvent;
public bool Selected = false;
private string _displayName;
protected bool _windowPositionSet = false;
private Vector2 _lastWindowPos = Vector2.Zero;
private Vector2 _positionOffset;
private Vector2 _contentMargin = new Vector2(4, 0);
private bool _draggingEnabled = false;
public bool DraggingEnabled
{
get => _draggingEnabled;
set
{
_draggingEnabled = value;
if (_draggingEnabled)
{
_windowPositionSet = false;
_minPos = null;
_maxPos = null;
}
}
}
public bool CanTakeInputForDrag = false;
public bool NeedsInputForDrag { get; private set; } = false;
public virtual Vector2 ParentPos() { return Vector2.Zero; } // override
protected sealed override void CreateDrawActions(Vector2 origin)
{
if (_draggingEnabled)
{
AddDrawAction(_config.StrataLevel, () =>
{
DrawDraggableArea(origin);
});
return;
}
DrawChildren(origin);
}
public virtual void DrawChildren(Vector2 origin) { }
private bool CalculateNeedsInput(Vector2 pos, Vector2 size, bool selected)
{
Vector2 mousePos = ImGui.GetMousePos();
if (ImGui.IsMouseHoveringRect(pos, pos + size))
{
return true;
}
if (!selected)
{
return false;
}
var arrowsPos = DraggablesHelper.GetArrowPositions(pos, size);
foreach (Vector2 arrowPos in arrowsPos)
{
if (ImGui.IsMouseHoveringRect(arrowPos, arrowPos + DraggablesHelper.ArrowSize))
{
return true;
}
}
return false;
}
protected virtual void DrawDraggableArea(Vector2 origin)
{
var windowFlags = ImGuiWindowFlags.NoScrollbar
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoSavedSettings;
// always update size
var size = MaxPos - MinPos + _contentMargin * 2;
ImGui.SetNextWindowSize(size, ImGuiCond.Always);
// needs input?
NeedsInputForDrag = CanTakeInputForDrag && CalculateNeedsInput(_lastWindowPos, size, Selected);
if (!NeedsInputForDrag)
{
windowFlags |= ImGuiWindowFlags.NoMove;
}
// set initial position
if (!_windowPositionSet)
{
ImGui.SetNextWindowPos(origin + MinPos - _contentMargin);
_windowPositionSet = true;
_positionOffset = _config.Position - MinPos + _contentMargin;
}
// update config object position
ImGui.Begin(ID + "_dragArea", windowFlags);
var windowPos = ImGui.GetWindowPos();
_lastWindowPos = windowPos;
_config.Position = windowPos + _positionOffset - origin;
// check selection
var tooltipText = "x: " + _config.Position.X.ToString() + " y: " + _config.Position.Y.ToString();
if (NeedsInputForDrag && ImGui.IsMouseHoveringRect(windowPos, windowPos + size))
{
bool cliked = ImGui.IsMouseClicked(ImGuiMouseButton.Left) || ImGui.IsMouseDown(ImGuiMouseButton.Left);
if (cliked && !Selected)
{
SelectEvent?.Invoke(this);
}
// tooltip
TooltipsHelper.Instance.ShowTooltipOnCursor(tooltipText);
}
// draw window
var drawList = ImGui.GetWindowDrawList();
var contentPos = windowPos + _contentMargin;
var contentSize = size - _contentMargin * 2;
// draw draggable indicators
drawList.AddRectFilled(contentPos, contentPos + contentSize, 0x88444444, 3);
var lineColor = Selected ? 0xEEFFFFFF : 0x66FFFFFF;
drawList.AddRect(contentPos, contentPos + contentSize, lineColor, 3, ImDrawFlags.None, 2);
drawList.AddLine(contentPos + new Vector2(contentSize.X / 2f, 0), contentPos + new Vector2(contentSize.X / 2, contentSize.Y), lineColor);
drawList.AddLine(contentPos + new Vector2(0, contentSize.Y / 2f), contentPos + new Vector2(contentSize.X, contentSize.Y / 2), lineColor);
ImGui.End();
// arrows
if (Selected)
{
if (DraggablesHelper.DrawArrows(windowPos, size, tooltipText, out var movement))
{
_minPos = null;
_maxPos = null;
_config.Position += movement;
_windowPositionSet = false;
}
}
// element name
var textSize = ImGui.CalcTextSize(_displayName);
var textColor = Selected ? 0xFFFFFFFF : 0xEEFFFFFF;
var textOutlineColor = Selected ? 0xFF000000 : 0xEE000000;
DrawHelper.DrawOutlinedText(_displayName, contentPos + contentSize / 2f - textSize / 2f, textColor, textOutlineColor, drawList);
}
#region draggable area
protected Vector2? _minPos = null;
public Vector2 MinPos
{
get
{
if (_minPos != null)
{
return (Vector2)_minPos;
}
var (positions, sizes) = ChildrenPositionsAndSizes();
if (positions.Count == 0 || sizes.Count == 0)
{
return Vector2.Zero;
}
float minX = float.MaxValue;
float minY = float.MaxValue;
var anchorConfig = _config as AnchorablePluginConfigObject;
for (int i = 0; i < positions.Count; i++)
{
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center);
minX = Math.Min(minX, pos.X);
minY = Math.Min(minY, pos.Y);
}
_minPos = new Vector2(minX, minY);
return (Vector2)_minPos;
}
}
protected Vector2? _maxPos = null;
public Vector2 MaxPos
{
get
{
if (_maxPos != null)
{
return (Vector2)_maxPos;
}
var (positions, sizes) = ChildrenPositionsAndSizes();
if (positions.Count == 0 || sizes.Count == 0)
{
return Vector2.Zero;
}
float maxX = float.MinValue;
float maxY = float.MinValue;
var anchorConfig = _config as AnchorablePluginConfigObject;
for (int i = 0; i < positions.Count; i++)
{
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center) + sizes[i];
maxX = Math.Max(maxX, pos.X);
maxY = Math.Max(maxY, pos.Y);
}
_maxPos = new Vector2(maxX, maxY);
return (Vector2)_maxPos;
}
}
public void FlagDraggableAreaDirty()
{
_minPos = null;
_maxPos = null;
}
protected virtual Vector2 GetAnchoredPosition(Vector2 position, Vector2 size, DrawAnchor anchor)
{
return Utils.GetAnchoredPosition(ParentPos() + position, size, anchor);
}
protected virtual (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>(), new List<Vector2>());
}
/// <summary>Returns the screen-space bounding box of the element for hover detection (e.g. visibility "hide unless hovered").</summary>
public virtual (Vector2 min, Vector2 max) GetScreenBounds(Vector2 origin)
{
var basePos = origin + ParentPos();
return (basePos + MinPos, basePos + MaxPos);
}
#endregion
}
public abstract class ParentAnchoredDraggableHudElement : DraggableHudElement
{
public ParentAnchoredDraggableHudElement(MovablePluginConfigObject config, string? displayName = null)
: base(config, displayName)
{
}
protected virtual bool AnchorToParent { get; }
protected virtual DrawAnchor ParentAnchor { get; }
public AnchorablePluginConfigObject? ParentConfig { get; set; }
private Vector2? _lastParentPosition = null;
private bool IsAnchored => AnchorToParent && ParentConfig != null;
public override Vector2 ParentPos()
{
if (!IsAnchored)
{
return Vector2.Zero;
}
Vector2 parentAnchoredPos = Utils.GetAnchoredPosition(ParentConfig!.Position, ParentConfig!.Size, ParentConfig!.Anchor);
return Utils.GetAnchoredPosition(parentAnchoredPos, -ParentConfig!.Size, ParentAnchor);
}
protected override void DrawDraggableArea(Vector2 origin)
{
// if the parent moved, update own draggable area
if (IsAnchored && (_lastParentPosition == null || _lastParentPosition != ParentConfig!.Position))
{
_windowPositionSet = false;
_minPos = null;
_maxPos = null;
_lastParentPosition = ParentConfig!.Position;
}
base.DrawDraggableArea(origin);
}
}
}