Compare commits

...

9 Commits

Author SHA1 Message Date
defnotken
012e80219e And more logging to test 2025-10-12 18:47:01 -05:00
defnotken
dae8127ac8 Merge branch '1.12.3' of https://git.lightless-sync.org/Lightless-Sync/LightlessClient into 1.12.3 2025-10-12 12:11:18 -05:00
defnotken
0635caab65 Safety checks for NullDrawObject 2025-10-12 12:09:06 -05:00
1530ac3911 Merge pull request 'notification-changes' (#59) from notification-changes into 1.12.3
Reviewed-on: #59
2025-10-12 18:08:52 +02:00
3f80467180 Merge branch '1.12.3' into notification-changes 2025-10-12 18:08:43 +02:00
defnotken
4b4e587a89 1.12.3 initial 2025-10-12 11:07:24 -05:00
choco
02c3846031 column rename for better UX c: 2025-10-12 18:02:53 +02:00
choco
a8a01b3034 added more customization to the notifs, settings improvemnts, left and right notifs, animations for sliding in and out 2025-10-12 18:00:49 +02:00
50a5046c96 1.12.2
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 51s
Reviewed-on: #52
2025-10-12 15:33:02 +02:00
10 changed files with 367 additions and 165 deletions

View File

@@ -150,7 +150,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
private void LightlessWatcher_FileChanged(object sender, FileSystemEventArgs e) private void LightlessWatcher_FileChanged(object sender, FileSystemEventArgs e)
{ {
Logger.LogTrace("Lightless FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath); Logger.LogInformation("Lightless FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
@@ -350,6 +350,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
public void InvokeScan() public void InvokeScan()
{ {
Logger.LogInformation("InvokeScan called");
TotalFiles = 0; TotalFiles = 0;
_currentFileProgress = 0; _currentFileProgress = 0;
_scanCancellationTokenSource = _scanCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource(); _scanCancellationTokenSource = _scanCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource();
@@ -388,6 +389,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
TotalFiles = 0; TotalFiles = 0;
_currentFileProgress = 0; _currentFileProgress = 0;
}, token); }, token);
Logger.LogInformation("InvokeScan finished");
} }
public void RecalculateFileCacheSize(CancellationToken token) public void RecalculateFileCacheSize(CancellationToken token)

View File

@@ -152,6 +152,7 @@ public sealed class FileCacheManager : IHostedService
_logger.LogTrace("Creating cache entry for {path}", path); _logger.LogTrace("Creating cache entry for {path}", path);
var cacheFolder = _configService.Current.CacheFolder; var cacheFolder = _configService.Current.CacheFolder;
if (string.IsNullOrEmpty(cacheFolder)) return null; if (string.IsNullOrEmpty(cacheFolder)) return null;
_logger.LogInformation("CreateCacheEntry finished for {path}", path);
return CreateFileEntity(cacheFolder, CachePrefix, fi); return CreateFileEntity(cacheFolder, CachePrefix, fi);
} }
@@ -309,6 +310,8 @@ public sealed class FileCacheManager : IHostedService
public Dictionary<string, FileCacheEntity?> GetFileCachesByPaths(string[] paths) public Dictionary<string, FileCacheEntity?> GetFileCachesByPaths(string[] paths)
{ {
_logger.LogInformation("GetFileCachesByPaths called for {count} paths", paths.Length);
_getCachesByPathsSemaphore.Wait(); _getCachesByPathsSemaphore.Wait();
try try
@@ -365,6 +368,8 @@ public sealed class FileCacheManager : IHostedService
} }
finally finally
{ {
_logger.LogInformation("GetFileCachesByPaths finished for {count} paths", paths.Length);
_getCachesByPathsSemaphore.Release(); _getCachesByPathsSemaphore.Release();
} }
} }

View File

