refactor(Dalamud): switch to file-scoped namespaces

This commit is contained in:
goat 2021-11-17 19:42:32 +01:00
parent 13cf3d93dc
commit b5f34c3199
No known key found for this signature in database
GPG key ID: 7773BB5B43BA52E5
325 changed files with 45878 additions and 46218 deletions

View file

@ -11,338 +11,337 @@ using Dalamud.Game.Gui;
using Dalamud.Plugin.Internal;
using Serilog;
namespace Dalamud.Interface.Internal
namespace Dalamud.Interface.Internal;
/// <summary>
/// Class handling Dalamud core commands.
/// </summary>
internal class DalamudCommands
{
/// <summary>
/// Class handling Dalamud core commands.
/// Initializes a new instance of the <see cref="DalamudCommands"/> class.
/// </summary>
internal class DalamudCommands
public DalamudCommands()
{
/// <summary>
/// Initializes a new instance of the <see cref="DalamudCommands"/> class.
/// </summary>
public DalamudCommands()
}
/// <summary>
/// Register all command handlers with the Dalamud instance.
/// </summary>
public void SetupCommands()
{
var commandManager = Service<CommandManager>.Get();
commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand)
{
}
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),
ShowInHelp = false,
});
/// <summary>
/// Register all command handlers with the Dalamud instance.
/// </summary>
public void SetupCommands()
commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand)
{
var commandManager = Service<CommandManager>.Get();
HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."),
ShowInHelp = false,
});
commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand)
{
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),
ShowInHelp = false,
});
commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand)
{
HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."),
ShowInHelp = false,
});
commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand)
{
HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."),
});
commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand)
{
HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute <word or sentence>"),
});
commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand)
{
HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."),
});
commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand)
{
HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute <word or sentence>"),
});
commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand)
{
HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."),
});
commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand)
{
HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset <BGM ID>"),
});
commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu)
{
HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"),
ShowInHelp = false,
});
commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu)
{
HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"),
ShowInHelp = false,
});
commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel)
{
HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"),
ShowInHelp = false,
});
commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog)
{
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"),
ShowInHelp = false,
});
commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand)
{
HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"),
});
commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand)
{
HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."),
});
commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand)
{
HelpMessage =
Loc.Localize(
"DalamudLanguageHelp",
"Set the language for the in-game addon and plugins that support it. Available languages: ") +
Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code),
});
commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand)
{
HelpMessage = Loc.Localize(
"DalamudSettingsHelp",
"Change various In-Game-Addon settings like chat channels and the discord bot setup."),
});
commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand)
{
HelpMessage = "ImGui DEBUG",
ShowInHelp = false,
});
}
private void OnUnloadCommand(string command, string arguments)
commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand)
{
Service<ChatGui>.Get().Print("Unloading...");
Service<Dalamud>.Get().Unload();
}
HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."),
});
private void OnHelpCommand(string command, string arguments)
commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand)
{
var chatGui = Service<ChatGui>.Get();
var commandManager = Service<CommandManager>.Get();
HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute <word or sentence>"),
});
var showDebug = arguments.Contains("debug");
chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:"));
foreach (var cmd in commandManager.Commands)
{
if (!cmd.Value.ShowInHelp && !showDebug)
continue;
chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}");
}
}
private void OnPluginReloadCommand(string command, string arguments)
commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand)
{
var chatGui = Service<ChatGui>.Get();
HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."),
});
chatGui.Print("Reloading...");
try
{
Service<PluginManager>.Get().ReloadAllPlugins();
chatGui.Print("OK");
}
catch (Exception ex)
{
Log.Error(ex, "Plugin reload failed.");
chatGui.PrintError("Reload failed.");
}
}
private void OnBadWordsAddCommand(string command, string arguments)
commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute <word or sentence>"),
});
configuration.BadWords ??= new List<string>();
if (string.IsNullOrEmpty(arguments))
{
chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute."));
return;
}
configuration.BadWords.Add(arguments);
configuration.Save();
chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments));
}
private void OnBadWordsListCommand(string command, string arguments)
commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."),
});
configuration.BadWords ??= new List<string>();
if (configuration.BadWords.Count == 0)
{
chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences."));
return;
}
configuration.Save();
foreach (var word in configuration.BadWords)
chatGui.Print($"\"{word}\"");
}
private void OnBadWordsRemoveCommand(string command, string arguments)
commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset <BGM ID>"),
});
configuration.BadWords ??= new List<string>();
configuration.BadWords.RemoveAll(x => x == arguments);
configuration.Save();
chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments));
}
private void OnLastLinkCommand(string command, string arguments)
commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu)
{
var chatHandlers = Service<ChatHandlers>.Get();
var chatGui = Service<ChatGui>.Get();
HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"),
ShowInHelp = false,
});
if (string.IsNullOrEmpty(chatHandlers.LastLink))
{
chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link..."));
return;
}
chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink));
Process.Start(new ProcessStartInfo(chatHandlers.LastLink)
{
UseShellExecute = true,
});
}
private void OnBgmSetCommand(string command, string arguments)
commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu)
{
var gameGui = Service<GameGui>.Get();
HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"),
ShowInHelp = false,
});
if (ushort.TryParse(arguments, out var value))
{
gameGui.SetBgm(value);
}
else
{
// Revert to the original BGM by specifying an invalid one
gameGui.SetBgm(9999);
}
}
private void OnDebugDrawDevMenu(string command, string arguments)
commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel)
{
Service<DalamudInterface>.Get().ToggleDevMenu();
}
HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"),
ShowInHelp = false,
});
private void OnDebugDrawDataMenu(string command, string arguments)
commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog)
{
var dalamudInterface = Service<DalamudInterface>.Get();
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"),
ShowInHelp = false,
});
if (string.IsNullOrEmpty(arguments))
dalamudInterface.ToggleDataWindow();
else
dalamudInterface.ToggleDataWindow(arguments);
}
private void OnDebugDrawIMEPanel(string command, string arguments)
commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand)
{
Service<DalamudInterface>.Get().OpenIMEWindow();
}
HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"),
});
private void OnOpenLog(string command, string arguments)
commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand)
{
Service<DalamudInterface>.Get().ToggleLogWindow();
}
HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."),
});
private void OnDebugImInfoCommand(string command, string arguments)
commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand)
{
var io = Service<InterfaceManager>.Get().LastImGuiIoPtr;
var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n";
info += $"WantCaptureMouse: {io.WantCaptureMouse}\n";
info += $"WantSetMousePos: {io.WantSetMousePos}\n";
info += $"WantTextInput: {io.WantTextInput}\n";
info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n";
info += $"BackendFlags: {(int)io.BackendFlags}\n";
info += $"DeltaTime: {io.DeltaTime}\n";
info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n";
info += $"Framerate: {io.Framerate}\n";
info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n";
info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n";
info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n";
info += $"MouseClicked: {io.MouseClicked}\n";
info += $"MouseDown: {io.MouseDown}\n";
info += $"NavActive: {io.NavActive}\n";
info += $"NavVisible: {io.NavVisible}\n";
HelpMessage =
Loc.Localize(
"DalamudLanguageHelp",
"Set the language for the in-game addon and plugins that support it. Available languages: ") +
Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code),
});
Log.Information(info);
}
private void OnOpenInstallerCommand(string command, string arguments)
commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand)
{
Service<DalamudInterface>.Get().TogglePluginInstallerWindow();
}
HelpMessage = Loc.Localize(
"DalamudSettingsHelp",
"Change various In-Game-Addon settings like chat channels and the discord bot setup."),
});
private void OnOpenCreditsCommand(string command, string arguments)
commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand)
{
Service<DalamudInterface>.Get().ToggleCreditsWindow();
}
HelpMessage = "ImGui DEBUG",
ShowInHelp = false,
});
}
private void OnSetLanguageCommand(string command, string arguments)
private void OnUnloadCommand(string command, string arguments)
{
Service<ChatGui>.Get().Print("Unloading...");
Service<Dalamud>.Get().Unload();
}
private void OnHelpCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
var commandManager = Service<CommandManager>.Get();
var showDebug = arguments.Contains("debug");
chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:"));
foreach (var cmd in commandManager.Commands)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
var localization = Service<Localization>.Get();
if (!cmd.Value.ShowInHelp && !showDebug)
continue;
if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en")
{
localization.SetupWithLangCode(arguments.ToLower());
configuration.LanguageOverride = arguments.ToLower();
chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments));
}
else
{
localization.SetupWithUiCulture();
configuration.LanguageOverride = null;
chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default"));
}
configuration.Save();
}
private void OnOpenSettingsCommand(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleSettingsWindow();
chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}");
}
}
private void OnPluginReloadCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
chatGui.Print("Reloading...");
try
{
Service<PluginManager>.Get().ReloadAllPlugins();
chatGui.Print("OK");
}
catch (Exception ex)
{
Log.Error(ex, "Plugin reload failed.");
chatGui.PrintError("Reload failed.");
}
}
private void OnBadWordsAddCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
configuration.BadWords ??= new List<string>();
if (string.IsNullOrEmpty(arguments))
{
chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute."));
return;
}
configuration.BadWords.Add(arguments);
configuration.Save();
chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments));
}
private void OnBadWordsListCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
configuration.BadWords ??= new List<string>();
if (configuration.BadWords.Count == 0)
{
chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences."));
return;
}
configuration.Save();
foreach (var word in configuration.BadWords)
chatGui.Print($"\"{word}\"");
}
private void OnBadWordsRemoveCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
configuration.BadWords ??= new List<string>();
configuration.BadWords.RemoveAll(x => x == arguments);
configuration.Save();
chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments));
}
private void OnLastLinkCommand(string command, string arguments)
{
var chatHandlers = Service<ChatHandlers>.Get();
var chatGui = Service<ChatGui>.Get();
if (string.IsNullOrEmpty(chatHandlers.LastLink))
{
chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link..."));
return;
}
chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink));
Process.Start(new ProcessStartInfo(chatHandlers.LastLink)
{
UseShellExecute = true,
});
}
private void OnBgmSetCommand(string command, string arguments)
{
var gameGui = Service<GameGui>.Get();
if (ushort.TryParse(arguments, out var value))
{
gameGui.SetBgm(value);
}
else
{
// Revert to the original BGM by specifying an invalid one
gameGui.SetBgm(9999);
}
}
private void OnDebugDrawDevMenu(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleDevMenu();
}
private void OnDebugDrawDataMenu(string command, string arguments)
{
var dalamudInterface = Service<DalamudInterface>.Get();
if (string.IsNullOrEmpty(arguments))
dalamudInterface.ToggleDataWindow();
else
dalamudInterface.ToggleDataWindow(arguments);
}
private void OnDebugDrawIMEPanel(string command, string arguments)
{
Service<DalamudInterface>.Get().OpenIMEWindow();
}
private void OnOpenLog(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleLogWindow();
}
private void OnDebugImInfoCommand(string command, string arguments)
{
var io = Service<InterfaceManager>.Get().LastImGuiIoPtr;
var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n";
info += $"WantCaptureMouse: {io.WantCaptureMouse}\n";
info += $"WantSetMousePos: {io.WantSetMousePos}\n";
info += $"WantTextInput: {io.WantTextInput}\n";
info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n";
info += $"BackendFlags: {(int)io.BackendFlags}\n";
info += $"DeltaTime: {io.DeltaTime}\n";
info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n";
info += $"Framerate: {io.Framerate}\n";
info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n";
info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n";
info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n";
info += $"MouseClicked: {io.MouseClicked}\n";
info += $"MouseDown: {io.MouseDown}\n";
info += $"NavActive: {io.NavActive}\n";
info += $"NavVisible: {io.NavVisible}\n";
Log.Information(info);
}
private void OnOpenInstallerCommand(string command, string arguments)
{
Service<DalamudInterface>.Get().TogglePluginInstallerWindow();
}
private void OnOpenCreditsCommand(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleCreditsWindow();
}
private void OnSetLanguageCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();
var configuration = Service<DalamudConfiguration>.Get();
var localization = Service<Localization>.Get();
if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en")
{
localization.SetupWithLangCode(arguments.ToLower());
configuration.LanguageOverride = arguments.ToLower();
chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments));
}
else
{
localization.SetupWithUiCulture();
configuration.LanguageOverride = null;
chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default"));
}
configuration.Save();
}
private void OnOpenSettingsCommand(string command, string arguments)
{
Service<DalamudInterface>.Get().ToggleSettingsWindow();
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,21 @@
namespace Dalamud.Interface.Internal.ManagedAsserts
namespace Dalamud.Interface.Internal.ManagedAsserts;
/// <summary>
/// Offsets to various data in ImGui context.
/// </summary>
/// <remarks>
/// Last updated for ImGui 1.83.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")]
internal static class ImGuiContextOffsets
{
/// <summary>
/// Offsets to various data in ImGui context.
/// </summary>
/// <remarks>
/// Last updated for ImGui 1.83.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")]
internal static class ImGuiContextOffsets
{
public const int CurrentWindowStackOffset = 0x73A;
public const int CurrentWindowStackOffset = 0x73A;
public const int ColorStackOffset = 0x79C;
public const int ColorStackOffset = 0x79C;
public const int StyleVarStackOffset = 0x7A0;
public const int StyleVarStackOffset = 0x7A0;
public const int FontStackOffset = 0x7A4;
public const int FontStackOffset = 0x7A4;
public const int BeginPopupStackOffset = 0x7B8;
}
public const int BeginPopupStackOffset = 0x7B8;
}

View file

@ -4,133 +4,132 @@ using ImGuiNET;
using static Dalamud.NativeFunctions;
namespace Dalamud.Interface.Internal.ManagedAsserts
namespace Dalamud.Interface.Internal.ManagedAsserts;
/// <summary>
/// Report ImGui problems with a MessageBox dialog.
/// </summary>
internal static class ImGuiManagedAsserts
{
/// <summary>
/// Report ImGui problems with a MessageBox dialog.
/// Gets or sets a value indicating whether asserts are enabled for ImGui.
/// </summary>
internal static class ImGuiManagedAsserts
public static bool AssertsEnabled { get; set; }
/// <summary>
/// Create a snapshot of the current ImGui context.
/// Should be called before rendering an ImGui frame.
/// </summary>
/// <returns>A snapshot of the current context.</returns>
public static unsafe ImGuiContextSnapshot GetSnapshot()
{
/// <summary>
/// Gets or sets a value indicating whether asserts are enabled for ImGui.
/// </summary>
public static bool AssertsEnabled { get; set; }
var contextPtr = ImGui.GetCurrentContext();
/// <summary>
/// Create a snapshot of the current ImGui context.
/// Should be called before rendering an ImGui frame.
/// </summary>
/// <returns>A snapshot of the current context.</returns>
public static unsafe ImGuiContextSnapshot GetSnapshot()
var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size
var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size
var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size
var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size
var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size
return new ImGuiContextSnapshot
{
var contextPtr = ImGui.GetCurrentContext();
StyleVarStackSize = styleVarStack,
ColorStackSize = colorStack,
FontStackSize = fontStack,
BeginPopupStackSize = popupStack,
WindowStackSize = windowStack,
};
}
var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size
var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size
var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size
var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size
var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size
return new ImGuiContextSnapshot
{
StyleVarStackSize = styleVarStack,
ColorStackSize = colorStack,
FontStackSize = fontStack,
BeginPopupStackSize = popupStack,
WindowStackSize = windowStack,
};
/// <summary>
/// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog.
/// </summary>
/// <param name="source">The source of any problems, something to blame.</param>
/// <param name="before">ImGui context snapshot.</param>
public static void ReportProblems(string source, ImGuiContextSnapshot before)
{
if (!AssertsEnabled)
{
return;
}
/// <summary>
/// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog.
/// </summary>
/// <param name="source">The source of any problems, something to blame.</param>
/// <param name="before">ImGui context snapshot.</param>
public static void ReportProblems(string source, ImGuiContextSnapshot before)
var cSnap = GetSnapshot();
if (before.StyleVarStackSize != cSnap.StyleVarStackSize)
{
if (!AssertsEnabled)
{
return;
}
var cSnap = GetSnapshot();
if (before.StyleVarStackSize != cSnap.StyleVarStackSize)
{
ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}");
return;
}
if (before.ColorStackSize != cSnap.ColorStackSize)
{
ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}");
return;
}
if (before.FontStackSize != cSnap.FontStackSize)
{
ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}");
return;
}
if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize)
{
ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}");
return;
}
if (cSnap.WindowStackSize != 1)
{
if (cSnap.WindowStackSize > 1)
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
else
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
}
ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}");
return;
}
private static void ShowAssert(string source, string message)
if (before.ColorStackSize != cSnap.ColorStackSize)
{
var caption = $"You fucked up";
message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them.";
var flags = MessageBoxType.Ok | MessageBoxType.IconError;
_ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags);
AssertsEnabled = false;
ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}");
return;
}
/// <summary>
/// A snapshot of various ImGui context properties.
/// </summary>
public class ImGuiContextSnapshot
if (before.FontStackSize != cSnap.FontStackSize)
{
/// <summary>
/// Gets the ImGui style var stack size.
/// </summary>
public int StyleVarStackSize { get; init; }
ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}");
return;
}
/// <summary>
/// Gets the ImGui color stack size.
/// </summary>
public int ColorStackSize { get; init; }
if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize)
{
ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}");
return;
}
/// <summary>
/// Gets the ImGui font stack size.
/// </summary>
public int FontStackSize { get; init; }
/// <summary>
/// Gets the ImGui begin popup stack size.
/// </summary>
public int BeginPopupStackSize { get; init; }
/// <summary>
/// Gets the ImGui window stack size.
/// </summary>
public int WindowStackSize { get; init; }
if (cSnap.WindowStackSize != 1)
{
if (cSnap.WindowStackSize > 1)
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
else
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
}
}
private static void ShowAssert(string source, string message)
{
var caption = $"You fucked up";
message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them.";
var flags = MessageBoxType.Ok | MessageBoxType.IconError;
_ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags);
AssertsEnabled = false;
}
/// <summary>
/// A snapshot of various ImGui context properties.
/// </summary>
public class ImGuiContextSnapshot
{
/// <summary>
/// Gets the ImGui style var stack size.
/// </summary>
public int StyleVarStackSize { get; init; }
/// <summary>
/// Gets the ImGui color stack size.
/// </summary>
public int ColorStackSize { get; init; }
/// <summary>
/// Gets the ImGui font stack size.
/// </summary>
public int FontStackSize { get; init; }
/// <summary>
/// Gets the ImGui begin popup stack size.
/// </summary>
public int BeginPopupStackSize { get; init; }
/// <summary>
/// Gets the ImGui window stack size.
/// </summary>
public int WindowStackSize { get; init; }
}
}

View file

