Add context menu support.

This commit is contained in:
Ottermandias 2023-07-24 14:23:36 +02:00
parent ec27c3e931
commit 662ec617dc
5 changed files with 180 additions and 7 deletions

View file

@ -27,6 +27,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool HideApplyCheckmarks { get; set; } = false; public bool HideApplyCheckmarks { get; set; } = false;
public bool SmallEquip { get; set; } = false; public bool SmallEquip { get; set; } = false;
public bool UnlockedItemMode { get; set; } = false; public bool UnlockedItemMode { get; set; } = false;
public bool EnableGameContextMenu { get; set; } = true;
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);

View file

@ -88,6 +88,7 @@
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" /> <ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" /> <ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -2,6 +2,7 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
@ -19,15 +20,17 @@ public class SettingsTab : ITab
private readonly StateListener _stateListener; private readonly StateListener _stateListener;
private readonly CodeService _codeService; private readonly CodeService _codeService;
private readonly PenumbraAutoRedraw _autoRedraw; private readonly PenumbraAutoRedraw _autoRedraw;
private readonly ContextMenuService _contextMenuService;
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener, public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener,
CodeService codeService, PenumbraAutoRedraw autoRedraw) CodeService codeService, PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService)
{ {
_config = config; _config = config;
_selector = selector; _selector = selector;
_stateListener = stateListener; _stateListener = stateListener;
_codeService = codeService; _codeService = codeService;
_autoRedraw = autoRedraw; _autoRedraw = autoRedraw;
_contextMenuService = contextMenuService;
} }
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
@ -59,6 +62,15 @@ public class SettingsTab : ITab
Checkbox("Hide Application Checkboxes", Checkbox("Hide Application Checkboxes",
"Hide the application checkboxes in the Customization and Equipment panels of the design tab, and only show them under Application Rules.", "Hide the application checkboxes in the Customization and Equipment panels of the design tab, and only show them under Application Rules.",
_config.HideApplyCheckmarks, v => _config.HideApplyCheckmarks = v); _config.HideApplyCheckmarks, v => _config.HideApplyCheckmarks = v);
Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.",
_config.EnableGameContextMenu, v =>
{
_config.EnableGameContextMenu = v;
if (v)
_contextMenuService.Enable();
else
_contextMenuService.Disable();
});
if (Widget.DoubleModifierSelector("Design Deletion Modifier", if (Widget.DoubleModifierSelector("Design Deletion Modifier",
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
_config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v)) _config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v))

View file

@ -0,0 +1,158 @@
using System;
using Dalamud.ContextMenu;
using Dalamud.Game.Gui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public class ContextMenuService : IDisposable
{
public const int ItemSearchContextItemId = 0x1738;
public const int ChatLogContextItemId = 0x948;
private readonly ItemManager _items;
private readonly DalamudContextMenu _contextMenu = new();
private readonly StateManager _state;
private readonly ObjectManager _objects;
private readonly GameGui _gameGui;
private readonly Configuration _config;
public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, GameGui gameGui, Configuration config)
{
_items = items;
_state = state;
_objects = objects;
_gameGui = gameGui;
_config = config;
Enable();
}
public void Enable()
{
_contextMenu.OnOpenGameObjectContextMenu += AddGameObjectItem;
_contextMenu.OnOpenInventoryContextMenu += AddInventoryItem;
}
public void Disable()
{
_contextMenu.OnOpenGameObjectContextMenu -= AddGameObjectItem;
_contextMenu.OnOpenInventoryContextMenu -= AddInventoryItem;
}
public void Dispose()
{
Disable();
_contextMenu.Dispose();
}
private static readonly SeString TryOnString = new SeStringBuilder().AddUiForeground(SeIconChar.BoxedLetterG.ToIconString(), 541)
.AddText(" Try On").AddUiForegroundOff().BuiltString;
private void AddInventoryItem(InventoryContextMenuOpenArgs args)
{
var item = CheckInventoryItem(args.ItemId);
if (item != null)
args.AddCustomItem(item);
}
private InventoryContextMenuItem? CheckInventoryItem(uint itemId)
{
if (itemId > 500000)
itemId -= 500000;
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item))
return null;
return new InventoryContextMenuItem(TryOnString, GetInventoryAction(item));
}
private GameObjectContextMenuItem? CheckGameObjectItem(uint itemId)
{
if (itemId > 500000)
itemId -= 500000;
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item))
return null;
return new GameObjectContextMenuItem(TryOnString, GetGameObjectAction(item));
}
private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset, Func<nint, bool> validate)
=> agent != IntPtr.Zero && validate(agent) ? CheckGameObjectItem(*(uint*)(agent + offset)) : null;
private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset)
=> agent != IntPtr.Zero ? CheckGameObjectItem(*(uint*)(agent + offset)) : null;
private GameObjectContextMenuItem? CheckGameObjectItem(string name, int offset, Func<nint, bool> validate)
=> CheckGameObjectItem(_gameGui.FindAgentInterface(name), offset, validate);
private void AddGameObjectItem(GameObjectContextMenuOpenArgs args)
{
var item = args.ParentAddonName switch
{
"ItemSearch" => CheckGameObjectItem(args.Agent, ItemSearchContextItemId),
"ChatLog" => CheckGameObjectItem("ChatLog", ChatLogContextItemId, ValidateChatLogContext),
_ => null,
};
if (item != null)
args.AddCustomItem(item);
}
private DalamudContextMenu.InventoryContextMenuItemSelectedDelegate GetInventoryAction(EquipItem item)
{
return _ =>
{
var (id, playerData) = _objects.PlayerData;
if (!playerData.Valid)
return;
if (!_state.GetOrCreate(id, playerData.Objects[0], out var state))
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.ModelId.Value is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
}
};
}
private DalamudContextMenu.GameObjectContextMenuItemSelectedDelegate GetGameObjectAction(EquipItem item)
{
return _ =>
{
var (id, playerData) = _objects.PlayerData;
if (!playerData.Valid)
return;
if (!_state.GetOrCreate(id, playerData.Objects[0], out var state))
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.ModelId.Value is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
}
};
}
private static unsafe bool ValidateChatLogContext(nint agent)
=> *(uint*)(agent + ChatLogContextItemId + 8) == 3;
}

View file

@ -92,7 +92,8 @@ public static class ServiceManager
.AddSingleton<CustomizeUnlockManager>() .AddSingleton<CustomizeUnlockManager>()
.AddSingleton<ItemUnlockManager>() .AddSingleton<ItemUnlockManager>()
.AddSingleton<DatFileService>() .AddSingleton<DatFileService>()
.AddSingleton<InventoryService>(); .AddSingleton<InventoryService>()
.AddSingleton<ContextMenuService>();
private static IServiceCollection AddDesigns(this IServiceCollection services) private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<DesignManager>() => services.AddSingleton<DesignManager>()