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, List) ChildrenPositionsAndSizes() { return (new List(), new List()); } #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); } } }