@ -7,306 +7,305 @@ using Dalamud.Interface.Colors;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Notifications
namespace Dalamud.Interface.Internal.Notifications;
/// <summary>
/// Class handling notifications/toasts in ImGui.
/// Ported from https://github.com/patrickcjk/imgui-notify.
/// </summary>
internal class NotificationManager
{
/// <summary>
/// Class handling notifications/toasts in ImGui.
/// Ported from https://github.com/patrickcjk/imgui-notify.
/// Value indicating the bottom-left X padding.
/// </summary>
internal class NotificationManager
internal const float NotifyPaddingX = 20.0f;
/// <summary>
/// Value indicating the bottom-left Y padding.
/// </summary>
internal const float NotifyPaddingY = 20.0f;
/// <summary>
/// Value indicating the Y padding between each message.
/// </summary>
internal const float NotifyPaddingMessageY = 10.0f;
/// <summary>
/// Value indicating the fade-in and out duration.
/// </summary>
internal const int NotifyFadeInOutTime = 500;
/// <summary>
/// Value indicating the default time until the notification is dismissed.
/// </summary>
internal const int NotifyDefaultDismiss = 3000;
/// <summary>
/// Value indicating the maximum opacity.
/// </summary>
internal const float NotifyOpacity = 0.82f;
/// <summary>
/// Value indicating default window flags for the notifications.
/// </summary>
internal const ImGuiWindowFlags NotifyToastFlags =
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs |
ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing;
private readonly List<Notification> notifications = new();
/// <summary>
/// Add a notification to the notification queue.
/// </summary>
/// <param name="content">The content of the notification.</param>
/// <param name="title">The title of the notification.</param>
/// <param name="type">The type of the notification.</param>
/// <param name="msDelay">The time the notification should be displayed for.</param>
public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss)
{
/// <summary>
/// Value indicating the bottom-left X padding.
/// </summary>
internal const float NotifyPaddingX = 20.0f;
/// <summary>
/// Value indicating the bottom-left Y padding.
/// </summary>
internal const float NotifyPaddingY = 20.0f;
/// <summary>
/// Value indicating the Y padding between each message.
/// </summary>
internal const float NotifyPaddingMessageY = 10.0f;
/// <summary>
/// Value indicating the fade-in and out duration.
/// </summary>
internal const int NotifyFadeInOutTime = 500;
/// <summary>
/// Value indicating the default time until the notification is dismissed.
/// </summary>
internal const int NotifyDefaultDismiss = 3000;
/// <summary>
/// Value indicating the maximum opacity.
/// </summary>
internal const float NotifyOpacity = 0.82f;
/// <summary>
/// Value indicating default window flags for the notifications.
/// </summary>
internal const ImGuiWindowFlags NotifyToastFlags =
ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs |
ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing;
private readonly List<Notification> notifications = new();
/// <summary>
/// Add a notification to the notification queue.
/// </summary>
/// <param name="content">The content of the notification.</param>
/// <param name="title">The title of the notification.</param>
/// <param name="type">The type of the notification.</param>
/// <param name="msDelay">The time the notification should be displayed for.</param>
public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss)
this.notifications.Add(new Notification
{
this.notifications.Add(new Notification
{
Content = content,
Title = title,
NotificationType = type,
DurationMs = msDelay,
});
}
Content = content,
Title = title,
NotificationType = type,
DurationMs = msDelay,
});
}
/// <summary>
/// Draw all currently queued notifications.
/// </summary>
public void Draw()
/// <summary>
/// Draw all currently queued notifications.
/// </summary>
public void Draw()
{
var viewportSize = ImGuiHelpers.MainViewport.Size;
var height = 0f;
for (var i = 0; i < this.notifications.Count; i++)
{
var viewportSize = ImGuiHelpers.MainViewport.Size;
var height = 0f;
var tn = this.notifications.ElementAt(i);
for (var i = 0; i < this.notifications.Count; i++)
if (tn.GetPhase() == Notification.Phase.Expired)
{
var tn = this.notifications.ElementAt(i);
this.notifications.RemoveAt(i);
continue;
}
if (tn.GetPhase() == Notification.Phase.Expired)
{
this.notifications.RemoveAt(i);
continue;
}
var opacity = tn.GetFadePercent();
var opacity = tn.GetFadePercent();
var iconColor = tn.Color;
iconColor.W = opacity;
var iconColor = tn.Color;
iconColor.W = opacity;
var windowName = $"##NOTIFY{i}";
var windowName = $"##NOTIFY{i}";
ImGuiHelpers.ForceNextWindowMainViewport();
ImGui.SetNextWindowBgAlpha(opacity);
ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One);
ImGui.Begin(windowName, NotifyToastFlags);
ImGuiHelpers.ForceNextWindowMainViewport();
ImGui.SetNextWindowBgAlpha(opacity);
ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One);
ImGui.Begin(windowName, NotifyToastFlags);
ImGui.PushTextWrapPos(viewportSize.X / 3.0f);
ImGui.PushTextWrapPos(viewportSize.X / 3.0f);
var wasTitleRendered = false;
var wasTitleRendered = false;
if (!tn.Icon.IsNullOrEmpty())
{
wasTitleRendered = true;
ImGui.PushFont(InterfaceManager.IconFont);
ImGui.TextColored(iconColor, tn.Icon);
ImGui.PopFont();
}
var textColor = ImGuiColors.DalamudWhite;
textColor.W = opacity;
ImGui.PushStyleColor(ImGuiCol.Text, textColor);
if (!tn.Title.IsNullOrEmpty())
{
if (!tn.Icon.IsNullOrEmpty())
{
wasTitleRendered = true;
ImGui.PushFont(InterfaceManager.IconFont);
ImGui.TextColored(iconColor, tn.Icon);
ImGui.PopFont();
ImGui.SameLine();
}
var textColor = ImGuiColors.DalamudWhite;
textColor.W = opacity;
ImGui.PushStyleColor(ImGuiCol.Text, textColor);
if (!tn.Title.IsNullOrEmpty())
{
if (!tn.Icon.IsNullOrEmpty())
{
ImGui.SameLine();
}
ImGui.TextUnformatted(tn.Title);
wasTitleRendered = true;
}
else if (!tn.DefaultTitle.IsNullOrEmpty())
{
if (!tn.Icon.IsNullOrEmpty())
{
ImGui.SameLine();
}
ImGui.TextUnformatted(tn.DefaultTitle);
wasTitleRendered = true;
}
if (wasTitleRendered && !tn.Content.IsNullOrEmpty())
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f);
}
if (!tn.Content.IsNullOrEmpty())
{
if (wasTitleRendered)
{
ImGui.Separator();
}
ImGui.TextUnformatted(tn.Content);
}
ImGui.PopStyleColor();
ImGui.PopTextWrapPos();
height += ImGui.GetWindowHeight() + NotifyPaddingMessageY;
ImGui.End();
ImGui.TextUnformatted(tn.Title);
wasTitleRendered = true;
}
else if (!tn.DefaultTitle.IsNullOrEmpty())
{
if (!tn.Icon.IsNullOrEmpty())
{
ImGui.SameLine();
}
ImGui.TextUnformatted(tn.DefaultTitle);
wasTitleRendered = true;
}
if (wasTitleRendered && !tn.Content.IsNullOrEmpty())
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f);
}
if (!tn.Content.IsNullOrEmpty())
{
if (wasTitleRendered)
{
ImGui.Separator();
}
ImGui.TextUnformatted(tn.Content);
}
ImGui.PopStyleColor();
ImGui.PopTextWrapPos();
height += ImGui.GetWindowHeight() + NotifyPaddingMessageY;
ImGui.End();
}
}
/// <summary>
/// Container class for notifications.
/// </summary>
internal class Notification
{
/// <summary>
/// Possible notification phases.
/// </summary>
internal enum Phase
{
/// <summary>
/// Phase indicating fade-in.
/// </summary>
FadeIn,
/// <summary>
/// Phase indicating waiting until fade-out.
/// </summary>
Wait,
/// <summary>
/// Phase indicating fade-out.
/// </summary>
FadeOut,
/// <summary>
/// Phase indicating that the notification has expired.
/// </summary>
Expired,
}
/// <summary>
/// Container class for notifications.
/// Gets the type of the notification.
/// </summary>
internal class Notification
internal NotificationType NotificationType { get; init; }
/// <summary>
/// Gets the title of the notification.
/// </summary>
internal string? Title { get; init; }
/// <summary>
/// Gets the content of the notification.
/// </summary>
internal string Content { get; init; }
/// <summary>
/// Gets the duration of the notification in milliseconds.
/// </summary>
internal uint DurationMs { get; init; }
/// <summary>
/// Gets the creation time of the notification.
/// </summary>
internal DateTime CreationTime { get; init; } = DateTime.Now;
/// <summary>
/// Gets the default color of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal Vector4 Color => this.NotificationType switch
{
/// <summary>
/// Possible notification phases.
/// </summary>
internal enum Phase
NotificationType.None => ImGuiColors.DalamudWhite,
NotificationType.Success => ImGuiColors.HealerGreen,
NotificationType.Warning => ImGuiColors.DalamudOrange,
NotificationType.Error => ImGuiColors.DalamudRed,
NotificationType.Info => ImGuiColors.TankBlue,
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the icon of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal string? Icon => this.NotificationType switch
{
NotificationType.None => null,
NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(),
NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(),
NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(),
NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(),
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the default title of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal string? DefaultTitle => this.NotificationType switch
{
NotificationType.None => null,
NotificationType.Success => NotificationType.Success.ToString(),
NotificationType.Warning => NotificationType.Warning.ToString(),
NotificationType.Error => NotificationType.Error.ToString(),
NotificationType.Info => NotificationType.Info.ToString(),
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the elapsed time since creating the notification.
/// </summary>
internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime;
/// <summary>
/// Gets the phase of the notification.
/// </summary>
/// <returns>The phase of the notification.</returns>
internal Phase GetPhase()
{
var elapsed = (int)this.ElapsedTime.TotalMilliseconds;
if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime)
return Phase.Expired;
else if (elapsed > NotifyFadeInOutTime + this.DurationMs)
return Phase.FadeOut;
else if (elapsed > NotifyFadeInOutTime)
return Phase.Wait;
else
return Phase.FadeIn;
}
/// <summary>
/// Gets the opacity of the notification.
/// </summary>
/// <returns>The opacity, in a range from 0 to 1.</returns>
internal float GetFadePercent()
{
var phase = this.GetPhase();
var elapsed = this.ElapsedTime.TotalMilliseconds;
if (phase == Phase.FadeIn)
{
/// <summary>
/// Phase indicating fade-in.
/// </summary>
FadeIn,
/// <summary>
/// Phase indicating waiting until fade-out.
/// </summary>
Wait,
/// <summary>
/// Phase indicating fade-out.
/// </summary>
FadeOut,
/// <summary>
/// Phase indicating that the notification has expired.
/// </summary>
Expired,
return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity;
}
else if (phase == Phase.FadeOut)
{
return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) /
NotifyFadeInOutTime)) * NotifyOpacity;
}
/// <summary>
/// Gets the type of the notification.
/// </summary>
internal NotificationType NotificationType { get; init; }
/// <summary>
/// Gets the title of the notification.
/// </summary>
internal string? Title { get; init; }
/// <summary>
/// Gets the content of the notification.
/// </summary>
internal string Content { get; init; }
/// <summary>
/// Gets the duration of the notification in milliseconds.
/// </summary>
internal uint DurationMs { get; init; }
/// <summary>
/// Gets the creation time of the notification.
/// </summary>
internal DateTime CreationTime { get; init; } = DateTime.Now;
/// <summary>
/// Gets the default color of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal Vector4 Color => this.NotificationType switch
{
NotificationType.None => ImGuiColors.DalamudWhite,
NotificationType.Success => ImGuiColors.HealerGreen,
NotificationType.Warning => ImGuiColors.DalamudOrange,
NotificationType.Error => ImGuiColors.DalamudRed,
NotificationType.Info => ImGuiColors.TankBlue,
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the icon of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal string? Icon => this.NotificationType switch
{
NotificationType.None => null,
NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(),
NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(),
NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(),
NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(),
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the default title of the notification.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="NotificationType"/> is set to an out-of-range value.</exception>
internal string? DefaultTitle => this.NotificationType switch
{
NotificationType.None => null,
NotificationType.Success => NotificationType.Success.ToString(),
NotificationType.Warning => NotificationType.Warning.ToString(),
NotificationType.Error => NotificationType.Error.ToString(),
NotificationType.Info => NotificationType.Info.ToString(),
_ => throw new ArgumentOutOfRangeException(),
};
/// <summary>
/// Gets the elapsed time since creating the notification.
/// </summary>
internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime;
/// <summary>
/// Gets the phase of the notification.
/// </summary>
/// <returns>The phase of the notification.</returns>
internal Phase GetPhase()
{
var elapsed = (int)this.ElapsedTime.TotalMilliseconds;
if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime)
return Phase.Expired;
else if (elapsed > NotifyFadeInOutTime + this.DurationMs)
return Phase.FadeOut;
else if (elapsed > NotifyFadeInOutTime)
return Phase.Wait;
else
return Phase.FadeIn;
}
/// <summary>
/// Gets the opacity of the notification.
/// </summary>
/// <returns>The opacity, in a range from 0 to 1.</returns>
internal float GetFadePercent()
{
var phase = this.GetPhase();
var elapsed = this.ElapsedTime.TotalMilliseconds;
if (phase == Phase.FadeIn)
{
return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity;
}
else if (phase == Phase.FadeOut)
{
return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) /
NotifyFadeInOutTime)) * NotifyOpacity;
}
return 1.0f * NotifyOpacity;
}
return 1.0f * NotifyOpacity;
}
}
}

View file

@ -1,33 +1,32 @@
namespace Dalamud.Interface.Internal.Notifications
namespace Dalamud.Interface.Internal.Notifications;
/// <summary>
/// Possible notification types.
/// </summary>
public enum NotificationType
{
/// <summary>
/// Possible notification types.
/// No special type.
/// </summary>
public enum NotificationType
{
/// <summary>
/// No special type.
/// </summary>
None,
None,
/// <summary>
/// Type indicating success.
/// </summary>
Success,
/// <summary>
/// Type indicating success.
/// </summary>
Success,
/// <summary>
/// Type indicating a warning.
/// </summary>
Warning,
/// <summary>
/// Type indicating a warning.
/// </summary>
Warning,
/// <summary>
/// Type indicating an error.
/// </summary>
Error,
/// <summary>
/// Type indicating an error.
/// </summary>
Error,
/// <summary>
/// Type indicating generic information.
/// </summary>
Info,
}
/// <summary>
/// Type indicating generic information.
/// </summary>
Info,
}

View file

