145 lines
5.2 KiB
C#
145 lines
5.2 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using HellionChat.Resources;
|
|
using HellionChat.Util;
|
|
|
|
namespace HellionChat.Ui.Components;
|
|
|
|
// Shared right-click menu for both tab layouts (Sidebar rows + TopTabBar). One
|
|
// source of truth instead of two divergent inline blocks. Static: it has no own
|
|
// state and reaches the live Config/Plugin through Plugin.Instance/Plugin.Config.
|
|
internal static class TabContextMenu
|
|
{
|
|
// MUST be called immediately after the row-carrying ImGui item (Sidebar
|
|
// "row" InvisibleButton / TopTabBar Selectable). popupId only names the
|
|
// popup; the open trigger is a right-click on the LAST submitted item
|
|
// (g.LastItemData via IsItemHovered) — any interactive item in between
|
|
// would steal the trigger. Only DrawList ops may sit between.
|
|
public static void Draw(Tab tab, string popupId, Windows.ChannelPopoutPool pool)
|
|
{
|
|
if (!ImGui.BeginPopupContextItem(popupId))
|
|
return;
|
|
|
|
// Rename: focus the field the first frame the popup appears.
|
|
if (ImGui.IsWindowAppearing())
|
|
ImGui.SetKeyboardFocusHere();
|
|
ImGui.SetNextItemWidth(250f * ImGuiHelpers.GlobalScale);
|
|
var name = tab.Name;
|
|
if (ImGui.InputText("##tab-name", ref name, 512) && ApplyTabRename(tab, name))
|
|
Plugin.Instance.SaveConfig();
|
|
|
|
// Per-tab notification sound (B3-3). The checkbox gates the picker so
|
|
// tabs that never want a sound keep the popup short.
|
|
if (
|
|
ImGui.Checkbox(
|
|
HellionStrings.Tabs_NotificationSound_Enable_Name,
|
|
ref tab.EnableNotificationSound
|
|
)
|
|
)
|
|
Plugin.Instance.SaveConfig();
|
|
ImGuiUtil.HelpMarker(HellionStrings.Tabs_NotificationSound_Description);
|
|
if (tab.EnableNotificationSound)
|
|
DrawSoundPicker(tab);
|
|
|
|
if (ImGui.MenuItem("Pop Out"))
|
|
pool.TryOpen(tab);
|
|
|
|
ImGui.EndPopup();
|
|
}
|
|
|
|
// Sound picker: 16 numbered game sounds, a separator, then the 3 bundled
|
|
// Hellion clips stored as ids 17-19 (1.5.6 parity order). The collapsed
|
|
// preview reuses the entry label scheme so the current pick reads the same
|
|
// open or closed.
|
|
private static void DrawSoundPicker(Tab tab)
|
|
{
|
|
var preview =
|
|
tab.NotificationSoundId <= 16
|
|
? $"{HellionStrings.Tabs_NotificationSound_Option} {tab.NotificationSoundId}"
|
|
: $"{HellionStrings.Tabs_NotificationSound_CustomOption} {tab.NotificationSoundId - 16}";
|
|
using (
|
|
var combo = ImGuiUtil.BeginComboVertical(
|
|
HellionStrings.Tabs_NotificationSound_Option,
|
|
preview
|
|
)
|
|
)
|
|
{
|
|
if (combo.Success)
|
|
{
|
|
for (uint s = 1; s <= 16; s++)
|
|
{
|
|
if (
|
|
ImGui.Selectable(
|
|
$"{HellionStrings.Tabs_NotificationSound_Option} {s}",
|
|
tab.NotificationSoundId == s
|
|
)
|
|
)
|
|
{
|
|
tab.NotificationSoundId = s;
|
|
Plugin.Instance.SaveConfig();
|
|
}
|
|
}
|
|
|
|
ImGui.Separator();
|
|
|
|
for (uint n = 1; n <= 3; n++)
|
|
{
|
|
var customId = 16 + n;
|
|
if (
|
|
ImGui.Selectable(
|
|
$"{HellionStrings.Tabs_NotificationSound_CustomOption} {n}",
|
|
tab.NotificationSoundId == customId
|
|
)
|
|
)
|
|
{
|
|
tab.NotificationSoundId = customId;
|
|
Plugin.Instance.SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
ImGuiUtil.IconButton(
|
|
FontAwesomeIcon.Play,
|
|
"tab-sound-preview",
|
|
HellionStrings.Tabs_NotificationSound_Preview
|
|
)
|
|
)
|
|
PreviewSound(tab.NotificationSoundId);
|
|
}
|
|
|
|
// Preview: 1-16 are game UI sounds (must hit the framework thread); 17+ are
|
|
// custom NAudio clips (own playback thread). Open range >= 17 (not 17-19); the
|
|
// 3-clip ceiling is guarded inside CustomAudioPlayer.
|
|
private static void PreviewSound(uint id)
|
|
{
|
|
if (id is >= 1 and <= 16)
|
|
{
|
|
Plugin.Framework.RunOnFrameworkThread(() =>
|
|
{
|
|
unsafe
|
|
{
|
|
UIGlobals.PlaySoundEffect(id);
|
|
}
|
|
});
|
|
}
|
|
else if (id >= 17)
|
|
{
|
|
Plugin.Instance.CustomAudioPlayer.Play((int)id - 16, Plugin.Config.CustomSoundVolume);
|
|
}
|
|
}
|
|
|
|
// Factored out so the SelfTest drives the real rename path, not a field poke.
|
|
// Returns true when the name actually changed (gates the SaveConfig write).
|
|
internal static bool ApplyTabRename(Tab tab, string newName)
|
|
{
|
|
if (string.IsNullOrEmpty(newName) || newName == tab.Name)
|
|
return false;
|
|
tab.Name = newName;
|
|
return true;
|
|
}
|
|
}
|