@@ -95,6 +95,7 @@ public class LightlessConfig : ILightlessConfiguration
public bool ShowNotificationTimestamp { get; set; } = false; public bool ShowNotificationTimestamp { get; set; } = false;
// Position & Layout // Position & Layout
public NotificationCorner NotificationCorner { get; set; } = NotificationCorner.Right;
public int NotificationOffsetY { get; set; } = 50; public int NotificationOffsetY { get; set; } = 50;
public int NotificationOffsetX { get; set; } = 0; public int NotificationOffsetX { get; set; } = 0;
public float NotificationWidth { get; set; } = 350f; public float NotificationWidth { get; set; } = 350f;
@@ -102,6 +103,7 @@ public class LightlessConfig : ILightlessConfiguration
// Animation & Effects // Animation & Effects
public float NotificationAnimationSpeed { get; set; } = 10f; public float NotificationAnimationSpeed { get; set; } = 10f;
public float NotificationSlideSpeed { get; set; } = 10f;
public float NotificationAccentBarWidth { get; set; } = 3f; public float NotificationAccentBarWidth { get; set; } = 3f;
// Duration per Type // Duration per Type

View File

@@ -18,4 +18,10 @@ public enum NotificationType
Error, Error,
PairRequest, PairRequest,
Download Download
}
public enum NotificationCorner
{
Right,
Left
} }

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>1.12.2</Version> <Version>1.12.3</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl> <PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>

View File

@@ -98,7 +98,19 @@ public class PlayerDataFactory
private unsafe bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer) private unsafe bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer)
{ {
return ((Character*)playerPointer)->GameObject.DrawObject == null; if (playerPointer == IntPtr.Zero)
return true;
var character = (Character*)playerPointer;
if (character == null)
return true;
var gameObject = &character->GameObject;
if (gameObject == null)
return true;
return gameObject->DrawObject == null;
} }
private async Task<CharacterDataFragment> CreateCharacterData(GameObjectHandler playerRelatedObject, CancellationToken ct) private async Task<CharacterDataFragment> CreateCharacterData(GameObjectHandler playerRelatedObject, CancellationToken ct)

View File