@ -5,402 +5,401 @@ using System.Linq;
using CheapLoc;
using Dalamud.Plugin.Internal.Types;
namespace Dalamud.Interface.Internal
namespace Dalamud.Interface.Internal;
/// <summary>
/// Manage category filters for PluginInstallerWindow.
/// </summary>
internal class PluginCategoryManager
{
/// <summary>
/// Manage category filters for PluginInstallerWindow.
/// First categoryId for tag based categories.
/// </summary>
internal class PluginCategoryManager
{
/// <summary>
/// First categoryId for tag based categories.
/// </summary>
public const int FirstTagBasedCategoryId = 100;
public const int FirstTagBasedCategoryId = 100;
private readonly CategoryInfo[] categoryList =
{
new(0, "special.all", () => Locs.Category_All),
new(10, "special.devInstalled", () => Locs.Category_DevInstalled),
new(11, "special.devIconTester", () => Locs.Category_IconTester),
new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames),
new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory),
new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound),
new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social),
new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility),
private readonly CategoryInfo[] categoryList =
{
new(0, "special.all", () => Locs.Category_All),
new(10, "special.devInstalled", () => Locs.Category_DevInstalled),
new(11, "special.devIconTester", () => Locs.Category_IconTester),
new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames),
new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory),
new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound),
new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social),
new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility),
// order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId
};
};
private GroupInfo[] groupList =
{
new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11),
new(GroupKind.Installed, () => Locs.Group_Installed, 0),
new(GroupKind.Available, () => Locs.Group_Available, 0),
private GroupInfo[] groupList =
{
new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11),
new(GroupKind.Installed, () => Locs.Group_Installed, 0),
new(GroupKind.Available, () => Locs.Group_Available, 0),
// order important, used for drawing, keep in sync with defaults for currentGroupIdx
};
};
private int currentGroupIdx = 2;
private int currentCategoryIdx = 0;
private bool isContentDirty;
private int currentGroupIdx = 2;
private int currentCategoryIdx = 0;
private bool isContentDirty;
private Dictionary<PluginManifest, int[]> mapPluginCategories = new();
private List<int> highlightedCategoryIds = new();
private Dictionary<PluginManifest, int[]> mapPluginCategories = new();
private List<int> highlightedCategoryIds = new();
/// <summary>
/// Type of category group.
/// </summary>
public enum GroupKind
{
/// <summary>
/// UI group: dev mode only.
/// </summary>
DevTools,
/// <summary>
/// Type of category group.
/// UI group: installed plugins.
/// </summary>
public enum GroupKind
Installed,
/// <summary>
/// UI group: plugins that can be installed.
/// </summary>
Available,
}
/// <summary>
/// Gets the list of all known categories.
/// </summary>
public CategoryInfo[] CategoryList => this.categoryList;
/// <summary>
/// Gets the list of all known UI groups.
/// </summary>
public GroupInfo[] GroupList => this.groupList;
/// <summary>
/// Gets or sets current group.
/// </summary>
public int CurrentGroupIdx
{
get => this.currentGroupIdx;
set
{
/// <summary>
/// UI group: dev mode only.
/// </summary>
DevTools,
/// <summary>
/// UI group: installed plugins.
/// </summary>
Installed,
/// <summary>
/// UI group: plugins that can be installed.
/// </summary>
Available,
}
/// <summary>
/// Gets the list of all known categories.
/// </summary>
public CategoryInfo[] CategoryList => this.categoryList;
/// <summary>
/// Gets the list of all known UI groups.
/// </summary>
public GroupInfo[] GroupList => this.groupList;
/// <summary>
/// Gets or sets current group.
/// </summary>
public int CurrentGroupIdx
{
get => this.currentGroupIdx;
set
{
if (this.currentGroupIdx != value)
{
this.currentGroupIdx = value;
this.currentCategoryIdx = 0;
this.isContentDirty = true;
}
}
}
/// <summary>
/// Gets or sets current category, index in current Group.Categories array.
/// </summary>
public int CurrentCategoryIdx
{
get => this.currentCategoryIdx;
set
{
if (this.currentCategoryIdx != value)
{
this.currentCategoryIdx = value;
this.isContentDirty = true;
}
}
}
/// <summary>
/// Gets a value indicating whether current group + category selection changed recently.
/// Changes in Available group should be followed with <see cref="GetCurrentCategoryContent"/>, everythine else can use <see cref="ResetContentDirty"/>.
/// </summary>
public bool IsContentDirty => this.isContentDirty;
/// <summary>
/// Gets a value indicating whether <see cref="CurrentCategoryIdx"/> and <see cref="CurrentGroupIdx"/> are valid.
/// </summary>
public bool IsSelectionValid =>
(this.currentGroupIdx >= 0) &&
(this.currentGroupIdx < this.groupList.Length) &&
(this.currentCategoryIdx >= 0) &&
(this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count);
/// <summary>
/// Rebuild available categories based on currently available plugins.
/// </summary>
/// <param name="availablePlugins">list of all available plugin manifests to install.</param>
public void BuildCategories(IEnumerable<PluginManifest> availablePlugins)
{
// rebuild map plugin name -> categoryIds
this.mapPluginCategories.Clear();
var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available);
var prevCategoryIds = new List<int>();
prevCategoryIds.AddRange(groupAvail.Categories);
var categoryList = new List<int>();
var allCategoryIndices = new List<int>();
foreach (var plugin in availablePlugins)
{
categoryList.Clear();
var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin);
if (pluginCategoryTags != null)
{
foreach (var tag in pluginCategoryTags)
{
// only tags from whitelist can be accepted
var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
if (matchIdx >= 0)
{
var categoryId = this.CategoryList[matchIdx].CategoryId;
if (categoryId >= FirstTagBasedCategoryId)
{
categoryList.Add(categoryId);
if (!allCategoryIndices.Contains(matchIdx))
{
allCategoryIndices.Add(matchIdx);
}
}
}
}
}
// always add, even if empty
this.mapPluginCategories.Add(plugin, categoryList.ToArray());
}
// sort all categories by their loc name
allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name));
// rebuild all categories in group, leaving first entry = All intact and always on top
if (groupAvail.Categories.Count > 1)
{
groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1);
}
foreach (var categoryIdx in allCategoryIndices)
{
groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId);
}
// compare with prev state and mark as dirty if needed
var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories);
if (!noCategoryChanges)
if (this.currentGroupIdx != value)
{
this.currentGroupIdx = value;
this.currentCategoryIdx = 0;
this.isContentDirty = true;
}
}
}
/// <summary>
/// Filters list of available plugins based on currently selected category.
/// Resets <see cref="IsContentDirty"/>.
/// </summary>
/// <param name="plugins">List of available plugins to install.</param>
/// <returns>Filtered list of plugins.</returns>
public List<PluginManifest> GetCurrentCategoryContent(IEnumerable<PluginManifest> plugins)
/// <summary>
/// Gets or sets current category, index in current Group.Categories array.
/// </summary>
public int CurrentCategoryIdx
{
get => this.currentCategoryIdx;
set
{
var result = new List<PluginManifest>();
if (this.IsSelectionValid)
if (this.currentCategoryIdx != value)
{
var groupInfo = this.groupList[this.currentGroupIdx];
var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available);
if (includeAll)
{
result.AddRange(plugins);
}
else
{
var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]);
foreach (var plugin in plugins)
{
if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds))
{
var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId);
if (matchIdx >= 0)
{
result.Add(plugin);
}
}
}
}
this.currentCategoryIdx = value;
this.isContentDirty = true;
}
this.ResetContentDirty();
return result;
}
/// <summary>
/// Clears <see cref="IsContentDirty"/> flag, indicating that all cached values about currently selected group + category have been updated.
/// </summary>
public void ResetContentDirty()
{
this.isContentDirty = false;
}
/// <summary>
/// Sets category highlight based on list of plugins. Used for searching.
/// </summary>
/// <param name="plugins">List of plugins whose categories should be highlighted.</param>
public void SetCategoryHighlightsForPlugins(IEnumerable<PluginManifest> plugins)
{
this.highlightedCategoryIds.Clear();
if (plugins != null)
{
foreach (var entry in plugins)
{
if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories))
{
foreach (var categoryId in pluginCategories)
{
if (!this.highlightedCategoryIds.Contains(categoryId))
{
this.highlightedCategoryIds.Add(categoryId);
}
}
}
}
}
}
/// <summary>
/// Checks if category should be highlighted.
/// </summary>
/// <param name="categoryId">CategoryId to check.</param>
/// <returns>true if highlight is needed.</returns>
public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId);
private IEnumerable<string> GetCategoryTagsForManifest(PluginManifest pluginManifest)
{
if (pluginManifest.CategoryTags != null)
{
return pluginManifest.CategoryTags;
}
return null;
}
/// <summary>
/// Plugin installer category info.
/// </summary>
public struct CategoryInfo
{
/// <summary>
/// Unique Id number of category, tag match based should be greater of equal <see cref="FirstTagBasedCategoryId"/>.
/// </summary>
public int CategoryId;
/// <summary>
/// Tag from plugin manifest to match.
/// </summary>
public string Tag;
private Func<string> nameFunc;
/// <summary>
/// Initializes a new instance of the <see cref="CategoryInfo"/> struct.
/// </summary>
/// <param name="categoryId">Unique id of category.</param>
/// <param name="tag">Tag to match.</param>
/// <param name="nameFunc">Function returning localized name of category.</param>
public CategoryInfo(int categoryId, string tag, Func<string> nameFunc)
{
this.CategoryId = categoryId;
this.Tag = tag;
this.nameFunc = nameFunc;
}
/// <summary>
/// Gets the name of category.
/// </summary>
public string Name => this.nameFunc();
}
/// <summary>
/// Plugin installer UI group, a container for categories.
/// </summary>
public struct GroupInfo
{
/// <summary>
/// Type of group.
/// </summary>
public GroupKind GroupKind;
/// <summary>
/// List of categories in container.
/// </summary>
public List<int> Categories;
private Func<string> nameFunc;
/// <summary>
/// Initializes a new instance of the <see cref="GroupInfo"/> struct.
/// </summary>
/// <param name="groupKind">Type of group.</param>
/// <param name="nameFunc">Function returning localized name of category.</param>
/// <param name="categories">List of category Ids to hardcode.</param>
public GroupInfo(GroupKind groupKind, Func<string> nameFunc, params int[] categories)
{
this.GroupKind = groupKind;
this.nameFunc = nameFunc;
this.Categories = new();
this.Categories.AddRange(categories);
}
/// <summary>
/// Gets the name of UI group.
/// </summary>
public string Name => this.nameFunc();
}
private static class Locs
{
#region UI groups
public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools");
public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins");
public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins");
#endregion
#region Categories
public static string Category_All => Loc.Localize("InstallerCategoryAll", "All");
public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
public static string Category_IconTester => "Image/Icon Tester";
public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");
public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI");
public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games");
public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory");
public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound");
public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social");
public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility");
#endregion
}
}
/// <summary>
/// Gets a value indicating whether current group + category selection changed recently.
/// Changes in Available group should be followed with <see cref="GetCurrentCategoryContent"/>, everythine else can use <see cref="ResetContentDirty"/>.
/// </summary>
public bool IsContentDirty => this.isContentDirty;
/// <summary>
/// Gets a value indicating whether <see cref="CurrentCategoryIdx"/> and <see cref="CurrentGroupIdx"/> are valid.
/// </summary>
public bool IsSelectionValid =>
(this.currentGroupIdx >= 0) &&
(this.currentGroupIdx < this.groupList.Length) &&
(this.currentCategoryIdx >= 0) &&
(this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count);
/// <summary>
/// Rebuild available categories based on currently available plugins.
/// </summary>
/// <param name="availablePlugins">list of all available plugin manifests to install.</param>
public void BuildCategories(IEnumerable<PluginManifest> availablePlugins)
{
// rebuild map plugin name -> categoryIds
this.mapPluginCategories.Clear();
var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available);
var prevCategoryIds = new List<int>();
prevCategoryIds.AddRange(groupAvail.Categories);
var categoryList = new List<int>();
var allCategoryIndices = new List<int>();
foreach (var plugin in availablePlugins)
{
categoryList.Clear();
var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin);
if (pluginCategoryTags != null)
{
foreach (var tag in pluginCategoryTags)
{
// only tags from whitelist can be accepted
var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
if (matchIdx >= 0)
{
var categoryId = this.CategoryList[matchIdx].CategoryId;
if (categoryId >= FirstTagBasedCategoryId)
{
categoryList.Add(categoryId);
if (!allCategoryIndices.Contains(matchIdx))
{
allCategoryIndices.Add(matchIdx);
}
}
}
}
}
// always add, even if empty
this.mapPluginCategories.Add(plugin, categoryList.ToArray());
}
// sort all categories by their loc name
allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name));
// rebuild all categories in group, leaving first entry = All intact and always on top
if (groupAvail.Categories.Count > 1)
{
groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1);
}
foreach (var categoryIdx in allCategoryIndices)
{
groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId);
}
// compare with prev state and mark as dirty if needed
var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories);
if (!noCategoryChanges)
{
this.isContentDirty = true;
}
}
/// <summary>
/// Filters list of available plugins based on currently selected category.
/// Resets <see cref="IsContentDirty"/>.
/// </summary>
/// <param name="plugins">List of available plugins to install.</param>
/// <returns>Filtered list of plugins.</returns>
public List<PluginManifest> GetCurrentCategoryContent(IEnumerable<PluginManifest> plugins)
{
var result = new List<PluginManifest>();
if (this.IsSelectionValid)
{
var groupInfo = this.groupList[this.currentGroupIdx];
var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available);
if (includeAll)
{
result.AddRange(plugins);
}
else
{
var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]);
foreach (var plugin in plugins)
{
if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds))
{
var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId);
if (matchIdx >= 0)
{
result.Add(plugin);
}
}
}
}
}
this.ResetContentDirty();
return result;
}
/// <summary>
/// Clears <see cref="IsContentDirty"/> flag, indicating that all cached values about currently selected group + category have been updated.
/// </summary>
public void ResetContentDirty()
{
this.isContentDirty = false;
}
/// <summary>
/// Sets category highlight based on list of plugins. Used for searching.
/// </summary>
/// <param name="plugins">List of plugins whose categories should be highlighted.</param>
public void SetCategoryHighlightsForPlugins(IEnumerable<PluginManifest> plugins)
{
this.highlightedCategoryIds.Clear();
if (plugins != null)
{
foreach (var entry in plugins)
{
if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories))
{
foreach (var categoryId in pluginCategories)
{
if (!this.highlightedCategoryIds.Contains(categoryId))
{
this.highlightedCategoryIds.Add(categoryId);
}
}
}
}
}
}
/// <summary>
/// Checks if category should be highlighted.
/// </summary>
/// <param name="categoryId">CategoryId to check.</param>
/// <returns>true if highlight is needed.</returns>
public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId);
private IEnumerable<string> GetCategoryTagsForManifest(PluginManifest pluginManifest)
{
if (pluginManifest.CategoryTags != null)
{
return pluginManifest.CategoryTags;
}
return null;
}
/// <summary>
/// Plugin installer category info.
/// </summary>
public struct CategoryInfo
{
/// <summary>
/// Unique Id number of category, tag match based should be greater of equal <see cref="FirstTagBasedCategoryId"/>.
/// </summary>
public int CategoryId;
/// <summary>
/// Tag from plugin manifest to match.
/// </summary>
public string Tag;
private Func<string> nameFunc;
/// <summary>
/// Initializes a new instance of the <see cref="CategoryInfo"/> struct.
/// </summary>
/// <param name="categoryId">Unique id of category.</param>
/// <param name="tag">Tag to match.</param>
/// <param name="nameFunc">Function returning localized name of category.</param>
public CategoryInfo(int categoryId, string tag, Func<string> nameFunc)
{
this.CategoryId = categoryId;
this.Tag = tag;
this.nameFunc = nameFunc;
}
/// <summary>
/// Gets the name of category.
/// </summary>
public string Name => this.nameFunc();
}
/// <summary>
/// Plugin installer UI group, a container for categories.
/// </summary>
public struct GroupInfo
{
/// <summary>
/// Type of group.
/// </summary>
public GroupKind GroupKind;
/// <summary>
/// List of categories in container.
/// </summary>
public List<int> Categories;
private Func<string> nameFunc;
/// <summary>
/// Initializes a new instance of the <see cref="GroupInfo"/> struct.
/// </summary>
/// <param name="groupKind">Type of group.</param>
/// <param name="nameFunc">Function returning localized name of category.</param>
/// <param name="categories">List of category Ids to hardcode.</param>
public GroupInfo(GroupKind groupKind, Func<string> nameFunc, params int[] categories)
{
this.GroupKind = groupKind;
this.nameFunc = nameFunc;
this.Categories = new();
this.Categories.AddRange(categories);
}
/// <summary>
/// Gets the name of UI group.
/// </summary>
public string Name => this.nameFunc();
}
private static class Locs
{
#region UI groups
public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools");
public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins");
public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins");
#endregion
#region Categories
public static string Category_All => Loc.Localize("InstallerCategoryAll", "All");
public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
public static string Category_IconTester => "Image/Icon Tester";
public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");
public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI");
public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games");
public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory");
public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound");
public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social");
public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility");
#endregion
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,20 +6,20 @@ using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// For major updates, an in-game Changelog window.
/// </summary>
internal sealed class ChangelogWindow : Window
{
/// <summary>
/// For major updates, an in-game Changelog window.
/// Whether the latest update warrants a changelog window.
/// </summary>
internal sealed class ChangelogWindow : Window
{
/// <summary>
/// Whether the latest update warrants a changelog window.
/// </summary>
public const string WarrantsChangelogForMajorMinor = "6.0.";
public const string WarrantsChangelogForMajorMinor = "6.0.";
private const string ChangeLog =
@"This is the biggest update of the in-game addon to date.
private const string ChangeLog =
@"This is the biggest update of the in-game addon to date.
We have redone most of the underlying systems, providing you with a better experience playing and browsing for plugins, including better performance, and developers with a better API and more comfortable development environment.
We have also added some new features:
@ -29,100 +29,99 @@ We have also added some new features:
If you note any issues or need help, please make sure to ask on our discord server.";
private const string UpdatePluginsInfo =
@"• All of your plugins were disabled automatically, due to this update. This is normal.
private const string UpdatePluginsInfo =
@"• All of your plugins were disabled automatically, due to this update. This is normal.
Open the plugin installer, then click 'update plugins'. Updated plugins should update and then re-enable themselves.
=> Please keep in mind that not all of your plugins may already be updated for the new version.
=> If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available.";
private readonly string assemblyVersion = Util.AssemblyVersion;
private readonly string assemblyVersion = Util.AssemblyVersion;
/// <summary>
/// Initializes a new instance of the <see cref="ChangelogWindow"/> class.
/// </summary>
public ChangelogWindow()
: base("What's new in XIVLauncher?")
/// <summary>
/// Initializes a new instance of the <see cref="ChangelogWindow"/> class.
/// </summary>
public ChangelogWindow()
: base("What's new in XIVLauncher?")
{
this.Namespace = "DalamudChangelogWindow";
this.Size = new Vector2(885, 463);
this.SizeCondition = ImGuiCond.Appearing;
}
/// <inheritdoc/>
public override void Draw()
{
ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}.");
ImGuiHelpers.ScaledDummy(10);
ImGui.Text("The following changes were introduced:");
ImGui.TextWrapped(ChangeLog);
ImGuiHelpers.ScaledDummy(5);
ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!");
ImGui.TextWrapped(UpdatePluginsInfo);
ImGuiHelpers.ScaledDummy(10);
ImGui.Text("Thank you for using our tools!");
ImGuiHelpers.ScaledDummy(10);
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Download.ToIconString()))
{
this.Namespace = "DalamudChangelogWindow";
this.Size = new Vector2(885, 463);
this.SizeCondition = ImGuiCond.Appearing;
Service<DalamudInterface>.Get().OpenPluginInstaller();
}
/// <inheritdoc/>
public override void Draw()
if (ImGui.IsItemHovered())
{
ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}.");
ImGuiHelpers.ScaledDummy(10);
ImGui.Text("The following changes were introduced:");
ImGui.TextWrapped(ChangeLog);
ImGuiHelpers.ScaledDummy(5);
ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!");
ImGui.TextWrapped(UpdatePluginsInfo);
ImGuiHelpers.ScaledDummy(10);
ImGui.Text("Thank you for using our tools!");
ImGuiHelpers.ScaledDummy(10);
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Download.ToIconString()))
{
Service<DalamudInterface>.Get().OpenPluginInstaller();
}
if (ImGui.IsItemHovered())
{
ImGui.PopFont();
ImGui.SetTooltip("Open Plugin Installer");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString()))
{
Process.Start("https://discord.gg/3NMcUV5");
}
if (ImGui.IsItemHovered())
{
ImGui.PopFont();
ImGui.SetTooltip("Join our Discord server");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString()))
{
Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher");
}
if (ImGui.IsItemHovered())
{
ImGui.PopFont();
ImGui.SetTooltip("See our GitHub repository");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.PopFont();
ImGui.SetTooltip("Open Plugin Installer");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(20, 0);
ImGui.SameLine();
ImGui.SameLine();
if (ImGui.Button("Close"))
{
this.IsOpen = false;
}
if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString()))
{
Process.Start("https://discord.gg/3NMcUV5");
}
if (ImGui.IsItemHovered())
{
ImGui.PopFont();
ImGui.SetTooltip("Join our Discord server");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString()))
{
Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher");
}
if (ImGui.IsItemHovered())
{
ImGui.PopFont();
ImGui.SetTooltip("See our GitHub repository");
ImGui.PushFont(UiBuilder.IconFont);
}
ImGui.PopFont();
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(20, 0);
ImGui.SameLine();
if (ImGui.Button("Close"))
{
this.IsOpen = false;
}
}
}

View file

@ -7,25 +7,25 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// Color Demo Window to view custom ImGui colors.
/// </summary>
internal sealed class ColorDemoWindow : Window
{
private readonly List<(string Name, Vector4 Color)> colors;
/// <summary>
/// Color Demo Window to view custom ImGui colors.
/// Initializes a new instance of the <see cref="ColorDemoWindow"/> class.
/// </summary>
internal sealed class ColorDemoWindow : Window
public ColorDemoWindow()
: base("Dalamud Colors Demo")
{
private readonly List<(string Name, Vector4 Color)> colors;
this.Size = new Vector2(600, 500);
this.SizeCondition = ImGuiCond.FirstUseEver;
/// <summary>
/// Initializes a new instance of the <see cref="ColorDemoWindow"/> class.
/// </summary>
public ColorDemoWindow()
: base("Dalamud Colors Demo")
{
this.Size = new Vector2(600, 500);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.colors = new List<(string Name, Vector4 Color)>()
this.colors = new List<(string Name, Vector4 Color)>()
{
("DalamudRed", ImGuiColors.DalamudRed),
("DalamudGrey", ImGuiColors.DalamudGrey),
@ -47,20 +47,19 @@ namespace Dalamud.Interface.Internal.Windows
("ParsedPink", ImGuiColors.ParsedPink),
("ParsedGold", ImGuiColors.ParsedGold),
}.OrderBy(colorDemo => colorDemo.Name).ToList();
}
}
/// <inheritdoc/>
public override void Draw()
/// <inheritdoc/>
public override void Draw()
{
ImGui.Text("This is a collection of UI colors you can use in your plugin.");
ImGui.Separator();
foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
ImGui.Text("This is a collection of UI colors you can use in your plugin.");
ImGui.Separator();
foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
var color = (Vector4)property.GetValue(null);
ImGui.TextColored(color, property.Name);
}
var color = (Vector4)property.GetValue(null);
ImGui.TextColored(color, property.Name);
}
}
}

View file

