333 lines
10 KiB
C#
333 lines
10 KiB
C#
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();
|
||
}
|
||
}
|