forked from goatcorp/Dalamud
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add IContextMenu service (goatcorp#1682)
- Loading branch information
1 parent
3d59fa3
commit 5f62c70
Showing
14 changed files
with
1,382 additions
and
141 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// The type of context menu. | ||
/// Each one has a different associated <see cref="MenuTarget"/>. | ||
/// </summary> | ||
public enum ContextMenuType | ||
{ | ||
/// <summary> | ||
/// The default context menu. | ||
/// </summary> | ||
Default, | ||
|
||
/// <summary> | ||
/// The inventory context menu. Used when right-clicked on an item. | ||
/// </summary> | ||
Inventory, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
using System.Collections.Generic; | ||
|
||
using Dalamud.Memory; | ||
using Dalamud.Plugin.Services; | ||
|
||
using FFXIVClientStructs.FFXIV.Client.UI.Agent; | ||
using FFXIVClientStructs.FFXIV.Component.GUI; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Base class for <see cref="IContextMenu"/> menu args. | ||
/// </summary> | ||
public abstract unsafe class MenuArgs | ||
{ | ||
private IReadOnlySet<nint>? eventInterfaces; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MenuArgs"/> class. | ||
/// </summary> | ||
/// <param name="addon">Addon associated with the context menu.</param> | ||
/// <param name="agent">Agent associated with the context menu.</param> | ||
/// <param name="type">The type of context menu.</param> | ||
/// <param name="eventInterfaces">List of AtkEventInterfaces associated with the context menu.</param> | ||
protected internal MenuArgs(AtkUnitBase* addon, AgentInterface* agent, ContextMenuType type, IReadOnlySet<nint>? eventInterfaces) | ||
{ | ||
this.AddonName = addon != null ? MemoryHelper.ReadString((nint)addon->Name, 32) : null; | ||
this.AddonPtr = (nint)addon; | ||
this.AgentPtr = (nint)agent; | ||
this.MenuType = type; | ||
this.eventInterfaces = eventInterfaces; | ||
this.Target = type switch | ||
{ | ||
ContextMenuType.Default => new MenuTargetDefault((AgentContext*)agent), | ||
ContextMenuType.Inventory => new MenuTargetInventory((AgentInventoryContext*)agent), | ||
_ => throw new ArgumentException("Invalid context menu type", nameof(type)), | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the name of the addon that opened the context menu. | ||
/// </summary> | ||
public string? AddonName { get; } | ||
|
||
/// <summary> | ||
/// Gets the memory pointer of the addon that opened the context menu. | ||
/// </summary> | ||
public nint AddonPtr { get; } | ||
|
||
/// <summary> | ||
/// Gets the memory pointer of the agent that opened the context menu. | ||
/// </summary> | ||
public nint AgentPtr { get; } | ||
|
||
/// <summary> | ||
/// Gets the type of the context menu. | ||
/// </summary> | ||
public ContextMenuType MenuType { get; } | ||
|
||
/// <summary> | ||
/// Gets the target info of the context menu. The actual type depends on <see cref="MenuType"/>. | ||
/// <see cref="ContextMenuType.Default"/> signifies a <see cref="MenuTargetDefault"/>. | ||
/// <see cref="ContextMenuType.Inventory"/> signifies a <see cref="MenuTargetInventory"/>. | ||
/// </summary> | ||
public MenuTarget Target { get; } | ||
|
||
/// <summary> | ||
/// Gets a list of AtkEventInterface pointers associated with the context menu. | ||
/// Only available with <see cref="ContextMenuType.Default"/>. | ||
/// Almost always an agent pointer. You can use this to find out what type of context menu it is. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException">Thrown when the context menu is not a <see cref="ContextMenuType.Default"/>.</exception> | ||
public IReadOnlySet<nint> EventInterfaces => | ||
this.MenuType != ContextMenuType.Default ? | ||
this.eventInterfaces : | ||
throw new InvalidOperationException("Not a default context menu"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
using Dalamud.Game.Text; | ||
using Dalamud.Game.Text.SeStringHandling; | ||
|
||
using Lumina.Excel.GeneratedSheets; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// A menu item that can be added to a context menu. | ||
/// </summary> | ||
public sealed record MenuItem | ||
{ | ||
/// <summary> | ||
/// Gets or sets the display name of the menu item. | ||
/// </summary> | ||
public SeString Name { get; set; } = SeString.Empty; | ||
|
||
/// <summary> | ||
/// Gets or sets the prefix attached to the beginning of <see cref="Name"/>. | ||
/// </summary> | ||
public SeIconChar? Prefix { get; set; } | ||
|
||
/// <summary> | ||
/// Sets the character to prefix the <see cref="Name"/> with. Will be converted into a fancy boxed letter icon. Must be an uppercase letter. | ||
/// </summary> | ||
/// <exception cref="ArgumentException"><paramref name="value"/> must be an uppercase letter.</exception> | ||
public char? PrefixChar | ||
{ | ||
set | ||
{ | ||
if (value is { } prefix) | ||
{ | ||
if (!char.IsAsciiLetterUpper(prefix)) | ||
throw new ArgumentException("Prefix must be an uppercase letter", nameof(value)); | ||
|
||
this.Prefix = SeIconChar.BoxedLetterA + prefix - 'A'; | ||
} | ||
else | ||
{ | ||
this.Prefix = null; | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the color of the <see cref="Prefix"/>. Specifies a <see cref="UIColor"/> row id. | ||
/// </summary> | ||
public ushort PrefixColor { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the callback to be invoked when the menu item is clicked. | ||
/// </summary> | ||
public Action<MenuItemClickedArgs>? OnClicked { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the priority (or order) with which the menu item should be displayed in descending order. | ||
/// Priorities below 0 will be displayed above the native menu items. | ||
/// Other priorities will be displayed below the native menu items. | ||
/// </summary> | ||
public int Priority { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the menu item is enabled. | ||
/// Disabled items will be faded and cannot be clicked on. | ||
/// </summary> | ||
public bool IsEnabled { get; set; } = true; | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the menu item is a submenu. | ||
/// This value is purely visual. Submenu items will have an arrow to its right. | ||
/// </summary> | ||
public bool IsSubmenu { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the menu item is a return item. | ||
/// This value is purely visual. Return items will have a back arrow to its left. | ||
/// If both <see cref="IsSubmenu"/> and <see cref="IsReturn"/> are true, the return arrow will take precedence. | ||
/// </summary> | ||
public bool IsReturn { get; set; } | ||
|
||
/// <summary> | ||
/// Gets the name with the given prefix. | ||
/// </summary> | ||
internal SeString PrefixedName => | ||
this.Prefix is { } prefix | ||
? new SeStringBuilder() | ||
.AddUiForeground($"{prefix.ToIconString()} ", this.PrefixColor) | ||
.Append(this.Name) | ||
.Build() | ||
: this.Name; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using System.Collections.Generic; | ||
|
||
using Dalamud.Game.Text.SeStringHandling; | ||
|
||
using FFXIVClientStructs.FFXIV.Component.GUI; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Callback args used when a menu item is clicked. | ||
/// </summary> | ||
public sealed unsafe class MenuItemClickedArgs : MenuArgs | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MenuItemClickedArgs"/> class. | ||
/// </summary> | ||
/// <param name="openSubmenu">Callback for opening a submenu.</param> | ||
/// <param name="addon">Addon associated with the context menu.</param> | ||
/// <param name="agent">Agent associated with the context menu.</param> | ||
/// <param name="type">The type of context menu.</param> | ||
/// <param name="eventInterfaces">List of AtkEventInterfaces associated with the context menu.</param> | ||
internal MenuItemClickedArgs(Action<SeString?, IReadOnlyList<MenuItem>> openSubmenu, AtkUnitBase* addon, AgentInterface* agent, ContextMenuType type, IReadOnlySet<nint> eventInterfaces) | ||
: base(addon, agent, type, eventInterfaces) | ||
{ | ||
this.OnOpenSubmenu = openSubmenu; | ||
} | ||
|
||
private Action<SeString?, IReadOnlyList<MenuItem>> OnOpenSubmenu { get; } | ||
|
||
/// <summary> | ||
/// Opens a submenu with the given name and items. | ||
/// </summary> | ||
/// <param name="name">The name of the submenu, displayed at the top.</param> | ||
/// <param name="items">The items to display in the submenu.</param> | ||
public void OpenSubmenu(SeString name, IReadOnlyList<MenuItem> items) => | ||
this.OnOpenSubmenu(name, items); | ||
|
||
/// <summary> | ||
/// Opens a submenu with the given items. | ||
/// </summary> | ||
/// <param name="items">The items to display in the submenu.</param> | ||
public void OpenSubmenu(IReadOnlyList<MenuItem> items) => | ||
this.OnOpenSubmenu(null, items); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Collections.Generic; | ||
|
||
using FFXIVClientStructs.FFXIV.Component.GUI; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Callback args used when a menu item is opened. | ||
/// </summary> | ||
public sealed unsafe class MenuOpenedArgs : MenuArgs | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MenuOpenedArgs"/> class. | ||
/// </summary> | ||
/// <param name="addMenuItem">Callback for adding a custom menu item.</param> | ||
/// <param name="addon">Addon associated with the context menu.</param> | ||
/// <param name="agent">Agent associated with the context menu.</param> | ||
/// <param name="type">The type of context menu.</param> | ||
/// <param name="eventInterfaces">List of AtkEventInterfaces associated with the context menu.</param> | ||
internal MenuOpenedArgs(Action<MenuItem> addMenuItem, AtkUnitBase* addon, AgentInterface* agent, ContextMenuType type, IReadOnlySet<nint> eventInterfaces) | ||
: base(addon, agent, type, eventInterfaces) | ||
{ | ||
this.OnAddMenuItem = addMenuItem; | ||
} | ||
|
||
private Action<MenuItem> OnAddMenuItem { get; } | ||
|
||
/// <summary> | ||
/// Adds a custom menu item to the context menu. | ||
/// </summary> | ||
/// <param name="item">The menu item to add.</param> | ||
public void AddMenuItem(MenuItem item) => | ||
this.OnAddMenuItem(item); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Base class for <see cref="MenuArgs"/> contexts. | ||
/// Discriminated based on <see cref="ContextMenuType"/>. | ||
/// </summary> | ||
public abstract class MenuTarget | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using Dalamud.Game.ClientState.Objects; | ||
using Dalamud.Game.ClientState.Objects.Types; | ||
using Dalamud.Game.ClientState.Resolvers; | ||
using Dalamud.Game.Network.Structures.InfoProxy; | ||
|
||
using FFXIVClientStructs.FFXIV.Client.UI.Agent; | ||
|
||
using Lumina.Excel.GeneratedSheets; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Target information on a default context menu. | ||
/// </summary> | ||
public sealed unsafe class MenuTargetDefault : MenuTarget | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MenuTargetDefault"/> class. | ||
/// </summary> | ||
/// <param name="context">The agent associated with the context menu.</param> | ||
internal MenuTargetDefault(AgentContext* context) | ||
{ | ||
this.Context = context; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the name of the target. | ||
/// </summary> | ||
public string TargetName => this.Context->TargetName.ToString(); | ||
|
||
/// <summary> | ||
/// Gets the object id of the target. | ||
/// </summary> | ||
public ulong TargetObjectId => this.Context->TargetObjectId; | ||
|
||
/// <summary> | ||
/// Gets the target object. | ||
/// </summary> | ||
public GameObject? TargetObject => Service<ObjectTable>.Get().SearchById(this.TargetObjectId); | ||
|
||
/// <summary> | ||
/// Gets the content id of the target. | ||
/// </summary> | ||
public ulong TargetContentId => this.Context->TargetContentId; | ||
|
||
/// <summary> | ||
/// Gets the home world id of the target. | ||
/// </summary> | ||
public ExcelResolver<World> TargetHomeWorld => new((uint)this.Context->TargetHomeWorldId); | ||
|
||
/// <summary> | ||
/// Gets the currently targeted character. Only shows up for specific targets, like friends, party finder listings, or party members. | ||
/// Just because this is <see langword="null"/> doesn't mean the target isn't a character. | ||
/// </summary> | ||
public CharacterData? TargetCharacter | ||
{ | ||
get | ||
{ | ||
var target = this.Context->CurrentContextMenuTarget; | ||
if (target != null) | ||
return new(target); | ||
return null; | ||
} | ||
} | ||
|
||
private AgentContext* Context { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using Dalamud.Game.Inventory; | ||
|
||
using FFXIVClientStructs.FFXIV.Client.UI.Agent; | ||
|
||
namespace Dalamud.Game.Gui.ContextMenu; | ||
|
||
/// <summary> | ||
/// Target information on an inventory context menu. | ||
/// </summary> | ||
public sealed unsafe class MenuTargetInventory : MenuTarget | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MenuTargetInventory"/> class. | ||
/// </summary> | ||
/// <param name="context">The agent associated with the context menu.</param> | ||
internal MenuTargetInventory(AgentInventoryContext* context) | ||
{ | ||
this.Context = context; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the target item. | ||
/// </summary> | ||
public GameInventoryItem? TargetItem | ||
{ | ||
get | ||
{ | ||
var target = this.Context->TargetInventorySlot; | ||
if (target != null) | ||
return new(*target); | ||
return null; | ||
} | ||
} | ||
|
||
private AgentInventoryContext* Context { get; } | ||
} |
Oops, something went wrong.