2.0.0 (#92)
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s

2.0.0 Changes:

- Reworked shell finder UI with compact or list view with profile tags showing with the listing, allowing moderators to broadcast the syncshell as well to have it be used more.
- Reworked user list in syncshell admin screen to have filter visible and moved away from table to its own thing, allowing to copy uid/note/alias when clicking on the name.
- Reworked download bars and download box to make it look more modern, removed the jitter around, so it shouldn't vibrate around much.
- Chat has been added to the top menu, working in Zone or in Syncshells to be used there.
- Paired system has been revamped to make pausing and unpausing faster, and loading people should be faster as well.
- Moved to the internal object table to have faster load times for users; people should load in faster
- Compactor is running on a multi-threaded level instead of single-threaded; this should increase the speed of compacting files
- Nameplate Service has been reworked so it wouldn't use the nameplate handler anymore.
- Files can be resized when downloading to reduce load on users if they aren't compressed. (can be toggled to resize all).
- Penumbra Collections are now only made when people are visible, reducing the load on boot-up when having many syncshells in your list.
- Lightfinder plates have been moved away from using Nameplates, but will use an overlay.
- Main UI has been changed a bit with a gradient, and on hover will glow up now.
- Reworked Profile UI for Syncshell and Users to be more user-facing with more customizable items.
- Reworked Settings UI to look more modern.
- Performance should be better due to new systems that would dispose of the collections and better caching of items.

Co-authored-by: defnotken <itsdefnotken@gmail.com>
Co-authored-by: azyges <aaaaaa@aaa.aaa>
Co-authored-by: choco <choco@patat.nl>
Co-authored-by: cake <admin@cakeandbanana.nl>
Co-authored-by: Minmoose <KennethBohr@outlook.com>
Reviewed-on: #92
This commit was merged in pull request #92.
This commit is contained in:
2025-12-21 17:19:34 +00:00
parent 906f401940
commit 835a0a637d
191 changed files with 32636 additions and 8841 deletions

View File

@@ -13,6 +13,8 @@ using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using Dalamud.Interface;
using LightlessSync.UI.Models;
using LightlessSync.UI.Style;
using LightlessSync.Utils;
namespace LightlessSync.UI;
@@ -25,39 +27,8 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
private ChangelogFile _changelog = new();
private CreditsFile _credits = new();
private bool _scrollToTop;
private int _selectedTab;
private bool _hasInitializedCollapsingHeaders;
private struct Particle
{
public Vector2 Position;
public Vector2 Velocity;
public float Life;
public float MaxLife;
public float Size;
public ParticleType Type;
public List<Vector2>? Trail;
public float Twinkle;
public float Depth;
public float Hue;
}
private enum ParticleType
{
TwinklingStar,
ShootingStar
}
private readonly List<Particle> _particles = [];
private float _particleSpawnTimer;
private readonly Random _random = new();
private const float _headerHeight = 150f;
private const float _particleSpawnInterval = 0.2f;
private const int _maxParticles = 50;
private const int _maxTrailLength = 50;
private const float _edgeFadeDistance = 30f;
private const float _extendedParticleHeight = 40f;
private readonly AnimatedHeader _animatedHeader = new();
public UpdateNotesUi(ILogger<UpdateNotesUi> logger,
LightlessMediator mediator,
@@ -70,21 +41,20 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
_uiShared = uiShared;
_configService = configService;
AllowClickthrough = false;
AllowPinning = false;
RespectCloseHotkey = true;
ShowCloseButton = true;
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse |
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoMove;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(800, 700), MaximumSize = new Vector2(800, 700),
};
PositionCondition = ImGuiCond.Always;
WindowBuilder.For(this)
.AllowPinning(false)
.AllowClickthrough(false)
.SetFixedSize(new Vector2(800, 700))
.Apply();
LoadEmbeddedResources();
logger.LogInformation("UpdateNotesUi constructor completed successfully");
}
@@ -95,6 +65,11 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
_hasInitializedCollapsingHeaders = false;
}
public override void OnClose()
{
_animatedHeader.ClearParticles();
}
private void CenterWindow()
{
var viewport = ImGui.GetMainViewport();
@@ -117,21 +92,18 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
private void DrawHeader()
{
var windowPos = ImGui.GetWindowPos();
var windowPadding = ImGui.GetStyle().WindowPadding;
var headerWidth = (800f * ImGuiHelpers.GlobalScale) - (windowPadding.X * 2);
var headerStart = windowPos + new Vector2(windowPadding.X, windowPadding.Y);
var headerEnd = headerStart + new Vector2(headerWidth, _headerHeight);
var buttons = new List<HeaderButton>
{
new(FontAwesomeIcon.Comments, "Join our Discord", () => Util.OpenLink("https://discord.gg/dsbjcXMnhA")),
new(FontAwesomeIcon.Code, "View on Git", () => Util.OpenLink("https://git.lightless-sync.org/Lightless-Sync"))
};
var extendedParticleSize = new Vector2(headerWidth, _headerHeight + _extendedParticleHeight);
_animatedHeader.DrawWithButtons(headerWidth, "Lightless Sync", "Update Notes", buttons, _uiShared.UidFont);
DrawGradientBackground(headerStart, headerEnd);
DrawHeaderText(headerStart);
DrawHeaderButtons(headerStart, headerWidth);
DrawBottomGradient(headerStart, headerEnd, headerWidth);
ImGui.SetCursorPosY(windowPadding.Y + _headerHeight + 5);
ImGui.SetCursorPosY(windowPadding.Y + _animatedHeader.Height + 5);
ImGui.SetCursorPosX(20);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
@@ -156,347 +128,8 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
}
ImGuiHelpers.ScaledDummy(3);
DrawParticleEffects(headerStart, extendedParticleSize);
}
private void DrawGradientBackground(Vector2 headerStart, Vector2 headerEnd)
{
var drawList = ImGui.GetWindowDrawList();
var darkPurple = new Vector4(0.08f, 0.05f, 0.15f, 1.0f);
var deepPurple = new Vector4(0.12f, 0.08f, 0.20f, 1.0f);
drawList.AddRectFilledMultiColor(
headerStart,
headerEnd,
ImGui.GetColorU32(darkPurple),
ImGui.GetColorU32(darkPurple),
ImGui.GetColorU32(deepPurple),
ImGui.GetColorU32(deepPurple)
);
var random = new Random(42);
for (int i = 0; i < 50; i++)
{
var starPos = headerStart + new Vector2(
(float)random.NextDouble() * (headerEnd.X - headerStart.X),
(float)random.NextDouble() * (headerEnd.Y - headerStart.Y)
);
var brightness = 0.3f + (float)random.NextDouble() * 0.4f;
drawList.AddCircleFilled(starPos, 1f, ImGui.GetColorU32(new Vector4(1f, 1f, 1f, brightness)));
}
}
private void DrawBottomGradient(Vector2 headerStart, Vector2 headerEnd, float width)
{
var drawList = ImGui.GetWindowDrawList();
var gradientHeight = 60f;
for (int i = 0; i < gradientHeight; i++)
{
var progress = i / gradientHeight;
var smoothProgress = progress * progress;
var r = 0.12f + (0.0f - 0.12f) * smoothProgress;
var g = 0.08f + (0.0f - 0.08f) * smoothProgress;
var b = 0.20f + (0.0f - 0.20f) * smoothProgress;
var alpha = 1f - smoothProgress;
var gradientColor = new Vector4(r, g, b, alpha);
drawList.AddLine(
new Vector2(headerStart.X, headerEnd.Y + i),
new Vector2(headerStart.X + width, headerEnd.Y + i),
ImGui.GetColorU32(gradientColor),
1f
);
}
}
private void DrawHeaderText(Vector2 headerStart)
{
var textX = 20f;
var textY = 30f;
ImGui.SetCursorScreenPos(headerStart + new Vector2(textX, textY));
using (_uiShared.UidFont.Push())
{
ImGui.TextColored(new Vector4(0.95f, 0.95f, 0.95f, 1.0f), "Lightless Sync");
}
ImGui.SetCursorScreenPos(headerStart + new Vector2(textX, textY + 45f));
ImGui.TextColored(UIColors.Get("LightlessBlue"), "Update Notes");
}
private void DrawHeaderButtons(Vector2 headerStart, float headerWidth)
{
var buttonSize = _uiShared.GetIconButtonSize(FontAwesomeIcon.Globe);
var spacing = 8f * ImGuiHelpers.GlobalScale;
var rightPadding = 15f * ImGuiHelpers.GlobalScale;
var topPadding = 15f * ImGuiHelpers.GlobalScale;
var buttonY = headerStart.Y + topPadding;
var gitButtonX = headerStart.X + headerWidth - rightPadding - buttonSize.X;
var discordButtonX = gitButtonX - buttonSize.X - spacing;
ImGui.SetCursorScreenPos(new Vector2(discordButtonX, buttonY));
using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0, 0, 0, 0)))
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple") with { W = 0.3f }))
using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive") with { W = 0.5f }))
{
if (_uiShared.IconButton(FontAwesomeIcon.Comments))
{
Util.OpenLink("https://discord.gg/dsbjcXMnhA");
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Join our Discord");
}
ImGui.SetCursorScreenPos(new Vector2(gitButtonX, buttonY));
if (_uiShared.IconButton(FontAwesomeIcon.Code))
{
Util.OpenLink("https://git.lightless-sync.org/Lightless-Sync");
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("View on Git");
}
}
}
private void DrawParticleEffects(Vector2 bannerStart, Vector2 bannerSize)
{
var deltaTime = ImGui.GetIO().DeltaTime;
_particleSpawnTimer += deltaTime;
if (_particleSpawnTimer > _particleSpawnInterval && _particles.Count < _maxParticles)
{
SpawnParticle(bannerSize);
_particleSpawnTimer = 0f;
}
if (_random.NextDouble() < 0.003)
{
SpawnShootingStar(bannerSize);
}
var drawList = ImGui.GetWindowDrawList();
for (int i = _particles.Count - 1; i >= 0; i--)
{
var particle = _particles[i];
var screenPos = bannerStart + particle.Position;
if (particle.Type == ParticleType.ShootingStar && particle.Trail != null)
{
particle.Trail.Insert(0, particle.Position);
if (particle.Trail.Count > _maxTrailLength)
particle.Trail.RemoveAt(particle.Trail.Count - 1);
}
if (particle.Type == ParticleType.TwinklingStar)
{
particle.Twinkle += 0.005f * particle.Depth;
}
particle.Position += particle.Velocity * deltaTime;
particle.Life -= deltaTime;
var isOutOfBounds = particle.Position.X < -50 || particle.Position.X > bannerSize.X + 50 ||
particle.Position.Y < -50 || particle.Position.Y > bannerSize.Y + 50;
if (particle.Life <= 0 || (particle.Type != ParticleType.TwinklingStar && isOutOfBounds))
{
_particles.RemoveAt(i);
continue;
}
if (particle.Type == ParticleType.TwinklingStar)
{
if (particle.Position.X < 0 || particle.Position.X > bannerSize.X)
particle.Velocity = particle.Velocity with { X = -particle.Velocity.X };
if (particle.Position.Y < 0 || particle.Position.Y > bannerSize.Y)
particle.Velocity = particle.Velocity with { Y = -particle.Velocity.Y };
}
var fadeIn = Math.Min(1f, (particle.MaxLife - particle.Life) / 20f);
var fadeOut = Math.Min(1f, particle.Life / 20f);
var lifeFade = Math.Min(fadeIn, fadeOut);
var edgeFadeX = Math.Min(
Math.Min(1f, (particle.Position.X + _edgeFadeDistance) / _edgeFadeDistance),
Math.Min(1f, (bannerSize.X - particle.Position.X + _edgeFadeDistance) / _edgeFadeDistance)
);
var edgeFadeY = Math.Min(
Math.Min(1f, (particle.Position.Y + _edgeFadeDistance) / _edgeFadeDistance),
Math.Min(1f, (bannerSize.Y - particle.Position.Y + _edgeFadeDistance) / _edgeFadeDistance)
);
var edgeFade = Math.Min(edgeFadeX, edgeFadeY);
var baseAlpha = lifeFade * edgeFade;
var finalAlpha = particle.Type == ParticleType.TwinklingStar
? baseAlpha * (0.6f + 0.4f * MathF.Sin(particle.Twinkle))
: baseAlpha;
if (particle.Type == ParticleType.ShootingStar && particle.Trail != null && particle.Trail.Count > 1)
{
var cyanColor = new Vector4(0.4f, 0.8f, 1.0f, 1.0f);
for (int t = 1; t < particle.Trail.Count; t++)
{
var trailProgress = (float)t / particle.Trail.Count;
var trailAlpha = Math.Min(1f, (1f - trailProgress) * finalAlpha * 1.8f);
var trailWidth = (1f - trailProgress) * 3f + 1f;
var glowAlpha = trailAlpha * 0.4f;
drawList.AddLine(
bannerStart + particle.Trail[t - 1],
bannerStart + particle.Trail[t],
ImGui.GetColorU32(cyanColor with { W = glowAlpha }),
trailWidth + 4f
);
drawList.AddLine(
bannerStart + particle.Trail[t - 1],
bannerStart + particle.Trail[t],
ImGui.GetColorU32(cyanColor with { W = trailAlpha }),
trailWidth
);
}
}
else if (particle.Type == ParticleType.TwinklingStar)
{
DrawTwinklingStar(drawList, screenPos, particle.Size, particle.Hue, finalAlpha, particle.Depth);
}
_particles[i] = particle;
}
}
private void DrawTwinklingStar(ImDrawListPtr drawList, Vector2 position, float size, float hue, float alpha,
float depth)
{
var color = HslToRgb(hue, 1.0f, 0.85f);
color.W = alpha;
drawList.AddCircleFilled(position, size, ImGui.GetColorU32(color));
var glowColor = color with { W = alpha * 0.3f };
drawList.AddCircleFilled(position, size * (1.2f + depth * 0.3f), ImGui.GetColorU32(glowColor));
}
private static Vector4 HslToRgb(float h, float s, float l)
{
h = h / 360f;
float c = (1 - MathF.Abs(2 * l - 1)) * s;
float x = c * (1 - MathF.Abs((h * 6) % 2 - 1));
float m = l - c / 2;
float r, g, b;
if (h < 1f / 6f)
{
r = c;
g = x;
b = 0;
}
else if (h < 2f / 6f)
{
r = x;
g = c;
b = 0;
}
else if (h < 3f / 6f)
{
r = 0;
g = c;
b = x;
}
else if (h < 4f / 6f)
{
r = 0;
g = x;
b = c;
}
else if (h < 5f / 6f)
{
r = x;
g = 0;
b = c;
}
else
{
r = c;
g = 0;
b = x;
}
return new Vector4(r + m, g + m, b + m, 1.0f);
}
private void SpawnParticle(Vector2 bannerSize)
{
var position = new Vector2(
(float)_random.NextDouble() * bannerSize.X,
(float)_random.NextDouble() * bannerSize.Y
);
var depthLayers = new[] { 0.5f, 1.0f, 1.5f };
var depth = depthLayers[_random.Next(depthLayers.Length)];
var velocity = new Vector2(
((float)_random.NextDouble() - 0.5f) * 0.05f * depth,
((float)_random.NextDouble() - 0.5f) * 0.05f * depth
);
var isBlue = _random.NextDouble() < 0.5;
var hue = isBlue ? 220f + (float)_random.NextDouble() * 30f : 270f + (float)_random.NextDouble() * 40f;
var size = (0.5f + (float)_random.NextDouble() * 2f) * depth;
var maxLife = 120f + (float)_random.NextDouble() * 60f;
_particles.Add(new Particle
{
Position = position,
Velocity = velocity,
Life = maxLife,
MaxLife = maxLife,
Size = size,
Type = ParticleType.TwinklingStar,
Trail = null,
Twinkle = (float)_random.NextDouble() * MathF.PI * 2,
Depth = depth,
Hue = hue
});
}
private void SpawnShootingStar(Vector2 bannerSize)
{
var maxLife = 80f + (float)_random.NextDouble() * 40f;
var startX = bannerSize.X * (0.3f + (float)_random.NextDouble() * 0.6f);
var startY = -10f;
_particles.Add(new Particle
{
Position = new Vector2(startX, startY),
Velocity = new Vector2(
-50f - (float)_random.NextDouble() * 40f,
30f + (float)_random.NextDouble() * 40f
),
Life = maxLife,
MaxLife = maxLife,
Size = 2.5f,
Type = ParticleType.ShootingStar,
Trail = new List<Vector2>(),
Twinkle = 0,
Depth = 1.0f,
Hue = 270f
});
}
private void DrawTabs()
{
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 6f))
@@ -513,7 +146,6 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
{
if (changelogTab)
{
_selectedTab = 0;
DrawChangelog();
}
}
@@ -524,7 +156,6 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
{
if (creditsTab)
{
_selectedTab = 1;
DrawCredits();
}
}
@@ -558,19 +189,21 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
}
}
private void DrawCreditCategory(CreditCategory category)
private static void DrawCreditCategory(CreditCategory category)
{
DrawFeatureSection(category.Category, UIColors.Get("LightlessBlue"));
foreach (var item in category.Items)
{
ImGui.Bullet();
ImGui.SameLine();
if (!string.IsNullOrEmpty(item.Role))
{
ImGui.BulletText($"{item.Name} — {item.Role}");
ImGui.TextWrapped($"{item.Name} — {item.Role}");
}
else
{
ImGui.BulletText(item.Name);
ImGui.TextWrapped(item.Name);
}
}
@@ -623,7 +256,7 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
ImGui.SetScrollHereY(0);
}
ImGui.PushTextWrapPos();
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X);
foreach (var entry in _changelog.Changelog)
{
@@ -683,7 +316,9 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
foreach (var item in version.Items)
{
ImGui.BulletText(item);
ImGui.Bullet();
ImGui.SameLine();
ImGui.TextWrapped(item);
}
ImGuiHelpers.ScaledDummy(5);
@@ -745,7 +380,7 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
using var changelogStream = assembly.GetManifestResourceStream("LightlessSync.Changelog.changelog.yaml");
if (changelogStream != null)
{
using var reader = new StreamReader(changelogStream, Encoding.UTF8, true, 128);
using var reader = new StreamReader(changelogStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, 128);
var yaml = reader.ReadToEnd();
_changelog = deserializer.Deserialize<ChangelogFile>(yaml) ?? new();
}
@@ -754,7 +389,7 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
using var creditsStream = assembly.GetManifestResourceStream("LightlessSync.Changelog.credits.yaml");
if (creditsStream != null)
{
using var reader = new StreamReader(creditsStream, Encoding.UTF8, true, 128);
using var reader = new StreamReader(creditsStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, 128);
var yaml = reader.ReadToEnd();
_credits = deserializer.Deserialize<CreditsFile>(yaml) ?? new();
}