feat(ui): add per-tab notification sound for inactive tabs
This commit is contained in:
@@ -446,6 +446,10 @@ public class Tab
|
||||
public bool AllSenderMessages;
|
||||
public TellTarget TellTarget = TellTarget.Empty();
|
||||
|
||||
// UI-3: per-tab notification sound for messages arriving in an inactive tab.
|
||||
public bool EnableNotificationSound;
|
||||
public uint NotificationSoundId = 1;
|
||||
|
||||
[NonSerialized]
|
||||
public uint Unread;
|
||||
|
||||
@@ -564,6 +568,8 @@ public class Tab
|
||||
IsPinned = IsPinned,
|
||||
AllSenderMessages = AllSenderMessages,
|
||||
TellTarget = TellTarget.Clone(),
|
||||
EnableNotificationSound = EnableNotificationSound,
|
||||
NotificationSoundId = NotificationSoundId,
|
||||
IsGreeted = IsGreeted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
@@ -330,6 +332,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
Store.UpsertMessage(message);
|
||||
|
||||
var currentMatches = Plugin.CurrentTab.Matches(message);
|
||||
uint? notificationSound = null;
|
||||
foreach (var tab in Plugin.Config.Tabs)
|
||||
{
|
||||
var unread = !(
|
||||
@@ -337,7 +340,31 @@ internal class MessageManager : IAsyncDisposable
|
||||
);
|
||||
|
||||
if (tab.Matches(message))
|
||||
{
|
||||
tab.AddMessage(message, unread);
|
||||
|
||||
// UI-3: per-tab notification sound. Fire once for the first
|
||||
// inactive tab that wants it — keeps a message matching several
|
||||
// background tabs from stacking sounds.
|
||||
// TEST-MIRROR: ../_Helpers/TabSoundDecision.cs
|
||||
if (notificationSound is null
|
||||
&& TabSoundDecision.ShouldPlay(
|
||||
Plugin.CurrentTab == tab,
|
||||
tab.EnableNotificationSound,
|
||||
Plugin.Config.PlaySounds))
|
||||
{
|
||||
notificationSound = tab.NotificationSoundId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationSound is { } soundId)
|
||||
{
|
||||
// ProcessMessage runs on the PendingMessageThread worker; the native
|
||||
// UIGlobals.PlaySoundEffect must be marshalled onto the framework
|
||||
// thread (reference_dalamud_framework_thread).
|
||||
Plugin.Framework.RunOnFrameworkThread(
|
||||
() => { unsafe { UIGlobals.PlaySoundEffect(soundId); } });
|
||||
}
|
||||
|
||||
MessageProcessed?.Invoke(message);
|
||||
|
||||
@@ -165,6 +165,27 @@ internal sealed class Tabs : ISettingsTab
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_Tabs_ShowTimestamps, ref tab.DisplayTimestamp);
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Tabs_NotificationSound_Enable_Name,
|
||||
ref tab.EnableNotificationSound
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Tabs_NotificationSound_Description);
|
||||
if (tab.EnableNotificationSound)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(10.0f);
|
||||
var soundPreview = $"{HellionStrings.Tabs_NotificationSound_Option} {tab.NotificationSoundId}";
|
||||
using var combo = ImRaii.Combo($"##notif-sound-{i}", soundPreview);
|
||||
if (combo.Success)
|
||||
{
|
||||
for (uint s = 1; s <= 16; s++)
|
||||
{
|
||||
if (ImGui.Selectable(
|
||||
$"{HellionStrings.Tabs_NotificationSound_Option} {s}",
|
||||
tab.NotificationSoundId == s))
|
||||
tab.NotificationSoundId = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Checkbox(Language.Options_Tabs_PopOut, ref tab.PopOut);
|
||||
if (tab.PopOut)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace HellionChat._Helpers;
|
||||
|
||||
// UI-3 pure decision helper: should an incoming message play a per-tab
|
||||
// notification sound? Kept Dalamud-free so the Build Suite can test the
|
||||
// "inactive + enabled + global-allowed" rule in isolation.
|
||||
// TEST-MIRROR: ../../../Hellion Build test/Ui/TabSoundDecisionTests.cs
|
||||
public static class TabSoundDecision
|
||||
{
|
||||
// True only when the message landed in a tab the user is not looking at,
|
||||
// that tab has its own sound switched on, and the global sound master is
|
||||
// not muted.
|
||||
public static bool ShouldPlay(bool isActiveTab, bool tabSoundEnabled, bool globalSoundsEnabled)
|
||||
=> !isActiveTab && tabSoundEnabled && globalSoundsEnabled;
|
||||
}
|
||||
Reference in New Issue
Block a user