2.0.0 (#92)
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m27s
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:
@@ -1,18 +1,21 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Gui.ContextMenu;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using System;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using LightlessSync.FileCache;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
using LightlessSync.Interop.Ipc.Framework;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Models;
|
||||
using LightlessSync.Localization;
|
||||
@@ -24,8 +27,11 @@ using LightlessSync.Utils;
|
||||
using LightlessSync.WebAPI;
|
||||
using LightlessSync.WebAPI.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -70,7 +76,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
private bool _isOneDrive = false;
|
||||
private bool _isPenumbraDirectory = false;
|
||||
private bool _moodlesExists = false;
|
||||
private Dictionary<string, DateTime> _oauthTokenExpiry = new();
|
||||
private readonly Dictionary<string, DateTime> _oauthTokenExpiry = [];
|
||||
private bool _penumbraExists = false;
|
||||
private bool _petNamesExists = false;
|
||||
private int _serverSelectionIndex = -1;
|
||||
@@ -178,15 +184,108 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
int i = 0;
|
||||
double dblSByte = bytes;
|
||||
|
||||
while (dblSByte >= 1000 && i < suffix.Length - 1)
|
||||
while (dblSByte >= 1024 && i < suffix.Length - 1)
|
||||
{
|
||||
dblSByte /= 1000.0;
|
||||
dblSByte /= 1024.0;
|
||||
i++;
|
||||
}
|
||||
|
||||
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
||||
}
|
||||
|
||||
public readonly struct TabOption<T>
|
||||
{
|
||||
public string Label { get; }
|
||||
public T Value { get; }
|
||||
public bool Enabled { get; }
|
||||
|
||||
public TabOption(string label, T value, bool enabled = true)
|
||||
{
|
||||
Label = label;
|
||||
Value = value;
|
||||
Enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Tab<T>(string id, IReadOnlyList<TabOption<T>> options, ref T selectedValue) where T : struct
|
||||
{
|
||||
if (options.Count == 0)
|
||||
return false;
|
||||
|
||||
var pushIdValue = string.IsNullOrEmpty(id)
|
||||
? $"UiSharedTab_{RuntimeHelpers.GetHashCode(options):X}"
|
||||
: id;
|
||||
using var tabId = ImRaii.PushId(pushIdValue);
|
||||
|
||||
var selectedIndex = -1;
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(options[i].Value, selectedValue))
|
||||
continue;
|
||||
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedIndex == -1 || !options[selectedIndex].Enabled)
|
||||
selectedIndex = GetFirstEnabledTabIndex(options);
|
||||
|
||||
if (selectedIndex == -1)
|
||||
return false;
|
||||
|
||||
var changed = DrawTabsInternal(options, ref selectedIndex);
|
||||
selectedValue = options[Math.Clamp(selectedIndex, 0, options.Count - 1)].Value;
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static int GetFirstEnabledTabIndex<T>(IReadOnlyList<TabOption<T>> options)
|
||||
{
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (options[i].Enabled)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool DrawTabsInternal<T>(IReadOnlyList<TabOption<T>> options, ref int selectedIndex)
|
||||
{
|
||||
selectedIndex = Math.Clamp(selectedIndex, 0, Math.Max(0, options.Count - 1));
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
var availableWidth = ImGui.GetContentRegionAvail().X;
|
||||
var spacingX = style.ItemSpacing.X;
|
||||
var buttonWidth = options.Count > 0 ? Math.Max(1f, (availableWidth - spacingX * (options.Count - 1)) / options.Count) : availableWidth;
|
||||
var buttonHeight = Math.Max(ImGui.GetFrameHeight() + style.FramePadding.Y, 28f * ImGuiHelpers.GlobalScale);
|
||||
var changed = false;
|
||||
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
ImGui.SameLine();
|
||||
|
||||
var tab = options[i];
|
||||
var isSelected = i == selectedIndex;
|
||||
|
||||
using (ImRaii.Disabled(!tab.Enabled))
|
||||
{
|
||||
using var tabIndexId = ImRaii.PushId(i);
|
||||
using var selectedButton = isSelected ? ImRaii.PushColor(ImGuiCol.Button, style.Colors[(int)ImGuiCol.TabActive]) : null;
|
||||
using var selectedHover = isSelected ? ImRaii.PushColor(ImGuiCol.ButtonHovered, style.Colors[(int)ImGuiCol.TabHovered]) : null;
|
||||
using var selectedActive = isSelected ? ImRaii.PushColor(ImGuiCol.ButtonActive, style.Colors[(int)ImGuiCol.TabActive]) : null;
|
||||
|
||||
if (ImGui.Button(tab.Label, new Vector2(buttonWidth, buttonHeight)))
|
||||
{
|
||||
selectedIndex = i;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static void CenterNextWindow(float width, float height, ImGuiCond cond = ImGuiCond.None)
|
||||
{
|
||||
var center = ImGui.GetMainViewport().GetCenter();
|
||||
@@ -400,10 +499,21 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
public static bool ShiftPressed() => (GetKeyState(0xA1) & 0x8000) != 0 || (GetKeyState(0xA0) & 0x8000) != 0;
|
||||
|
||||
public static void TextWrapped(string text, float wrapPos = 0)
|
||||
public static void TextWrapped(string text, float wrapPos = 0, Vector4? color = null)
|
||||
{
|
||||
ImGui.PushTextWrapPos(wrapPos);
|
||||
if (color.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, color.Value);
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(text);
|
||||
|
||||
if (color.HasValue)
|
||||
{
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.PopTextWrapPos();
|
||||
}
|
||||
|
||||
@@ -475,7 +585,22 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
);
|
||||
}
|
||||
|
||||
public void ColoredSeparator(Vector4? color = null, float thickness = 1f, float indent = 0f)
|
||||
public static void AddContextMenuItem(IMenuOpenedArgs args, SeString name, char prefixChar, ushort colorMenuItem, Func<Task> onClick)
|
||||
{
|
||||
args.AddMenuItem(new MenuItem
|
||||
{
|
||||
Name = name,
|
||||
PrefixChar = prefixChar,
|
||||
UseDefaultPrefix = false,
|
||||
PrefixColor = colorMenuItem,
|
||||
OnClicked = _ =>
|
||||
{
|
||||
onClick();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public static void ColoredSeparator(Vector4? color = null, float thickness = 1f, float indent = 0f)
|
||||
{
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var min = ImGui.GetCursorScreenPos();
|
||||
@@ -519,8 +644,9 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
bool changed = ImGui.Checkbox(label, ref value);
|
||||
|
||||
var boxSize = ImGui.GetFrameHeight();
|
||||
var min = pos;
|
||||
var max = ImGui.GetItemRectMax();
|
||||
var max = new Vector2(pos.X + boxSize, pos.Y + boxSize);
|
||||
|
||||
var col = ImGui.GetColorU32(borderColor ?? ImGuiColors.DalamudGrey);
|
||||
ImGui.GetWindowDrawList().AddRect(min, max, col, rounding, ImDrawFlags.None, borderThickness);
|
||||
@@ -945,36 +1071,36 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
ImGui.SameLine(150);
|
||||
ColorText("Penumbra", GetBoolColor(_penumbraExists));
|
||||
AttachToolTip($"Penumbra is " + (_penumbraExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Penumbra", _penumbraExists, _ipcManager.Penumbra.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("Glamourer", GetBoolColor(_glamourerExists));
|
||||
AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Glamourer", _glamourerExists, _ipcManager.Glamourer.State));
|
||||
|
||||
ImGui.TextUnformatted("Optional Plugins:");
|
||||
ImGui.SameLine(150);
|
||||
ColorText("SimpleHeels", GetBoolColor(_heelsExists));
|
||||
AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("SimpleHeels", _heelsExists, _ipcManager.Heels.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("Customize+", GetBoolColor(_customizePlusExists));
|
||||
AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Customize+", _customizePlusExists, _ipcManager.CustomizePlus.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("Honorific", GetBoolColor(_honorificExists));
|
||||
AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Honorific", _honorificExists, _ipcManager.Honorific.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("Moodles", GetBoolColor(_moodlesExists));
|
||||
AttachToolTip($"Moodles is " + (_moodlesExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Moodles", _moodlesExists, _ipcManager.Moodles.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("PetNicknames", GetBoolColor(_petNamesExists));
|
||||
AttachToolTip($"PetNicknames is " + (_petNamesExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("PetNicknames", _petNamesExists, _ipcManager.PetNames.State));
|
||||
|
||||
ImGui.SameLine();
|
||||
ColorText("Brio", GetBoolColor(_brioExists));
|
||||
AttachToolTip($"Brio is " + (_brioExists ? "available and up to date." : "unavailable or not up to date."));
|
||||
AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State));
|
||||
|
||||
if (!_penumbraExists || !_glamourerExists)
|
||||
{
|
||||
@@ -985,6 +1111,25 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string BuildPluginTooltip(string pluginName, bool isAvailable, IpcConnectionState state)
|
||||
{
|
||||
var availability = isAvailable ? "available and up to date." : "unavailable or not up to date.";
|
||||
return $"{pluginName} is {availability}{Environment.NewLine}IPC State: {DescribeIpcState(state)}";
|
||||
}
|
||||
|
||||
private static string DescribeIpcState(IpcConnectionState state)
|
||||
=> state switch
|
||||
{
|
||||
IpcConnectionState.Unknown => "Not evaluated yet",
|
||||
IpcConnectionState.MissingPlugin => "Plugin not installed",
|
||||
IpcConnectionState.VersionMismatch => "Installed version below required minimum",
|
||||
IpcConnectionState.PluginDisabled => "Plugin installed but disabled",
|
||||
IpcConnectionState.NotReady => "Plugin is not ready yet",
|
||||
IpcConnectionState.Available => "Available",
|
||||
IpcConnectionState.Error => "Error occurred while checking IPC",
|
||||
_ => state.ToString()
|
||||
};
|
||||
|
||||
public int DrawServiceSelection(bool selectOnChange = false, bool showConnect = true)
|
||||
{
|
||||
string[] comboEntries = _serverConfigurationManager.GetServerNames();
|
||||
@@ -1067,7 +1212,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
using (ImRaii.Disabled(_discordOAuthUIDs == null))
|
||||
{
|
||||
var aliasPairs = _discordOAuthUIDs?.Result?.Select(t => new UIDAliasPair(t.Key, t.Value)).ToList() ?? [new UIDAliasPair(item.UID ?? null, null)];
|
||||
var aliasPairs = _discordOAuthUIDs?.Result?.Select(t => new UidAliasPair(t.Key, t.Value)).ToList() ?? [new UidAliasPair(item.UID ?? null, null)];
|
||||
var uidComboName = "UID###" + item.CharacterName + item.WorldId + serverUri + indexOffset + aliasPairs.Count;
|
||||
DrawCombo(uidComboName, aliasPairs,
|
||||
(v) =>
|
||||
@@ -1220,6 +1365,100 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
return _textureProvider.CreateFromImageAsync(imageData).Result;
|
||||
}
|
||||
|
||||
private static readonly (bool ItemHq, bool HiRes)[] IconLookupOrders =
|
||||
[
|
||||
(false, true),
|
||||
(true, true),
|
||||
(false, false),
|
||||
(true, false)
|
||||
];
|
||||
|
||||
public bool TryGetIcon(uint iconId, out IDalamudTextureWrap? wrap)
|
||||
{
|
||||
foreach (var (itemHq, hiRes) in IconLookupOrders)
|
||||
{
|
||||
if (TryGetIconWithLookup(iconId, itemHq, hiRes, out wrap))
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var (itemHq, hiRes) in IconLookupOrders)
|
||||
{
|
||||
if (!_textureProvider.TryGetIconPath(new GameIconLookup(iconId, itemHq, hiRes), out var path) || string.IsNullOrEmpty(path))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var reference = _textureProvider.GetFromGame(path);
|
||||
if (reference.TryGetWrap(out var texture, out _))
|
||||
{
|
||||
wrap = texture;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogTrace(ex, "Failed to load icon {IconId} from path {Path}", iconId, path);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var hiRes in new[] { true, false })
|
||||
{
|
||||
var manualPath = BuildIconPath(iconId, hiRes);
|
||||
if (TryLoadTextureFromPath(manualPath, iconId, out wrap))
|
||||
return true;
|
||||
}
|
||||
|
||||
wrap = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryLoadTextureFromPath(string path, uint iconId, out IDalamudTextureWrap? wrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reference = _textureProvider.GetFromGame(path);
|
||||
if (reference.TryGetWrap(out var texture, out _))
|
||||
{
|
||||
wrap = texture;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogTrace(ex, "Failed to load icon {IconId} from manual path {Path}", iconId, path);
|
||||
}
|
||||
|
||||
wrap = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string BuildIconPath(uint iconId, bool hiRes)
|
||||
{
|
||||
var folder = iconId - iconId % 1000;
|
||||
var basePath = $"ui/icon/{folder:000000}/{iconId:000000}";
|
||||
return hiRes ? $"{basePath}_hr1.tex" : $"{basePath}.tex";
|
||||
}
|
||||
|
||||
private bool TryGetIconWithLookup(uint iconId, bool itemHq, bool hiRes, out IDalamudTextureWrap? wrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
var icon = _textureProvider.GetFromGameIcon(new GameIconLookup(iconId, itemHq, hiRes));
|
||||
if (icon.TryGetWrap(out var texture, out _))
|
||||
{
|
||||
wrap = texture;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogTrace(ex, "Failed to load icon {IconId} (HQ:{ItemHq}, HR:{HiRes})", iconId, itemHq, hiRes);
|
||||
}
|
||||
|
||||
wrap = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void LoadLocalization(string languageCode)
|
||||
{
|
||||
_localization.SetupWithLangCode(languageCode);
|
||||
@@ -1253,6 +1492,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
UidFont.Dispose();
|
||||
GameFont.Dispose();
|
||||
MediumFont.Dispose();
|
||||
_discordOAuthGetCts.Dispose();
|
||||
}
|
||||
|
||||
private static void CenterWindow(float width, float height, ImGuiCond cond = ImGuiCond.None)
|
||||
@@ -1285,13 +1525,24 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
num++;
|
||||
}
|
||||
|
||||
ImGui.PushID(text);
|
||||
string displayText = text;
|
||||
string idText = text;
|
||||
int idSeparatorIndex = text.IndexOf("##", StringComparison.Ordinal);
|
||||
if (idSeparatorIndex >= 0)
|
||||
{
|
||||
displayText = text[..idSeparatorIndex];
|
||||
idText = text[(idSeparatorIndex + 2)..];
|
||||
if (string.IsNullOrEmpty(idText))
|
||||
idText = displayText;
|
||||
}
|
||||
|
||||
ImGui.PushID(idText);
|
||||
|
||||
Vector2 vector;
|
||||
using (IconFont.Push())
|
||||
vector = ImGui.CalcTextSize(icon.ToIconString());
|
||||
|
||||
Vector2 vector2 = ImGui.CalcTextSize(text);
|
||||
Vector2 vector2 = ImGui.CalcTextSize(displayText);
|
||||
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
|
||||
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
|
||||
float num2 = 3f * ImGuiHelpers.GlobalScale;
|
||||
@@ -1316,7 +1567,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
windowDrawList.AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
|
||||
|
||||
Vector2 pos2 = new Vector2(pos.X + vector.X + num2, cursorScreenPos.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
windowDrawList.AddText(pos2, ImGui.GetColorU32(ImGuiCol.Text), text);
|
||||
windowDrawList.AddText(pos2, ImGui.GetColorU32(ImGuiCol.Text), displayText);
|
||||
ImGui.PopID();
|
||||
if (num > 0)
|
||||
{
|
||||
@@ -1325,6 +1576,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public sealed record IconScaleData(Vector2 IconSize, Vector2 NormalizedIconScale, float OffsetX, float IconScaling);
|
||||
private record UIDAliasPair(string? UID, string? Alias);
|
||||
private sealed record UidAliasPair(string? UID, string? Alias);
|
||||
}
|
||||
Reference in New Issue
Block a user