@ -9,154 +9,153 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
{
/// <summary>
/// Component Demo Window to view custom ImGui components.
/// </summary>
internal sealed class ComponentDemoWindow : Window
{
private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700);
namespace Dalamud.Interface.Internal.Windows;
private readonly List<(string Name, Action Demo)> componentDemos;
private readonly IReadOnlyList<Easing> easings = new Easing[]
{
/// <summary>
/// Component Demo Window to view custom ImGui components.
/// </summary>
internal sealed class ComponentDemoWindow : Window
{
private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700);
private readonly List<(string Name, Action Demo)> componentDemos;
private readonly IReadOnlyList<Easing> easings = new Easing[]
{
new InSine(DefaultEasingTime), new OutSine(DefaultEasingTime), new InOutSine(DefaultEasingTime),
new InCubic(DefaultEasingTime), new OutCubic(DefaultEasingTime), new InOutCubic(DefaultEasingTime),
new InQuint(DefaultEasingTime), new OutQuint(DefaultEasingTime), new InOutQuint(DefaultEasingTime),
new InCirc(DefaultEasingTime), new OutCirc(DefaultEasingTime), new InOutCirc(DefaultEasingTime),
new InElastic(DefaultEasingTime), new OutElastic(DefaultEasingTime), new InOutElastic(DefaultEasingTime),
};
};
private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds;
private Vector4 defaultColor = ImGuiColors.DalamudOrange;
private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds;
private Vector4 defaultColor = ImGuiColors.DalamudOrange;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentDemoWindow"/> class.
/// </summary>
public ComponentDemoWindow()
: base("Dalamud Components Demo")
/// <summary>
/// Initializes a new instance of the <see cref="ComponentDemoWindow"/> class.
/// </summary>
public ComponentDemoWindow()
: base("Dalamud Components Demo")
{
this.Size = new Vector2(600, 500);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.RespectCloseHotkey = false;
this.componentDemos = new()
{
this.Size = new Vector2(600, 500);
this.SizeCondition = ImGuiCond.FirstUseEver;
("Test", ImGuiComponents.Test),
("HelpMarker", HelpMarkerDemo),
("IconButton", IconButtonDemo),
("TextWithLabel", TextWithLabelDemo),
("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo),
};
}
this.RespectCloseHotkey = false;
/// <inheritdoc/>
public override void OnOpen()
{
foreach (var easing in this.easings)
{
easing.Restart();
}
}
this.componentDemos = new()
/// <inheritdoc/>
public override void OnClose()
{
foreach (var easing in this.easings)
{
easing.Stop();
}
}
/// <inheritdoc/>
public override void Draw()
{
ImGui.Text("This is a collection of UI components you can use in your plugin.");
for (var i = 0; i < this.componentDemos.Count; i++)
{
var componentDemo = this.componentDemos[i];
if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}"))
{
("Test", ImGuiComponents.Test),
("HelpMarker", HelpMarkerDemo),
("IconButton", IconButtonDemo),
("TextWithLabel", TextWithLabelDemo),
("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo),
};
componentDemo.Demo();
}
}
/// <inheritdoc/>
public override void OnOpen()
if (ImGui.CollapsingHeader("Easing animations"))
{
foreach (var easing in this.easings)
this.EasingsDemo();
}
}
private static void HelpMarkerDemo()
{
ImGui.Text("Hover over the icon to learn more.");
ImGuiComponents.HelpMarker("help me!");
}
private static void IconButtonDemo()
{
ImGui.Text("Click on the icon to use as a button.");
ImGui.SameLine();
if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot))
{
ImGui.OpenPopup("IconButtonDemoPopup");
}
if (ImGui.BeginPopup("IconButtonDemoPopup"))
{
ImGui.Text("You clicked!");
ImGui.EndPopup();
}
}
private static void TextWithLabelDemo()
{
ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more");
}
private void EasingsDemo()
{
ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000);
foreach (var easing in this.easings)
{
easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs);
if (!easing.IsRunning)
{
easing.Start();
}
var cursor = ImGui.GetCursorPos();
var p1 = new Vector2(cursor.X + 5, cursor.Y);
var p2 = p1 + new Vector2(45, 0);
easing.Point1 = p1;
easing.Point2 = p2;
easing.Update();
if (easing.IsDone)
{
easing.Restart();
}
}
/// <inheritdoc/>
public override void OnClose()
{
foreach (var easing in this.easings)
{
easing.Stop();
}
}
ImGui.SetCursorPos(easing.EasedPoint);
ImGui.Bullet();
/// <inheritdoc/>
public override void Draw()
{
ImGui.Text("This is a collection of UI components you can use in your plugin.");
for (var i = 0; i < this.componentDemos.Count; i++)
{
var componentDemo = this.componentDemos[i];
if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}"))
{
componentDemo.Demo();
}
}
if (ImGui.CollapsingHeader("Easing animations"))
{
this.EasingsDemo();
}
}
private static void HelpMarkerDemo()
{
ImGui.Text("Hover over the icon to learn more.");
ImGuiComponents.HelpMarker("help me!");
}
private static void IconButtonDemo()
{
ImGui.Text("Click on the icon to use as a button.");
ImGui.SameLine();
if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot))
{
ImGui.OpenPopup("IconButtonDemoPopup");
}
if (ImGui.BeginPopup("IconButtonDemoPopup"))
{
ImGui.Text("You clicked!");
ImGui.EndPopup();
}
}
private static void TextWithLabelDemo()
{
ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more");
}
private void EasingsDemo()
{
ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000);
foreach (var easing in this.easings)
{
easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs);
if (!easing.IsRunning)
{
easing.Start();
}
var cursor = ImGui.GetCursorPos();
var p1 = new Vector2(cursor.X + 5, cursor.Y);
var p2 = p1 + new Vector2(45, 0);
easing.Point1 = p1;
easing.Point2 = p2;
easing.Update();
if (easing.IsDone)
{
easing.Restart();
}
ImGui.SetCursorPos(easing.EasedPoint);
ImGui.Bullet();
ImGui.SetCursorPos(cursor + new Vector2(0, 10));
ImGui.Text($"{easing.GetType().Name} ({easing.Value})");
ImGuiHelpers.ScaledDummy(5);
}
}
private void ColorPickerWithPaletteDemo()
{
ImGui.Text("Click on the color button to use the picker.");
ImGui.SameLine();
this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor);
ImGui.SetCursorPos(cursor + new Vector2(0, 10));
ImGui.Text($"{easing.GetType().Name} ({easing.Value})");
ImGuiHelpers.ScaledDummy(5);
}
}
private void ColorPickerWithPaletteDemo()
{
ImGui.Text("Click on the color button to use the picker.");
ImGui.SameLine();
this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor);
}
}

View file

@ -16,488 +16,487 @@ using ImGuiNET;
using Serilog;
using Serilog.Events;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// The window that displays the Dalamud log file in-game.
/// </summary>
internal class ConsoleWindow : Window, IDisposable
{
private readonly List<LogEntry> logText = new();
private readonly object renderLock = new();
private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" };
private List<LogEntry> filteredLogText = new();
private bool autoScroll;
private bool openAtStartup;
private bool? lastCmdSuccess;
private string commandText = string.Empty;
private string textFilter = string.Empty;
private LogEventLevel? levelFilter = null;
private bool isFiltered = false;
private int historyPos;
private List<string> history = new();
/// <summary>
/// The window that displays the Dalamud log file in-game.
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
/// </summary>
internal class ConsoleWindow : Window, IDisposable
public ConsoleWindow()
: base("Dalamud Console")
{
private readonly List<LogEntry> logText = new();
private readonly object renderLock = new();
var configuration = Service<DalamudConfiguration>.Get();
private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" };
this.autoScroll = configuration.LogAutoScroll;
this.openAtStartup = configuration.LogOpenAtStartup;
SerilogEventSink.Instance.LogLine += this.OnLogLine;
private List<LogEntry> filteredLogText = new();
private bool autoScroll;
private bool openAtStartup;
this.Size = new Vector2(500, 400);
this.SizeCondition = ImGuiCond.FirstUseEver;
private bool? lastCmdSuccess;
this.RespectCloseHotkey = false;
}
private string commandText = string.Empty;
private List<LogEntry> LogEntries => this.isFiltered ? this.filteredLogText : this.logText;
private string textFilter = string.Empty;
private LogEventLevel? levelFilter = null;
private bool isFiltered = false;
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
SerilogEventSink.Instance.LogLine -= this.OnLogLine;
}
private int historyPos;
private List<string> history = new();
/// <summary>
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
/// </summary>
public ConsoleWindow()
: base("Dalamud Console")
/// <summary>
/// Clear the window of all log entries.
/// </summary>
public void Clear()
{
lock (this.renderLock)
{
var configuration = Service<DalamudConfiguration>.Get();
this.autoScroll = configuration.LogAutoScroll;
this.openAtStartup = configuration.LogOpenAtStartup;
SerilogEventSink.Instance.LogLine += this.OnLogLine;
this.Size = new Vector2(500, 400);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.RespectCloseHotkey = false;
this.logText.Clear();
this.filteredLogText.Clear();
}
}
private List<LogEntry> LogEntries => this.isFiltered ? this.filteredLogText : this.logText;
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
/// <summary>
/// Add a single log line to the display.
/// </summary>
/// <param name="line">The line to add.</param>
/// <param name="level">The level of the event.</param>
/// <param name="offset">The <see cref="DateTimeOffset"/> of the event.</param>
public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset)
{
if (line.IndexOfAny(new[] { '\n', '\r' }) != -1)
{
SerilogEventSink.Instance.LogLine -= this.OnLogLine;
}
var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
/// <summary>
/// Clear the window of all log entries.
/// </summary>
public void Clear()
{
lock (this.renderLock)
this.AddAndFilter(subLines[0], level, offset, false);
for (var i = 1; i < subLines.Length; i++)
{
this.logText.Clear();
this.filteredLogText.Clear();
this.AddAndFilter(subLines[i], level, offset, true);
}
}
/// <summary>
/// Add a single log line to the display.
/// </summary>
/// <param name="line">The line to add.</param>
/// <param name="level">The level of the event.</param>
/// <param name="offset">The <see cref="DateTimeOffset"/> of the event.</param>
public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset)
else
{
if (line.IndexOfAny(new[] { '\n', '\r' }) != -1)
this.AddAndFilter(line, level, offset, false);
}
}
/// <inheritdoc/>
public override void Draw()
{
// Options menu
if (ImGui.BeginPopup("Options"))
{
var dalamud = Service<Dalamud>.Get();
var configuration = Service<DalamudConfiguration>.Get();
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
{
var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
configuration.LogAutoScroll = this.autoScroll;
configuration.Save();
}
this.AddAndFilter(subLines[0], level, offset, false);
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
{
configuration.LogOpenAtStartup = this.openAtStartup;
configuration.Save();
}
for (var i = 1; i < subLines.Length; i++)
var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel;
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
{
dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
configuration.LogLevel = (LogEventLevel)prevLevel;
configuration.Save();
}
ImGui.EndPopup();
}
// Filter menu
if (ImGui.BeginPopup("Filters"))
{
ImGui.Checkbox("Enabled", ref this.isFiltered);
if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue))
{
this.Refilter();
}
ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm.");
var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0;
if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7))
{
this.levelFilter = (LogEventLevel)(filterVal - 1);
this.Refilter();
}
ImGui.EndPopup();
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
ImGui.OpenPopup("Options");
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Options");
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
ImGui.OpenPopup("Filters");
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Filters");
ImGui.SameLine();
var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Clear Log");
ImGui.SameLine();
var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy Log");
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull))
Process.GetCurrentProcess().Kill();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Kill game");
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
if (clear)
{
this.Clear();
}
if (copy)
{
ImGui.LogToClipboard();
}
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGuiListClipperPtr clipper;
unsafe
{
clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
}
ImGui.PushFont(InterfaceManager.MonoFont);
var childPos = ImGui.GetWindowPos();
var childDrawList = ImGui.GetWindowDrawList();
var childSize = ImGui.GetWindowSize();
var cursorDiv = ImGuiHelpers.GlobalScale * 92;
var cursorLogLevel = ImGuiHelpers.GlobalScale * 100;
var cursorLogLine = ImGuiHelpers.GlobalScale * 135;
lock (this.renderLock)
{
clipper.Begin(this.LogEntries.Count);
while (clipper.Step())
{
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
this.AddAndFilter(subLines[i], level, offset, true);
var line = this.LogEntries[i];
if (!line.IsMultiline)
ImGui.Separator();
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
ImGui.SameLine();
ImGui.PopStyleColor(3);
if (!line.IsMultiline)
{
ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff"));
ImGui.SameLine();
ImGui.SetCursorPosX(cursorDiv);
ImGui.TextUnformatted("|");
ImGui.SameLine();
ImGui.SetCursorPosX(cursorLogLevel);
ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level));
ImGui.SameLine();
}
ImGui.SetCursorPosX(cursorLogLine);
ImGui.TextUnformatted(line.Line);
}
}
clipper.End();
}
ImGui.PopFont();
ImGui.PopStyleVar();
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
{
ImGui.SetScrollHereY(1.0f);
}
// Draw dividing line
var offset = ImGuiHelpers.GlobalScale * 127;
childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f);
ImGui.EndChild();
var hadColor = false;
if (this.lastCmdSuccess.HasValue)
{
hadColor = true;
if (this.lastCmdSuccess.Value)
{
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f));
}
else
{
this.AddAndFilter(line, level, offset, false);
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f));
}
}
/// <inheritdoc/>
public override void Draw()
ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80);
var getFocus = false;
unsafe
{
// Options menu
if (ImGui.BeginPopup("Options"))
{
var dalamud = Service<Dalamud>.Get();
var configuration = Service<DalamudConfiguration>.Get();
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
{
configuration.LogAutoScroll = this.autoScroll;
configuration.Save();
}
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
{
configuration.LogOpenAtStartup = this.openAtStartup;
configuration.Save();
}
var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel;
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
{
dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
configuration.LogLevel = (LogEventLevel)prevLevel;
configuration.Save();
}
ImGui.EndPopup();
}
// Filter menu
if (ImGui.BeginPopup("Filters"))
{
ImGui.Checkbox("Enabled", ref this.isFiltered);
if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue))
{
this.Refilter();
}
ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm.");
var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0;
if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7))
{
this.levelFilter = (LogEventLevel)(filterVal - 1);
this.Refilter();
}
ImGui.EndPopup();
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
ImGui.OpenPopup("Options");
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Options");
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
ImGui.OpenPopup("Filters");
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Filters");
ImGui.SameLine();
var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Clear Log");
ImGui.SameLine();
var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy Log");
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull))
Process.GetCurrentProcess().Kill();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Kill game");
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
if (clear)
{
this.Clear();
}
if (copy)
{
ImGui.LogToClipboard();
}
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGuiListClipperPtr clipper;
unsafe
{
clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
}
ImGui.PushFont(InterfaceManager.MonoFont);
var childPos = ImGui.GetWindowPos();
var childDrawList = ImGui.GetWindowDrawList();
var childSize = ImGui.GetWindowSize();
var cursorDiv = ImGuiHelpers.GlobalScale * 92;
var cursorLogLevel = ImGuiHelpers.GlobalScale * 100;
var cursorLogLine = ImGuiHelpers.GlobalScale * 135;
lock (this.renderLock)
{
clipper.Begin(this.LogEntries.Count);
while (clipper.Step())
{
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
var line = this.LogEntries[i];
if (!line.IsMultiline)
ImGui.Separator();
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
ImGui.SameLine();
ImGui.PopStyleColor(3);
if (!line.IsMultiline)
{
ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff"));
ImGui.SameLine();
ImGui.SetCursorPosX(cursorDiv);
ImGui.TextUnformatted("|");
ImGui.SameLine();
ImGui.SetCursorPosX(cursorLogLevel);
ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level));
ImGui.SameLine();
}
ImGui.SetCursorPosX(cursorLogLine);
ImGui.TextUnformatted(line.Line);
}
}
clipper.End();
}
ImGui.PopFont();
ImGui.PopStyleVar();
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
{
ImGui.SetScrollHereY(1.0f);
}
// Draw dividing line
var offset = ImGuiHelpers.GlobalScale * 127;
childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f);
ImGui.EndChild();
var hadColor = false;
if (this.lastCmdSuccess.HasValue)
{
hadColor = true;
if (this.lastCmdSuccess.Value)
{
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f));
}
else
{
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f));
}
}
ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80);
var getFocus = false;
unsafe
{
if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
{
this.ProcessCommand();
getFocus = true;
}
ImGui.SameLine();
}
ImGui.SetItemDefaultFocus();
if (getFocus)
ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget
if (hadColor)
ImGui.PopStyleColor();
if (ImGui.Button("Send"))
if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
{
this.ProcessCommand();
}
}
private void ProcessCommand()
{
try
{
this.historyPos = -1;
for (var i = this.history.Count - 1; i >= 0; i--)
{
if (this.history[i] == this.commandText)
{
this.history.RemoveAt(i);
break;
}
}
this.history.Add(this.commandText);
if (this.commandText == "clear" || this.commandText == "cls")
{
this.Clear();
return;
}
this.lastCmdSuccess = Service<CommandManager>.Get().ProcessCommand("/" + this.commandText);
this.commandText = string.Empty;
// TODO: Force scroll to bottom
}
catch (Exception ex)
{
Log.Error(ex, "Error during command dispatch");
this.lastCmdSuccess = false;
}
}
private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)
{
var ptr = new ImGuiInputTextCallbackDataPtr(data);
switch (data->EventFlag)
{
case ImGuiInputTextFlags.CallbackCompletion:
var textBytes = new byte[data->BufTextLen];
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
var text = Encoding.UTF8.GetString(textBytes);
var words = text.Split();
// We can't do any completion for parameters at the moment since it just calls into CommandHandler
if (words.Length > 1)
return 0;
// TODO: Improve this, add partial completion
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
var candidates = Service<CommandManager>.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList();
if (candidates.Count > 0)
{
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty));
}
break;
case ImGuiInputTextFlags.CallbackHistory:
var prevPos = this.historyPos;
if (ptr.EventKey == ImGuiKey.UpArrow)
{
if (this.historyPos == -1)
this.historyPos = this.history.Count - 1;
else if (this.historyPos > 0)
this.historyPos--;
}
else if (data->EventKey == ImGuiKey.DownArrow)
{
if (this.historyPos != -1)
{
if (++this.historyPos >= this.history.Count)
{
this.historyPos = -1;
}
}
}
if (prevPos != this.historyPos)
{
var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty;
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, historyStr);
}
break;
getFocus = true;
}
return 0;
ImGui.SameLine();
}
private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline)
ImGui.SetItemDefaultFocus();
if (getFocus)
ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget
if (hadColor)
ImGui.PopStyleColor();
if (ImGui.Button("Send"))
{
if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:"))
return;
var entry = new LogEntry
{
IsMultiline = isMultiline,
Level = level,
Line = line,
TimeStamp = offset,
};
this.logText.Add(entry);
if (!this.isFiltered)
return;
if (this.IsFilterApplicable(entry))
this.filteredLogText.Add(entry);
}
private bool IsFilterApplicable(LogEntry entry)
{
if (this.levelFilter.HasValue)
{
return entry.Level == this.levelFilter.Value;
}
if (!string.IsNullOrEmpty(this.textFilter))
return entry.Line.Contains(this.textFilter);
return true;
}
private void Refilter()
{
lock (this.renderLock)
{
this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList();
}
}
private string GetTextForLogEventLevel(LogEventLevel level) => level switch
{
LogEventLevel.Error => "ERR",
LogEventLevel.Verbose => "VRB",
LogEventLevel.Debug => "DBG",
LogEventLevel.Information => "INF",
LogEventLevel.Warning => "WRN",
LogEventLevel.Fatal => "FTL",
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
};
private uint GetColorForLogEventLevel(LogEventLevel level) => level switch
{
LogEventLevel.Error => 0x800000EE,
LogEventLevel.Verbose => 0x00000000,
LogEventLevel.Debug => 0x00000000,
LogEventLevel.Information => 0x00000000,
LogEventLevel.Warning => 0x8A0070EE,
LogEventLevel.Fatal => 0xFF00000A,
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
};
private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent)
{
this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset);
}
private class LogEntry
{
public string Line { get; set; }
public LogEventLevel Level { get; set; }
public DateTimeOffset TimeStamp { get; set; }
public bool IsMultiline { get; set; }
this.ProcessCommand();
}
}
private void ProcessCommand()
{
try
{
this.historyPos = -1;
for (var i = this.history.Count - 1; i >= 0; i--)
{
if (this.history[i] == this.commandText)
{
this.history.RemoveAt(i);
break;
}
}
this.history.Add(this.commandText);
if (this.commandText == "clear" || this.commandText == "cls")
{
this.Clear();
return;
}
this.lastCmdSuccess = Service<CommandManager>.Get().ProcessCommand("/" + this.commandText);
this.commandText = string.Empty;
// TODO: Force scroll to bottom
}
catch (Exception ex)
{
Log.Error(ex, "Error during command dispatch");
this.lastCmdSuccess = false;
}
}
private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)
{
var ptr = new ImGuiInputTextCallbackDataPtr(data);
switch (data->EventFlag)
{
case ImGuiInputTextFlags.CallbackCompletion:
var textBytes = new byte[data->BufTextLen];
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
var text = Encoding.UTF8.GetString(textBytes);
var words = text.Split();
// We can't do any completion for parameters at the moment since it just calls into CommandHandler
if (words.Length > 1)
return 0;
// TODO: Improve this, add partial completion
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
var candidates = Service<CommandManager>.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList();
if (candidates.Count > 0)
{
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty));
}
break;
case ImGuiInputTextFlags.CallbackHistory:
var prevPos = this.historyPos;
if (ptr.EventKey == ImGuiKey.UpArrow)
{
if (this.historyPos == -1)
this.historyPos = this.history.Count - 1;
else if (this.historyPos > 0)
this.historyPos--;
}
else if (data->EventKey == ImGuiKey.DownArrow)
{
if (this.historyPos != -1)
{
if (++this.historyPos >= this.history.Count)
{
this.historyPos = -1;
}
}
}
if (prevPos != this.historyPos)
{
var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty;
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, historyStr);
}
break;
}
return 0;
}
private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline)
{
if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:"))
return;
var entry = new LogEntry
{
IsMultiline = isMultiline,
Level = level,
Line = line,
TimeStamp = offset,
};
this.logText.Add(entry);
if (!this.isFiltered)
return;
if (this.IsFilterApplicable(entry))
this.filteredLogText.Add(entry);
}
private bool IsFilterApplicable(LogEntry entry)
{
if (this.levelFilter.HasValue)
{
return entry.Level == this.levelFilter.Value;
}
if (!string.IsNullOrEmpty(this.textFilter))
return entry.Line.Contains(this.textFilter);
return true;
}
private void Refilter()
{
lock (this.renderLock)
{
this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList();
}
}
private string GetTextForLogEventLevel(LogEventLevel level) => level switch
{
LogEventLevel.Error => "ERR",
LogEventLevel.Verbose => "VRB",
LogEventLevel.Debug => "DBG",
LogEventLevel.Information => "INF",
LogEventLevel.Warning => "WRN",
LogEventLevel.Fatal => "FTL",
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
};
private uint GetColorForLogEventLevel(LogEventLevel level) => level switch
{
LogEventLevel.Error => 0x800000EE,
LogEventLevel.Verbose => 0x00000000,
LogEventLevel.Debug => 0x00000000,
LogEventLevel.Information => 0x00000000,
LogEventLevel.Warning => 0x8A0070EE,
LogEventLevel.Fatal => 0xFF00000A,
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
};
private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent)
{
this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset);
}
private class LogEntry
{
public string Line { get; set; }
public LogEventLevel Level { get; set; }
public DateTimeOffset TimeStamp { get; set; }
public bool IsMultiline { get; set; }
}
}

