Files
LightlessClient/LightlessSync/UI/UpdateNotesUi.cs

333 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Numerics;
using System.Reflection;
using System.Text;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LightlessSync.UI;
public class UpdateNotesUi : WindowMediatorSubscriberBase
{
private readonly LightlessConfigService _configService;
private readonly UiSharedService _uiShared;
private readonly List<string> _contributors = [];
private readonly List<string> _credits = [];
private readonly List<string> _supporters = [];
private ChangelogFile _changelog = new();
private bool _scrollToTop;
private int _selectedTab;
public UpdateNotesUi(ILogger<UpdateNotesUi> logger,
LightlessMediator mediator,
UiSharedService uiShared,
LightlessConfigService configService,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Lightless Sync — Update Notes", performanceCollectorService)
{
_configService = configService;
_uiShared = uiShared;
AllowClickthrough = false;
AllowPinning = false;
RespectCloseHotkey = true;
ShowCloseButton = true;
Flags = ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(600, 500),
MaximumSize = new Vector2(900, 2000),
};
LoadEmbeddedResources();
}
public override void OnOpen()
{
_scrollToTop = true;
}
protected override void DrawInternal()
{
if (_uiShared.IsInGpose)
return;
DrawHeader();
DrawLinkButtons();
ImGuiHelpers.ScaledDummy(6);
DrawTabs();
DrawCloseButton();
}
private void DrawHeader()
{
using (_uiShared.UidFont.Push())
{
ImGui.TextUnformatted("Lightless Sync");
}
_uiShared.ColoredSeparator(UIColors.Get("LightlessBlue"), thickness: 2f);
if (!string.IsNullOrEmpty(_changelog.Tagline))
{
_uiShared.MediumText(_changelog.Tagline, UIColors.Get("LightlessBlue"));
if (!string.IsNullOrEmpty(_changelog.Subline))
{
ImGui.SameLine();
ImGui.TextColored(new Vector4(.75f, .75f, .85f, 1f), $" {_changelog.Subline}");
}
}
ImGuiHelpers.ScaledDummy(5);
}
private void DrawLinkButtons()
{
var segmentSize = ImGui.GetWindowSize().X / 4.2f;
var buttonSize = new Vector2(segmentSize, ImGui.GetTextLineHeight() * 1.6f);
if (ImGui.Button("Discord", buttonSize))
Util.OpenLink("https://discord.gg/dsbjcXMnhA");
ImGui.SameLine();
if (ImGui.Button("GitHub", buttonSize))
Util.OpenLink("https://github.com/Light-Public-Syncshells/LightlessSync");
ImGui.SameLine();
if (ImGui.Button("Ko-fi", buttonSize))
Util.OpenLink("https://ko-fi.com/lightlesssync");
ImGui.SameLine();
if (ImGui.Button("More Links", buttonSize))
Util.OpenLink("https://lightless.link");
}
private void DrawCloseButton()
{
var closeWidth = 300f * ImGuiHelpers.GlobalScale;
ImGui.SetCursorPosX((ImGui.GetWindowSize().X - closeWidth) / 2);
if (ImGui.Button("Close", new Vector2(closeWidth, 0)))
{
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()
{
using var child = ImRaii.Child("###ll_changelog", new Vector2(0, ImGui.GetContentRegionAvail().Y - 44), false);
if (!child)
return;
if (_scrollToTop)
{
_scrollToTop = false;
ImGui.SetScrollHereY(0);
}
foreach (var entry in _changelog.Changelog)
DrawChangelogEntry(entry);
ImGui.Spacing();
}
private void DrawChangelogEntry(ChangelogEntry entry)
{
var currentColor = entry.IsCurrent == true
? new Vector4(0.5f, 0.9f, 0.5f, 1.0f)
: new Vector4(0.75f, 0.75f, 0.85f, 1.0f);
bool isOpen;
using (ImRaii.PushColor(ImGuiCol.Text, currentColor))
{
isOpen = ImGui.CollapsingHeader($" {entry.Name} — {entry.Date} ");
}
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.75f, 0.75f, 0.85f, 1.0f), $" — {entry.Tagline}");
if (!isOpen)
return;
ImGuiHelpers.ScaledDummy(5);
if (!string.IsNullOrEmpty(entry.Message))
{
ImGui.TextWrapped(entry.Message);
ImGuiHelpers.ScaledDummy(5);
return;
}
if (entry.Versions != null)
{
foreach (var version in entry.Versions)
{
DrawFeatureHeader(version.Number, new Vector4(0.5f, 0.9f, 0.5f, 1.0f));
foreach (var item in version.Items)
ImGui.BulletText(item);
}
}
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);
ImGui.TextUnformatted("Credits");
_uiShared.RoundedSeparator(UIColors.Get("LightlessYellow"), thickness: 2f);
foreach (var credit in _credits)
ImGui.BulletText(credit);
}
private void LoadEmbeddedResources()
{
try
{
var assembly = Assembly.GetExecutingAssembly();
ReadLines(assembly, "LightlessSync.UI.Changelog.supporters.txt", _supporters);
ReadLines(assembly, "LightlessSync.UI.Changelog.contributors.txt", _contributors);
ReadLines(assembly, "LightlessSync.UI.Changelog.credits.txt", _credits);
using var changelogStream = assembly.GetManifestResourceStream("LightlessSync.UI.Changelog.changelog.yaml");
if (changelogStream != null)
{
using var reader = new StreamReader(changelogStream, Encoding.UTF8, true, 128);
var yaml = reader.ReadToEnd();
var deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
_changelog = deserializer.Deserialize<ChangelogFile>(yaml) ?? new();
}
}
catch
{
// Ignore - window will gracefully render with defaults
}
}
private static void ReadLines(Assembly assembly, string resourceName, List<string> target)
{
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
return;
using var reader = new StreamReader(stream, Encoding.UTF8, true, 128);
string? line;
while ((line = reader.ReadLine()) != null)
{
if (!string.IsNullOrWhiteSpace(line))
target.Add(line.Trim());
}
}
private sealed record ChangelogFile
{
public string Tagline { get; init; } = string.Empty;
public string Subline { get; init; } = string.Empty;
public List<ChangelogEntry> Changelog { get; init; } = new();
}
private sealed record ChangelogEntry
{
public string Name { get; init; } = string.Empty;
public string Date { get; init; } = string.Empty;
public string Tagline { get; init; } = string.Empty;
public bool? IsCurrent { get; init; }
public string? Message { get; init; }
public List<ChangelogVersion>? Versions { get; init; }
}
private sealed record ChangelogVersion
{
public string Number { get; init; } = string.Empty;
public List<string> Items { get; init; } = new();
}
}