@@ -48,7 +48,6 @@ public record PetNamesMessage(string PetNicknamesData) : MessageBase;
public record HonorificReadyMessage : MessageBase; public record HonorificReadyMessage : MessageBase;
public record TransientResourceChangedMessage(IntPtr Address) : MessageBase; public record TransientResourceChangedMessage(IntPtr Address) : MessageBase;
public record HaltScanMessage(string Source) : MessageBase; public record HaltScanMessage(string Source) : MessageBase;
public record ResumeScanMessage(string Source) : MessageBase;
public record NotificationMessage public record NotificationMessage
(string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase; (string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase;
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
@@ -56,12 +55,14 @@ public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) :
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage; public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
public record LightlessNotificationMessage(LightlessSync.UI.Models.LightlessNotification Notification) : MessageBase; public record LightlessNotificationMessage(LightlessSync.UI.Models.LightlessNotification Notification) : MessageBase;
public record LightlessNotificationDismissMessage(string NotificationId) : MessageBase; public record LightlessNotificationDismissMessage(string NotificationId) : MessageBase;
public record ClearAllNotificationsMessage : MessageBase;
public record CharacterDataAnalyzedMessage : MessageBase; public record CharacterDataAnalyzedMessage : MessageBase;
public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase; public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase;
public record PenumbraEndRedrawMessage(IntPtr Address) : MessageBase; public record PenumbraEndRedrawMessage(IntPtr Address) : MessageBase;
public record HubReconnectingMessage(Exception? Exception) : SameThreadMessage; public record HubReconnectingMessage(Exception? Exception) : SameThreadMessage;
public record HubReconnectedMessage(string? Arg) : SameThreadMessage; public record HubReconnectedMessage(string? Arg) : SameThreadMessage;
public record HubClosedMessage(Exception? Exception) : SameThreadMessage; public record HubClosedMessage(Exception? Exception) : SameThreadMessage;
public record ResumeScanMessage(string Source) : MessageBase;
public record DownloadReadyMessage(Guid RequestId) : MessageBase; public record DownloadReadyMessage(Guid RequestId) : MessageBase;
public record DownloadStartedMessage(GameObjectHandler DownloadId, Dictionary<string, FileDownloadStatus> DownloadStatus) : MessageBase; public record DownloadStartedMessage(GameObjectHandler DownloadId, Dictionary<string, FileDownloadStatus> DownloadStatus) : MessageBase;
public record DownloadFinishedMessage(GameObjectHandler DownloadId) : MessageBase; public record DownloadFinishedMessage(GameObjectHandler DownloadId) : MessageBase;

View File

@@ -201,6 +201,9 @@ public unsafe class NameplateHandler : IMediatorSubscriber
private void UpdateNameplateNodes() private void UpdateNameplateNodes()
{ {
var framework = Framework.Instance(); var framework = Framework.Instance();
if (framework == null) return;
var ui3DModule = framework->GetUIModule()->GetUI3DModule(); var ui3DModule = framework->GetUIModule()->GetUI3DModule();
if (ui3DModule == null) if (ui3DModule == null)
@@ -208,7 +211,13 @@ public unsafe class NameplateHandler : IMediatorSubscriber
for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i) for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i)
{ {
var objectInfo = ui3DModule->NamePlateObjectInfoPointers[i].Value; if (ui3DModule->NamePlateObjectInfoPointers.IsEmpty) continue;
var objectInfoPtr = ui3DModule->NamePlateObjectInfoPointers[i];
if (objectInfoPtr == null) continue;
var objectInfo = objectInfoPtr.Value;
if (objectInfo == null || objectInfo->GameObject == null) if (objectInfo == null || objectInfo->GameObject == null)
continue; continue;

View File

@@ -26,6 +26,8 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private readonly List<LightlessNotification> _notifications = new(); private readonly List<LightlessNotification> _notifications = new();
private readonly object _notificationLock = new(); private readonly object _notificationLock = new();
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly Dictionary<string, float> _notificationYOffsets = new();
private readonly Dictionary<string, float> _notificationTargetYOffsets = new();
public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService) public LightlessNotificationUI(ILogger<LightlessNotificationUI> logger, LightlessMediator mediator, PerformanceCollectorService performanceCollector, LightlessConfigService configService)
: base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector) : base(logger, mediator, "Lightless Notifications##LightlessNotifications", performanceCollector)
@@ -49,12 +51,11 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage); Mediator.Subscribe<LightlessNotificationMessage>(this, HandleNotificationMessage);
Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage); Mediator.Subscribe<LightlessNotificationDismissMessage>(this, HandleNotificationDismissMessage);
Mediator.Subscribe<ClearAllNotificationsMessage>(this, HandleClearAllNotifications);
} }
private void HandleNotificationMessage(LightlessNotificationMessage message) => private void HandleNotificationMessage(LightlessNotificationMessage message) => AddNotification(message.Notification);
AddNotification(message.Notification); private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) => RemoveNotification(message.NotificationId);
private void HandleClearAllNotifications(ClearAllNotificationsMessage message) => ClearAllNotifications();
private void HandleNotificationDismissMessage(LightlessNotificationDismissMessage message) =>
RemoveNotification(message.NotificationId);
public void AddNotification(LightlessNotification notification) public void AddNotification(LightlessNotification notification)
{ {
@@ -96,20 +97,34 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
} }
public void ClearAllNotifications()
{
lock (_notificationLock)
{
foreach (var notification in _notifications)
{
StartOutAnimation(notification);
}
}
}
private void StartOutAnimation(LightlessNotification notification) private void StartOutAnimation(LightlessNotification notification)
{ {
notification.IsAnimatingOut = true; notification.IsAnimatingOut = true;
notification.IsAnimatingIn = false; notification.IsAnimatingIn = false;
} }
private bool ShouldRemoveNotification(LightlessNotification notification) =>
notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f;
protected override void DrawInternal() protected override void DrawInternal()
{ {
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
lock (_notificationLock) lock (_notificationLock)
{ {
UpdateNotifications(); UpdateNotifications();
if (_notifications.Count == 0) if (_notifications.Count == 0)
{ {
ImGui.PopStyleVar(); ImGui.PopStyleVar();
@@ -118,33 +133,49 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
var viewport = ImGui.GetMainViewport(); var viewport = ImGui.GetMainViewport();
// Set window to full viewport height
var width = _configService.Current.NotificationWidth;
Size = new Vector2(width, viewport.WorkSize.Y);
SizeCondition = ImGuiCond.Always;
Position = CalculateWindowPosition(viewport); Position = CalculateWindowPosition(viewport);
PositionCondition = ImGuiCond.Always;
DrawAllNotifications(); DrawAllNotifications();
} }
ImGui.PopStyleVar(); ImGui.PopStyleVar();
} }
private Vector2 CalculateWindowPosition(ImGuiViewportPtr viewport) private Vector2 CalculateWindowPosition(ImGuiViewportPtr viewport)
{ {
var x = viewport.WorkPos.X + viewport.WorkSize.X - var corner = _configService.Current.NotificationCorner;
_configService.Current.NotificationWidth - var offsetX = _configService.Current.NotificationOffsetX;
_configService.Current.NotificationOffsetX - var width = _configService.Current.NotificationWidth;
WindowPaddingOffset;
var y = viewport.WorkPos.Y + _configService.Current.NotificationOffsetY; float posX = corner == NotificationCorner.Left
return new Vector2(x, y); ? viewport.WorkPos.X + offsetX - WindowPaddingOffset
: viewport.WorkPos.X + viewport.WorkSize.X - width - offsetX - WindowPaddingOffset;
return new Vector2(posX, viewport.WorkPos.Y);
} }
private void DrawAllNotifications() private void DrawAllNotifications()
{ {
var offsetY = _configService.Current.NotificationOffsetY;
var startY = ImGui.GetCursorPosY() + offsetY;
for (int i = 0; i < _notifications.Count; i++) for (int i = 0; i < _notifications.Count; i++)
{ {
DrawNotification(_notifications[i], i); var notification = _notifications[i];
if (i < _notifications.Count - 1) if (_notificationYOffsets.TryGetValue(notification.Id, out var yOffset))
{ {
ImGui.Dummy(new Vector2(0, _configService.Current.NotificationSpacing)); ImGui.SetCursorPosY(startY + yOffset);
} }
DrawNotification(notification, i);
} }
} }
@@ -174,18 +205,65 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private void UpdateAnimationsAndRemoveExpired(float deltaTime) private void UpdateAnimationsAndRemoveExpired(float deltaTime)
{ {
UpdateTargetYPositions();
for (int i = _notifications.Count - 1; i >= 0; i--) for (int i = _notifications.Count - 1; i >= 0; i--)
{ {
var notification = _notifications[i]; var notification = _notifications[i];
UpdateNotificationAnimation(notification, deltaTime); UpdateNotificationAnimation(notification, deltaTime);
UpdateNotificationYOffset(notification, deltaTime);
if (ShouldRemoveNotification(notification)) if (ShouldRemoveNotification(notification))
{ {
_notifications.RemoveAt(i); _notifications.RemoveAt(i);
_notificationYOffsets.Remove(notification.Id);
_notificationTargetYOffsets.Remove(notification.Id);
} }
} }
} }
private void UpdateTargetYPositions()
{
float currentY = 0f;
for (int i = 0; i < _notifications.Count; i++)
{
var notification = _notifications[i];
if (!_notificationTargetYOffsets.ContainsKey(notification.Id))
{
_notificationTargetYOffsets[notification.Id] = currentY;
_notificationYOffsets[notification.Id] = currentY;
}
else
{
_notificationTargetYOffsets[notification.Id] = currentY;
}
currentY += CalculateNotificationHeight(notification) + _configService.Current.NotificationSpacing;
}
}
private void UpdateNotificationYOffset(LightlessNotification notification, float deltaTime)
{
if (!_notificationYOffsets.ContainsKey(notification.Id) || !_notificationTargetYOffsets.ContainsKey(notification.Id))
return;
var current = _notificationYOffsets[notification.Id];
var target = _notificationTargetYOffsets[notification.Id];
var diff = target - current;
if (Math.Abs(diff) < 0.5f)
{
_notificationYOffsets[notification.Id] = target;
}
else
{
var speed = _configService.Current.NotificationSlideSpeed;
_notificationYOffsets[notification.Id] = current + (diff * deltaTime * speed);
}
}
private void UpdateNotificationAnimation(LightlessNotification notification, float deltaTime) private void UpdateNotificationAnimation(LightlessNotification notification, float deltaTime)
{ {
if (notification.IsAnimatingIn && notification.AnimationProgress < 1f) if (notification.IsAnimatingIn && notification.AnimationProgress < 1f)
@@ -209,20 +287,24 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
} }
} }
private bool ShouldRemoveNotification(LightlessNotification notification) => private Vector2 CalculateSlideOffset(float alpha)
notification.IsAnimatingOut && notification.AnimationProgress <= 0.01f; {
var distance = (1f - alpha) * SlideAnimationDistance;
var corner = _configService.Current.NotificationCorner;
return corner == NotificationCorner.Left ? new Vector2(-distance, 0) : new Vector2(distance, 0);
}
private void DrawNotification(LightlessNotification notification, int index) private void DrawNotification(LightlessNotification notification, int index)
{ {
var alpha = notification.AnimationProgress; var alpha = notification.AnimationProgress;
if (alpha <= 0f) return; if (alpha <= 0f) return;
var slideOffset = (1f - alpha) * SlideAnimationDistance; var slideOffset = CalculateSlideOffset(alpha);
var originalCursorPos = ImGui.GetCursorPos(); var originalCursorPos = ImGui.GetCursorPos();
ImGui.SetCursorPosX(originalCursorPos.X + slideOffset); ImGui.SetCursorPos(originalCursorPos + slideOffset);
var notificationHeight = CalculateNotificationHeight(notification); var notificationHeight = CalculateNotificationHeight(notification);
var notificationWidth = _configService.Current.NotificationWidth - slideOffset; var notificationWidth = _configService.Current.NotificationWidth - Math.Abs(slideOffset.X);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
@@ -308,15 +390,28 @@ public class LightlessNotificationUI : WindowMediatorSubscriberBase
private void DrawAccentBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 accentColor) private void DrawAccentBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, Vector4 accentColor)
{ {
var accentWidth = _configService.Current.NotificationAccentBarWidth; var accentWidth = _configService.Current.NotificationAccentBarWidth;
if (accentWidth > 0f) if (accentWidth <= 0f) return;
var corner = _configService.Current.NotificationCorner;
Vector2 accentStart, accentEnd;
if (corner == NotificationCorner.Left)
{ {
drawList.AddRectFilled( accentStart = windowPos + new Vector2(windowSize.X - accentWidth, 0);
windowPos, accentEnd = windowPos + windowSize;
windowPos + new Vector2(accentWidth, windowSize.Y),
ImGui.ColorConvertFloat4ToU32(accentColor),
3f
);
} }
else
{
accentStart = windowPos;
accentEnd = windowPos + new Vector2(accentWidth, windowSize.Y);
}
drawList.AddRectFilled(
accentStart,
accentEnd,
ImGui.ColorConvertFloat4ToU32(accentColor),
3f
);
} }
private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList) private void DrawDurationProgressBar(LightlessNotification notification, float alpha, Vector2 windowPos, Vector2 windowSize, ImDrawListPtr drawList)

