Slight refactor/moving namespaces
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using AetherBags.Nodes.Layout;
|
||||
|
||||
namespace AetherBags.Nodes.Inventory;
|
||||
|
||||
public sealed class InventoryCategoryHoverCoordinator
|
||||
{
|
||||
private InventoryCategoryNode? _active;
|
||||
private int _activeRowIndex = -1;
|
||||
|
||||
public void OnCategoryHoverChanged(
|
||||
WrappingGridNode<InventoryCategoryNode> grid,
|
||||
InventoryCategoryNode source,
|
||||
bool hovering)
|
||||
{
|
||||
grid.RecalculateLayout();
|
||||
|
||||
if (hovering)
|
||||
{
|
||||
_active = source;
|
||||
|
||||
if (!grid.TryGetRowIndex(source, out _activeRowIndex))
|
||||
{
|
||||
SuppressAllExcept(grid, source);
|
||||
source.SetHeaderSuppressed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearAll(grid);
|
||||
|
||||
var row = grid.Rows[_activeRowIndex];
|
||||
for (int i = 0; i < row.Count; i++)
|
||||
{
|
||||
if (row[i] is InventoryCategoryNode cat && !ReferenceEquals(cat, source))
|
||||
cat.SetHeaderSuppressed(true);
|
||||
}
|
||||
|
||||
source.SetHeaderSuppressed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(_active, source))
|
||||
return;
|
||||
|
||||
_active = null;
|
||||
|
||||
if (_activeRowIndex >= 0 && _activeRowIndex < grid.Rows.Count)
|
||||
{
|
||||
var row = grid.Rows[_activeRowIndex];
|
||||
for (int i = 0; i < row.Count; i++)
|
||||
{
|
||||
if (row[i] is InventoryCategoryNode cat)
|
||||
cat.SetHeaderSuppressed(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearAll(grid);
|
||||
}
|
||||
|
||||
_activeRowIndex = -1;
|
||||
}
|
||||
|
||||
public void ResetAll(WrappingGridNode<InventoryCategoryNode> grid)
|
||||
{
|
||||
_active = null;
|
||||
_activeRowIndex = -1;
|
||||
ClearAll(grid);
|
||||
}
|
||||
|
||||
private static void ClearAll(WrappingGridNode<InventoryCategoryNode> grid)
|
||||
{
|
||||
foreach (var cat in grid.GetNodes<InventoryCategoryNode>())
|
||||
cat.SetHeaderSuppressed(false);
|
||||
}
|
||||
|
||||
private static void SuppressAllExcept(WrappingGridNode<InventoryCategoryNode> grid, InventoryCategoryNode source)
|
||||
{
|
||||
foreach (var cat in grid.GetNodes<InventoryCategoryNode>())
|
||||
cat.SetHeaderSuppressed(!ReferenceEquals(cat, source));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Nodes.Layout;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
// TODO: Switch back to CS version when Dalamud Updated
|
||||
using DragDropFixedNode = AetherBags.Nodes.DragDropNode;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace AetherBags.Nodes.Inventory;
|
||||
|
||||
public class InventoryCategoryNode : SimpleComponentNode
|
||||
{
|
||||
private readonly TextNode _categoryNameTextNode;
|
||||
private readonly HybridDirectionalFlexNode<DragDropFixedNode> _itemGridNode;
|
||||
|
||||
private const float FallbackItemSize = 46;
|
||||
private const float HeaderHeight = 16;
|
||||
private const float MinWidth = 40;
|
||||
|
||||
private float? _fixedWidth;
|
||||
|
||||
private int _hoverRefs;
|
||||
private bool _headerSuppressed;
|
||||
private bool _headerExpanded;
|
||||
|
||||
private float _baseHeaderWidth = 96f;
|
||||
|
||||
private string _fullHeaderText = string.Empty;
|
||||
|
||||
public event Action<InventoryCategoryNode, bool>? HeaderHoverChanged;
|
||||
|
||||
public InventoryCategoryNode()
|
||||
{
|
||||
_categoryNameTextNode = new TextNode
|
||||
{
|
||||
Size = new Vector2(96, 16),
|
||||
AlignmentType = AlignmentType.Left,
|
||||
};
|
||||
|
||||
_categoryNameTextNode.AddEvent(AtkEventType.MouseOver, BeginHeaderHover);
|
||||
_categoryNameTextNode.AddEvent(AtkEventType.MouseOut, EndHeaderHover);
|
||||
|
||||
_categoryNameTextNode.TextFlags |= TextFlags.OverflowHidden | TextFlags.Ellipsis;
|
||||
_categoryNameTextNode.TextFlags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
||||
|
||||
_categoryNameTextNode.AddFlags(NodeFlags.EmitsEvents | NodeFlags.HasCollision);
|
||||
_categoryNameTextNode.AttachNode(this);
|
||||
|
||||
_itemGridNode = new HybridDirectionalFlexNode<DragDropFixedNode>
|
||||
{
|
||||
Position = new Vector2(0, HeaderHeight),
|
||||
Size = new Vector2(240, 92),
|
||||
FillRowsFirst = true,
|
||||
ItemsPerLine = 10,
|
||||
HorizontalPadding = 5,
|
||||
VerticalPadding = 2,
|
||||
};
|
||||
|
||||
_itemGridNode.NodeFlags |= NodeFlags.EmitsEvents;
|
||||
_itemGridNode.AttachNode(this);
|
||||
}
|
||||
|
||||
public CategorizedInventory CategorizedInventory
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
_fullHeaderText = value.Category.Name;
|
||||
|
||||
_categoryNameTextNode.String = _fullHeaderText;
|
||||
_categoryNameTextNode.TextColor = value.Category.Color;
|
||||
|
||||
_categoryNameTextNode.TooltipString = value.Category.Description;
|
||||
|
||||
UpdateItemGrid();
|
||||
RecalculateSize();
|
||||
}
|
||||
}
|
||||
|
||||
public int ItemsPerLine
|
||||
{
|
||||
get => _itemGridNode.ItemsPerLine;
|
||||
set
|
||||
{
|
||||
if (_itemGridNode.ItemsPerLine == value) return;
|
||||
_itemGridNode.ItemsPerLine = value;
|
||||
RecalculateSize();
|
||||
}
|
||||
}
|
||||
|
||||
public float? FixedWidth
|
||||
{
|
||||
get => _fixedWidth;
|
||||
set
|
||||
{
|
||||
if (_fixedWidth.Equals(value)) return;
|
||||
_fixedWidth = value;
|
||||
RecalculateSize();
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginHeaderHover()
|
||||
{
|
||||
_hoverRefs++;
|
||||
if (_hoverRefs != 1) return;
|
||||
|
||||
_headerExpanded = true;
|
||||
ApplyHeaderVisualStateAndSize();
|
||||
|
||||
HeaderHoverChanged?.Invoke(this, true);
|
||||
}
|
||||
|
||||
public void EndHeaderHover()
|
||||
{
|
||||
if (_hoverRefs <= 0) return;
|
||||
|
||||
_hoverRefs--;
|
||||
if (_hoverRefs != 0) return;
|
||||
|
||||
_headerExpanded = false;
|
||||
ApplyHeaderVisualStateAndSize();
|
||||
|
||||
HeaderHoverChanged?.Invoke(this, false);
|
||||
}
|
||||
|
||||
public void SetHeaderSuppressed(bool suppressed)
|
||||
{
|
||||
if (_headerSuppressed == suppressed) return;
|
||||
_headerSuppressed = suppressed;
|
||||
ApplyHeaderVisualStateAndSize();
|
||||
}
|
||||
|
||||
private void ApplyHeaderVisualStateAndSize()
|
||||
{
|
||||
_categoryNameTextNode.IsVisible = !_headerSuppressed;
|
||||
if (_headerSuppressed)
|
||||
return;
|
||||
|
||||
var flags = _categoryNameTextNode.TextFlags;
|
||||
|
||||
flags &= ~(TextFlags.WordWrap | TextFlags.MultiLine);
|
||||
|
||||
if (_headerExpanded)
|
||||
{
|
||||
flags &= ~(TextFlags.OverflowHidden | TextFlags.Ellipsis);
|
||||
_categoryNameTextNode.TextFlags = flags;
|
||||
|
||||
if (!string.IsNullOrEmpty(_fullHeaderText))
|
||||
_categoryNameTextNode.String = _fullHeaderText;
|
||||
|
||||
Vector2 drawSize = _categoryNameTextNode.GetTextDrawSize();
|
||||
float expandedWidth = MathF.Max(_baseHeaderWidth, drawSize.X + 4f);
|
||||
_categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = expandedWidth };
|
||||
}
|
||||
else
|
||||
{
|
||||
_categoryNameTextNode.Size = _categoryNameTextNode.Size with { X = _baseHeaderWidth };
|
||||
|
||||
if (!string.IsNullOrEmpty(_fullHeaderText))
|
||||
_categoryNameTextNode.String = _fullHeaderText;
|
||||
|
||||
flags |= (TextFlags.OverflowHidden | TextFlags.Ellipsis);
|
||||
_categoryNameTextNode.TextFlags = flags;
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateSize()
|
||||
{
|
||||
int itemCount = CategorizedInventory.Items.Count;
|
||||
|
||||
if (itemCount == 0)
|
||||
{
|
||||
float width = _fixedWidth ?? MinWidth;
|
||||
|
||||
Size = new Vector2(width, HeaderHeight);
|
||||
|
||||
_baseHeaderWidth = width;
|
||||
|
||||
_itemGridNode.Position = new Vector2(0, HeaderHeight);
|
||||
_itemGridNode.Size = new Vector2(width, 0);
|
||||
|
||||
ApplyHeaderVisualStateAndSize();
|
||||
return;
|
||||
}
|
||||
|
||||
int itemsPerLine = _itemGridNode.ItemsPerLine;
|
||||
if (itemsPerLine < 1) itemsPerLine = 1;
|
||||
|
||||
int rows = (itemCount + itemsPerLine - 1) / itemsPerLine;
|
||||
int actualColumns = Math.Min(itemCount, itemsPerLine);
|
||||
|
||||
float cellW, cellH;
|
||||
if (_itemGridNode.Nodes.Count > 0)
|
||||
{
|
||||
var firstChild = _itemGridNode.Nodes[0];
|
||||
cellW = firstChild.Width;
|
||||
cellH = firstChild.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellW = FallbackItemSize;
|
||||
cellH = FallbackItemSize;
|
||||
}
|
||||
|
||||
float hPad = _itemGridNode.HorizontalPadding;
|
||||
float vPad = _itemGridNode.VerticalPadding;
|
||||
|
||||
float calculatedWidth;
|
||||
if (_fixedWidth.HasValue)
|
||||
{
|
||||
calculatedWidth = _fixedWidth.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
calculatedWidth = actualColumns * cellW + (actualColumns - 1) * hPad;
|
||||
if (calculatedWidth < MinWidth) calculatedWidth = MinWidth;
|
||||
}
|
||||
|
||||
float height = HeaderHeight + rows * cellH + (rows - 1) * vPad;
|
||||
|
||||
Size = new Vector2(calculatedWidth, height);
|
||||
|
||||
_itemGridNode.Position = new Vector2(0, HeaderHeight);
|
||||
_itemGridNode.Size = new Vector2(calculatedWidth, height - HeaderHeight);
|
||||
|
||||
_baseHeaderWidth = calculatedWidth;
|
||||
|
||||
ApplyHeaderVisualStateAndSize();
|
||||
}
|
||||
|
||||
private void UpdateItemGrid()
|
||||
{
|
||||
_itemGridNode.SyncWithListData(
|
||||
CategorizedInventory.Items,
|
||||
node => node.ItemInfo,
|
||||
CreateInventoryDragDropNode);
|
||||
}
|
||||
|
||||
private InventoryDragDropNode CreateInventoryDragDropNode(ItemInfo data)
|
||||
{
|
||||
InventoryItem item = data.Item;
|
||||
|
||||
var node = new InventoryDragDropNode
|
||||
{
|
||||
Size = new Vector2(42, 46),
|
||||
IsVisible = true,
|
||||
IconId = item.IconId,
|
||||
AcceptedType = DragDropType.Item,
|
||||
IsDraggable = true,
|
||||
Payload = new DragDropPayload
|
||||
{
|
||||
Type = DragDropType.Inventory_Item,
|
||||
Int1 = (int)item.GetInventoryType(),
|
||||
Int2 = item.Slot,
|
||||
},
|
||||
IsClickable = true,
|
||||
OnEnd = _ =>
|
||||
{
|
||||
System.AddonInventoryWindow.ManualInventoryRefresh();
|
||||
},
|
||||
OnPayloadAccepted = (n, p) => OnPayloadAccepted(n, p, data),
|
||||
OnRollOver = n =>
|
||||
{
|
||||
BeginHeaderHover();
|
||||
n.ShowInventoryItemTooltip(item.Container, item.Slot);
|
||||
},
|
||||
OnRollOut = n =>
|
||||
{
|
||||
EndHeaderHover();
|
||||
n.HideTooltip();
|
||||
},
|
||||
|
||||
ItemInfo = data
|
||||
};
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private unsafe void OnPayloadAccepted(DragDropNode node, DragDropPayload payload, ItemInfo itemInfo)
|
||||
{
|
||||
if (payload.Type != DragDropType.Item) return;
|
||||
InventoryItem item = itemInfo.Item;
|
||||
Services.Logger.Debug($"Inventory DragDropNode Payload Accepted: {payload.Type} Int1: {payload.Int1} Int2: {payload.Int2} ReferenceIndex: {payload.ReferenceIndex}");
|
||||
InventoryType inventoryType = InventoryType.GetInventoryTypeFromContainerId(payload.Int1);
|
||||
ushort sourceSlot = (ushort)payload.Int2;
|
||||
ItemOrderModuleSorterItemEntry* itemEntry = item.GetItemOrderData();
|
||||
Services.Logger.Debug($"{item.Slot} vs {item.GetSlot()}: entry: {itemEntry->Slot}");
|
||||
Services.Logger.Info($"[OnPayload] Moving {inventoryType}@{sourceSlot} -> {item.Container}@{item.Slot} -> {item.Name.ExtractText()}");
|
||||
InventoryManager.Instance()->MoveItemSlot(inventoryType, sourceSlot, item.Container, item.GetSlot(), true);
|
||||
|
||||
|
||||
// System.AddonInventoryWindow.ManualInventoryRefresh();
|
||||
|
||||
// Should work for swapping item but need a fake empty slot to put new items in probably.
|
||||
// Services.Logger.Debug($"Moving Item from {inventoryType} Slot {sourceSlot} to {itemInfo.Item.Container} Slot {itemInfo.Item.GetSlot()}");
|
||||
//MoveItem(inventoryType, sourceSlot, itemInfo.Item.Container, itemInfo.Item.GetSlot());
|
||||
}
|
||||
|
||||
// Possibly still use this
|
||||
private unsafe void MoveItem(InventoryType sourceInventory, uint sourceSlot, InventoryType destinationInventory, uint destinationSlot)
|
||||
{
|
||||
var sourceContainerId = sourceInventory.AgentItemContainerId;
|
||||
var destinationContainerId = destinationInventory.AgentItemContainerId;
|
||||
|
||||
if (sourceContainerId != 0 && destinationContainerId != 0) {
|
||||
var atkValues = stackalloc AtkValue[4];
|
||||
for (var i = 0; i < 4; i++) atkValues[i].Type = ValueType.UInt;
|
||||
|
||||
atkValues[0].UInt = sourceContainerId;
|
||||
atkValues[1].UInt = sourceSlot;
|
||||
atkValues[2].UInt = destinationContainerId;
|
||||
atkValues[3].UInt = destinationSlot;
|
||||
|
||||
var retVal = stackalloc AtkValue[1];
|
||||
|
||||
RaptureAtkModule* atkModule = RaptureAtkModule.Instance();
|
||||
// (RaptureAtkModule* a1, void* outValue, AtkValue* atkValues);
|
||||
// (AtkValue* returnValue, AtkValue* values, uint valueCount)
|
||||
atkModule->HandleItemMove(retVal, atkValues, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Numerics;
|
||||
using AetherBags.Inventory;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
// TODO: Switch back to CS version when Dalamud Updated
|
||||
using DragDropFixedNode = AetherBags.Nodes.DragDropNode;
|
||||
|
||||
namespace AetherBags.Nodes.Inventory;
|
||||
|
||||
public class InventoryDragDropNode : DragDropFixedNode
|
||||
{
|
||||
private readonly TextNode _quantityTextNode;
|
||||
public unsafe InventoryDragDropNode()
|
||||
{
|
||||
_quantityTextNode = new TextNode {
|
||||
Size = new Vector2(40.0f, 12.0f),
|
||||
Position = new Vector2(4.0f, 34.0f),
|
||||
NodeFlags = NodeFlags.Enabled | NodeFlags.EmitsEvents,
|
||||
Color = ColorHelper.GetColor(50),
|
||||
TextOutlineColor = ColorHelper.GetColor(51),
|
||||
TextFlags = TextFlags.Edge,
|
||||
AlignmentType = AlignmentType.Right,
|
||||
};
|
||||
_quantityTextNode.AttachNode(this);
|
||||
CollisionNode.AddEvent(AtkEventType.MouseDown, OnItemMouseDown);
|
||||
CollisionNode.AddEvent(AtkEventType.MouseClick, OnItemClicked);
|
||||
}
|
||||
|
||||
public required ItemInfo ItemInfo
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
_quantityTextNode.String = value.ItemCount.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void OnItemMouseDown(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
InventoryItem item = ItemInfo.Item;
|
||||
if (!atkEventData->IsRightClick) return;
|
||||
|
||||
AgentInventoryContext* context = AgentInventoryContext.Instance();
|
||||
context->OpenForItemSlot(item.Container, item.Slot, 0, context->AddonId);
|
||||
}
|
||||
|
||||
private unsafe void OnItemClicked(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) {
|
||||
InventoryItem item = ItemInfo.Item;
|
||||
if (!atkEventData->IsLeftClick) return;
|
||||
item.UseItem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AetherBags.Currency;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Nodes.Currency;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Nodes.Inventory;
|
||||
|
||||
public sealed class InventoryFooterNode : SimpleComponentNode
|
||||
{
|
||||
private readonly TextNode _slotAmountTextNode;
|
||||
private readonly CurrencyListNode _currencyListNode;
|
||||
|
||||
public InventoryFooterNode()
|
||||
{
|
||||
_slotAmountTextNode = new TextNode
|
||||
{
|
||||
Position = new Vector2(Size.X - 10, 0),
|
||||
Size = new Vector2(82, 20),
|
||||
AlignmentType = AlignmentType.Right,
|
||||
FontType = FontType.MiedingerMed,
|
||||
TextFlags = TextFlags.Glare,
|
||||
TextColor = ColorHelper.GetColor(50),
|
||||
TextOutlineColor = ColorHelper.GetColor(32) // Could also be Color 65
|
||||
};
|
||||
_slotAmountTextNode.AttachNode(this);
|
||||
|
||||
_currencyListNode = new CurrencyListNode
|
||||
{
|
||||
Position = new Vector2(0, 0),
|
||||
Size = new Vector2(120, 28),
|
||||
IsVisible = System.Config.Currency.Enabled
|
||||
};
|
||||
_currencyListNode.AttachNode(this);
|
||||
|
||||
RefreshCurrencies();
|
||||
}
|
||||
|
||||
public void RefreshCurrencies()
|
||||
{
|
||||
_currencyListNode.IsVisible = System.Config.Currency.Enabled;
|
||||
|
||||
IReadOnlyList<CurrencyInfo> currencyInfoList = InventoryState.GetCurrencyInfoList([1, 28, 0xFFFF_FFFE, 0xFFFF_FFFD]);
|
||||
_currencyListNode.SyncWithListDataByKey<CurrencyInfo, CurrencyNode, uint>(
|
||||
dataList: currencyInfoList,
|
||||
getKeyFromData: c => c.ItemId,
|
||||
getKeyFromNode: n => n.Currency.ItemId,
|
||||
updateNode: (node, data) =>
|
||||
{
|
||||
node.Currency = data;
|
||||
},
|
||||
createNodeMethod: data => new CurrencyNode
|
||||
{
|
||||
Size = new Vector2(120, 28),
|
||||
Currency = data
|
||||
});
|
||||
}
|
||||
|
||||
public string SlotAmountText
|
||||
{
|
||||
get => _slotAmountTextNode.String;
|
||||
set => _slotAmountTextNode.String = value;
|
||||
}
|
||||
|
||||
protected override void OnSizeChanged() {
|
||||
base.OnSizeChanged();
|
||||
|
||||
_slotAmountTextNode.Position = new Vector2(Size.X - _slotAmountTextNode.Size.X - 10, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user