View file

@ -11,15 +11,15 @@ using Dalamud.Plugin.Internal;
using ImGuiNET;
using ImGuiScene;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// A window documenting contributors to the project.
/// </summary>
internal class CreditsWindow : Window, IDisposable
{
/// <summary>
/// A window documenting contributors to the project.
/// </summary>
internal class CreditsWindow : Window, IDisposable
{
private const float CreditFPS = 60.0f;
private const string CreditsTextTempl = @"
private const float CreditFPS = 60.0f;
private const string CreditsTextTempl = @"
Dalamud
A FFXIV Plugin Framework
Version D{0}
@ -122,121 +122,120 @@ Contribute at: https://github.com/goatsoft/Dalamud
Thank you for using XIVLauncher and Dalamud!
";
private readonly TextureWrap logoTexture;
private readonly Stopwatch creditsThrottler;
private readonly TextureWrap logoTexture;
private readonly Stopwatch creditsThrottler;
private string creditsText;
private string creditsText;
/// <summary>
/// Initializes a new instance of the <see cref="CreditsWindow"/> class.
/// </summary>
public CreditsWindow()
: base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true)
/// <summary>
/// Initializes a new instance of the <see cref="CreditsWindow"/> class.
/// </summary>
public CreditsWindow()
: base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true)
{
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"));
this.creditsThrottler = new();
this.Size = new Vector2(500, 400);
this.SizeCondition = ImGuiCond.Always;
this.PositionCondition = ImGuiCond.Always;
this.BgAlpha = 0.8f;
}
/// <inheritdoc/>
public override void OnOpen()
{
var pluginCredits = Service<PluginManager>.Get().InstalledPlugins
.Where(plugin => plugin.Manifest != null)
.Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n")
.Aggregate(string.Empty, (current, next) => $"{current}{next}");
this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits);
Service<GameGui>.Get().SetBgm(132);
this.creditsThrottler.Restart();
}
/// <inheritdoc/>
public override void OnClose()
{
this.creditsThrottler.Reset();
Service<GameGui>.Get().SetBgm(9999);
}
/// <inheritdoc/>
public override void PreDraw()
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
base.PreDraw();
}
/// <inheritdoc/>
public override void PostDraw()
{
ImGui.PopStyleVar();
base.PostDraw();
}
/// <inheritdoc/>
public override void Draw()
{
var screenSize = ImGui.GetMainViewport().Size;
var windowSize = ImGui.GetWindowSize();
this.Position = (screenSize - windowSize) / 2;
ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGuiHelpers.ScaledDummy(0, 340f);
ImGui.Text(string.Empty);
ImGui.SameLine(150f);
ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f));
ImGuiHelpers.ScaledDummy(0, 20f);
var windowX = ImGui.GetWindowSize().X;
foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None))
{
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
var lineLenX = ImGui.CalcTextSize(creditsLine).X;
this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"));
this.creditsThrottler = new();
this.Size = new Vector2(500, 400);
this.SizeCondition = ImGuiCond.Always;
this.PositionCondition = ImGuiCond.Always;
this.BgAlpha = 0.8f;
ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f));
ImGui.SameLine();
ImGui.TextUnformatted(creditsLine);
}
/// <inheritdoc/>
public override void OnOpen()
ImGui.PopStyleVar();
if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS))
{
var pluginCredits = Service<PluginManager>.Get().InstalledPlugins
.Where(plugin => plugin.Manifest != null)
.Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n")
.Aggregate(string.Empty, (current, next) => $"{current}{next}");
var curY = ImGui.GetScrollY();
var maxY = ImGui.GetScrollMaxY();
this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits);
Service<GameGui>.Get().SetBgm(132);
this.creditsThrottler.Restart();
}
/// <inheritdoc/>
public override void OnClose()
{
this.creditsThrottler.Reset();
Service<GameGui>.Get().SetBgm(9999);
}
/// <inheritdoc/>
public override void PreDraw()
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
base.PreDraw();
}
/// <inheritdoc/>
public override void PostDraw()
{
ImGui.PopStyleVar();
base.PostDraw();
}
/// <inheritdoc/>
public override void Draw()
{
var screenSize = ImGui.GetMainViewport().Size;
var windowSize = ImGui.GetWindowSize();
this.Position = (screenSize - windowSize) / 2;
ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGuiHelpers.ScaledDummy(0, 340f);
ImGui.Text(string.Empty);
ImGui.SameLine(150f);
ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f));
ImGuiHelpers.ScaledDummy(0, 20f);
var windowX = ImGui.GetWindowSize().X;
foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None))
if (curY < maxY - 1)
{
var lineLenX = ImGui.CalcTextSize(creditsLine).X;
ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f));
ImGui.SameLine();
ImGui.TextUnformatted(creditsLine);
ImGui.SetScrollY(curY + 1);
}
ImGui.PopStyleVar();
if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS))
{
var curY = ImGui.GetScrollY();
var maxY = ImGui.GetScrollMaxY();
if (curY < maxY - 1)
{
ImGui.SetScrollY(curY + 1);
}
}
ImGui.EndChild();
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.logoTexture?.Dispose();
}
ImGui.EndChild();
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.logoTexture?.Dispose();
}
}

File diff suppressed because it is too large Load diff

View file

@ -4,46 +4,45 @@ using CheapLoc;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// Class responsible for drawing a notifier on screen that gamepad mode is active.
/// </summary>
internal class GamepadModeNotifierWindow : Window
{
/// <summary>
/// Class responsible for drawing a notifier on screen that gamepad mode is active.
/// Initializes a new instance of the <see cref="GamepadModeNotifierWindow"/> class.
/// </summary>
internal class GamepadModeNotifierWindow : Window
public GamepadModeNotifierWindow()
: base(
"###DalamudGamepadModeNotifier",
ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs
| ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings,
true)
{
/// <summary>
/// Initializes a new instance of the <see cref="GamepadModeNotifierWindow"/> class.
/// </summary>
public GamepadModeNotifierWindow()
: base(
"###DalamudGamepadModeNotifier",
ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs
| ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings,
true)
{
this.Size = Vector2.Zero;
this.SizeCondition = ImGuiCond.Always;
this.IsOpen = false;
this.Size = Vector2.Zero;
this.SizeCondition = ImGuiCond.Always;
this.IsOpen = false;
this.RespectCloseHotkey = false;
}
this.RespectCloseHotkey = false;
}
/// <summary>
/// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode.
/// </summary>
public override void Draw()
{
var drawList = ImGui.GetBackgroundDrawList();
drawList.PushClipRectFullScreen();
drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A);
drawList.AddText(
Vector2.One,
0xFFFFFFFF,
Loc.Localize(
"DalamudGamepadModeNotifierText",
"Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller."));
drawList.PopClipRect();
}
/// <summary>
/// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode.
/// </summary>
public override void Draw()
{
var drawList = ImGui.GetBackgroundDrawList();
drawList.PushClipRectFullScreen();
drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A);
drawList.AddText(
Vector2.One,
0xFFFFFFFF,
Loc.Localize(
"DalamudGamepadModeNotifierText",
"Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller."));
drawList.PopClipRect();
}
}

View file

@ -5,65 +5,64 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// A window for displaying IME details.
/// </summary>
internal class IMEWindow : Window
{
private const int ImePageSize = 9;
/// <summary>
/// A window for displaying IME details.
/// Initializes a new instance of the <see cref="IMEWindow"/> class.
/// </summary>
internal class IMEWindow : Window
public IMEWindow()
: base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize)
{
private const int ImePageSize = 9;
this.Size = new Vector2(100, 200);
this.SizeCondition = ImGuiCond.FirstUseEver;
/// <summary>
/// Initializes a new instance of the <see cref="IMEWindow"/> class.
/// </summary>
public IMEWindow()
: base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize)
this.RespectCloseHotkey = false;
}
/// <inheritdoc/>
public override void Draw()
{
var ime = Service<DalamudIME>.GetNullable();
if (ime == null || !ime.IsEnabled)
{
this.Size = new Vector2(100, 200);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.RespectCloseHotkey = false;
ImGui.Text("IME is unavailable.");
return;
}
/// <inheritdoc/>
public override void Draw()
ImGui.Text(ime.ImmComp);
ImGui.Separator();
var native = ime.ImmCandNative;
for (var i = 0; i < ime.ImmCand.Count; i++)
{
var ime = Service<DalamudIME>.GetNullable();
var selected = i == (native.Selection % ImePageSize);
if (ime == null || !ime.IsEnabled)
{
ImGui.Text("IME is unavailable.");
return;
}
if (selected)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.Text(ime.ImmComp);
ImGui.Text($"{i + 1}. {ime.ImmCand[i]}");
ImGui.Separator();
var native = ime.ImmCandNative;
for (var i = 0; i < ime.ImmCand.Count; i++)
{
var selected = i == (native.Selection % ImePageSize);
if (selected)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
ImGui.Text($"{i + 1}. {ime.ImmCand[i]}");
if (selected)
ImGui.PopStyleColor();
}
var totalIndex = native.Selection + 1;
var totalSize = native.Count;
var pageStart = native.PageStart;
var pageIndex = (pageStart / ImePageSize) + 1;
var pageCount = (totalSize / ImePageSize) + 1;
ImGui.Separator();
ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})");
if (selected)
ImGui.PopStyleColor();
}
var totalIndex = native.Selection + 1;
var totalSize = native.Count;
var pageStart = native.PageStart;
var pageIndex = (pageStart / ImePageSize) + 1;
var pageCount = (totalSize / ImePageSize) + 1;
ImGui.Separator();
ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})");
}
}

View file

@ -14,394 +14,330 @@ using Dalamud.Utility;
using ImGuiScene;
using Serilog;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// A cache for plugin icons and images.
/// </summary>
internal class PluginImageCache : IDisposable
{
/// <summary>
/// A cache for plugin icons and images.
/// Maximum plugin image width.
/// </summary>
internal class PluginImageCache : IDisposable
public const int PluginImageWidth = 730;
/// <summary>
/// Maximum plugin image height.
/// </summary>
public const int PluginImageHeight = 380;
/// <summary>
/// Maximum plugin icon width.
/// </summary>
public const int PluginIconWidth = 512;
/// <summary>
/// Maximum plugin height.
/// </summary>
public const int PluginIconHeight = 512;
// TODO: Change back to master after release
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}";
private BlockingCollection<Func<Task>> downloadQueue = new();
private CancellationTokenSource downloadToken = new();
private Dictionary<string, TextureWrap?> pluginIconMap = new();
private Dictionary<string, TextureWrap?[]> pluginImagesMap = new();
/// <summary>
/// Initializes a new instance of the <see cref="PluginImageCache"/> class.
/// </summary>
public PluginImageCache()
{
/// <summary>
/// Maximum plugin image width.
/// </summary>
public const int PluginImageWidth = 730;
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
/// <summary>
/// Maximum plugin image height.
/// </summary>
public const int PluginImageHeight = 380;
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"));
this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"));
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"));
/// <summary>
/// Maximum plugin icon width.
/// </summary>
public const int PluginIconWidth = 512;
var task = new Task(
() => this.DownloadTask(this.downloadToken.Token),
this.downloadToken.Token,
TaskCreationOptions.LongRunning);
task.Start();
}
/// <summary>
/// Maximum plugin height.
/// </summary>
public const int PluginIconHeight = 512;
/// <summary>
/// Gets the default plugin icon.
/// </summary>
public TextureWrap DefaultIcon { get; }
// TODO: Change back to master after release
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}";
/// <summary>
/// Gets the plugin trouble icon overlay.
/// </summary>
public TextureWrap TroubleIcon { get; }
private BlockingCollection<Func<Task>> downloadQueue = new();
private CancellationTokenSource downloadToken = new();
/// <summary>
/// Gets the plugin update icon overlay.
/// </summary>
public TextureWrap UpdateIcon { get; }
private Dictionary<string, TextureWrap?> pluginIconMap = new();
private Dictionary<string, TextureWrap?[]> pluginImagesMap = new();
/// <inheritdoc/>
public void Dispose()
{
this.DefaultIcon?.Dispose();
this.TroubleIcon?.Dispose();
this.UpdateIcon?.Dispose();
/// <summary>
/// Initializes a new instance of the <see cref="PluginImageCache"/> class.
/// </summary>
public PluginImageCache()
this.downloadToken?.Cancel();
this.downloadToken?.Dispose();
this.downloadQueue?.CompleteAdding();
this.downloadQueue?.Dispose();
foreach (var icon in this.pluginIconMap.Values)
{
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"));
this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"));
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"));
var task = new Task(
() => this.DownloadTask(this.downloadToken.Token),
this.downloadToken.Token,
TaskCreationOptions.LongRunning);
task.Start();
icon?.Dispose();
}
/// <summary>
/// Gets the default plugin icon.
/// </summary>
public TextureWrap DefaultIcon { get; }
/// <summary>
/// Gets the plugin trouble icon overlay.
/// </summary>
public TextureWrap TroubleIcon { get; }
/// <summary>
/// Gets the plugin update icon overlay.
/// </summary>
public TextureWrap UpdateIcon { get; }
/// <inheritdoc/>
public void Dispose()
foreach (var images in this.pluginImagesMap.Values)
{
this.DefaultIcon?.Dispose();
this.TroubleIcon?.Dispose();
this.UpdateIcon?.Dispose();
this.downloadToken?.Cancel();
this.downloadToken?.Dispose();
this.downloadQueue?.CompleteAdding();
this.downloadQueue?.Dispose();
foreach (var icon in this.pluginIconMap.Values)
foreach (var image in images)
{
icon?.Dispose();
image?.Dispose();
}
foreach (var images in this.pluginImagesMap.Values)
{
foreach (var image in images)
{
image?.Dispose();
}
}
this.pluginIconMap.Clear();
this.pluginImagesMap.Clear();
}
/// <summary>
/// Clear the cache of downloaded icons.
/// </summary>
public void ClearIconCache()
this.pluginIconMap.Clear();
this.pluginImagesMap.Clear();
}
/// <summary>
/// Clear the cache of downloaded icons.
/// </summary>
public void ClearIconCache()
{
this.pluginIconMap.Clear();
this.pluginImagesMap.Clear();
}
/// <summary>
/// Try to get the icon associated with the internal name of a plugin.
/// Uses the name within the manifest to search.
/// </summary>
/// <param name="plugin">The installed plugin, if available.</param>
/// <param name="manifest">The plugin manifest.</param>
/// <param name="isThirdParty">If the plugin was third party sourced.</param>
/// <param name="iconTexture">Cached image textures, or an empty array.</param>
/// <returns>True if an entry exists, may be null if currently downloading.</returns>
public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture)
{
if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture))
return true;
iconTexture = null;
this.pluginIconMap.Add(manifest.InternalName, iconTexture);
if (!this.downloadQueue.IsCompleted)
{
this.pluginIconMap.Clear();
this.pluginImagesMap.Clear();
this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty));
}
/// <summary>
/// Try to get the icon associated with the internal name of a plugin.
/// Uses the name within the manifest to search.
/// </summary>
/// <param name="plugin">The installed plugin, if available.</param>
/// <param name="manifest">The plugin manifest.</param>
/// <param name="isThirdParty">If the plugin was third party sourced.</param>
/// <param name="iconTexture">Cached image textures, or an empty array.</param>
/// <returns>True if an entry exists, may be null if currently downloading.</returns>
public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture)
return false;
}
/// <summary>
/// Try to get any images associated with the internal name of a plugin.
/// Uses the name within the manifest to search.
/// </summary>
/// <param name="plugin">The installed plugin, if available.</param>
/// <param name="manifest">The plugin manifest.</param>
/// <param name="isThirdParty">If the plugin was third party sourced.</param>
/// <param name="imageTextures">Cached image textures, or an empty array.</param>
/// <returns>True if the image array exists, may be empty if currently downloading.</returns>
public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures)
{
if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures))
return true;
imageTextures = Array.Empty<TextureWrap>();
this.pluginImagesMap.Add(manifest.InternalName, imageTextures);
if (!this.downloadQueue.IsCompleted)
{
if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture))
return true;
iconTexture = null;
this.pluginIconMap.Add(manifest.InternalName, iconTexture);
if (!this.downloadQueue.IsCompleted)
{
this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty));
}
return false;
this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty));
}
/// <summary>
/// Try to get any images associated with the internal name of a plugin.
/// Uses the name within the manifest to search.
/// </summary>
/// <param name="plugin">The installed plugin, if available.</param>
/// <param name="manifest">The plugin manifest.</param>
/// <param name="isThirdParty">If the plugin was third party sourced.</param>
/// <param name="imageTextures">Cached image textures, or an empty array.</param>
/// <returns>True if the image array exists, may be empty if currently downloading.</returns>
public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures)
return false;
}
private async void DownloadTask(CancellationToken token)
{
while (true)
{
if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures))
return true;
imageTextures = Array.Empty<TextureWrap>();
this.pluginImagesMap.Add(manifest.InternalName, imageTextures);
if (!this.downloadQueue.IsCompleted)
try
{
this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty));
}
return false;
}
private async void DownloadTask(CancellationToken token)
{
while (true)
{
try
{
if (token.IsCancellationRequested)
return;
if (!this.downloadQueue.TryTake(out var task, -1, token))
return;
await task.Invoke();
}
catch (OperationCanceledException)
{
// Shutdown signal.
break;
}
catch (Exception ex)
{
Log.Error(ex, "An unhandled exception occurred in the plugin image downloader");
}
}
Log.Debug("Plugin image downloader has shutdown");
}
private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
{
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon)
{
icon = interfaceManager.LoadImage(bytes);
if (icon == null)
{
Log.Error($"Could not load icon for {manifest.InternalName} at {loc}");
return false;
}
if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight)
{
Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight}).");
return false;
}
if (icon.Height != icon.Width)
{
Log.Error($"Icon for {manifest.InternalName} at {loc} was not square.");
return false;
}
return true;
}
if (plugin != null && plugin.IsDev)
{
var file = this.GetPluginIconFileInfo(plugin);
if (file != null)
{
Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}");
var bytes = await File.ReadAllBytesAsync(file.FullName);
if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon))
return;
this.pluginIconMap[manifest.InternalName] = icon;
Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk");
return;
}
// Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null.
// So instead, set the value manually so we download from the urls specified.
isThirdParty = true;
}
var useTesting = pluginManager.UseTesting(manifest);
var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting);
if (!url.IsNullOrEmpty())
{
Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}");
HttpResponseMessage data;
try
{
data = await Util.HttpClient.GetAsync(url);
}
catch (InvalidOperationException)
{
Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI");
return;
}
catch (Exception ex)
{
Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}");
return;
}
if (data.StatusCode == HttpStatusCode.NotFound)
if (token.IsCancellationRequested)
return;
data.EnsureSuccessStatusCode();
if (!this.downloadQueue.TryTake(out var task, -1, token))
return;
var bytes = await data.Content.ReadAsByteArrayAsync();
if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon))
await task.Invoke();
}
catch (OperationCanceledException)
{
// Shutdown signal.
break;
}
catch (Exception ex)
{
Log.Error(ex, "An unhandled exception occurred in the plugin image downloader");
}
}
Log.Debug("Plugin image downloader has shutdown");
}
private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
{
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon)
{
icon = interfaceManager.LoadImage(bytes);
if (icon == null)
{
Log.Error($"Could not load icon for {manifest.InternalName} at {loc}");
return false;
}
if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight)
{
Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight}).");
return false;
}
if (icon.Height != icon.Width)
{
Log.Error($"Icon for {manifest.InternalName} at {loc} was not square.");
return false;
}
return true;
}
if (plugin != null && plugin.IsDev)
{
var file = this.GetPluginIconFileInfo(plugin);
if (file != null)
{
Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}");
var bytes = await File.ReadAllBytesAsync(file.FullName);
if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon))
return;
this.pluginIconMap[manifest.InternalName] = icon;
Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded");
Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk");
return;
}
Log.Verbose($"Plugin icon for {manifest.InternalName} is not available");
// Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null.
// So instead, set the value manually so we download from the urls specified.
isThirdParty = true;
}
private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
var useTesting = pluginManager.UseTesting(manifest);
var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting);
if (!url.IsNullOrEmpty())
{
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}");
static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image)
HttpResponseMessage data;
try
{
image = interfaceManager.LoadImage(bytes);
if (image == null)
{
Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}");
return false;
}
if (image.Width > PluginImageWidth || image.Height > PluginImageHeight)
{
Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight}).");
return false;
}
return true;
data = await Util.HttpClient.GetAsync(url);
}
catch (InvalidOperationException)
{
Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI");
return;
}
catch (Exception ex)
{
Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}");
return;
}
if (plugin != null && plugin.IsDev)
if (data.StatusCode == HttpStatusCode.NotFound)
return;
data.EnsureSuccessStatusCode();
var bytes = await data.Content.ReadAsByteArrayAsync();
if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon))
return;
this.pluginIconMap[manifest.InternalName] = icon;
Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded");
return;
}
Log.Verbose($"Plugin icon for {manifest.InternalName} is not available");
}
private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
{
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image)
{
image = interfaceManager.LoadImage(bytes);
if (image == null)
{
var files = this.GetPluginImageFileInfos(plugin);
if (files != null)
{
var didAny = false;
var pluginImages = new TextureWrap[files.Count];
for (var i = 0; i < files.Count; i++)
{
var file = files[i];
if (file == null)
continue;
Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}");
var bytes = await File.ReadAllBytesAsync(file.FullName);
if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image))
continue;
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk");
pluginImages[i] = image;
didAny = true;
}
if (didAny)
{
Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk");
if (pluginImages.Contains(null))
pluginImages = pluginImages.Where(image => image != null).ToArray();
this.pluginImagesMap[manifest.InternalName] = pluginImages;
return;
}
}
// Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null.
// So instead, set the value manually so we download from the urls specified.
isThirdParty = true;
Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}");
return false;
}
var useTesting = pluginManager.UseTesting(manifest);
var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting);
if (urls != null)
if (image.Width > PluginImageWidth || image.Height > PluginImageHeight)
{
Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight}).");
return false;
}
return true;
}
if (plugin != null && plugin.IsDev)
{
var files = this.GetPluginImageFileInfos(plugin);
if (files != null)
{
var didAny = false;
var pluginImages = new TextureWrap[urls.Count];
for (var i = 0; i < urls.Count; i++)
var pluginImages = new TextureWrap[files.Count];
for (var i = 0; i < files.Count; i++)
{
var url = urls[i];
var file = files[i];
if (url.IsNullOrEmpty())
if (file == null)
continue;
Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}");
Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}");
var bytes = await File.ReadAllBytesAsync(file.FullName);
HttpResponseMessage data;
try
{
data = await Util.HttpClient.GetAsync(url);
}
catch (InvalidOperationException)
{
Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI");
continue;
}
catch (Exception ex)
{
Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}");
continue;
}
if (data.StatusCode == HttpStatusCode.NotFound)
if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image))
continue;
data.EnsureSuccessStatusCode();
var bytes = await data.Content.ReadAsByteArrayAsync();
if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image))
continue;
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded");
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk");
pluginImages[i] = image;
didAny = true;
@ -409,7 +345,7 @@ namespace Dalamud.Interface.Internal.Windows
if (didAny)
{
Log.Verbose($"Plugin images for {manifest.InternalName} downloaded");
Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk");
if (pluginImages.Contains(null))
pluginImages = pluginImages.Where(image => image != null).ToArray();
@ -420,67 +356,130 @@ namespace Dalamud.Interface.Internal.Windows
}
}
Log.Verbose($"Images for {manifest.InternalName} are not available");
// Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null.
// So instead, set the value manually so we download from the urls specified.
isThirdParty = true;
}
private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting)
var useTesting = pluginManager.UseTesting(manifest);
var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting);
if (urls != null)
{
if (isThirdParty)
return manifest.IconUrl;
return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png");
}
private List<string?>? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting)
{
if (isThirdParty)
var didAny = false;
var pluginImages = new TextureWrap[urls.Count];
for (var i = 0; i < urls.Count; i++)
{
if (manifest.ImageUrls?.Count > 5)
var url = urls[i];
if (url.IsNullOrEmpty())
continue;
Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}");
HttpResponseMessage data;
try
{
Log.Warning($"Plugin {manifest.InternalName} has too many images");
return manifest.ImageUrls.Take(5).ToList();
data = await Util.HttpClient.GetAsync(url);
}
return manifest.ImageUrls;
}
var output = new List<string>();
for (var i = 1; i <= 5; i++)
{
output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png"));
}
return output;
}
private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png"));
if (devUrl.Exists)
return devUrl;
return null;
}
private List<FileInfo?> GetPluginImageFileInfos(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var output = new List<FileInfo>();
for (var i = 1; i <= 5; i++)
{
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png"));
if (devUrl.Exists)
catch (InvalidOperationException)
{
output.Add(devUrl);
Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI");
continue;
}
catch (Exception ex)
{
Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}");
continue;
}
output.Add(null);
if (data.StatusCode == HttpStatusCode.NotFound)
continue;
data.EnsureSuccessStatusCode();
var bytes = await data.Content.ReadAsByteArrayAsync();
if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image))
continue;
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded");
pluginImages[i] = image;
didAny = true;
}
return output;
if (didAny)
{
Log.Verbose($"Plugin images for {manifest.InternalName} downloaded");
if (pluginImages.Contains(null))
pluginImages = pluginImages.Where(image => image != null).ToArray();
this.pluginImagesMap[manifest.InternalName] = pluginImages;
return;
}
}
Log.Verbose($"Images for {manifest.InternalName} are not available");
}
private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting)
{
if (isThirdParty)
return manifest.IconUrl;
return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png");
}
private List<string?>? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting)
{
if (isThirdParty)
{
if (manifest.ImageUrls?.Count > 5)
{
Log.Warning($"Plugin {manifest.InternalName} has too many images");
return manifest.ImageUrls.Take(5).ToList();
}
return manifest.ImageUrls;
}
var output = new List<string>();
for (var i = 1; i <= 5; i++)
{
output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png"));
}
return output;
}
private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png"));
if (devUrl.Exists)
return devUrl;
return null;
}
private List<FileInfo?> GetPluginImageFileInfos(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var output = new List<FileInfo>();
for (var i = 1; i <= 5; i++)
{
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png"));
if (devUrl.Exists)
{
output.Add(devUrl);
continue;
}
output.Add(null);
}
return output;
}
}

