banner with particles
This commit is contained in:
@@ -160,6 +160,8 @@ public class LightlessPlugin : MediatorSubscriberBase, IHostedService
|
|||||||
var currentVersion = ver == null ? string.Empty : $"{ver.Major}.{ver.Minor}.{ver.Build}";
|
var currentVersion = ver == null ? string.Empty : $"{ver.Major}.{ver.Minor}.{ver.Build}";
|
||||||
var lastSeen = _lightlessConfigService.Current.LastSeenVersion ?? string.Empty;
|
var lastSeen = _lightlessConfigService.Current.LastSeenVersion ?? string.Empty;
|
||||||
Logger?.LogDebug("Last seen version: {lastSeen}, current version: {currentVersion}", lastSeen, currentVersion);
|
Logger?.LogDebug("Last seen version: {lastSeen}, current version: {currentVersion}", lastSeen, currentVersion);
|
||||||
|
Mediator.Publish(new UiToggleMessage(typeof(UpdateNotesUi)));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(lastSeen))
|
if (string.IsNullOrEmpty(lastSeen))
|
||||||
{
|
{
|
||||||
_lightlessConfigService.Current.LastSeenVersion = currentVersion;
|
_lightlessConfigService.Current.LastSeenVersion = currentVersion;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
namespace LightlessSync.UI;
|
||||||
|
|
||||||
@@ -28,6 +29,36 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
private bool _scrollToTop;
|
private bool _scrollToTop;
|
||||||
private int _selectedTab;
|
private int _selectedTab;
|
||||||
|
|
||||||
|
// Particle system for visual effects
|
||||||
|
private struct Particle
|
||||||
|
{
|
||||||
|
public Vector2 Position;
|
||||||
|
public Vector2 Velocity;
|
||||||
|
public float Life;
|
||||||
|
public float MaxLife;
|
||||||
|
public float Size;
|
||||||
|
public Vector4 Color;
|
||||||
|
public ParticleType Type;
|
||||||
|
public float Rotation;
|
||||||
|
public float RotationSpeed;
|
||||||
|
public List<Vector2>? Trail;
|
||||||
|
public bool IsLargeMoon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ParticleType
|
||||||
|
{
|
||||||
|
Star,
|
||||||
|
Moon,
|
||||||
|
Sparkle,
|
||||||
|
FastFallingStar
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Particle> _particles = [];
|
||||||
|
private float _particleTimer;
|
||||||
|
private readonly Random _particleRandom = new();
|
||||||
|
private Particle? _largeMoon;
|
||||||
|
private float _largeMoonTimer;
|
||||||
|
|
||||||
public UpdateNotesUi(ILogger<UpdateNotesUi> logger,
|
public UpdateNotesUi(ILogger<UpdateNotesUi> logger,
|
||||||
LightlessMediator mediator,
|
LightlessMediator mediator,
|
||||||
UiSharedService uiShared,
|
UiSharedService uiShared,
|
||||||
@@ -43,12 +74,12 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
RespectCloseHotkey = true;
|
RespectCloseHotkey = true;
|
||||||
ShowCloseButton = true;
|
ShowCloseButton = true;
|
||||||
|
|
||||||
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse;
|
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoTitleBar;
|
||||||
|
|
||||||
SizeConstraints = new WindowSizeConstraints()
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
{
|
{
|
||||||
MinimumSize = new Vector2(600, 500),
|
MinimumSize = new Vector2(800, 700),
|
||||||
MaximumSize = new Vector2(900, 2000),
|
MaximumSize = new Vector2(800, 700),
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadEmbeddedResources();
|
LoadEmbeddedResources();
|
||||||
@@ -65,93 +96,540 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
DrawHeader();
|
DrawHeader();
|
||||||
DrawLinkButtons();
|
|
||||||
ImGuiHelpers.ScaledDummy(6);
|
ImGuiHelpers.ScaledDummy(6);
|
||||||
DrawTabs();
|
DrawChangelog();
|
||||||
DrawCloseButton();
|
DrawCloseButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHeader()
|
private void DrawHeader()
|
||||||
{
|
{
|
||||||
using (_uiShared.UidFont.Push())
|
var windowPos = ImGui.GetWindowPos();
|
||||||
{
|
var windowPadding = ImGui.GetStyle().WindowPadding;
|
||||||
ImGui.TextUnformatted("Lightless Sync");
|
var headerWidth = (800f * ImGuiHelpers.GlobalScale) - (windowPadding.X * 2);
|
||||||
}
|
var headerHeight = 140f * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessBlue"), thickness: 2f);
|
var headerStart = windowPos + new Vector2(windowPadding.X, windowPadding.Y);
|
||||||
|
var headerEnd = headerStart + new Vector2(headerWidth, headerHeight);
|
||||||
|
|
||||||
|
DrawGradientBackground(headerStart, headerEnd);
|
||||||
|
DrawParticleEffects(headerStart, new Vector2(headerWidth, headerHeight));
|
||||||
|
DrawHeaderText(headerStart);
|
||||||
|
DrawHeaderButtons(headerStart, headerWidth);
|
||||||
|
|
||||||
|
ImGui.SetCursorPosY(windowPadding.Y + headerHeight + 5);
|
||||||
|
|
||||||
|
// Version badge with icon
|
||||||
|
ImGui.SetCursorPosX(12);
|
||||||
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
ImGui.TextColored(UIColors.Get("LightlessGreen"), FontAwesomeIcon.Star.ToIconString());
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.TextColored(UIColors.Get("LightlessGreen"), "What's New");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_changelog.Tagline))
|
if (!string.IsNullOrEmpty(_changelog.Tagline))
|
||||||
{
|
{
|
||||||
_uiShared.MediumText(_changelog.Tagline, UIColors.Get("LightlessBlue"));
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 10);
|
||||||
|
ImGui.TextColored(new Vector4(0.75f, 0.75f, 0.85f, 1.0f), _changelog.Tagline);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_changelog.Subline))
|
if (!string.IsNullOrEmpty(_changelog.Subline))
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextColored(new Vector4(.75f, .75f, .85f, 1f), $" – {_changelog.Subline}");
|
ImGui.TextColored(new Vector4(0.65f, 0.65f, 0.75f, 1.0f), $" – {_changelog.Subline}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGui.Separator();
|
||||||
|
ImGuiHelpers.ScaledDummy(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawLinkButtons()
|
private void DrawGradientBackground(Vector2 headerStart, Vector2 headerEnd)
|
||||||
{
|
{
|
||||||
var segmentSize = ImGui.GetWindowSize().X / 4.2f;
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
var buttonSize = new Vector2(segmentSize, ImGui.GetTextLineHeight() * 1.6f);
|
|
||||||
|
|
||||||
if (ImGui.Button("Discord", buttonSize))
|
// Dark night sky background with stars pattern
|
||||||
Util.OpenLink("https://discord.gg/dsbjcXMnhA");
|
var darkPurple = new Vector4(0.08f, 0.05f, 0.15f, 1.0f);
|
||||||
ImGui.SameLine();
|
var deepPurple = new Vector4(0.12f, 0.08f, 0.20f, 1.0f);
|
||||||
|
|
||||||
if (ImGui.Button("GitHub", buttonSize))
|
drawList.AddRectFilledMultiColor(
|
||||||
Util.OpenLink("https://github.com/Light-Public-Syncshells/LightlessSync");
|
headerStart,
|
||||||
ImGui.SameLine();
|
headerEnd,
|
||||||
|
ImGui.GetColorU32(darkPurple),
|
||||||
|
ImGui.GetColorU32(darkPurple),
|
||||||
|
ImGui.GetColorU32(deepPurple),
|
||||||
|
ImGui.GetColorU32(deepPurple)
|
||||||
|
);
|
||||||
|
|
||||||
if (ImGui.Button("Ko-fi", buttonSize))
|
// Add some static "distant stars" for depth
|
||||||
Util.OpenLink("https://ko-fi.com/lightlesssync");
|
var random = new Random(42); // Fixed seed for consistent pattern
|
||||||
ImGui.SameLine();
|
for (int i = 0; i < 50; i++)
|
||||||
|
{
|
||||||
if (ImGui.Button("More Links", buttonSize))
|
var starPos = headerStart + new Vector2(
|
||||||
Util.OpenLink("https://lightless.link");
|
(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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accent border at bottom with glow
|
||||||
|
drawList.AddLine(
|
||||||
|
new Vector2(headerStart.X, headerEnd.Y),
|
||||||
|
headerEnd,
|
||||||
|
ImGui.GetColorU32(UIColors.Get("LightlessPurple")),
|
||||||
|
2f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeaderText(Vector2 headerStart)
|
||||||
|
{
|
||||||
|
// Title text overlay - drawn after particles so it's on top
|
||||||
|
ImGui.SetCursorScreenPos(headerStart + new Vector2(20, 30));
|
||||||
|
|
||||||
|
using (_uiShared.UidFont.Push())
|
||||||
|
{
|
||||||
|
ImGui.TextColored(new Vector4(0.95f, 0.95f, 0.95f, 1.0f), "Lightless Sync");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetCursorScreenPos(headerStart + new Vector2(20, 75));
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Position for buttons in top right
|
||||||
|
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;
|
||||||
|
_particleTimer += deltaTime;
|
||||||
|
_largeMoonTimer += deltaTime;
|
||||||
|
|
||||||
|
// Spawn new particles
|
||||||
|
if (_particleTimer > 0.3f && _particles.Count < 30)
|
||||||
|
{
|
||||||
|
SpawnParticle(bannerStart, bannerSize);
|
||||||
|
_particleTimer = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn or update large moon
|
||||||
|
if (_largeMoon == null || _largeMoonTimer > 45f)
|
||||||
|
{
|
||||||
|
SpawnLargeMoon(bannerStart, bannerSize);
|
||||||
|
_largeMoonTimer = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
// Update and draw large moon first (background layer)
|
||||||
|
if (_largeMoon != null)
|
||||||
|
{
|
||||||
|
var moon = _largeMoon.Value;
|
||||||
|
moon.Position += moon.Velocity * deltaTime;
|
||||||
|
moon.Life -= deltaTime;
|
||||||
|
|
||||||
|
// Keep moon within banner bounds with padding
|
||||||
|
var padding = moon.Size + 10;
|
||||||
|
if (moon.Life <= 0 ||
|
||||||
|
moon.Position.X < bannerStart.X - padding ||
|
||||||
|
moon.Position.X > bannerStart.X + bannerSize.X + padding ||
|
||||||
|
moon.Position.Y < bannerStart.Y - padding ||
|
||||||
|
moon.Position.Y > bannerStart.Y + bannerSize.Y + padding)
|
||||||
|
{
|
||||||
|
_largeMoon = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float alpha = Math.Min(1f, moon.Life / moon.MaxLife);
|
||||||
|
var color = moon.Color with { W = moon.Color.W * alpha };
|
||||||
|
DrawMoon(drawList, moon.Position, moon.Size, color);
|
||||||
|
_largeMoon = moon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update and draw regular particles
|
||||||
|
for (int i = _particles.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var particle = _particles[i];
|
||||||
|
|
||||||
|
// Update trail for stars
|
||||||
|
if ((particle.Type == ParticleType.Star || particle.Type == ParticleType.FastFallingStar) && particle.Trail != null)
|
||||||
|
{
|
||||||
|
particle.Trail.Insert(0, particle.Position);
|
||||||
|
var maxTrailLength = particle.Type == ParticleType.FastFallingStar ? 15 : 8;
|
||||||
|
if (particle.Trail.Count > maxTrailLength)
|
||||||
|
particle.Trail.RemoveAt(particle.Trail.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
particle.Position += particle.Velocity * deltaTime;
|
||||||
|
particle.Life -= deltaTime;
|
||||||
|
particle.Rotation += particle.RotationSpeed * deltaTime;
|
||||||
|
|
||||||
|
if (particle.Life <= 0 ||
|
||||||
|
particle.Position.X > bannerStart.X + bannerSize.X + 50 ||
|
||||||
|
particle.Position.X < bannerStart.X - 50 ||
|
||||||
|
particle.Position.Y < bannerStart.Y - 50 ||
|
||||||
|
particle.Position.Y > bannerStart.Y + bannerSize.Y + 50)
|
||||||
|
{
|
||||||
|
_particles.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float alpha = Math.Min(1f, particle.Life / particle.MaxLife);
|
||||||
|
var color = particle.Color with { W = particle.Color.W * alpha };
|
||||||
|
|
||||||
|
// Draw trail for stars
|
||||||
|
if ((particle.Type == ParticleType.Star || particle.Type == ParticleType.FastFallingStar) && particle.Trail != null && particle.Trail.Count > 1)
|
||||||
|
{
|
||||||
|
for (int t = 1; t < particle.Trail.Count; t++)
|
||||||
|
{
|
||||||
|
float trailAlpha = alpha * (1f - (t / (float)particle.Trail.Count)) * (particle.Type == ParticleType.FastFallingStar ? 0.7f : 0.5f);
|
||||||
|
var trailColor = color with { W = trailAlpha };
|
||||||
|
float thickness = particle.Type == ParticleType.FastFallingStar ? 2.5f : 1.5f;
|
||||||
|
drawList.AddLine(
|
||||||
|
particle.Trail[t - 1],
|
||||||
|
particle.Trail[t],
|
||||||
|
ImGui.GetColorU32(trailColor),
|
||||||
|
thickness
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw based on particle type
|
||||||
|
switch (particle.Type)
|
||||||
|
{
|
||||||
|
case ParticleType.Star:
|
||||||
|
case ParticleType.FastFallingStar:
|
||||||
|
DrawStar(drawList, particle.Position, particle.Size, color, particle.Rotation);
|
||||||
|
break;
|
||||||
|
case ParticleType.Moon:
|
||||||
|
DrawMoon(drawList, particle.Position, particle.Size, color);
|
||||||
|
break;
|
||||||
|
case ParticleType.Sparkle:
|
||||||
|
DrawSparkle(drawList, particle.Position, particle.Size, color, particle.Rotation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_particles[i] = particle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawStar(ImDrawListPtr drawList, Vector2 position, float size, Vector4 color, float rotation)
|
||||||
|
{
|
||||||
|
// Draw a 5-pointed star
|
||||||
|
var points = new Vector2[10];
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
float angle = (i * MathF.PI / 5) + rotation;
|
||||||
|
float radius = (i % 2 == 0) ? size : size * 0.4f;
|
||||||
|
points[i] = position + new Vector2(MathF.Cos(angle) * radius, MathF.Sin(angle) * radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw filled star
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
drawList.AddTriangleFilled(
|
||||||
|
position,
|
||||||
|
points[i * 2],
|
||||||
|
points[(i * 2 + 2) % 10],
|
||||||
|
ImGui.GetColorU32(color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glow effect
|
||||||
|
var glowColor = color with { W = color.W * 0.3f };
|
||||||
|
drawList.AddCircleFilled(position, size * 1.5f, ImGui.GetColorU32(glowColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMoon(ImDrawListPtr drawList, Vector2 position, float size, Vector4 color)
|
||||||
|
{
|
||||||
|
// Enhanced glow for larger moons
|
||||||
|
var glowRadius = size > 15f ? 2.5f : 1.8f;
|
||||||
|
var glowColor = color with { W = color.W * (size > 15f ? 0.15f : 0.25f) };
|
||||||
|
drawList.AddCircleFilled(position, size * glowRadius, ImGui.GetColorU32(glowColor));
|
||||||
|
|
||||||
|
// Draw crescent moon
|
||||||
|
drawList.AddCircleFilled(position, size, ImGui.GetColorU32(color));
|
||||||
|
|
||||||
|
// Draw shadow circle to create crescent
|
||||||
|
var shadowColor = new Vector4(0.08f, 0.05f, 0.15f, 1.0f);
|
||||||
|
drawList.AddCircleFilled(position + new Vector2(size * 0.4f, 0), size * 0.8f, ImGui.GetColorU32(shadowColor));
|
||||||
|
|
||||||
|
// Additional glow layer for large moons
|
||||||
|
if (size > 15f)
|
||||||
|
{
|
||||||
|
var outerGlow = color with { W = color.W * 0.08f };
|
||||||
|
drawList.AddCircleFilled(position, size * 3.5f, ImGui.GetColorU32(outerGlow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSparkle(ImDrawListPtr drawList, Vector2 position, float size, Vector4 color, float rotation)
|
||||||
|
{
|
||||||
|
// Draw a 4-pointed sparkle (plus shape)
|
||||||
|
var thickness = size * 0.3f;
|
||||||
|
|
||||||
|
// Horizontal line
|
||||||
|
drawList.AddLine(
|
||||||
|
position + new Vector2(-size, 0),
|
||||||
|
position + new Vector2(size, 0),
|
||||||
|
ImGui.GetColorU32(color),
|
||||||
|
thickness
|
||||||
|
);
|
||||||
|
|
||||||
|
// Vertical line
|
||||||
|
drawList.AddLine(
|
||||||
|
position + new Vector2(0, -size),
|
||||||
|
position + new Vector2(0, size),
|
||||||
|
ImGui.GetColorU32(color),
|
||||||
|
thickness
|
||||||
|
);
|
||||||
|
|
||||||
|
// Center glow
|
||||||
|
drawList.AddCircleFilled(position, size * 0.4f, ImGui.GetColorU32(color));
|
||||||
|
var glowColor = color with { W = color.W * 0.4f };
|
||||||
|
drawList.AddCircleFilled(position, size * 1.2f, ImGui.GetColorU32(glowColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnParticle(Vector2 bannerStart, Vector2 bannerSize)
|
||||||
|
{
|
||||||
|
var typeRoll = _particleRandom.Next(100);
|
||||||
|
var particleType = typeRoll switch
|
||||||
|
{
|
||||||
|
< 35 => ParticleType.Star,
|
||||||
|
< 50 => ParticleType.Moon,
|
||||||
|
< 65 => ParticleType.FastFallingStar,
|
||||||
|
_ => ParticleType.Sparkle
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector2 position;
|
||||||
|
Vector2 velocity;
|
||||||
|
|
||||||
|
// Stars: spawn from top, move diagonally down
|
||||||
|
if (particleType == ParticleType.Star)
|
||||||
|
{
|
||||||
|
// Spawn from top edge
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X + (float)_particleRandom.NextDouble() * bannerSize.X,
|
||||||
|
bannerStart.Y - 10
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move diagonally down (shooting star effect)
|
||||||
|
var angle = MathF.PI * 0.25f + (float)(_particleRandom.NextDouble() - 0.5) * 0.5f; // 45° ± variation
|
||||||
|
var speed = 30f + (float)_particleRandom.NextDouble() * 40f;
|
||||||
|
velocity = new Vector2(MathF.Cos(angle) * speed, MathF.Sin(angle) * speed);
|
||||||
|
}
|
||||||
|
// Fast falling stars: spawn from top, fall straight down very fast
|
||||||
|
else if (particleType == ParticleType.FastFallingStar)
|
||||||
|
{
|
||||||
|
// Spawn from top edge, random X position
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X + (float)_particleRandom.NextDouble() * bannerSize.X,
|
||||||
|
bannerStart.Y - 10
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fall almost straight down with slight horizontal drift
|
||||||
|
var horizontalDrift = -10f + (float)_particleRandom.NextDouble() * 20f;
|
||||||
|
var speed = 120f + (float)_particleRandom.NextDouble() * 80f; // Much faster!
|
||||||
|
velocity = new Vector2(horizontalDrift, speed);
|
||||||
|
}
|
||||||
|
// Moons: drift slowly across
|
||||||
|
else if (particleType == ParticleType.Moon)
|
||||||
|
{
|
||||||
|
// Spawn from left side
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X - 10,
|
||||||
|
bannerStart.Y + (float)_particleRandom.NextDouble() * bannerSize.Y
|
||||||
|
);
|
||||||
|
|
||||||
|
// Drift slowly to the right with slight vertical movement
|
||||||
|
velocity = new Vector2(
|
||||||
|
15f + (float)_particleRandom.NextDouble() * 10f,
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Sparkles: float gently
|
||||||
|
else
|
||||||
|
{
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X + (float)_particleRandom.NextDouble() * bannerSize.X,
|
||||||
|
bannerStart.Y + (float)_particleRandom.NextDouble() * bannerSize.Y
|
||||||
|
);
|
||||||
|
|
||||||
|
velocity = new Vector2(
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f,
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var particle = new Particle
|
||||||
|
{
|
||||||
|
Position = position,
|
||||||
|
Velocity = velocity,
|
||||||
|
MaxLife = particleType switch
|
||||||
|
{
|
||||||
|
ParticleType.Star => 3f + (float)_particleRandom.NextDouble() * 2f,
|
||||||
|
ParticleType.Moon => 8f + (float)_particleRandom.NextDouble() * 4f,
|
||||||
|
ParticleType.FastFallingStar => 1.5f + (float)_particleRandom.NextDouble() * 1f,
|
||||||
|
_ => 6f + (float)_particleRandom.NextDouble() * 4f
|
||||||
|
},
|
||||||
|
Size = particleType switch
|
||||||
|
{
|
||||||
|
ParticleType.Star => 2.5f + (float)_particleRandom.NextDouble() * 2f,
|
||||||
|
ParticleType.Moon => 3f + (float)_particleRandom.NextDouble() * 2f,
|
||||||
|
ParticleType.FastFallingStar => 3f + (float)_particleRandom.NextDouble() * 2f,
|
||||||
|
_ => 2f + (float)_particleRandom.NextDouble() * 2f
|
||||||
|
},
|
||||||
|
Color = particleType switch
|
||||||
|
{
|
||||||
|
ParticleType.Star => new Vector4(1.0f, 1.0f, 0.9f, 0.9f),
|
||||||
|
ParticleType.Moon => UIColors.Get("LightlessBlue") with { W = 0.7f },
|
||||||
|
ParticleType.FastFallingStar => new Vector4(1.0f, 0.95f, 0.85f, 1.0f), // Bright white-yellow
|
||||||
|
_ => UIColors.Get("LightlessPurple") with { W = 0.8f }
|
||||||
|
},
|
||||||
|
Type = particleType,
|
||||||
|
Rotation = (float)_particleRandom.NextDouble() * MathF.PI * 2,
|
||||||
|
RotationSpeed = particleType == ParticleType.Star || particleType == ParticleType.FastFallingStar ? 2f : -0.5f + (float)_particleRandom.NextDouble() * 1f,
|
||||||
|
Trail = particleType == ParticleType.Star || particleType == ParticleType.FastFallingStar ? new List<Vector2>() : null,
|
||||||
|
IsLargeMoon = false
|
||||||
|
};
|
||||||
|
|
||||||
|
particle.Life = particle.MaxLife;
|
||||||
|
_particles.Add(particle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnLargeMoon(Vector2 bannerStart, Vector2 bannerSize)
|
||||||
|
{
|
||||||
|
// Large moon travels across the banner like a celestial body
|
||||||
|
var spawnSide = _particleRandom.Next(4);
|
||||||
|
Vector2 position;
|
||||||
|
Vector2 velocity;
|
||||||
|
|
||||||
|
switch (spawnSide)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// Spawn from left, move to right
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X - 50,
|
||||||
|
bannerStart.Y + (float)_particleRandom.NextDouble() * bannerSize.Y
|
||||||
|
);
|
||||||
|
velocity = new Vector2(
|
||||||
|
15f + (float)_particleRandom.NextDouble() * 10f,
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// Spawn from top, move down and across
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X + (float)_particleRandom.NextDouble() * bannerSize.X,
|
||||||
|
bannerStart.Y - 50
|
||||||
|
);
|
||||||
|
velocity = new Vector2(
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f,
|
||||||
|
10f + (float)_particleRandom.NextDouble() * 8f
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// Spawn from right, move to left
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X + bannerSize.X + 50,
|
||||||
|
bannerStart.Y + (float)_particleRandom.NextDouble() * bannerSize.Y
|
||||||
|
);
|
||||||
|
velocity = new Vector2(
|
||||||
|
-(15f + (float)_particleRandom.NextDouble() * 10f),
|
||||||
|
-5f + (float)_particleRandom.NextDouble() * 10f
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Spawn from top-left corner, move diagonally
|
||||||
|
position = new Vector2(
|
||||||
|
bannerStart.X - 30,
|
||||||
|
bannerStart.Y - 30
|
||||||
|
);
|
||||||
|
velocity = new Vector2(
|
||||||
|
12f + (float)_particleRandom.NextDouble() * 8f,
|
||||||
|
12f + (float)_particleRandom.NextDouble() * 8f
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_largeMoon = new Particle
|
||||||
|
{
|
||||||
|
Position = position,
|
||||||
|
Velocity = velocity,
|
||||||
|
MaxLife = 40f,
|
||||||
|
Life = 40f,
|
||||||
|
Size = 25f + (float)_particleRandom.NextDouble() * 10f,
|
||||||
|
Color = UIColors.Get("LightlessBlue") with { W = 0.35f },
|
||||||
|
Type = ParticleType.Moon,
|
||||||
|
Rotation = 0,
|
||||||
|
RotationSpeed = 0,
|
||||||
|
Trail = null,
|
||||||
|
IsLargeMoon = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawCloseButton()
|
private void DrawCloseButton()
|
||||||
{
|
{
|
||||||
var closeWidth = 300f * ImGuiHelpers.GlobalScale;
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
|
var closeWidth = 200f * ImGuiHelpers.GlobalScale;
|
||||||
|
var closeHeight = 35f * ImGuiHelpers.GlobalScale;
|
||||||
ImGui.SetCursorPosX((ImGui.GetWindowSize().X - closeWidth) / 2);
|
ImGui.SetCursorPosX((ImGui.GetWindowSize().X - closeWidth) / 2);
|
||||||
if (ImGui.Button("Close", new Vector2(closeWidth, 0)))
|
|
||||||
|
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 8f))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessPurple")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurpleActive")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("ButtonDefault")))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Got it!", new Vector2(closeWidth, closeHeight)))
|
||||||
{
|
{
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTabs()
|
|
||||||
{
|
|
||||||
using var tabBar = ImRaii.TabBar("lightless_update_tabs");
|
|
||||||
if (!tabBar)
|
|
||||||
return;
|
|
||||||
|
|
||||||
using (var changelogTab = ImRaii.TabItem(" Changelog "))
|
|
||||||
{
|
|
||||||
if (changelogTab)
|
|
||||||
{
|
|
||||||
_selectedTab = 0;
|
|
||||||
DrawChangelog();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
using (var creditsTab = ImRaii.TabItem(" Supporters & Credits "))
|
|
||||||
{
|
|
||||||
if (creditsTab)
|
|
||||||
{
|
|
||||||
_selectedTab = 1;
|
|
||||||
DrawCredits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawChangelog()
|
private void DrawChangelog()
|
||||||
{
|
{
|
||||||
using var child = ImRaii.Child("###ll_changelog", new Vector2(0, ImGui.GetContentRegionAvail().Y - 44), false);
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 6f))
|
||||||
|
using (var child = ImRaii.Child("###ll_changelog", new Vector2(0, ImGui.GetContentRegionAvail().Y - 60), false, ImGuiWindowFlags.AlwaysVerticalScrollbar))
|
||||||
|
{
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -161,22 +639,34 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SetScrollHereY(0);
|
ImGui.SetScrollHereY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.PushTextWrapPos();
|
||||||
|
|
||||||
foreach (var entry in _changelog.Changelog)
|
foreach (var entry in _changelog.Changelog)
|
||||||
DrawChangelogEntry(entry);
|
DrawChangelogEntry(entry);
|
||||||
|
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawChangelogEntry(ChangelogEntry entry)
|
private void DrawChangelogEntry(ChangelogEntry entry)
|
||||||
{
|
{
|
||||||
var currentColor = entry.IsCurrent == true
|
var currentColor = entry.IsCurrent == true
|
||||||
? new Vector4(0.5f, 0.9f, 0.5f, 1.0f)
|
? UIColors.Get("LightlessGreen")
|
||||||
: new Vector4(0.75f, 0.75f, 0.85f, 1.0f);
|
: new Vector4(0.75f, 0.75f, 0.85f, 1.0f);
|
||||||
|
|
||||||
bool isOpen;
|
bool isOpen;
|
||||||
|
var flags = entry.IsCurrent == true
|
||||||
|
? ImGuiTreeNodeFlags.DefaultOpen
|
||||||
|
: ImGuiTreeNodeFlags.None;
|
||||||
|
|
||||||
|
using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 4f))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Header, UIColors.Get("ButtonDefault")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, UIColors.Get("LightlessPurple")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.HeaderActive, UIColors.Get("LightlessPurpleActive")))
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, currentColor))
|
using (ImRaii.PushColor(ImGuiCol.Text, currentColor))
|
||||||
{
|
{
|
||||||
isOpen = ImGui.CollapsingHeader($" {entry.Name} — {entry.Date} ");
|
isOpen = ImGui.CollapsingHeader($" {entry.Name} — {entry.Date} ", flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -185,12 +675,12 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
if (!isOpen)
|
if (!isOpen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(8);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(entry.Message))
|
if (!string.IsNullOrEmpty(entry.Message))
|
||||||
{
|
{
|
||||||
ImGui.TextWrapped(entry.Message);
|
ImGui.TextWrapped(entry.Message);
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(8);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,70 +688,56 @@ public class UpdateNotesUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
foreach (var version in entry.Versions)
|
foreach (var version in entry.Versions)
|
||||||
{
|
{
|
||||||
DrawFeatureHeader(version.Number, new Vector4(0.5f, 0.9f, 0.5f, 1.0f));
|
DrawFeatureSection(version.Number, UIColors.Get("LightlessGreen"));
|
||||||
|
|
||||||
foreach (var item in version.Items)
|
foreach (var item in version.Items)
|
||||||
|
{
|
||||||
ImGui.BulletText(item);
|
ImGui.BulletText(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawFeatureHeader(string title, Vector4 accentColor)
|
|
||||||
{
|
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
|
||||||
var startPos = ImGui.GetCursorScreenPos();
|
|
||||||
|
|
||||||
var backgroundMin = startPos + new Vector2(-10, -5);
|
|
||||||
var backgroundMax = startPos + new Vector2(ImGui.GetContentRegionAvail().X + 10, 25);
|
|
||||||
drawList.AddRectFilled(backgroundMin, backgroundMax, ImGui.GetColorU32(new Vector4(0.12f, 0.12f, 0.15f, 0.6f)), 4f);
|
|
||||||
drawList.AddRectFilled(backgroundMin, backgroundMin + new Vector2(3, backgroundMax.Y - backgroundMin.Y), ImGui.GetColorU32(accentColor), 2f);
|
|
||||||
|
|
||||||
ImGui.Spacing();
|
|
||||||
ImGui.TextColored(accentColor, title);
|
|
||||||
ImGui.Spacing();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawCredits()
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("Maintained & Developed by the Lightless Sync team.");
|
|
||||||
ImGui.TextUnformatted("Thank you to all supporters and contributors!");
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
|
|
||||||
var availableRegion = ImGui.GetContentRegionAvail();
|
|
||||||
var halfWidth = new Vector2(
|
|
||||||
availableRegion.X / 2f - ImGui.GetStyle().ItemSpacing.X / 2f,
|
|
||||||
availableRegion.Y - 120 * ImGuiHelpers.GlobalScale);
|
|
||||||
|
|
||||||
using (var leftChild = ImRaii.Child("left_supporters", halfWidth))
|
|
||||||
{
|
|
||||||
if (leftChild)
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("Supporters (Ko-fi / Patreon)");
|
|
||||||
_uiShared.RoundedSeparator(UIColors.Get("LightlessBlue"), thickness: 2f);
|
|
||||||
foreach (var supporter in _supporters)
|
|
||||||
ImGui.BulletText(supporter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
using (var rightChild = ImRaii.Child("right_contributors", halfWidth))
|
|
||||||
{
|
|
||||||
if (rightChild)
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("Contributors");
|
|
||||||
_uiShared.RoundedSeparator(UIColors.Get("LightlessBlue"), thickness: 2f);
|
|
||||||
foreach (var contributor in _contributors)
|
|
||||||
ImGui.BulletText(contributor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(8);
|
ImGuiHelpers.ScaledDummy(8);
|
||||||
ImGui.TextUnformatted("Credits");
|
}
|
||||||
_uiShared.RoundedSeparator(UIColors.Get("LightlessYellow"), thickness: 2f);
|
|
||||||
foreach (var credit in _credits)
|
private static void DrawFeatureSection(string title, Vector4 accentColor)
|
||||||
ImGui.BulletText(credit);
|
{
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
var startPos = ImGui.GetCursorScreenPos();
|
||||||
|
var availableWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
|
||||||
|
var backgroundMin = startPos + new Vector2(-8, -4);
|
||||||
|
var backgroundMax = startPos + new Vector2(availableWidth + 8, 28);
|
||||||
|
|
||||||
|
// Background with subtle gradient
|
||||||
|
var bgColor = new Vector4(0.12f, 0.12f, 0.15f, 0.7f);
|
||||||
|
drawList.AddRectFilled(backgroundMin, backgroundMax, ImGui.GetColorU32(bgColor), 6f);
|
||||||
|
|
||||||
|
// Accent line on left
|
||||||
|
drawList.AddRectFilled(
|
||||||
|
backgroundMin,
|
||||||
|
backgroundMin + new Vector2(4, backgroundMax.Y - backgroundMin.Y),
|
||||||
|
ImGui.GetColorU32(accentColor),
|
||||||
|
3f
|
||||||
|
);
|
||||||
|
|
||||||
|
// Subtle glow effect
|
||||||
|
var glowColor = accentColor with { W = 0.15f };
|
||||||
|
drawList.AddRect(
|
||||||
|
backgroundMin,
|
||||||
|
backgroundMax,
|
||||||
|
ImGui.GetColorU32(glowColor),
|
||||||
|
6f,
|
||||||
|
ImDrawFlags.None,
|
||||||
|
1.5f
|
||||||
|
);
|
||||||
|
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 8);
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextColored(accentColor, title);
|
||||||
|
ImGui.Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadEmbeddedResources()
|
private void LoadEmbeddedResources()
|
||||||
|
|||||||
Reference in New Issue
Block a user