WIP Abstraction
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Inventory.Context;
|
||||
using AetherBags.Inventory.State;
|
||||
using AetherBags.Nodes.Input;
|
||||
using AetherBags.Nodes.Inventory;
|
||||
using AetherBags.Nodes.Layout;
|
||||
@@ -11,43 +9,20 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
namespace AetherBags.Addons;
|
||||
|
||||
public class AddonInventoryWindow : NativeAddon
|
||||
public unsafe class AddonInventoryWindow : InventoryAddonBase
|
||||
{
|
||||
private readonly InventoryCategoryHoverCoordinator _hoverCoordinator = new();
|
||||
private readonly InventoryCategoryPinCoordinator _pinCoordinator = new();
|
||||
private readonly HashSet<InventoryCategoryNode> _hoverSubscribed = new();
|
||||
|
||||
private readonly MainBagState _inventoryState = new();
|
||||
private InventoryNotificationNode _notificationNode = null!;
|
||||
private WrappingGridNode<InventoryCategoryNode> _categoriesNode = null!;
|
||||
private TextInputWithHintNode _searchInputNode = null!;
|
||||
private CircleButtonNode _settingsButtonNode = null!;
|
||||
private InventoryFooterNode _footerNode = null!;
|
||||
|
||||
// Window constraints
|
||||
private const float MinWindowWidth = 300;
|
||||
private const float MaxWindowWidth = 800;
|
||||
private const float MinWindowHeight = 200;
|
||||
private const float MaxWindowHeight = 1000;
|
||||
protected override InventoryStateBase InventoryState => _inventoryState;
|
||||
|
||||
// Layout settings
|
||||
private const float CategorySpacing = 12;
|
||||
private const float ItemSize = 40;
|
||||
private const float ItemPadding = 4;
|
||||
|
||||
private const float FooterHeight = 28f;
|
||||
private const float FooterTopSpacing = 4f;
|
||||
|
||||
private bool _refreshQueued;
|
||||
private bool _refreshAutosizeQueued;
|
||||
|
||||
protected override unsafe void OnSetup(AtkUnitBase* addon)
|
||||
protected override void OnSetup(AtkUnitBase* addon)
|
||||
{
|
||||
_categoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||
CategoriesNode = new WrappingGridNode<InventoryCategoryNode>
|
||||
{
|
||||
Position = ContentStartPosition,
|
||||
Size = ContentSize,
|
||||
@@ -56,7 +31,7 @@ public class AddonInventoryWindow : NativeAddon
|
||||
TopPadding = 4.0f,
|
||||
BottomPadding = 4.0f,
|
||||
};
|
||||
_categoriesNode.AttachNode(this);
|
||||
CategoriesNode.AttachNode(this);
|
||||
|
||||
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
|
||||
|
||||
@@ -77,49 +52,48 @@ public class AddonInventoryWindow : NativeAddon
|
||||
};
|
||||
_notificationNode.AttachNode(this);
|
||||
|
||||
_searchInputNode = new TextInputWithHintNode
|
||||
SearchInputNode = new TextInputWithHintNode
|
||||
{
|
||||
Position = new Vector2(x, y),
|
||||
Size = size,
|
||||
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
|
||||
};
|
||||
_searchInputNode.AttachNode(this);
|
||||
SearchInputNode.AttachNode(this);
|
||||
|
||||
_settingsButtonNode = new CircleButtonNode
|
||||
SettingsButtonNode = new CircleButtonNode
|
||||
{
|
||||
Position = new Vector2(headerW - 48f, y),
|
||||
Size = new Vector2(28f),
|
||||
Icon = ButtonIcon.GearCog,
|
||||
OnClick = System.AddonConfigurationWindow.Toggle
|
||||
};
|
||||
_settingsButtonNode.AttachNode(this);
|
||||
SettingsButtonNode.AttachNode(this);
|
||||
|
||||
_footerNode = new InventoryFooterNode
|
||||
FooterNode = new InventoryFooterNode
|
||||
{
|
||||
Size = ContentSize with { Y = FooterHeight },
|
||||
SlotAmountText = InventoryState.GetEmptyItemSlotsString(),
|
||||
SlotAmountText = _inventoryState.GetEmptySlotsString(),
|
||||
};
|
||||
_footerNode.AttachNode(this);
|
||||
FooterNode.AttachNode(this);
|
||||
|
||||
LayoutContent();
|
||||
|
||||
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
|
||||
addon->SubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||
|
||||
InventoryState.RefreshFromGame();
|
||||
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
_inventoryState.RefreshFromGame();
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
|
||||
base.OnSetup(addon);
|
||||
}
|
||||
|
||||
protected override unsafe void OnUpdate(AtkUnitBase* addon)
|
||||
protected override void OnUpdate(AtkUnitBase* addon)
|
||||
{
|
||||
if (_refreshQueued)
|
||||
if (RefreshQueued)
|
||||
{
|
||||
bool doAutosize = _refreshAutosizeQueued;
|
||||
_refreshQueued = false;
|
||||
_refreshAutosizeQueued = false;
|
||||
bool doAutosize = RefreshAutosizeQueued;
|
||||
RefreshQueued = false;
|
||||
RefreshAutosizeQueued = false;
|
||||
|
||||
RefreshCategoriesCore(doAutosize);
|
||||
}
|
||||
@@ -127,191 +101,31 @@ public class AddonInventoryWindow : NativeAddon
|
||||
base.OnUpdate(addon);
|
||||
}
|
||||
|
||||
public void ManualInventoryRefresh()
|
||||
{
|
||||
if (!Services.ClientState.IsLoggedIn) return;
|
||||
InventoryState.RefreshFromGame();
|
||||
RefreshCategoriesCore(true);
|
||||
}
|
||||
|
||||
/*public void UpdateLootedCategory(IReadOnlyList<LootedItemInfo> lootedItemInfos)
|
||||
{
|
||||
if (!Services.ClientState.IsLoggedIn) return;
|
||||
_recentlyLootedCategoryNode?.CategorizedInventory.Items.AddRange(
|
||||
lootedItemInfos.Select(x => new ItemInfo
|
||||
{
|
||||
ItemCount = x.Quantity,
|
||||
Key = uint.MaxValue - 1,
|
||||
Item = x.Item,
|
||||
})
|
||||
.ToList());
|
||||
RefreshCategoriesCore(true);
|
||||
}*/
|
||||
|
||||
public void ManualCurrencyRefresh()
|
||||
{
|
||||
if (!Services.ClientState.IsLoggedIn) return;
|
||||
_footerNode.RefreshCurrencies();
|
||||
FooterNode.RefreshCurrencies();
|
||||
}
|
||||
|
||||
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
InventoryState.RefreshFromGame();
|
||||
|
||||
_inventoryState.RefreshFromGame();
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
}
|
||||
|
||||
protected override unsafe void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
|
||||
|
||||
InventoryState.RefreshFromGame();
|
||||
|
||||
_inventoryState.RefreshFromGame();
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
}
|
||||
|
||||
private void RefreshCategoriesCore(bool autosize)
|
||||
{
|
||||
_footerNode.SlotAmountText = InventoryState.GetEmptyItemSlotsString();
|
||||
_footerNode.RefreshCurrencies();
|
||||
|
||||
string filter = _searchInputNode.SearchString.ExtractText();
|
||||
IReadOnlyList<CategorizedInventory> categories = InventoryState.GetInventoryItemCategories(filter);
|
||||
|
||||
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
||||
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
||||
|
||||
_categoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>(
|
||||
dataList: categories,
|
||||
getKeyFromData: c => c.Key,
|
||||
getKeyFromNode: n => n.CategorizedInventory.Key,
|
||||
updateNode: (node, data) =>
|
||||
{
|
||||
node.CategorizedInventory = data;
|
||||
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
|
||||
},
|
||||
createNodeMethod: _ => new InventoryCategoryNode
|
||||
{
|
||||
Size = ContentSize with { Y = 120 },
|
||||
});
|
||||
|
||||
bool pinsChanged = _pinCoordinator.ApplyPinnedStates(_categoriesNode);
|
||||
if (pinsChanged)
|
||||
_hoverCoordinator.ResetAll(_categoriesNode);
|
||||
|
||||
WireHoverHandlers();
|
||||
|
||||
if (autosize) AutoSizeWindow();
|
||||
else
|
||||
{
|
||||
LayoutContent();
|
||||
_categoriesNode.RecalculateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void WireHoverHandlers()
|
||||
{
|
||||
var nodes = _categoriesNode.Nodes;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i] is not InventoryCategoryNode node)
|
||||
continue;
|
||||
|
||||
if (!_hoverSubscribed.Add(node))
|
||||
continue;
|
||||
|
||||
node.HeaderHoverChanged += (src, hovering) =>
|
||||
{
|
||||
_hoverCoordinator.OnCategoryHoverChanged(_categoriesNode, src, hovering);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateOptimalItemsPerLine(float availableWidth)
|
||||
{
|
||||
return Math.Clamp((int)MathF.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15);
|
||||
}
|
||||
|
||||
private void LayoutContent()
|
||||
{
|
||||
Vector2 contentPos = ContentStartPosition;
|
||||
Vector2 contentSize = ContentSize;
|
||||
|
||||
float footerH = FooterHeight;
|
||||
|
||||
_footerNode.Position = new Vector2(contentPos.X, contentPos.Y + contentSize.Y - footerH);
|
||||
_footerNode.Size = new Vector2(contentSize.X, footerH);
|
||||
|
||||
float gridH = contentSize.Y - footerH - FooterTopSpacing;
|
||||
if (gridH < 0) gridH = 0;
|
||||
|
||||
_categoriesNode.Position = contentPos;
|
||||
_categoriesNode.Size = new Vector2(contentSize.X, gridH);
|
||||
}
|
||||
|
||||
private void AutoSizeWindow()
|
||||
{
|
||||
var nodes = _categoriesNode.Nodes;
|
||||
|
||||
float maxChildWidth = 0f;
|
||||
int childCount = 0;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i] is not InventoryCategoryNode cat)
|
||||
continue;
|
||||
|
||||
childCount++;
|
||||
float w = cat.Width;
|
||||
if (w > maxChildWidth) maxChildWidth = w;
|
||||
}
|
||||
|
||||
if (childCount == 0)
|
||||
{
|
||||
ResizeWindow(MinWindowWidth, MinWindowHeight, recalcLayout: true);
|
||||
return;
|
||||
}
|
||||
|
||||
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
||||
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||
|
||||
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||
|
||||
float gridBudget = Math.Max(0f, MaxWindowHeight - FooterHeight - FooterTopSpacing);
|
||||
|
||||
_categoriesNode.Position = ContentStartPosition;
|
||||
_categoriesNode.Size = new Vector2(contentWidth, gridBudget);
|
||||
|
||||
_categoriesNode.RecalculateLayout();
|
||||
|
||||
float requiredGridHeight = _categoriesNode.GetRequiredHeight();
|
||||
float requiredContentHeight = requiredGridHeight + FooterTopSpacing + FooterHeight;
|
||||
|
||||
float requiredWindowHeight = requiredContentHeight + ContentStartPosition.Y + ContentStartPosition.X;
|
||||
float finalHeight = Math.Clamp(requiredWindowHeight, MinWindowHeight, MaxWindowHeight);
|
||||
|
||||
ResizeWindow(finalWidth, finalHeight, recalcLayout: false);
|
||||
}
|
||||
|
||||
private void ResizeWindow(float width, float height, bool recalcLayout)
|
||||
{
|
||||
SetWindowSize(width, height);
|
||||
LayoutContent();
|
||||
|
||||
if (recalcLayout)
|
||||
_categoriesNode.RecalculateLayout();
|
||||
}
|
||||
|
||||
private void ResizeWindow(float width, float height)
|
||||
=> ResizeWindow(width, height, recalcLayout: true);
|
||||
|
||||
public void SetNotification(InventoryNotificationInfo info)
|
||||
{
|
||||
Services.Framework.RunOnTick(() =>
|
||||
{
|
||||
if(IsOpen) _notificationNode.NotificationInfo = info;
|
||||
if (IsOpen) _notificationNode.NotificationInfo = info;
|
||||
}, delayTicks: 1);
|
||||
}
|
||||
|
||||
@@ -319,13 +133,12 @@ public class AddonInventoryWindow : NativeAddon
|
||||
{
|
||||
Services.Framework.RunOnTick(() =>
|
||||
{
|
||||
if(IsOpen) _searchInputNode.SearchString = searchText;
|
||||
if (IsOpen) SearchInputNode.SearchString = searchText;
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
}, delayTicks: 1);
|
||||
}
|
||||
|
||||
|
||||
protected override unsafe void OnFinalize(AtkUnitBase* addon)
|
||||
protected override void OnFinalize(AtkUnitBase* addon)
|
||||
{
|
||||
ref var blockingAddonId = ref AgentInventoryContext.Instance()->BlockingAddonId;
|
||||
if (blockingAddonId != 0)
|
||||
@@ -336,10 +149,6 @@ public class AddonInventoryWindow : NativeAddon
|
||||
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
|
||||
addon->UnsubscribeAtkArrayData(1, (int)NumberArrayType.Inventory);
|
||||
|
||||
_hoverSubscribed.Clear();
|
||||
_refreshQueued = false;
|
||||
_refreshAutosizeQueued = false;
|
||||
|
||||
base.OnFinalize(addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using AetherBags.Configuration;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Inventory.Categories;
|
||||
using KamiToolKit.Premade;
|
||||
|
||||
namespace AetherBags.Addons;
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AetherBags;
|
||||
using AetherBags.Inventory;
|
||||
using AetherBags.Inventory.Categories;
|
||||
using AetherBags.Inventory.State;
|
||||
using AetherBags.Nodes.Input;
|
||||
using AetherBags.Nodes.Inventory;
|
||||
using AetherBags.Nodes.Layout;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit;
|
||||
using KamiToolKit.Nodes;
|
||||
|
||||
public abstract unsafe class InventoryAddonBase : NativeAddon
|
||||
{
|
||||
protected readonly InventoryCategoryHoverCoordinator HoverCoordinator = new();
|
||||
protected readonly InventoryCategoryPinCoordinator PinCoordinator = new();
|
||||
protected readonly HashSet<InventoryCategoryNode> HoverSubscribed = new();
|
||||
|
||||
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
|
||||
protected TextInputWithHintNode SearchInputNode = null!;
|
||||
protected InventoryFooterNode FooterNode = null!;
|
||||
protected CircleButtonNode SettingsButtonNode = null!;
|
||||
|
||||
protected virtual float MinWindowWidth => 600;
|
||||
protected virtual float MaxWindowWidth => 800;
|
||||
protected virtual float MinWindowHeight => 200;
|
||||
protected virtual float MaxWindowHeight => 1000;
|
||||
|
||||
protected const float CategorySpacing = 12;
|
||||
protected const float ItemSize = 40;
|
||||
protected const float ItemPadding = 4;
|
||||
protected const float FooterHeight = 28f;
|
||||
protected const float FooterTopSpacing = 4f;
|
||||
|
||||
protected bool RefreshQueued;
|
||||
protected bool RefreshAutosizeQueued;
|
||||
|
||||
protected abstract InventoryStateBase InventoryState { get; }
|
||||
|
||||
protected virtual bool HasFooter => true;
|
||||
protected virtual bool HasPinning => true;
|
||||
|
||||
public void ManualInventoryRefresh()
|
||||
{
|
||||
if (!Services.ClientState.IsLoggedIn) return;
|
||||
InventoryState.RefreshFromGame();
|
||||
RefreshCategoriesCore(autosize: true);
|
||||
}
|
||||
|
||||
protected virtual void RefreshCategoriesCore(bool autosize)
|
||||
{
|
||||
if (HasFooter)
|
||||
{
|
||||
FooterNode.SlotAmountText = InventoryState.GetEmptySlotsString();
|
||||
FooterNode.RefreshCurrencies();
|
||||
}
|
||||
|
||||
string filter = SearchInputNode.SearchString.ExtractText();
|
||||
var categories = InventoryState.GetCategories(filter);
|
||||
|
||||
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
|
||||
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
|
||||
|
||||
CategoriesNode.SyncWithListDataByKey<CategorizedInventory, InventoryCategoryNode, uint>(
|
||||
dataList: categories,
|
||||
getKeyFromData: categorizedInventory => categorizedInventory.Key,
|
||||
getKeyFromNode: node => node.CategorizedInventory.Key,
|
||||
updateNode: (node, data) =>
|
||||
{
|
||||
node.CategorizedInventory = data;
|
||||
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
|
||||
},
|
||||
createNodeMethod: _ => CreateCategoryNode());
|
||||
|
||||
if (HasPinning)
|
||||
{
|
||||
bool pinsChanged = PinCoordinator.ApplyPinnedStates(CategoriesNode);
|
||||
if (pinsChanged)
|
||||
HoverCoordinator.ResetAll(CategoriesNode);
|
||||
}
|
||||
|
||||
WireHoverHandlers();
|
||||
|
||||
if (autosize)
|
||||
AutoSizeWindow();
|
||||
else
|
||||
{
|
||||
LayoutContent();
|
||||
CategoriesNode.RecalculateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual InventoryCategoryNode CreateCategoryNode()
|
||||
{
|
||||
return new InventoryCategoryNode
|
||||
{
|
||||
Size = ContentSize with { Y = 120 },
|
||||
};
|
||||
}
|
||||
|
||||
protected void WireHoverHandlers()
|
||||
{
|
||||
var nodes = CategoriesNode.Nodes;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i] is not InventoryCategoryNode node)
|
||||
continue;
|
||||
|
||||
if (!HoverSubscribed.Add(node))
|
||||
continue;
|
||||
|
||||
node.HeaderHoverChanged += (src, hovering) =>
|
||||
{
|
||||
HoverCoordinator.OnCategoryHoverChanged(CategoriesNode, src, hovering);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected int CalculateOptimalItemsPerLine(float availableWidth)
|
||||
=> Math.Clamp((int)MathF.Floor((availableWidth + ItemPadding) / (ItemSize + ItemPadding)), 1, 15);
|
||||
|
||||
protected virtual void LayoutContent()
|
||||
{
|
||||
Vector2 contentPos = ContentStartPosition;
|
||||
Vector2 contentSize = ContentSize;
|
||||
|
||||
if (HasFooter)
|
||||
{
|
||||
FooterNode.Position = new Vector2(contentPos.X, contentPos.Y + contentSize.Y - FooterHeight);
|
||||
FooterNode.Size = new Vector2(contentSize.X, FooterHeight);
|
||||
}
|
||||
|
||||
float gridH = contentSize.Y - (HasFooter ? FooterHeight + FooterTopSpacing : 0);
|
||||
if (gridH < 0) gridH = 0;
|
||||
|
||||
CategoriesNode.Position = contentPos;
|
||||
CategoriesNode.Size = new Vector2(contentSize.X, gridH);
|
||||
}
|
||||
|
||||
protected virtual void AutoSizeWindow()
|
||||
{
|
||||
var nodes = CategoriesNode.Nodes;
|
||||
|
||||
float maxChildWidth = 0f;
|
||||
int childCount = 0;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i] is not InventoryCategoryNode cat)
|
||||
continue;
|
||||
|
||||
childCount++;
|
||||
float w = cat.Width;
|
||||
if (w > maxChildWidth) maxChildWidth = w;
|
||||
}
|
||||
|
||||
if (childCount == 0)
|
||||
{
|
||||
ResizeWindow(MinWindowWidth, MinWindowHeight, recalcLayout: true);
|
||||
return;
|
||||
}
|
||||
|
||||
float requiredWidth = maxChildWidth + (ContentStartPosition.X * 2);
|
||||
float finalWidth = Math.Clamp(requiredWidth, MinWindowWidth, MaxWindowWidth);
|
||||
|
||||
float contentWidth = finalWidth - (ContentStartPosition.X * 2);
|
||||
|
||||
float footerSpace = HasFooter ? FooterHeight + FooterTopSpacing : 0;
|
||||
float gridBudget = Math.Max(0f, MaxWindowHeight - footerSpace);
|
||||
|
||||
CategoriesNode.Position = ContentStartPosition;
|
||||
CategoriesNode.Size = new Vector2(contentWidth, gridBudget);
|
||||
|
||||
CategoriesNode.RecalculateLayout();
|
||||
|
||||
float requiredGridHeight = CategoriesNode.GetRequiredHeight();
|
||||
float requiredContentHeight = requiredGridHeight + footerSpace;
|
||||
|
||||
float requiredWindowHeight = requiredContentHeight + ContentStartPosition.Y + ContentStartPosition.X;
|
||||
float finalHeight = Math.Clamp(requiredWindowHeight, MinWindowHeight, MaxWindowHeight);
|
||||
|
||||
ResizeWindow(finalWidth, finalHeight, recalcLayout: false);
|
||||
}
|
||||
|
||||
protected void ResizeWindow(float width, float height, bool recalcLayout)
|
||||
{
|
||||
SetWindowSize(width, height);
|
||||
LayoutContent();
|
||||
|
||||
if (recalcLayout)
|
||||
CategoriesNode.RecalculateLayout();
|
||||
}
|
||||
|
||||
protected void ResizeWindow(float width, float height)
|
||||
=> ResizeWindow(width, height, recalcLayout: true);
|
||||
|
||||
protected override void OnFinalize(AtkUnitBase* addon)
|
||||
{
|
||||
HoverSubscribed.Clear();
|
||||
RefreshQueued = false;
|
||||
RefreshAutosizeQueued = false;
|
||||
|
||||
base.OnFinalize(addon);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user