File diff suppressed because it is too large Load diff

View file

@ -11,264 +11,263 @@ using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows
namespace Dalamud.Interface.Internal.Windows;
/// <summary>
/// This window displays plugin statistics for troubleshooting.
/// </summary>
internal class PluginStatWindow : Window
{
private bool showDalamudHooks;
/// <summary>
/// This window displays plugin statistics for troubleshooting.
/// Initializes a new instance of the <see cref="PluginStatWindow"/> class.
/// </summary>
internal class PluginStatWindow : Window
public PluginStatWindow()
: base("Plugin Statistics###DalamudPluginStatWindow")
{
private bool showDalamudHooks;
this.RespectCloseHotkey = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="PluginStatWindow"/> class.
/// </summary>
public PluginStatWindow()
: base("Plugin Statistics###DalamudPluginStatWindow")
/// <inheritdoc/>
public override void Draw()
{
var pluginManager = Service<PluginManager>.Get();
ImGui.BeginTabBar("Stat Tabs");
if (ImGui.BeginTabItem("Draw times"))
{
this.RespectCloseHotkey = false;
}
var doStats = UiBuilder.DoStats;
/// <inheritdoc/>
public override void Draw()
{
var pluginManager = Service<PluginManager>.Get();
ImGui.BeginTabBar("Stat Tabs");
if (ImGui.BeginTabItem("Draw times"))
if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats))
{
var doStats = UiBuilder.DoStats;
if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats))
{
UiBuilder.DoStats = doStats;
}
if (doStats)
{
ImGui.SameLine();
if (ImGui.Button("Reset"))
{
foreach (var plugin in pluginManager.InstalledPlugins)
{
plugin.DalamudInterface.UiBuilder.LastDrawTime = -1;
plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1;
plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear();
}
}
ImGui.Columns(4);
ImGui.SetColumnWidth(0, 180f);
ImGui.SetColumnWidth(1, 100f);
ImGui.SetColumnWidth(2, 100f);
ImGui.SetColumnWidth(3, 100f);
ImGui.Text("Plugin");
ImGui.NextColumn();
ImGui.Text("Last");
ImGui.NextColumn();
ImGui.Text("Longest");
ImGui.NextColumn();
ImGui.Text("Average");
ImGui.NextColumn();
ImGui.Separator();
foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded))
{
ImGui.Text(plugin.Manifest.Name);
ImGui.NextColumn();
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms");
ImGui.NextColumn();
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms");
ImGui.NextColumn();
if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0)
{
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms");
}
else
{
ImGui.Text("-");
}
ImGui.NextColumn();
}
ImGui.Columns(1);
}
ImGui.EndTabItem();
UiBuilder.DoStats = doStats;
}
if (ImGui.BeginTabItem("Framework times"))
if (doStats)
{
var doStats = Framework.StatsEnabled;
if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats))
ImGui.SameLine();
if (ImGui.Button("Reset"))
{
Framework.StatsEnabled = doStats;
foreach (var plugin in pluginManager.InstalledPlugins)
{
plugin.DalamudInterface.UiBuilder.LastDrawTime = -1;
plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1;
plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear();
}
}
if (doStats)
{
ImGui.SameLine();
if (ImGui.Button("Reset"))
{
Framework.StatsHistory.Clear();
}
ImGui.Columns(4);
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300);
ImGui.SetColumnWidth(1, 100f);
ImGui.SetColumnWidth(2, 100f);
ImGui.SetColumnWidth(3, 100f);
ImGui.Text("Method");
ImGui.NextColumn();
ImGui.Text("Last");
ImGui.NextColumn();
ImGui.Text("Longest");
ImGui.NextColumn();
ImGui.Text("Average");
ImGui.NextColumn();
ImGui.Separator();
ImGui.Separator();
foreach (var handlerHistory in Framework.StatsHistory)
{
if (handlerHistory.Value.Count == 0)
continue;
ImGui.SameLine();
ImGui.Text($"{handlerHistory.Key}");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Last():F4}ms");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Max():F4}ms");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Average():F4}ms");
ImGui.NextColumn();
ImGui.Separator();
}
ImGui.Columns(0);
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Hooks"))
{
ImGui.Columns(4);
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330);
ImGui.SetColumnWidth(1, 180f);
ImGui.SetColumnWidth(0, 180f);
ImGui.SetColumnWidth(1, 100f);
ImGui.SetColumnWidth(2, 100f);
ImGui.SetColumnWidth(3, 100f);
ImGui.Text("Detour Method");
ImGui.SameLine();
ImGui.Text(" ");
ImGui.SameLine();
ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks);
ImGui.Text("Plugin");
ImGui.NextColumn();
ImGui.Text("Address");
ImGui.Text("Last");
ImGui.NextColumn();
ImGui.Text("Status");
ImGui.Text("Longest");
ImGui.NextColumn();
ImGui.Text("Backend");
ImGui.Text("Average");
ImGui.NextColumn();
ImGui.Separator();
ImGui.Separator();
foreach (var trackedHook in HookManager.TrackedHooks)
foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded))
{
try
ImGui.Text(plugin.Manifest.Name);
ImGui.NextColumn();
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms");
ImGui.NextColumn();
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms");
ImGui.NextColumn();
if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0)
{
if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly())
continue;
ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}");
ImGui.TextDisabled(trackedHook.Assembly.FullName);
ImGui.NextColumn();
if (!trackedHook.Hook.IsDisposed)
{
ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}");
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}");
}
var processMemoryOffset = trackedHook.InProcessMemory;
if (processMemoryOffset.HasValue)
{
ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}");
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}");
}
}
}
ImGui.NextColumn();
if (trackedHook.Hook.IsDisposed)
{
ImGui.Text("Disposed");
}
else
{
ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled");
}
ImGui.NextColumn();
ImGui.Text(trackedHook.Hook.BackendName);
ImGui.NextColumn();
ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms");
}
catch (Exception ex)
else
{
ImGui.Text(ex.Message);
ImGui.NextColumn();
while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn();
ImGui.Text("-");
}
ImGui.NextColumn();
}
ImGui.Columns(1);
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Framework times"))
{
var doStats = Framework.StatsEnabled;
if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats))
{
Framework.StatsEnabled = doStats;
}
if (doStats)
{
ImGui.SameLine();
if (ImGui.Button("Reset"))
{
Framework.StatsHistory.Clear();
}
ImGui.Columns(4);
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300);
ImGui.SetColumnWidth(1, 100f);
ImGui.SetColumnWidth(2, 100f);
ImGui.SetColumnWidth(3, 100f);
ImGui.Text("Method");
ImGui.NextColumn();
ImGui.Text("Last");
ImGui.NextColumn();
ImGui.Text("Longest");
ImGui.NextColumn();
ImGui.Text("Average");
ImGui.NextColumn();
ImGui.Separator();
ImGui.Separator();
foreach (var handlerHistory in Framework.StatsHistory)
{
if (handlerHistory.Value.Count == 0)
continue;
ImGui.SameLine();
ImGui.Text($"{handlerHistory.Key}");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Last():F4}ms");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Max():F4}ms");
ImGui.NextColumn();
ImGui.Text($"{handlerHistory.Value.Average():F4}ms");
ImGui.NextColumn();
ImGui.Separator();
}
ImGui.Columns();
ImGui.Columns(0);
}
if (ImGui.IsWindowAppearing())
{
HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed);
}
ImGui.EndTabBar();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Hooks"))
{
ImGui.Columns(4);
ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330);
ImGui.SetColumnWidth(1, 180f);
ImGui.SetColumnWidth(2, 100f);
ImGui.SetColumnWidth(3, 100f);
ImGui.Text("Detour Method");
ImGui.SameLine();
ImGui.Text(" ");
ImGui.SameLine();
ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks);
ImGui.NextColumn();
ImGui.Text("Address");
ImGui.NextColumn();
ImGui.Text("Status");
ImGui.NextColumn();
ImGui.Text("Backend");
ImGui.NextColumn();
ImGui.Separator();
ImGui.Separator();
foreach (var trackedHook in HookManager.TrackedHooks)
{
try
{
if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly())
continue;
ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}");
ImGui.TextDisabled(trackedHook.Assembly.FullName);
ImGui.NextColumn();
if (!trackedHook.Hook.IsDisposed)
{
ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}");
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}");
}
var processMemoryOffset = trackedHook.InProcessMemory;
if (processMemoryOffset.HasValue)
{
ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}");
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}");
}
}
}
ImGui.NextColumn();
if (trackedHook.Hook.IsDisposed)
{
ImGui.Text("Disposed");
}
else
{
ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled");
}
ImGui.NextColumn();
ImGui.Text(trackedHook.Hook.BackendName);
ImGui.NextColumn();
}
catch (Exception ex)
{
ImGui.Text(ex.Message);
ImGui.NextColumn();
while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn();
}
ImGui.Separator();
}
ImGui.Columns();
}
if (ImGui.IsWindowAppearing())
{
HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed);
}
ImGui.EndTabBar();
}
}

View file