View File

@@ -3493,69 +3493,162 @@ public class SettingsUi : WindowMediatorSubscriberBase
if (useLightlessNotifications) if (useLightlessNotifications)
{ {
// Lightless notification locations // Lightless notification locations
ImGui.Indent();
var lightlessLocations = GetLightlessNotificationLocations(); var lightlessLocations = GetLightlessNotificationLocations();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Info Notifications:");
ImGui.SameLine();
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
_uiShared.DrawCombo("###enhanced_info", lightlessLocations, GetNotificationLocationLabel, (location) =>
{
_configService.Current.LightlessInfoNotification = location;
_configService.Save();
}, _configService.Current.LightlessInfoNotification);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Warning Notifications:");
ImGui.SameLine();
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
_uiShared.DrawCombo("###enhanced_warning", lightlessLocations, GetNotificationLocationLabel,
(location) =>
{
_configService.Current.LightlessWarningNotification = location;
_configService.Save();
}, _configService.Current.LightlessWarningNotification);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Error Notifications:");
ImGui.SameLine();
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
_uiShared.DrawCombo("###enhanced_error", lightlessLocations, GetNotificationLocationLabel, (location) =>
{
_configService.Current.LightlessErrorNotification = location;
_configService.Save();
}, _configService.Current.LightlessErrorNotification);
ImGuiHelpers.ScaledDummy(3);
_uiShared.DrawHelpText("Special notification types:");
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Pair Request Notifications:");
ImGui.SameLine();
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
_uiShared.DrawCombo("###enhanced_pairrequest", lightlessLocations, GetNotificationLocationLabel,
(location) =>
{
_configService.Current.LightlessPairRequestNotification = location;
_configService.Save();
}, _configService.Current.LightlessPairRequestNotification);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Download Progress Notifications:");
ImGui.SameLine();
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
var downloadLocations = GetDownloadNotificationLocations(); var downloadLocations = GetDownloadNotificationLocations();
_uiShared.DrawCombo("###enhanced_download", downloadLocations, GetNotificationLocationLabel,
(location) => if (ImGui.BeginTable("##NotificationLocationTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit))
{
ImGui.TableSetupColumn("Notification Type", ImGuiTableColumnFlags.WidthFixed, 200f * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Location", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Test", ImGuiTableColumnFlags.WidthFixed, 40f * ImGuiHelpers.GlobalScale);
ImGui.TableHeadersRow();
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Info Notifications");
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(-1);
_uiShared.DrawCombo("###enhanced_info", lightlessLocations, GetNotificationLocationLabel, (location) =>
{ {
_configService.Current.LightlessDownloadNotification = location; _configService.Current.LightlessInfoNotification = location;
_configService.Save(); _configService.Save();
}, _configService.Current.LightlessDownloadNotification); }, _configService.Current.LightlessInfoNotification);
ImGui.TableSetColumnIndex(2);
var availableWidth = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_info", new Vector2(availableWidth, 0)))
{
Mediator.Publish(new NotificationMessage("Test Info",
"This is a test info notification to let you know Chocola is cute :3", NotificationType.Info));
}
}
UiSharedService.AttachToolTip("Test info notification");
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Warning Notifications");
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(-1);
_uiShared.DrawCombo("###enhanced_warning", lightlessLocations, GetNotificationLocationLabel,
(location) =>
{
_configService.Current.LightlessWarningNotification = location;
_configService.Save();
}, _configService.Current.LightlessWarningNotification);
ImGui.TableSetColumnIndex(2);
availableWidth = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_warning", new Vector2(availableWidth, 0)))
{
Mediator.Publish(new NotificationMessage("Test Warning", "This is a test warning notification!",
NotificationType.Warning));
}
}
UiSharedService.AttachToolTip("Test warning notification");
ImGui.Unindent(); ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Error Notifications");
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(-1);
_uiShared.DrawCombo("###enhanced_error", lightlessLocations, GetNotificationLocationLabel, (location) =>
{
_configService.Current.LightlessErrorNotification = location;
_configService.Save();
}, _configService.Current.LightlessErrorNotification);
ImGui.TableSetColumnIndex(2);
availableWidth = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_error", new Vector2(availableWidth, 0)))
{
Mediator.Publish(new NotificationMessage("Test Error", "This is a test error notification!",
NotificationType.Error));
}
}
UiSharedService.AttachToolTip("Test error notification");
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Pair Request Notifications");
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(-1);
_uiShared.DrawCombo("###enhanced_pairrequest", lightlessLocations, GetNotificationLocationLabel,
(location) =>
{
_configService.Current.LightlessPairRequestNotification = location;
_configService.Save();
}, _configService.Current.LightlessPairRequestNotification);
ImGui.TableSetColumnIndex(2);
availableWidth = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_pair", new Vector2(availableWidth, 0)))
{
_lightlessNotificationService.ShowPairRequestNotification(
"Test User",
"test-uid-123",
() =>
{
Mediator.Publish(new NotificationMessage("Accepted", "You accepted the test pair request.",
NotificationType.Info));
},
() =>
{
Mediator.Publish(new NotificationMessage("Declined", "You declined the test pair request.",
NotificationType.Info));
}
);
}
}
UiSharedService.AttachToolTip("Test pair request notification");
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Download Progress Notifications");
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(-1);
_uiShared.DrawCombo("###enhanced_download", downloadLocations, GetNotificationLocationLabel,
(location) =>
{
_configService.Current.LightlessDownloadNotification = location;
_configService.Save();
}, _configService.Current.LightlessDownloadNotification);
ImGui.TableSetColumnIndex(2);
availableWidth = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
if (ImGui.Button($"{FontAwesomeIcon.Play.ToIconString()}##test_download", new Vector2(availableWidth, 0)))
{
_lightlessNotificationService.ShowPairDownloadNotification(
new List<(string playerName, float progress, string status)>
{
("Player One", 0.35f, "downloading"),
("Player Two", 0.75f, "downloading"),
("Player Three", 1.0f, "downloading")
},
queueWaiting: 2
);
}
}
UiSharedService.AttachToolTip("Test download progress notification");
ImGui.EndTable();
}
ImGuiHelpers.ScaledDummy(5);
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Clear All Notifications"))
{
Mediator.Publish(new ClearAllNotificationsMessage());
}
_uiShared.DrawHelpText("Dismiss all active notifications immediately.");
} }
else else
{ {
@@ -3602,73 +3695,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator(); ImGui.Separator();
if (useLightlessNotifications) if (useLightlessNotifications)
{ {
if (_uiShared.MediumTreeNode("Test Notifications", UIColors.Get("LightlessPurple")))
{
ImGui.Indent();
// Test notification buttons
if (_uiShared.IconTextButton(FontAwesomeIcon.Bell, "Test Info"))
{
Mediator.Publish(new NotificationMessage("Test Info",
"This is a test info notification to let you know Chocola is cute :3", NotificationType.Info));
}
ImGui.SameLine();
if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationTriangle, "Test Warning"))
{
Mediator.Publish(new NotificationMessage("Test Warning", "This is a test warning notification!",
NotificationType.Warning));
}
ImGui.SameLine();
if (_uiShared.IconTextButton(FontAwesomeIcon.ExclamationCircle, "Test Error"))
{
Mediator.Publish(new NotificationMessage("Test Error", "This is a test error notification!",
NotificationType.Error));
}
ImGuiHelpers.ScaledDummy(3);
if (_uiShared.IconTextButton(FontAwesomeIcon.UserPlus, "Test Pair Request"))
{
_lightlessNotificationService.ShowPairRequestNotification(
"Test User",
"test-uid-123",
() =>
{
Mediator.Publish(new NotificationMessage("Accepted", "You accepted the test pair request.",
NotificationType.Info));
},
() =>
{
Mediator.Publish(new NotificationMessage("Declined", "You declined the test pair request.",
NotificationType.Info));
}
);
}
ImGui.SameLine();
if (_uiShared.IconTextButton(FontAwesomeIcon.Download, "Test Download Progress"))
{
_lightlessNotificationService.ShowPairDownloadNotification(
new List<(string playerName, float progress, string status)>
{
("Player One", 0.35f, "downloading"),
("Player Two", 0.75f, "downloading"),
("Player Three", 1.0f, "downloading")
},
queueWaiting: 2
);
}
_uiShared.DrawHelpText("Preview how notifications will appear with your current settings.");
ImGui.Unindent();
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
ImGui.TreePop();
}
ImGui.Separator();
if (_uiShared.MediumTreeNode("Basic Settings", UIColors.Get("LightlessPurple"))) if (_uiShared.MediumTreeNode("Basic Settings", UIColors.Get("LightlessPurple")))
{ {
int maxNotifications = _configService.Current.MaxSimultaneousNotifications; int maxNotifications = _configService.Current.MaxSimultaneousNotifications;
@@ -3768,10 +3794,28 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Spacing(); ImGui.Spacing();
ImGui.TextUnformatted("Position"); ImGui.TextUnformatted("Position");
int offsetY = _configService.Current.NotificationOffsetY; var currentCorner = _configService.Current.NotificationCorner;
if (ImGui.SliderInt("Vertical Offset", ref offsetY, 0, 500)) if (ImGui.BeginCombo("Notification Position", GetNotificationCornerLabel(currentCorner)))
{ {
_configService.Current.NotificationOffsetY = Math.Clamp(offsetY, 0, 500); foreach (NotificationCorner corner in Enum.GetValues(typeof(NotificationCorner)))
{
bool isSelected = currentCorner == corner;
if (ImGui.Selectable(GetNotificationCornerLabel(corner), isSelected))
{
_configService.Current.NotificationCorner = corner;
_configService.Save();
}
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
_uiShared.DrawHelpText("Choose which corner of the screen notifications appear in.");
int offsetY = _configService.Current.NotificationOffsetY;
if (ImGui.SliderInt("Vertical Offset", ref offsetY, 0, 1000))
{
_configService.Current.NotificationOffsetY = Math.Clamp(offsetY, 0, 1000);
_configService.Save(); _configService.Save();
} }
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
@@ -3781,7 +3825,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Right click to reset to default (50)."); ImGui.SetTooltip("Right click to reset to default (50).");
_uiShared.DrawHelpText("Move notifications down from the top-right corner."); _uiShared.DrawHelpText("Distance from the top edge of the screen.");
int offsetX = _configService.Current.NotificationOffsetX; int offsetX = _configService.Current.NotificationOffsetX;
if (ImGui.SliderInt("Horizontal Offset", ref offsetX, 0, 500)) if (ImGui.SliderInt("Horizontal Offset", ref offsetX, 0, 500))
@@ -3802,9 +3846,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted("Animation Settings"); ImGui.TextUnformatted("Animation Settings");
float animSpeed = _configService.Current.NotificationAnimationSpeed; float animSpeed = _configService.Current.NotificationAnimationSpeed;
if (ImGui.SliderFloat("Animation Speed", ref animSpeed, 1f, 30f, "%.1f")) if (ImGui.SliderFloat("Animation Speed", ref animSpeed, 1f, 20f, "%.1f"))
{ {
_configService.Current.NotificationAnimationSpeed = Math.Clamp(animSpeed, 1f, 30f); _configService.Current.NotificationAnimationSpeed = Math.Clamp(animSpeed, 1f, 20f);
_configService.Save(); _configService.Save();
} }
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
@@ -3816,6 +3860,21 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.SetTooltip("Right click to reset to default (10)."); ImGui.SetTooltip("Right click to reset to default (10).");
_uiShared.DrawHelpText("How fast notifications slide in/out. Higher = faster."); _uiShared.DrawHelpText("How fast notifications slide in/out. Higher = faster.");
float slideSpeed = _configService.Current.NotificationSlideSpeed;
if (ImGui.SliderFloat("Slide Speed", ref slideSpeed, 1f, 20f, "%.1f"))
{
_configService.Current.NotificationSlideSpeed = Math.Clamp(slideSpeed, 1f, 20f);
_configService.Save();
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
_configService.Current.NotificationSlideSpeed = 10f;
_configService.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Right click to reset to default (10).");
_uiShared.DrawHelpText("How fast notifications slide into position when others disappear. Higher = faster.");
ImGui.Spacing(); ImGui.Spacing();
ImGui.TextUnformatted("Visual Effects"); ImGui.TextUnformatted("Visual Effects");
@@ -3999,6 +4058,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator(); ImGui.Separator();
// Location descriptions removed - information is now inline with each setting // Location descriptions removed - information is now inline with each setting
} }
} }
@@ -4043,6 +4103,16 @@ public class SettingsUi : WindowMediatorSubscriberBase
}; };
} }
private string GetNotificationCornerLabel(NotificationCorner corner)
{
return corner switch
{
NotificationCorner.Right => "Right",
NotificationCorner.Left => "Left",
_ => corner.ToString()
};
}
private void DrawSoundTable() private void DrawSoundTable()
{ {
var soundEffects = new[] var soundEffects = new[]
@@ -4087,7 +4157,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
var currentIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentSoundId); var currentIndex = Array.FindIndex(soundEffects, s => s.Item1 == currentSoundId);
if (currentIndex == -1) currentIndex = 1; if (currentIndex == -1) currentIndex = 1;
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(-1);
if (ImGui.Combo($"##sound_{typeIndex}", ref currentIndex, if (ImGui.Combo($"##sound_{typeIndex}", ref currentIndex,
soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length)) soundEffects.Select(s => s.Item2).ToArray(), soundEffects.Length))
{ {