@ -2,47 +2,46 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the Actor Table.
/// </summary>
internal class ActorTableAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the Actor Table.
/// </summary>
internal class ActorTableAgingStep : IAgingStep
private int index = 0;
/// <inheritdoc/>
public string Name => "Test ActorTable";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private int index = 0;
var objectTable = Service<ObjectTable>.Get();
/// <inheritdoc/>
public string Name => "Test ActorTable";
ImGui.Text("Checking actor table...");
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (this.index == objectTable.Length - 1)
{
var objectTable = Service<ObjectTable>.Get();
return SelfTestStepResult.Pass;
}
ImGui.Text("Checking actor table...");
if (this.index == objectTable.Length - 1)
{
return SelfTestStepResult.Pass;
}
var actor = objectTable[this.index];
this.index++;
if (actor == null)
{
return SelfTestStepResult.Waiting;
}
Util.ShowObject(actor);
var actor = objectTable[this.index];
this.index++;
if (actor == null)
{
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
Util.ShowObject(actor);
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -3,71 +3,70 @@ using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for Chat.
/// </summary>
internal class ChatAgingStep : IAgingStep
{
/// <summary>
/// Test setup for Chat.
/// </summary>
internal class ChatAgingStep : IAgingStep
private int step = 0;
private bool subscribed = false;
private bool hasPassed = false;
/// <inheritdoc/>
public string Name => "Test Chat";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private int step = 0;
private bool subscribed = false;
private bool hasPassed = false;
var chatGui = Service<ChatGui>.Get();
/// <inheritdoc/>
public string Name => "Test Chat";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
switch (this.step)
{
var chatGui = Service<ChatGui>.Get();
case 0:
chatGui.Print("Testing!");
this.step++;
switch (this.step)
{
case 0:
chatGui.Print("Testing!");
this.step++;
break;
break;
case 1:
ImGui.Text("Type \"/e DALAMUD\" in chat...");
case 1:
ImGui.Text("Type \"/e DALAMUD\" in chat...");
if (!this.subscribed)
{
this.subscribed = true;
chatGui.ChatMessage += this.ChatOnOnChatMessage;
}
if (!this.subscribed)
{
this.subscribed = true;
chatGui.ChatMessage += this.ChatOnOnChatMessage;
}
if (this.hasPassed)
{
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
if (this.hasPassed)
{
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
break;
}
return SelfTestStepResult.Waiting;
break;
}
/// <inheritdoc/>
public void CleanUp()
{
var chatGui = Service<ChatGui>.Get();
return SelfTestStepResult.Waiting;
}
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
this.subscribed = false;
}
/// <inheritdoc/>
public void CleanUp()
{
var chatGui = Service<ChatGui>.Get();
private void ChatOnOnChatMessage(
XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled)
chatGui.ChatMessage -= this.ChatOnOnChatMessage;
this.subscribed = false;
}
private void ChatOnOnChatMessage(
XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled)
{
if (type == XivChatType.Echo && message.TextValue == "DALAMUD")
{
if (type == XivChatType.Echo && message.TextValue == "DALAMUD")
{
this.hasPassed = true;
}
this.hasPassed = true;
}
}
}

View file

@ -2,36 +2,35 @@ using Dalamud.Game.ClientState.Conditions;
using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for Condition.
/// </summary>
internal class ConditionAgingStep : IAgingStep
{
/// <summary>
/// Test setup for Condition.
/// </summary>
internal class ConditionAgingStep : IAgingStep
/// <inheritdoc/>
public string Name => "Test Condition";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
/// <inheritdoc/>
public string Name => "Test Condition";
var condition = Service<Condition>.Get();
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (!condition.Any())
{
var condition = Service<Condition>.Get();
if (!condition.Any())
{
Log.Error("No condition flags present.");
return SelfTestStepResult.Fail;
}
ImGui.Text("Please jump...");
return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
Log.Error("No condition flags present.");
return SelfTestStepResult.Fail;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
ImGui.Text("Please jump...");
return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,70 +1,69 @@
using Dalamud.Game.ClientState;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for Territory Change.
/// </summary>
internal class EnterTerritoryAgingStep : IAgingStep
{
private readonly ushort territory;
private readonly string terriName;
private bool subscribed = false;
private bool hasPassed = false;
/// <summary>
/// Test setup for Territory Change.
/// Initializes a new instance of the <see cref="EnterTerritoryAgingStep"/> class.
/// </summary>
internal class EnterTerritoryAgingStep : IAgingStep
/// <param name="terri">The territory to check for.</param>
/// <param name="name">Name to show.</param>
public EnterTerritoryAgingStep(ushort terri, string name)
{
private readonly ushort territory;
private readonly string terriName;
private bool subscribed = false;
private bool hasPassed = false;
this.terriName = name;
this.territory = terri;
}
/// <summary>
/// Initializes a new instance of the <see cref="EnterTerritoryAgingStep"/> class.
/// </summary>
/// <param name="terri">The territory to check for.</param>
/// <param name="name">Name to show.</param>
public EnterTerritoryAgingStep(ushort terri, string name)
/// <inheritdoc/>
public string Name => $"Enter Terri: {this.terriName}";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
var clientState = Service<ClientState>.Get();
ImGui.TextUnformatted(this.Name);
if (!this.subscribed)
{
this.terriName = name;
this.territory = terri;
clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged;
this.subscribed = true;
}
/// <inheritdoc/>
public string Name => $"Enter Terri: {this.terriName}";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (this.hasPassed)
{
var clientState = Service<ClientState>.Get();
ImGui.TextUnformatted(this.Name);
if (!this.subscribed)
{
clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged;
this.subscribed = true;
}
if (this.hasPassed)
{
clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
var clientState = Service<ClientState>.Get();
clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
private void ClientStateOnTerritoryChanged(object sender, ushort e)
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
var clientState = Service<ClientState>.Get();
clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged;
this.subscribed = false;
}
private void ClientStateOnTerritoryChanged(object sender, ushort e)
{
if (e == this.territory)
{
if (e == this.territory)
{
this.hasPassed = true;
}
this.hasPassed = true;
}
}
}

View file

@ -2,47 +2,46 @@ using Dalamud.Game.ClientState.Fates;
using Dalamud.Utility;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the Fate Table.
/// </summary>
internal class FateTableAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the Fate Table.
/// </summary>
internal class FateTableAgingStep : IAgingStep
private int index = 0;
/// <inheritdoc/>
public string Name => "Test FateTable";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private int index = 0;
var fateTable = Service<FateTable>.Get();
/// <inheritdoc/>
public string Name => "Test FateTable";
ImGui.Text("Checking fate table...");
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (this.index == fateTable.Length - 1)
{
var fateTable = Service<FateTable>.Get();
return SelfTestStepResult.Pass;
}
ImGui.Text("Checking fate table...");
if (this.index == fateTable.Length - 1)
{
return SelfTestStepResult.Pass;
}
var actor = fateTable[this.index];
this.index++;
if (actor == null)
{
return SelfTestStepResult.Waiting;
}
Util.ShowObject(actor);
var actor = fateTable[this.index];
this.index++;
if (actor == null)
{
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
Util.ShowObject(actor);
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,37 +1,36 @@
using Dalamud.Game.ClientState.GamePad;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the Gamepad State.
/// </summary>
internal class GamepadStateAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the Gamepad State.
/// </summary>
internal class GamepadStateAgingStep : IAgingStep
/// <inheritdoc/>
public string Name => "Test GamePadState";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
/// <inheritdoc/>
public string Name => "Test GamePadState";
var gamepadState = Service<GamepadState>.Get();
/// <inheritdoc/>
public SelfTestStepResult RunStep()
ImGui.Text("Hold down North, East, L1");
if (gamepadState.Pressed(GamepadButtons.North) == 1
&& gamepadState.Pressed(GamepadButtons.East) == 1
&& gamepadState.Pressed(GamepadButtons.L1) == 1)
{
var gamepadState = Service<GamepadState>.Get();
ImGui.Text("Hold down North, East, L1");
if (gamepadState.Pressed(GamepadButtons.North) == 1
&& gamepadState.Pressed(GamepadButtons.East) == 1
&& gamepadState.Pressed(GamepadButtons.L1) == 1)
{
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
return SelfTestStepResult.Pass;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,35 +1,34 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test dedicated to handling of Access Violations.
/// </summary>
internal class HandledExceptionAgingStep : IAgingStep
{
/// <summary>
/// Test dedicated to handling of Access Violations.
/// </summary>
internal class HandledExceptionAgingStep : IAgingStep
/// <inheritdoc/>
public string Name => "Test Handled Exception";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
/// <inheritdoc/>
public string Name => "Test Handled Exception";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
try
{
try
{
Marshal.ReadByte(IntPtr.Zero);
}
catch (AccessViolationException)
{
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Fail;
Marshal.ReadByte(IntPtr.Zero);
}
catch (AccessViolationException)
{
return SelfTestStepResult.Pass;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
return SelfTestStepResult.Fail;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,58 +1,57 @@
using Dalamud.Game.Gui;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the Hover events.
/// </summary>
internal class HoverAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the Hover events.
/// </summary>
internal class HoverAgingStep : IAgingStep
private bool clearedItem = false;
private bool clearedAction = false;
/// <inheritdoc/>
public string Name => "Test Hover";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private bool clearedItem = false;
private bool clearedAction = false;
var gameGui = Service<GameGui>.Get();
/// <inheritdoc/>
public string Name => "Test Hover";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (!this.clearedItem)
{
var gameGui = Service<GameGui>.Get();
ImGui.Text("Hover WHM soul crystal...");
if (!this.clearedItem)
if (gameGui.HoveredItem == 4547)
{
ImGui.Text("Hover WHM soul crystal...");
if (gameGui.HoveredItem == 4547)
{
this.clearedItem = true;
}
this.clearedItem = true;
}
if (!this.clearedAction)
{
ImGui.Text("Hover \"Open Linkshells\" action...");
if (gameGui.HoveredAction != null &&
gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand &&
gameGui.HoveredAction.ActionID == 28)
{
this.clearedAction = true;
}
}
if (this.clearedItem && this.clearedAction)
{
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
if (!this.clearedAction)
{
// ignored
ImGui.Text("Hover \"Open Linkshells\" action...");
if (gameGui.HoveredAction != null &&
gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand &&
gameGui.HoveredAction.ActionID == 28)
{
this.clearedAction = true;
}
}
if (this.clearedItem && this.clearedAction)
{
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,24 +1,23 @@
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Interface for test implementations.
/// </summary>
internal interface IAgingStep
{
/// <summary>
/// Interface for test implementations.
/// Gets the name of the test.
/// </summary>
internal interface IAgingStep
{
/// <summary>
/// Gets the name of the test.
/// </summary>
public string Name { get; }
public string Name { get; }
/// <summary>
/// Run the test step, once per frame it is active.
/// </summary>
/// <returns>The result of this frame, test is discarded once a result other than <see cref="SelfTestStepResult.Waiting"/> is returned.</returns>
public SelfTestStepResult RunStep();
/// <summary>
/// Run the test step, once per frame it is active.
/// </summary>
/// <returns>The result of this frame, test is discarded once a result other than <see cref="SelfTestStepResult.Waiting"/> is returned.</returns>
public SelfTestStepResult RunStep();
/// <summary>
/// Clean up this test.
/// </summary>
public void CleanUp();
}
/// <summary>
/// Clean up this test.
/// </summary>
public void CleanUp();
}

View file

@ -1,39 +1,38 @@
using Dalamud.Game.ClientState.Keys;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the Key State.
/// </summary>
internal class KeyStateAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the Key State.
/// </summary>
internal class KeyStateAgingStep : IAgingStep
/// <inheritdoc/>
public string Name => "Test KeyState";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
/// <inheritdoc/>
public string Name => "Test KeyState";
var keyState = Service<KeyState>.Get();
/// <inheritdoc/>
public SelfTestStepResult RunStep()
ImGui.Text("Hold down D,A,L,M,U");
if (keyState[VirtualKey.D]
&& keyState[VirtualKey.A]
&& keyState[VirtualKey.L]
&& keyState[VirtualKey.M]
&& keyState[VirtualKey.U])
{
var keyState = Service<KeyState>.Get();
ImGui.Text("Hold down D,A,L,M,U");
if (keyState[VirtualKey.D]
&& keyState[VirtualKey.A]
&& keyState[VirtualKey.L]
&& keyState[VirtualKey.M]
&& keyState[VirtualKey.U])
{
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
return SelfTestStepResult.Pass;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -3,57 +3,56 @@ using System;
using Dalamud.Game.ClientState;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the login events.
/// </summary>
internal class LoginEventAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the login events.
/// </summary>
internal class LoginEventAgingStep : IAgingStep
private bool subscribed = false;
private bool hasPassed = false;
/// <inheritdoc/>
public string Name => "Test Log-In";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private bool subscribed = false;
private bool hasPassed = false;
var clientState = Service<ClientState>.Get();
/// <inheritdoc/>
public string Name => "Test Log-In";
ImGui.Text("Log in now...");
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (!this.subscribed)
{
var clientState = Service<ClientState>.Get();
ImGui.Text("Log in now...");
if (!this.subscribed)
{
clientState.Login += this.ClientStateOnOnLogin;
this.subscribed = true;
}
if (this.hasPassed)
{
clientState.Login -= this.ClientStateOnOnLogin;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
clientState.Login += this.ClientStateOnOnLogin;
this.subscribed = true;
}
/// <inheritdoc/>
public void CleanUp()
if (this.hasPassed)
{
var clientState = Service<ClientState>.Get();
if (this.subscribed)
{
clientState.Login -= this.ClientStateOnOnLogin;
this.subscribed = false;
}
clientState.Login -= this.ClientStateOnOnLogin;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
private void ClientStateOnOnLogin(object sender, EventArgs e)
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
var clientState = Service<ClientState>.Get();
if (this.subscribed)
{
this.hasPassed = true;
clientState.Login -= this.ClientStateOnOnLogin;
this.subscribed = false;
}
}
private void ClientStateOnOnLogin(object sender, EventArgs e)
{
this.hasPassed = true;
}
}

View file

@ -3,57 +3,56 @@ using System;
using Dalamud.Game.ClientState;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for the login events.
/// </summary>
internal class LogoutEventAgingStep : IAgingStep
{
/// <summary>
/// Test setup for the login events.
/// </summary>
internal class LogoutEventAgingStep : IAgingStep
private bool subscribed = false;
private bool hasPassed = false;
/// <inheritdoc/>
public string Name => "Test Log-In";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private bool subscribed = false;
private bool hasPassed = false;
var clientState = Service<ClientState>.Get();
/// <inheritdoc/>
public string Name => "Test Log-In";
ImGui.Text("Log out now...");
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (!this.subscribed)
{
var clientState = Service<ClientState>.Get();
ImGui.Text("Log out now...");
if (!this.subscribed)
{
clientState.Logout += this.ClientStateOnOnLogout;
this.subscribed = true;
}
if (this.hasPassed)
{
clientState.Logout -= this.ClientStateOnOnLogout;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Waiting;
clientState.Logout += this.ClientStateOnOnLogout;
this.subscribed = true;
}
/// <inheritdoc/>
public void CleanUp()
if (this.hasPassed)
{
var clientState = Service<ClientState>.Get();
if (this.subscribed)
{
clientState.Logout -= this.ClientStateOnOnLogout;
this.subscribed = false;
}
clientState.Logout -= this.ClientStateOnOnLogout;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
private void ClientStateOnOnLogout(object sender, EventArgs e)
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
var clientState = Service<ClientState>.Get();
if (this.subscribed)
{
this.hasPassed = true;
clientState.Logout -= this.ClientStateOnOnLogout;
this.subscribed = false;
}
}
private void ClientStateOnOnLogout(object sender, EventArgs e)
{
this.hasPassed = true;
}
}

View file

@ -5,38 +5,37 @@ using Dalamud.Data;
using Dalamud.Utility;
using Lumina.Excel;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for Lumina.
/// </summary>
/// <typeparam name="T">ExcelRow to run test on.</typeparam>
internal class LuminaAgingStep<T> : IAgingStep
where T : ExcelRow
{
/// <summary>
/// Test setup for Lumina.
/// </summary>
/// <typeparam name="T">ExcelRow to run test on.</typeparam>
internal class LuminaAgingStep<T> : IAgingStep
where T : ExcelRow
private int step = 0;
private List<T> rows;
/// <inheritdoc/>
public string Name => "Test Lumina";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private int step = 0;
private List<T> rows;
var dataManager = Service<DataManager>.Get();
/// <inheritdoc/>
public string Name => "Test Lumina";
this.rows ??= dataManager.GetExcelSheet<T>().ToList();
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
var dataManager = Service<DataManager>.Get();
Util.ShowObject(this.rows[this.step]);
this.rows ??= dataManager.GetExcelSheet<T>().ToList();
this.step++;
return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
}
Util.ShowObject(this.rows[this.step]);
this.step++;
return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -2,57 +2,56 @@ using Dalamud.Game.Gui.PartyFinder;
using Dalamud.Game.Gui.PartyFinder.Types;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for Party Finder events.
/// </summary>
internal class PartyFinderAgingStep : IAgingStep
{
/// <summary>
/// Test setup for Party Finder events.
/// </summary>
internal class PartyFinderAgingStep : IAgingStep
private bool subscribed = false;
private bool hasPassed = false;
/// <inheritdoc/>
public string Name => "Test Party Finder";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private bool subscribed = false;
private bool hasPassed = false;
var partyFinderGui = Service<PartyFinderGui>.Get();
/// <inheritdoc/>
public string Name => "Test Party Finder";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
if (!this.subscribed)
{
var partyFinderGui = Service<PartyFinderGui>.Get();
if (!this.subscribed)
{
partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing;
this.subscribed = true;
}
if (this.hasPassed)
{
partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
ImGui.Text("Open Party Finder");
return SelfTestStepResult.Waiting;
partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing;
this.subscribed = true;
}
/// <inheritdoc/>
public void CleanUp()
if (this.hasPassed)
{
var partyFinderGui = Service<PartyFinderGui>.Get();
if (this.subscribed)
{
partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing;
this.subscribed = false;
}
partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing;
this.subscribed = false;
return SelfTestStepResult.Pass;
}
private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args)
ImGui.Text("Open Party Finder");
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
var partyFinderGui = Service<PartyFinderGui>.Get();
if (this.subscribed)
{
this.hasPassed = true;
partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing;
this.subscribed = false;
}
}
private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args)
{
this.hasPassed = true;
}
}

View file

@ -3,74 +3,73 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using ImGuiNET;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for targets.
/// </summary>
internal class TargetAgingStep : IAgingStep
{
/// <summary>
/// Test setup for targets.
/// </summary>
internal class TargetAgingStep : IAgingStep
private int step = 0;
/// <inheritdoc/>
public string Name => "Test Target";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
private int step = 0;
var targetManager = Service<TargetManager>.Get();
/// <inheritdoc/>
public string Name => "Test Target";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
switch (this.step)
{
var targetManager = Service<TargetManager>.Get();
case 0:
targetManager.ClearTarget();
targetManager.ClearFocusTarget();
switch (this.step)
{
case 0:
targetManager.ClearTarget();
targetManager.ClearFocusTarget();
this.step++;
break;
case 1:
ImGui.Text("Target a player...");
var cTarget = targetManager.Target;
if (cTarget is PlayerCharacter)
{
this.step++;
}
break;
break;
case 1:
ImGui.Text("Target a player...");
case 2:
ImGui.Text("Focus-Target a Battle NPC...");
var cTarget = targetManager.Target;
if (cTarget is PlayerCharacter)
{
this.step++;
}
var fTarget = targetManager.FocusTarget;
if (fTarget is BattleNpc)
{
this.step++;
}
break;
break;
case 2:
ImGui.Text("Focus-Target a Battle NPC...");
case 3:
ImGui.Text("Soft-Target an EventObj...");
var fTarget = targetManager.FocusTarget;
if (fTarget is BattleNpc)
{
this.step++;
}
var sTarget = targetManager.FocusTarget;
if (sTarget is EventObj)
{
return SelfTestStepResult.Pass;
}
break;
case 3:
ImGui.Text("Soft-Target an EventObj...");
var sTarget = targetManager.FocusTarget;
if (sTarget is EventObj)
{
return SelfTestStepResult.Pass;
}
break;
}
return SelfTestStepResult.Waiting;
break;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
return SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,30 +1,29 @@
using Dalamud.Game.Gui.Toast;
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test setup for toasts.
/// </summary>
internal class ToastAgingStep : IAgingStep
{
/// <summary>
/// Test setup for toasts.
/// </summary>
internal class ToastAgingStep : IAgingStep
/// <inheritdoc/>
public string Name => "Test Toasts";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
/// <inheritdoc/>
public string Name => "Test Toasts";
var toastGui = Service<ToastGui>.Get();
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
var toastGui = Service<ToastGui>.Get();
toastGui.ShowNormal("Normal Toast");
toastGui.ShowError("Error Toast");
toastGui.ShowNormal("Normal Toast");
toastGui.ShowError("Error Toast");
return SelfTestStepResult.Pass;
}
return SelfTestStepResult.Pass;
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
/// <inheritdoc/>
public void CleanUp()
{
// ignored
}
}

View file

@ -1,38 +1,37 @@
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
/// <summary>
/// Test that waits N frames.
/// </summary>
internal class WaitFramesAgingStep : IAgingStep
{
private readonly int frames;
private int cFrames;
/// <summary>
/// Test that waits N frames.
/// Initializes a new instance of the <see cref="WaitFramesAgingStep"/> class.
/// </summary>
internal class WaitFramesAgingStep : IAgingStep
/// <param name="frames">Amount of frames to wait.</param>
public WaitFramesAgingStep(int frames)
{
private readonly int frames;
private int cFrames;
this.frames = frames;
this.cFrames = frames;
}
/// <summary>
/// Initializes a new instance of the <see cref="WaitFramesAgingStep"/> class.
/// </summary>
/// <param name="frames">Amount of frames to wait.</param>
public WaitFramesAgingStep(int frames)
{
this.frames = frames;
this.cFrames = frames;
}
/// <inheritdoc/>
public string Name => $"Wait {this.cFrames} frames";
/// <inheritdoc/>
public string Name => $"Wait {this.cFrames} frames";
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
this.cFrames--;
/// <inheritdoc/>
public SelfTestStepResult RunStep()
{
this.cFrames--;
return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
}
return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting;
}
/// <inheritdoc/>
public void CleanUp()
{
this.cFrames = this.frames;
}
/// <inheritdoc/>
public void CleanUp()
{
this.cFrames = this.frames;
}
}

View file

@ -1,28 +1,27 @@
namespace Dalamud.Interface.Internal.Windows.SelfTest
namespace Dalamud.Interface.Internal.Windows.SelfTest;
/// <summary>
/// Enum declaring result states of tests.
/// </summary>
internal enum SelfTestStepResult
{
/// <summary>
/// Enum declaring result states of tests.
/// Test was not ran.
/// </summary>
internal enum SelfTestStepResult
{
/// <summary>
/// Test was not ran.
/// </summary>
NotRan,
NotRan,
/// <summary>
/// Test is waiting for completion.
/// </summary>
Waiting,
/// <summary>
/// Test is waiting for completion.
/// </summary>
Waiting,
/// <summary>
/// Test has failed.
/// </summary>
Fail,
/// <summary>
/// Test has failed.
/// </summary>
Fail,
/// <summary>
/// Test has passed.
/// </summary>
Pass,
}
/// <summary>
/// Test has passed.
/// </summary>
Pass,
}

View file

@ -11,246 +11,245 @@ using Dalamud.Logging.Internal;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
namespace Dalamud.Interface.Internal.Windows.SelfTest
namespace Dalamud.Interface.Internal.Windows.SelfTest;
/// <summary>
/// Window for the Self-Test logic.
/// </summary>
internal class SelfTestWindow : Window
{
private static readonly ModuleLog Log = new("AGING");
private readonly List<IAgingStep> steps =
new()
{
new LoginEventAgingStep(),
new WaitFramesAgingStep(1000),
new EnterTerritoryAgingStep(148, "Central Shroud"),
new ActorTableAgingStep(),
new FateTableAgingStep(),
new ConditionAgingStep(),
new ToastAgingStep(),
new TargetAgingStep(),
new KeyStateAgingStep(),
new GamepadStateAgingStep(),
new ChatAgingStep(),
new HoverAgingStep(),
new LuminaAgingStep<TerritoryType>(),
new PartyFinderAgingStep(),
new HandledExceptionAgingStep(),
new LogoutEventAgingStep(),
};
private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new();
private bool selfTestRunning = false;
private int currentStep = 0;
private DateTimeOffset lastTestStart;
/// <summary>
/// Window for the Self-Test logic.
/// Initializes a new instance of the <see cref="SelfTestWindow"/> class.
/// </summary>
internal class SelfTestWindow : Window
public SelfTestWindow()
: base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
{
private static readonly ModuleLog Log = new("AGING");
this.Size = new Vector2(800, 800);
this.SizeCondition = ImGuiCond.FirstUseEver;
private readonly List<IAgingStep> steps =
new()
{
new LoginEventAgingStep(),
new WaitFramesAgingStep(1000),
new EnterTerritoryAgingStep(148, "Central Shroud"),
new ActorTableAgingStep(),
new FateTableAgingStep(),
new ConditionAgingStep(),
new ToastAgingStep(),
new TargetAgingStep(),
new KeyStateAgingStep(),
new GamepadStateAgingStep(),
new ChatAgingStep(),
new HoverAgingStep(),
new LuminaAgingStep<TerritoryType>(),
new PartyFinderAgingStep(),
new HandledExceptionAgingStep(),
new LogoutEventAgingStep(),
};
this.RespectCloseHotkey = false;
}
private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new();
private bool selfTestRunning = false;
private int currentStep = 0;
private DateTimeOffset lastTestStart;
/// <summary>
/// Initializes a new instance of the <see cref="SelfTestWindow"/> class.
/// </summary>
public SelfTestWindow()
: base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
/// <inheritdoc/>
public override void Draw()
{
if (this.selfTestRunning)
{
this.Size = new Vector2(800, 800);
this.SizeCondition = ImGuiCond.FirstUseEver;
this.RespectCloseHotkey = false;
}
/// <inheritdoc/>
public override void Draw()
{
if (this.selfTestRunning)
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
{
this.StopTests();
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward))
{
this.stepResults.Add((SelfTestStepResult.NotRan, null));
this.currentStep++;
this.lastTestStart = DateTimeOffset.Now;
if (this.currentStep >= this.steps.Count)
{
this.StopTests();
}
}
}
else
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{
this.selfTestRunning = true;
this.currentStep = 0;
this.stepResults.Clear();
this.lastTestStart = DateTimeOffset.Now;
}
this.StopTests();
}
ImGui.SameLine();
ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}");
ImGuiHelpers.ScaledDummy(10);
this.DrawResultTable();
ImGuiHelpers.ScaledDummy(10);
if (this.currentStep >= this.steps.Count)
if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward))
{
if (this.selfTestRunning)
this.stepResults.Add((SelfTestStepResult.NotRan, null));
this.currentStep++;
this.lastTestStart = DateTimeOffset.Now;
if (this.currentStep >= this.steps.Count)
{
this.StopTests();
}
if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail))
{
ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!");
}
else
{
ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!");
}
return;
}
if (!this.selfTestRunning)
}
else
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{
return;
}
ImGui.Separator();
var step = this.steps[this.currentStep];
ImGui.TextUnformatted($"Current: {step.Name}");
ImGuiHelpers.ScaledDummy(10);
SelfTestStepResult result;
try
{
result = step.RunStep();
}
catch (Exception ex)
{
Log.Error(ex, $"Step failed: {step.Name}");
result = SelfTestStepResult.Fail;
}
ImGui.Separator();
if (result != SelfTestStepResult.Waiting)
{
var duration = DateTimeOffset.Now - this.lastTestStart;
this.currentStep++;
this.stepResults.Add((result, duration));
this.selfTestRunning = true;
this.currentStep = 0;
this.stepResults.Clear();
this.lastTestStart = DateTimeOffset.Now;
}
}
private void DrawResultTable()
ImGui.SameLine();
ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}");
ImGuiHelpers.ScaledDummy(10);
this.DrawResultTable();
ImGuiHelpers.ScaledDummy(10);
if (this.currentStep >= this.steps.Count)
{
if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders))
if (this.selfTestRunning)
{
ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f);
ImGui.TableSetupColumn("Name");
ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f);
ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f);
ImGui.TableHeadersRow();
for (var i = 0; i < this.steps.Count; i++)
{
var step = this.steps[i];
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.Text(i.ToString());
ImGui.TableSetColumnIndex(1);
ImGui.Text(step.Name);
ImGui.TableSetColumnIndex(2);
ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont);
if (this.stepResults.Count > i)
{
var result = this.stepResults[i];
switch (result.Result)
{
case SelfTestStepResult.Pass:
ImGui.TextColored(ImGuiColors.HealerGreen, "PASS");
break;
case SelfTestStepResult.Fail:
ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL");
break;
default:
ImGui.TextColored(ImGuiColors.DalamudGrey, "NR");
break;
}
}
else
{
if (this.selfTestRunning && this.currentStep == i)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT");
}
else
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "NR");
}
}
ImGui.PopFont();
ImGui.TableSetColumnIndex(3);
if (this.stepResults.Count > i)
{
var (_, duration) = this.stepResults[i];
if (duration.HasValue)
{
ImGui.TextUnformatted(duration.Value.ToString("g"));
}
}
else
{
if (this.selfTestRunning && this.currentStep == i)
{
ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g"));
}
}
}
ImGui.EndTable();
this.StopTests();
}
if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail))
{
ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!");
}
else
{
ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!");
}
return;
}
private void StopTests()
if (!this.selfTestRunning)
{
this.selfTestRunning = false;
return;
}
foreach (var agingStep in this.steps)
ImGui.Separator();
var step = this.steps[this.currentStep];
ImGui.TextUnformatted($"Current: {step.Name}");
ImGuiHelpers.ScaledDummy(10);
SelfTestStepResult result;
try
{
result = step.RunStep();
}
catch (Exception ex)
{
Log.Error(ex, $"Step failed: {step.Name}");
result = SelfTestStepResult.Fail;
}
ImGui.Separator();
if (result != SelfTestStepResult.Waiting)
{
var duration = DateTimeOffset.Now - this.lastTestStart;
this.currentStep++;
this.stepResults.Add((result, duration));
this.lastTestStart = DateTimeOffset.Now;
}
}
private void DrawResultTable()
{
if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders))
{
ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f);
ImGui.TableSetupColumn("Name");
ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f);
ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f);
ImGui.TableHeadersRow();
for (var i = 0; i < this.steps.Count; i++)
{
try
var step = this.steps[i];
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.Text(i.ToString());
ImGui.TableSetColumnIndex(1);
ImGui.Text(step.Name);
ImGui.TableSetColumnIndex(2);
ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont);
if (this.stepResults.Count > i)
{
agingStep.CleanUp();
var result = this.stepResults[i];
switch (result.Result)
{
case SelfTestStepResult.Pass:
ImGui.TextColored(ImGuiColors.HealerGreen, "PASS");
break;
case SelfTestStepResult.Fail:
ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL");
break;
default:
ImGui.TextColored(ImGuiColors.DalamudGrey, "NR");
break;
}
}
catch (Exception ex)
else
{
Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}");
if (this.selfTestRunning && this.currentStep == i)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT");
}
else
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "NR");
}
}
ImGui.PopFont();
ImGui.TableSetColumnIndex(3);
if (this.stepResults.Count > i)
{
var (_, duration) = this.stepResults[i];
if (duration.HasValue)
{
ImGui.TextUnformatted(duration.Value.ToString("g"));
}
}
else
{
if (this.selfTestRunning && this.currentStep == i)
{
ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g"));
}
}
}
ImGui.EndTable();
}
}
private void StopTests()
{
this.selfTestRunning = false;
foreach (var agingStep in this.steps)
{
try
{
agingStep.CleanUp();
}
catch (Exception ex)
{
Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -15,388 +15,387 @@ using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Serilog;
namespace Dalamud.Interface.Internal.Windows.StyleEditor
namespace Dalamud.Interface.Internal.Windows.StyleEditor;
/// <summary>
/// Window for the Dalamud style editor.
/// </summary>
public class StyleEditorWindow : Window
{
private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
private int currentSel = 0;
private string initialStyle = string.Empty;
private bool didSave = false;
private string renameText = string.Empty;
private bool renameModalDrawing = false;
/// <summary>
/// Window for the Dalamud style editor.
/// Initializes a new instance of the <see cref="StyleEditorWindow"/> class.
/// </summary>
public class StyleEditorWindow : Window
public StyleEditorWindow()
: base("Dalamud Style Editor")
{
private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
private int currentSel = 0;
private string initialStyle = string.Empty;
private bool didSave = false;
private string renameText = string.Empty;
private bool renameModalDrawing = false;
/// <summary>
/// Initializes a new instance of the <see cref="StyleEditorWindow"/> class.
/// </summary>
public StyleEditorWindow()
: base("Dalamud Style Editor")
this.IsOpen = true;
this.SizeConstraints = new WindowSizeConstraints
{
this.IsOpen = true;
this.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(890, 560),
MaximumSize = new Vector2(10000, 10000),
};
}
MinimumSize = new Vector2(890, 560),
MaximumSize = new Vector2(10000, 10000),
};
}
/// <inheritdoc />
public override void OnOpen()
{
this.didSave = false;
/// <inheritdoc />
public override void OnOpen()
{
this.didSave = false;
var config = Service<DalamudConfiguration>.Get();
config.SavedStyles ??= new List<StyleModel>();
this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle);
var config = Service<DalamudConfiguration>.Get();
config.SavedStyles ??= new List<StyleModel>();
this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle);
this.initialStyle = config.ChosenStyle;
this.initialStyle = config.ChosenStyle;
base.OnOpen();
}
base.OnOpen();
}
/// <inheritdoc />
public override void OnClose()
{
if (!this.didSave)
{
var config = Service<DalamudConfiguration>.Get();
var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle);
newStyle?.Apply();
}
base.OnClose();
}
/// <inheritdoc />
public override void Draw()
/// <inheritdoc />
public override void OnClose()
{
if (!this.didSave)
{
var config = Service<DalamudConfiguration>.Get();
var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style");
var workStyle = config.SavedStyles[this.currentSel];
workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors;
var appliedThisFrame = false;
var styleAry = config.SavedStyles.Select(x => x.Name).ToArray();
ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:"));
if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length))
{
var newStyle = config.SavedStyles[this.currentSel];
newStyle.Apply();
appliedThisFrame = true;
}
if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style")))
{
this.SaveStyle();
var newStyle = StyleModelV1.DalamudStandard;
newStyle.Name = GetRandomName();
config.SavedStyles.Add(newStyle);
this.currentSel = config.SavedStyles.Count - 1;
newStyle.Apply();
appliedThisFrame = true;
config.Save();
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0)
{
this.currentSel--;
var newStyle = config.SavedStyles[this.currentSel];
newStyle.Apply();
appliedThisFrame = true;
config.SavedStyles.RemoveAt(this.currentSel + 1);
config.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style"));
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0)
{
var newStyle = config.SavedStyles[this.currentSel];
this.renameText = newStyle.Name;
this.renameModalDrawing = true;
ImGui.OpenPopup(renameModalTitle);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style"));
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport))
{
var selectedStyle = config.SavedStyles[this.currentSel];
var newStyle = StyleModelV1.Get();
newStyle.Name = selectedStyle.Name;
ImGui.SetClipboardText(newStyle.Serialize());
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing"));
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport))
{
this.SaveStyle();
var styleJson = ImGui.GetClipboardText();
try
{
var newStyle = StyleModel.Deserialize(styleJson);
newStyle.Name ??= GetRandomName();
if (config.SavedStyles.Any(x => x.Name == newStyle.Name))
{
newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)";
}
config.SavedStyles.Add(newStyle);
newStyle.Apply();
appliedThisFrame = true;
this.currentSel = config.SavedStyles.Count - 1;
config.Save();
}
catch (Exception ex)
{
Log.Error(ex, "Could not import style");
}
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard"));
ImGui.Separator();
ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f);
if (this.currentSel < 2)
{
ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first."));
}
else if (appliedThisFrame)
{
ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style..."));
}
else if (ImGui.BeginTabBar("StyleEditorTabs"))
{
var style = ImGui.GetStyle();
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables")))
{
ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f");
ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f");
ImGui.Text("Borders");
ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.Text("Rounding");
ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f");
ImGui.Text("Alignment");
ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1;
if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0"))
style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1);
ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f");
ImGui.SameLine();
ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content.");
ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f");
ImGui.SameLine();
ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content.");
ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f");
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).");
ImGui.EndTabItem();
ImGui.EndChild();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors")))
{
ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None))
this.alphaFlags = ImGuiColorEditFlags.None;
ImGui.SameLine();
if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview))
this.alphaFlags = ImGuiColorEditFlags.AlphaPreview;
ImGui.SameLine();
if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf))
this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"In the color list:\n" +
"Left-click on color square to open color picker,\n" +
"Right-click to open edit options menu.");
foreach (var imGuiCol in Enum.GetValues<ImGuiCol>())
{
if (imGuiCol == ImGuiCol.COUNT)
continue;
ImGui.PushID(imGuiCol.ToString());
ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags);
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
ImGui.TextUnformatted(imGuiCol.ToString());
ImGui.PopID();
}
ImGui.Separator();
foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
ImGui.PushID(property.Name);
var colorVal = property.GetValue(workStyle.BuiltInColors);
if (colorVal == null)
{
colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors);
property.SetValue(workStyle.BuiltInColors, colorVal);
}
var color = (Vector4)colorVal;
if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags))
{
property.SetValue(workStyle.BuiltInColors, color);
workStyle.BuiltInColors?.Apply();
}
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
ImGui.TextUnformatted(property.Name);
ImGui.PopID();
}
ImGui.EndChild();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.PopItemWidth();
ImGui.Separator();
if (ImGui.Button(Loc.Localize("Close", "Close")))
{
this.IsOpen = false;
}
ImGui.SameLine();
if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close")))
{
this.SaveStyle();
config.ChosenStyle = config.SavedStyles[this.currentSel].Name;
Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle);
this.didSave = true;
this.IsOpen = false;
}
if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar))
{
ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style."));
ImGui.Spacing();
ImGui.InputText("###renameModalInput", ref this.renameText, 255);
const float buttonWidth = 120f;
ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2);
if (ImGui.Button("OK", new Vector2(buttonWidth, 40)))
{
config.SavedStyles[this.currentSel].Name = this.renameText;
config.Save();
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle);
newStyle?.Apply();
}
private static string GetRandomName()
base.OnClose();
}
/// <inheritdoc />
public override void Draw()
{
var config = Service<DalamudConfiguration>.Get();
var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style");
var workStyle = config.SavedStyles[this.currentSel];
workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors;
var appliedThisFrame = false;
var styleAry = config.SavedStyles.Select(x => x.Name).ToArray();
ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:"));
if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length))
{
var data = Service<DataManager>.Get();
var names = data.GetExcelSheet<BNpcName>(ClientLanguage.English);
var rng = new Random();
return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString;
}
private void SaveStyle()
{
if (this.currentSel < 2)
return;
var config = Service<DalamudConfiguration>.Get();
var newStyle = StyleModelV1.Get();
newStyle.Name = config.SavedStyles[this.currentSel].Name;
config.SavedStyles[this.currentSel] = newStyle;
var newStyle = config.SavedStyles[this.currentSel];
newStyle.Apply();
appliedThisFrame = true;
}
if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style")))
{
this.SaveStyle();
var newStyle = StyleModelV1.DalamudStandard;
newStyle.Name = GetRandomName();
config.SavedStyles.Add(newStyle);
this.currentSel = config.SavedStyles.Count - 1;
newStyle.Apply();
appliedThisFrame = true;
config.Save();
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0)
{
this.currentSel--;
var newStyle = config.SavedStyles[this.currentSel];
newStyle.Apply();
appliedThisFrame = true;
config.SavedStyles.RemoveAt(this.currentSel + 1);
config.Save();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style"));
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0)
{
var newStyle = config.SavedStyles[this.currentSel];
this.renameText = newStyle.Name;
this.renameModalDrawing = true;
ImGui.OpenPopup(renameModalTitle);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style"));
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport))
{
var selectedStyle = config.SavedStyles[this.currentSel];
var newStyle = StyleModelV1.Get();
newStyle.Name = selectedStyle.Name;
ImGui.SetClipboardText(newStyle.Serialize());
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing"));
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport))
{
this.SaveStyle();
var styleJson = ImGui.GetClipboardText();
try
{
var newStyle = StyleModel.Deserialize(styleJson);
newStyle.Name ??= GetRandomName();
if (config.SavedStyles.Any(x => x.Name == newStyle.Name))
{
newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)";
}
config.SavedStyles.Add(newStyle);
newStyle.Apply();
appliedThisFrame = true;
this.currentSel = config.SavedStyles.Count - 1;
config.Save();
}
catch (Exception ex)
{
Log.Error(ex, "Could not import style");
}
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard"));
ImGui.Separator();
ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f);
if (this.currentSel < 2)
{
ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first."));
}
else if (appliedThisFrame)
{
ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style..."));
}
else if (ImGui.BeginTabBar("StyleEditorTabs"))
{
var style = ImGui.GetStyle();
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables")))
{
ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f");
ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f");
ImGui.Text("Borders");
ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f");
ImGui.Text("Rounding");
ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f");
ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f");
ImGui.Text("Alignment");
ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1;
if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0"))
style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1);
ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f");
ImGui.SameLine();
ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content.");
ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f");
ImGui.SameLine();
ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content.");
ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f");
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).");
ImGui.EndTabItem();
ImGui.EndChild();
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors")))
{
ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None))
this.alphaFlags = ImGuiColorEditFlags.None;
ImGui.SameLine();
if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview))
this.alphaFlags = ImGuiColorEditFlags.AlphaPreview;
ImGui.SameLine();
if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf))
this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"In the color list:\n" +
"Left-click on color square to open color picker,\n" +
"Right-click to open edit options menu.");
foreach (var imGuiCol in Enum.GetValues<ImGuiCol>())
{
if (imGuiCol == ImGuiCol.COUNT)
continue;
ImGui.PushID(imGuiCol.ToString());
ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags);
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
ImGui.TextUnformatted(imGuiCol.ToString());
ImGui.PopID();
}
ImGui.Separator();
foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
ImGui.PushID(property.Name);
var colorVal = property.GetValue(workStyle.BuiltInColors);
if (colorVal == null)
{
colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors);
property.SetValue(workStyle.BuiltInColors, colorVal);
}
var color = (Vector4)colorVal;
if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags))
{
property.SetValue(workStyle.BuiltInColors, color);
workStyle.BuiltInColors?.Apply();
}
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
ImGui.TextUnformatted(property.Name);
ImGui.PopID();
}
ImGui.EndChild();
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.PopItemWidth();
ImGui.Separator();
if (ImGui.Button(Loc.Localize("Close", "Close")))
{
this.IsOpen = false;
}
ImGui.SameLine();
if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close")))
{
this.SaveStyle();
config.ChosenStyle = config.SavedStyles[this.currentSel].Name;
Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle);
this.didSave = true;
this.IsOpen = false;
}
if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar))
{
ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style."));
ImGui.Spacing();
ImGui.InputText("###renameModalInput", ref this.renameText, 255);
const float buttonWidth = 120f;
ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2);
if (ImGui.Button("OK", new Vector2(buttonWidth, 40)))
{
config.SavedStyles[this.currentSel].Name = this.renameText;
config.Save();
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
}
private static string GetRandomName()
{
var data = Service<DataManager>.Get();
var names = data.GetExcelSheet<BNpcName>(ClientLanguage.English);
var rng = new Random();
return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString;
}
private void SaveStyle()
{
if (this.currentSel < 2)
return;
var config = Service<DalamudConfiguration>.Get();
var newStyle = StyleModelV1.Get();
newStyle.Name = config.SavedStyles[this.currentSel].Name;
config.SavedStyles[this.currentSel] = newStyle;
newStyle.Apply();
config.Save();
}
}