Merge branch 'master' into v9

This commit is contained in:
Ava Chaney 2023-06-18 00:45:43 -07:00
commit 2e0e46384c
70 changed files with 2694 additions and 623 deletions

View file

@ -29,13 +29,19 @@ internal class DalamudCommands : IServiceType
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),
ShowInHelp = false,
});
commandManager.AddHandler("/xlkill", new CommandInfo(this.OnKillCommand)
{
HelpMessage = "Kill the game.",
ShowInHelp = false,
});
commandManager.AddHandler("/xlrestart", new CommandInfo(this.OnRestartCommand)
{
HelpMessage = "Restart the game.",
ShowInHelp = false,
});
commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand)
{
HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available. If an argument is provided, shows help for that command."),
@ -147,12 +153,17 @@ internal class DalamudCommands : IServiceType
Service<ChatGui>.Get().Print("Unloading...");
Service<Dalamud>.Get().Unload();
}
private void OnKillCommand(string command, string arguments)
{
Process.GetCurrentProcess().Kill();
}
private void OnRestartCommand(string command, string arguments)
{
Dalamud.RestartGame();
}
private void OnHelpCommand(string command, string arguments)
{
var chatGui = Service<ChatGui>.Get();

View file

@ -717,12 +717,7 @@ internal class DalamudInterface : IDisposable, IServiceType
if (ImGui.MenuItem("Restart game"))
{
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments);
RaiseException(0x12345678, 0, 0, IntPtr.Zero);
Process.GetCurrentProcess().Kill();
Dalamud.RestartGame();
}
if (ImGui.MenuItem("Kill game"))
@ -802,6 +797,11 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGui.SetWindowFocus(null);
}
if (ImGui.MenuItem("Clear stacks"))
{
Service<InterfaceManager>.Get().ClearStacks();
}
if (ImGui.MenuItem("Dump style"))
{
var info = string.Empty;

View file

@ -435,6 +435,15 @@ internal class InterfaceManager : IDisposable, IServiceType
return null;
}
/// <summary>
/// Clear font, style, and color stack. Dangerous, only use when you know
/// no one else has something pushed they may try to pop.
/// </summary>
public void ClearStacks()
{
this.scene?.ClearStacksOnContext();
}
/// <summary>
/// Toggle Windows 11 immersive mode on the game window.
/// </summary>
@ -892,6 +901,7 @@ internal class InterfaceManager : IDisposable, IServiceType
Log.Verbose("[FONT] ImGui.IO.Build will be called.");
ioFonts.Build();
gameFontManager.AfterIoFontsBuild();
this.ClearStacks();
Log.Verbose("[FONT] ImGui.IO.Build OK!");
gameFontManager.AfterBuildFonts();
@ -1005,8 +1015,8 @@ internal class InterfaceManager : IDisposable, IServiceType
Log.Error(ex, "Could not enable immersive mode");
}
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour, true);
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour, true);
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour);
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
Log.Verbose("===== S W A P C H A I N =====");
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");

View file

@ -28,6 +28,7 @@ internal class PluginCategoryManager
new(11, "special.devIconTester", () => Locs.Category_IconTester),
new(12, "special.dalamud", () => Locs.Category_Dalamud),
new(13, "special.plugins", () => Locs.Category_Plugins),
new(14, "special.profiles", () => Locs.Category_PluginProfiles, CategoryInfo.AppearCondition.ProfilesEnabled),
new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
@ -43,7 +44,7 @@ internal class PluginCategoryManager
private GroupInfo[] groupList =
{
new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11),
new(GroupKind.Installed, () => Locs.Group_Installed, 0, 1),
new(GroupKind.Installed, () => Locs.Group_Installed, 0, 1, 14),
new(GroupKind.Available, () => Locs.Group_Available, 0),
new(GroupKind.Changelog, () => Locs.Group_Changelog, 0, 12, 13),
@ -352,6 +353,11 @@ internal class PluginCategoryManager
/// Check if plugin testing is enabled.
/// </summary>
DoPluginTest,
/// <summary>
/// Check if plugin profiles are enabled.
/// </summary>
ProfilesEnabled,
}
/// <summary>
@ -430,6 +436,8 @@ internal class PluginCategoryManager
public static string Category_IconTester => "Image/Icon Tester";
public static string Category_PluginProfiles => Loc.Localize("InstallerCategoryPluginProfiles", "Plugin Collections");
public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");

View file

@ -1792,7 +1792,8 @@ internal class DataWindow : Window
ImGui.TableNextColumn();
ImGui.TextUnformatted(string.Join(", ", share.Users));
}
} finally
}
finally
{
ImGui.EndTable();
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Dalamud.Networking.Http;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;

View file

@ -15,12 +15,14 @@ using Dalamud.Game.Command;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Raii;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
using Dalamud.Logging.Internal;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Exceptions;
using Dalamud.Plugin.Internal.Profiles;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Support;
using Dalamud.Utility;
@ -48,6 +50,8 @@ internal class PluginInstallerWindow : Window, IDisposable
private readonly object listLock = new();
private readonly ProfileManagerWidget profileManagerWidget;
private DalamudChangelogManager? dalamudChangelogManager;
private Task? dalamudChangelogRefreshTask;
private CancellationTokenSource? dalamudChangelogRefreshTaskCts;
@ -148,6 +152,8 @@ internal class PluginInstallerWindow : Window, IDisposable
});
this.timeLoaded = DateTime.Now;
this.profileManagerWidget = new(this);
}
private enum OperationStatus
@ -166,6 +172,7 @@ internal class PluginInstallerWindow : Window, IDisposable
UpdatingAll,
Installing,
Manager,
ProfilesLoading,
}
private enum PluginSortKind
@ -212,6 +219,8 @@ internal class PluginInstallerWindow : Window, IDisposable
this.updatePluginCount = 0;
this.updatedPlugins = null;
}
this.profileManagerWidget.Reset();
}
/// <inheritdoc/>
@ -284,17 +293,96 @@ internal class PluginInstallerWindow : Window, IDisposable
this.searchText = text;
}
/// <summary>
/// Start a plugin install and handle errors visually.
/// </summary>
/// <param name="manifest">The manifest to install.</param>
/// <param name="useTesting">Install the testing version.</param>
public void StartInstall(RemotePluginManifest manifest, bool useTesting)
{
var pluginManager = Service<PluginManager>.Get();
var notifications = Service<NotificationManager>.Get();
this.installStatus = OperationStatus.InProgress;
this.loadingIndicatorKind = LoadingIndicatorKind.Installing;
Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting || manifest.IsTestingExclusive, PluginLoadReason.Installer))
.ContinueWith(task =>
{
// There is no need to set as Complete for an individual plugin installation
this.installStatus = OperationStatus.Idle;
if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name)))
{
// Fine as long as we aren't in an error state
if (task.Result.State is PluginState.Loaded or PluginState.Unloaded)
{
notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success);
}
else
{
notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error);
this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name));
}
}
});
}
/// <summary>
/// A continuation task that displays any errors received into the error modal.
/// </summary>
/// <param name="task">The previous task.</param>
/// <param name="state">An error message to be displayed.</param>
/// <returns>A value indicating whether to continue with the next task.</returns>
public bool DisplayErrorContinuation(Task task, object state)
{
if (task.IsFaulted)
{
var errorModalMessage = state as string;
foreach (var ex in task.Exception.InnerExceptions)
{
if (ex is PluginException)
{
Log.Error(ex, "Plugin installer threw an error");
#if DEBUG
if (!string.IsNullOrEmpty(ex.Message))
errorModalMessage += $"\n\n{ex.Message}";
#endif
}
else
{
Log.Error(ex, "Plugin installer threw an unexpected error");
#if DEBUG
if (!string.IsNullOrEmpty(ex.Message))
errorModalMessage += $"\n\n{ex.Message}";
#endif
}
}
this.ShowErrorModal(errorModalMessage);
return false;
}
return true;
}
private void DrawProgressOverlay()
{
var pluginManager = Service<PluginManager>.Get();
var profileManager = Service<ProfileManager>.Get();
var isWaitingManager = !pluginManager.PluginsReady ||
!pluginManager.ReposReady;
var isWaitingProfiles = profileManager.IsBusy;
var isLoading = this.AnyOperationInProgress ||
isWaitingManager;
isWaitingManager || isWaitingProfiles;
if (isWaitingManager)
this.loadingIndicatorKind = LoadingIndicatorKind.Manager;
else if (isWaitingProfiles)
this.loadingIndicatorKind = LoadingIndicatorKind.ProfilesLoading;
if (!isLoading)
return;
@ -378,6 +466,9 @@ internal class PluginInstallerWindow : Window, IDisposable
}
}
break;
case LoadingIndicatorKind.ProfilesLoading:
ImGuiHelpers.CenteredText("Collections are being applied...");
break;
default:
throw new ArgumentOutOfRangeException();
@ -386,9 +477,7 @@ internal class PluginInstallerWindow : Window, IDisposable
if (DateTime.Now - this.timeLoaded > TimeSpan.FromSeconds(90) && !pluginManager.PluginsReady)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGuiHelpers.CenteredText("This is embarrassing, but...");
ImGuiHelpers.CenteredText("one of your plugins may be blocking the installer.");
ImGuiHelpers.CenteredText("You should tell us about this, please keep this window open.");
ImGuiHelpers.CenteredText("One of your plugins may be blocking the installer.");
ImGui.PopStyleColor();
}
@ -433,54 +522,57 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth);
var searchTextChanged = false;
ImGui.SetNextItemWidth(searchInputWidth);
searchTextChanged |= ImGui.InputTextWithHint(
"###XlPluginInstaller_Search",
Locs.Header_SearchPlaceholder,
ref this.searchText,
100);
ImGui.SameLine();
ImGui.SetCursorPosY(downShift);
ImGui.SetNextItemWidth(searchClearButtonWidth);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
var isProfileManager =
this.categoryManager.CurrentGroupIdx == 1 && this.categoryManager.CurrentCategoryIdx == 2;
// Disable search if profile editor
using (ImRaii.Disabled(isProfileManager))
{
this.searchText = string.Empty;
searchTextChanged = true;
}
var searchTextChanged = false;
ImGui.SetNextItemWidth(searchInputWidth);
searchTextChanged |= ImGui.InputTextWithHint(
"###XlPluginInstaller_Search",
Locs.Header_SearchPlaceholder,
ref this.searchText,
100);
if (searchTextChanged)
this.UpdateCategoriesOnSearchChange();
ImGui.SameLine();
ImGui.SetCursorPosY(downShift);
// Changelog group
var isSortDisabled = this.categoryManager.CurrentGroupIdx == 3;
if (isSortDisabled)
ImGui.BeginDisabled();
ImGui.SameLine();
ImGui.SetCursorPosY(downShift);
ImGui.SetNextItemWidth(selectableWidth);
if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton))
{
foreach (var selectable in sortSelectables)
ImGui.SetNextItemWidth(searchClearButtonWidth);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
{
if (ImGui.Selectable(selectable.Localization))
{
this.sortKind = selectable.SortKind;
this.filterText = selectable.Localization;
lock (this.listLock)
this.ResortPlugins();
}
this.searchText = string.Empty;
searchTextChanged = true;
}
ImGui.EndCombo();
if (searchTextChanged)
this.UpdateCategoriesOnSearchChange();
}
if (isSortDisabled)
ImGui.EndDisabled();
// Disable sort if changelogs or profile editor
using (ImRaii.Disabled(this.categoryManager.CurrentGroupIdx == 3 || isProfileManager))
{
ImGui.SameLine();
ImGui.SetCursorPosY(downShift);
ImGui.SetNextItemWidth(selectableWidth);
if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton))
{
foreach (var selectable in sortSelectables)
{
if (ImGui.Selectable(selectable.Localization))
{
this.sortKind = selectable.SortKind;
this.filterText = selectable.Localization;
lock (this.listLock)
this.ResortPlugins();
}
}
ImGui.EndCombo();
}
}
}
private void DrawFooter()
@ -1089,6 +1181,10 @@ internal class PluginInstallerWindow : Window, IDisposable
if (!Service<DalamudConfiguration>.Get().DoPluginTest)
continue;
break;
case PluginCategoryManager.CategoryInfo.AppearCondition.ProfilesEnabled:
if (!Service<DalamudConfiguration>.Get().ProfilesEnabled)
continue;
break;
default:
throw new ArgumentOutOfRangeException();
}
@ -1194,6 +1290,10 @@ internal class PluginInstallerWindow : Window, IDisposable
case 1:
this.DrawInstalledPluginList(true);
break;
case 2:
this.profileManagerWidget.Draw();
break;
}
break;
@ -1533,7 +1633,7 @@ internal class PluginInstallerWindow : Window, IDisposable
ImGui.SetCursorPos(startCursor);
var pluginDisabled = plugin is { IsDisabled: true };
var pluginDisabled = plugin is { IsWantedByAnyProfile: false };
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
var cursorBeforeImage = ImGui.GetCursorPos();
@ -1831,27 +1931,7 @@ internal class PluginInstallerWindow : Window, IDisposable
var buttonText = Locs.PluginButton_InstallVersion(versionString);
if (ImGui.Button($"{buttonText}##{buttonText}{index}"))
{
this.installStatus = OperationStatus.InProgress;
this.loadingIndicatorKind = LoadingIndicatorKind.Installing;
Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting || manifest.IsTestingExclusive, PluginLoadReason.Installer))
.ContinueWith(task =>
{
// There is no need to set as Complete for an individual plugin installation
this.installStatus = OperationStatus.Idle;
if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name)))
{
if (task.Result.State == PluginState.Loaded)
{
notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success);
}
else
{
notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error);
this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name));
}
}
});
this.StartInstall(manifest, useTesting);
}
}
@ -1958,7 +2038,7 @@ internal class PluginInstallerWindow : Window, IDisposable
}
// Disabled
if (plugin.IsDisabled || !plugin.CheckPolicy())
if (!plugin.IsWantedByAnyProfile || !plugin.CheckPolicy())
{
label += Locs.PluginTitleMod_Disabled;
trouble = true;
@ -2118,7 +2198,7 @@ internal class PluginInstallerWindow : Window, IDisposable
this.DrawSendFeedbackButton(plugin.Manifest, plugin.IsTesting);
}
if (availablePluginUpdate != default)
if (availablePluginUpdate != default && !plugin.IsDev)
this.DrawUpdateSinglePluginButton(availablePluginUpdate);
ImGui.SameLine();
@ -2240,6 +2320,11 @@ internal class PluginInstallerWindow : Window, IDisposable
{
var notifications = Service<NotificationManager>.Get();
var pluginManager = Service<PluginManager>.Get();
var profileManager = Service<ProfileManager>.Get();
var config = Service<DalamudConfiguration>.Get();
var applicableForProfiles = plugin.Manifest.SupportsProfiles;
var isDefaultPlugin = profileManager.IsInDefaultProfile(plugin.Manifest.InternalName);
// Disable everything if the updater is running or another plugin is operating
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
@ -2255,15 +2340,70 @@ internal class PluginInstallerWindow : Window, IDisposable
// Now handled by the first case below
// disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed;
// Disable everything if we're working
// Disable everything if we're loading plugins
disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading;
// Disable everything if we're applying profiles
disabled = disabled || profileManager.IsBusy;
var toggleId = plugin.Manifest.InternalName;
var isLoadedAndUnloadable = plugin.State == PluginState.Loaded ||
plugin.State == PluginState.DependencyResolutionFailed;
StyleModelV1.DalamudStandard.Push();
var profileChooserPopupName = $"###pluginProfileChooser{plugin.Manifest.InternalName}";
if (ImGui.BeginPopup(profileChooserPopupName))
{
var didAny = false;
foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile))
{
var inProfile = profile.WantsPlugin(plugin.Manifest.InternalName) != null;
if (ImGui.Checkbox($"###profilePick{profile.Guid}{plugin.Manifest.InternalName}", ref inProfile))
{
if (inProfile)
{
Task.Run(() => profile.AddOrUpdate(plugin.Manifest.InternalName, true))
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotAdd);
}
else
{
Task.Run(() => profile.Remove(plugin.Manifest.InternalName))
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotRemove);
}
}
ImGui.SameLine();
ImGui.TextUnformatted(profile.Name);
didAny = true;
}
if (!didAny)
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.Profiles_None);
ImGui.Separator();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
{
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, plugin.IsLoaded, false);
foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile && x.Plugins.Any(y => y.InternalName == plugin.Manifest.InternalName)))
{
profile.Remove(plugin.Manifest.InternalName, false);
}
// TODO error handling
Task.Run(() => profileManager.ApplyAllWantStates());
}
ImGui.SameLine();
ImGui.Text(Locs.Profiles_RemoveFromAll);
ImGui.EndPopup();
}
if (plugin.State is PluginState.UnloadError or PluginState.LoadError or PluginState.DependencyResolutionFailed && !plugin.IsDev)
{
ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown);
@ -2271,14 +2411,18 @@ internal class PluginInstallerWindow : Window, IDisposable
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed);
}
else if (disabled)
else if (disabled || !isDefaultPlugin)
{
ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable);
if (!isDefaultPlugin && ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.PluginButtonToolTip_NeedsToBeInDefault);
}
else
{
if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable))
{
// TODO: We can technically let profile manager take care of unloading/loading the plugin, but we should figure out error handling first.
if (!isLoadedAndUnloadable)
{
this.enableDisableStatus = OperationStatus.InProgress;
@ -2301,15 +2445,9 @@ internal class PluginInstallerWindow : Window, IDisposable
return;
}
var disableTask = Task.Run(() => plugin.Disable())
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name));
disableTask.Wait();
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, false, false);
this.enableDisableStatus = OperationStatus.Complete;
if (!disableTask.Result)
return;
notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
});
}
@ -2325,17 +2463,7 @@ internal class PluginInstallerWindow : Window, IDisposable
plugin.ReloadManifest();
}
var enableTask = Task.Run(plugin.Enable)
.ContinueWith(
this.DisplayErrorContinuation,
Locs.ErrorModal_EnableFail(plugin.Name));
enableTask.Wait();
if (!enableTask.Result)
{
this.enableDisableStatus = OperationStatus.Complete;
return;
}
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
.ContinueWith(
@ -2388,6 +2516,29 @@ internal class PluginInstallerWindow : Window, IDisposable
// Only if the plugin isn't broken.
this.DrawOpenPluginSettingsButton(plugin);
}
if (applicableForProfiles && config.ProfilesEnabled)
{
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Toolbox))
{
ImGui.OpenPopup(profileChooserPopupName);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.PluginButtonToolTip_PickProfiles);
}
else if (!applicableForProfiles && config.ProfilesEnabled)
{
ImGui.SameLine();
ImGui.BeginDisabled();
ImGuiComponents.IconButton(FontAwesomeIcon.Toolbox);
ImGui.EndDisabled();
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.PluginButtonToolTip_ProfilesNotSupported);
}
}
private async Task<bool> UpdateSinglePlugin(AvailablePluginUpdate update)
@ -2474,26 +2625,32 @@ internal class PluginInstallerWindow : Window, IDisposable
if (localPlugin is LocalDevPlugin plugin)
{
var isInDefaultProfile =
Service<ProfileManager>.Get().IsInDefaultProfile(localPlugin.Manifest.InternalName);
// https://colorswall.com/palette/2868/
var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF;
var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF;
// Load on boot
ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff))
using (ImRaii.Disabled(!isInDefaultProfile))
{
plugin.StartOnBoot ^= true;
configuration.QueueSave();
}
ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor);
ImGui.PopStyleColor(2);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff))
{
plugin.StartOnBoot ^= true;
configuration.QueueSave();
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot);
ImGui.PopStyleColor(2);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(isInDefaultProfile ? Locs.PluginButtonToolTip_StartOnBoot : Locs.PluginButtonToolTip_NeedsToBeInDefault);
}
}
// Automatic reload
@ -2705,8 +2862,8 @@ internal class PluginInstallerWindow : Window, IDisposable
return true;
return hasSearchString && !(
manifest.Name.ToLowerInvariant().Contains(searchString) ||
manifest.InternalName.ToLowerInvariant().Contains(searchString) ||
(!manifest.Name.IsNullOrEmpty() && manifest.Name.ToLowerInvariant().Contains(searchString)) ||
(!manifest.InternalName.IsNullOrEmpty() && manifest.InternalName.ToLowerInvariant().Contains(searchString)) ||
(!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) ||
(!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) ||
(manifest.Tags != null && manifest.Tags.Any(tag => tag.ToLowerInvariant().Contains(searchString))));
@ -2800,46 +2957,6 @@ internal class PluginInstallerWindow : Window, IDisposable
private bool WasPluginSeen(string internalName) =>
Service<DalamudConfiguration>.Get().SeenPluginInternalName.Contains(internalName);
/// <summary>
/// A continuation task that displays any errors received into the error modal.
/// </summary>
/// <param name="task">The previous task.</param>
/// <param name="state">An error message to be displayed.</param>
/// <returns>A value indicating whether to continue with the next task.</returns>
private bool DisplayErrorContinuation(Task task, object state)
{
if (task.IsFaulted)
{
var errorModalMessage = state as string;
foreach (var ex in task.Exception.InnerExceptions)
{
if (ex is PluginException)
{
Log.Error(ex, "Plugin installer threw an error");
#if DEBUG
if (!string.IsNullOrEmpty(ex.Message))
errorModalMessage += $"\n\n{ex.Message}";
#endif
}
else
{
Log.Error(ex, "Plugin installer threw an unexpected error");
#if DEBUG
if (!string.IsNullOrEmpty(ex.Message))
errorModalMessage += $"\n\n{ex.Message}";
#endif
}
}
this.ShowErrorModal(errorModalMessage);
return false;
}
return true;
}
private Task ShowErrorModal(string message)
{
this.errorModalMessage = message;
@ -2890,7 +3007,7 @@ internal class PluginInstallerWindow : Window, IDisposable
#region Header
public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers.");
public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove Dalamud plugins.\nThey are made by the community.");
public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search");
@ -3047,6 +3164,10 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration");
public static string PluginButtonToolTip_PickProfiles => Loc.Localize("InstallerPickProfiles", "Pick collections for this plugin");
public static string PluginButtonToolTip_ProfilesNotSupported => Loc.Localize("InstallerProfilesNotSupported", "This plugin does not support collections");
public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot");
public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading");
@ -3067,6 +3188,8 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerLoadUnloadFailedTooltip", "Plugin load/unload failed, please restart your game and try again.");
public static string PluginButtonToolTip_NeedsToBeInDefault => Loc.Localize("InstallerUnloadNeedsToBeInDefault", "This plugin is in one or more collections. If you want to enable or disable it, please do so by enabling or disabling the collections it is in.\nIf you want to manage it manually, remove it from all collections.");
#endregion
#region Notifications
@ -3226,5 +3349,20 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string SafeModeDisclaimer => Loc.Localize("SafeModeDisclaimer", "You enabled safe mode, no plugins will be loaded.\nYou may delete plugins from the \"Installed plugins\" tab.\nSimply restart your game to disable safe mode.");
#endregion
#region Profiles
public static string Profiles_CouldNotAdd =>
Loc.Localize("InstallerProfilesCouldNotAdd", "Couldn't add plugin to this collection.");
public static string Profiles_CouldNotRemove =>
Loc.Localize("InstallerProfilesCouldNotRemove", "Couldn't remove plugin from this collection.");
public static string Profiles_None => Loc.Localize("InstallerProfilesNone", "No collections! Go add some in \"Plugin Collections\"!");
public static string Profiles_RemoveFromAll =>
Loc.Localize("InstallerProfilesRemoveFromAll", "Remove from all collections");
#endregion
}
}

View file

@ -0,0 +1,506 @@
using System;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Raii;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Profiles;
using Dalamud.Utility;
using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.Internal.Windows.PluginInstaller;
/// <summary>
/// ImGui widget used to manage profiles.
/// </summary>
internal class ProfileManagerWidget
{
private readonly PluginInstallerWindow installer;
private Mode mode = Mode.Overview;
private Guid? editingProfileGuid;
private string pickerSearch = string.Empty;
private string profileNameEdit = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="ProfileManagerWidget"/> class.
/// </summary>
/// <param name="installer">The plugin installer.</param>
public ProfileManagerWidget(PluginInstallerWindow installer)
{
this.installer = installer;
}
private enum Mode
{
Overview,
EditSingleProfile,
}
/// <summary>
/// Draw this widget's contents.
/// </summary>
public void Draw()
{
switch (this.mode)
{
case Mode.Overview:
this.DrawOverview();
break;
case Mode.EditSingleProfile:
this.DrawEdit();
break;
}
}
/// <summary>
/// Reset the widget.
/// </summary>
public void Reset()
{
this.mode = Mode.Overview;
this.editingProfileGuid = null;
this.pickerSearch = string.Empty;
}
private void DrawOverview()
{
var didAny = false;
var profman = Service<ProfileManager>.Get();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
profman.AddNewProfile();
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.AddProfile);
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport))
{
try
{
profman.ImportProfile(ImGui.GetClipboardText());
Service<NotificationManager>.Get().AddNotification(Locs.NotificationImportSuccess, type: NotificationType.Success);
}
catch (Exception ex)
{
Log.Error(ex, "Could not import profile");
Service<NotificationManager>.Get().AddNotification(Locs.NotificationImportError, type: NotificationType.Error);
}
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.ImportProfileHint);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5);
var windowSize = ImGui.GetWindowSize();
if (ImGui.BeginChild("###profileChooserScrolling"))
{
Guid? toCloneGuid = null;
foreach (var profile in profman.Profiles)
{
if (profile.IsDefaultProfile)
continue;
var isEnabled = profile.IsEnabled;
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
{
Task.Run(() => profile.SetState(isEnabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(3);
ImGui.SameLine();
ImGui.Text(profile.Name);
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30));
if (ImGuiComponents.IconButton($"###editButton{profile.Guid}", FontAwesomeIcon.PencilAlt))
{
this.mode = Mode.EditSingleProfile;
this.editingProfileGuid = profile.Guid;
this.profileNameEdit = profile.Name;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.EditProfileHint);
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30 * 2) - 5);
if (ImGuiComponents.IconButton($"###cloneButton{profile.Guid}", FontAwesomeIcon.Copy))
toCloneGuid = profile.Guid;
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.CloneProfileHint);
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30 * 3) - 5);
if (ImGuiComponents.IconButton($"###exportButton{profile.Guid}", FontAwesomeIcon.FileExport))
{
ImGui.SetClipboardText(profile.Model.Serialize());
Service<NotificationManager>.Get().AddNotification(Locs.CopyToClipboardNotification, type: NotificationType.Success);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.CopyToClipboardHint);
didAny = true;
ImGuiHelpers.ScaledDummy(2);
}
if (toCloneGuid != null)
{
profman.CloneProfile(profman.Profiles.First(x => x.Guid == toCloneGuid));
}
if (!didAny)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
ImGuiHelpers.CenteredText(Locs.AddProfileHint);
ImGui.PopStyleColor();
}
ImGui.EndChild();
}
}
private void DrawEdit()
{
if (this.editingProfileGuid == null)
{
Log.Error("Editing profile guid was null");
this.Reset();
return;
}
var profman = Service<ProfileManager>.Get();
var pm = Service<PluginManager>.Get();
var pic = Service<PluginImageCache>.Get();
var profile = profman.Profiles.FirstOrDefault(x => x.Guid == this.editingProfileGuid);
if (profile == null)
{
Log.Error("Could not find profile {Guid} for edit", this.editingProfileGuid);
this.Reset();
return;
}
const string addPluginToProfilePopup = "###addPluginToProfile";
using (var popup = ImRaii.Popup(addPluginToProfilePopup))
{
if (popup.Success)
{
var width = ImGuiHelpers.GlobalScale * 300;
using var disabled = ImRaii.Disabled(profman.IsBusy);
ImGui.SetNextItemWidth(width);
ImGui.InputTextWithHint("###pluginPickerSearch", Locs.SearchHint, ref this.pickerSearch, 255);
if (ImGui.BeginListBox("###pluginPicker", new Vector2(width, width - 80)))
{
// TODO: Plugin searching should be abstracted... installer and this should use the same search
foreach (var plugin in pm.InstalledPlugins.Where(x => x.Manifest.SupportsProfiles &&
(this.pickerSearch.IsNullOrWhitespace() || x.Manifest.Name.ToLowerInvariant().Contains(this.pickerSearch.ToLowerInvariant()))))
{
using var disabled2 =
ImRaii.Disabled(profile.Plugins.Any(y => y.InternalName == plugin.Manifest.InternalName));
if (ImGui.Selectable($"{plugin.Manifest.Name}###selector{plugin.Manifest.InternalName}"))
{
// TODO this sucks
profile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
Task.Run(() => profman.ApplyAllWantStates())
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
}
ImGui.EndListBox();
}
}
}
var didAny = false;
// ======== Top bar ========
var windowSize = ImGui.GetWindowSize();
if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowLeft))
this.Reset();
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.BackToOverview);
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport))
{
ImGui.SetClipboardText(profile.Model.Serialize());
Service<NotificationManager>.Get().AddNotification(Locs.CopyToClipboardNotification, type: NotificationType.Success);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.CopyToClipboardHint);
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash))
{
this.Reset();
// DeleteProfile() is sync, it doesn't apply and we are modifying the plugins collection. Will throw below when iterating
profman.DeleteProfile(profile);
Task.Run(() => profman.ApplyAllWantStates())
.ContinueWith(t =>
{
this.installer.DisplayErrorContinuation(t, Locs.ErrorCouldNotChangeState);
});
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.DeleteProfileHint);
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
ImGui.SetNextItemWidth(windowSize.X / 3);
if (ImGui.InputText("###profileNameInput", ref this.profileNameEdit, 255))
{
profile.Name = this.profileNameEdit;
}
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGui.GetFrameHeight() * 1.55f * ImGuiHelpers.GlobalScale));
var isEnabled = profile.IsEnabled;
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
{
Task.Run(() => profile.SetState(isEnabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.TooltipEnableDisable);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5);
var enableAtBoot = profile.AlwaysEnableAtBoot;
if (ImGui.Checkbox(Locs.AlwaysEnableAtBoot, ref enableAtBoot))
{
profile.AlwaysEnableAtBoot = enableAtBoot;
}
ImGuiHelpers.ScaledDummy(5);
ImGui.Separator();
var wantPluginAddPopup = false;
if (ImGui.BeginChild("###profileEditorPluginList"))
{
var pluginLineHeight = 32 * ImGuiHelpers.GlobalScale;
string? wantRemovePluginInternalName = null;
foreach (var plugin in profile.Plugins)
{
didAny = true;
var pmPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.InternalName == plugin.InternalName);
var btnOffset = 2;
if (pmPlugin != null)
{
pic.TryGetIcon(pmPlugin, pmPlugin.Manifest, pmPlugin.Manifest.IsThirdParty, out var icon);
icon ??= pic.DefaultIcon;
ImGui.Image(icon.ImGuiHandle, new Vector2(pluginLineHeight));
ImGui.SameLine();
var text = $"{pmPlugin.Name}";
var textHeight = ImGui.CalcTextSize(text);
var before = ImGui.GetCursorPos();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (textHeight.Y / 2));
ImGui.TextUnformatted(text);
ImGui.SetCursorPos(before);
}
else
{
ImGui.Image(pic.DefaultIcon.ImGuiHandle, new Vector2(pluginLineHeight));
ImGui.SameLine();
var text = Locs.NotInstalled(plugin.InternalName);
var textHeight = ImGui.CalcTextSize(text);
var before = ImGui.GetCursorPos();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (textHeight.Y / 2));
ImGui.TextUnformatted(text);
var available =
pm.AvailablePlugins.FirstOrDefault(
x => x.InternalName == plugin.InternalName && !x.SourceRepo.IsThirdParty);
if (available != null)
{
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30 * 2) - 2);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (ImGui.GetFrameHeight() / 2));
btnOffset = 3;
if (ImGuiComponents.IconButton($"###installMissingPlugin{available.InternalName}", FontAwesomeIcon.Download))
{
this.installer.StartInstall(available, false);
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.InstallPlugin);
}
ImGui.SetCursorPos(before);
}
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30));
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (ImGui.GetFrameHeight() / 2));
var enabled = plugin.IsEnabled;
if (ImGui.Checkbox($"###{this.editingProfileGuid}-{plugin.InternalName}", ref enabled))
{
Task.Run(() => profile.AddOrUpdate(plugin.InternalName, enabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
ImGui.SameLine();
ImGui.SetCursorPosX(windowSize.X - (ImGuiHelpers.GlobalScale * 30 * btnOffset) - 5);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (pluginLineHeight / 2) - (ImGui.GetFrameHeight() / 2));
if (ImGuiComponents.IconButton($"###removePlugin{plugin.InternalName}", FontAwesomeIcon.Trash))
{
wantRemovePluginInternalName = plugin.InternalName;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Locs.RemovePlugin);
}
if (wantRemovePluginInternalName != null)
{
// TODO: handle error
profile.Remove(wantRemovePluginInternalName, false);
Task.Run(() => profman.ApplyAllWantStates())
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotRemove);
}
if (!didAny)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.NoPluginsInProfile);
}
ImGuiHelpers.ScaledDummy(10);
var addPluginsText = Locs.AddPlugin;
ImGuiHelpers.CenterCursorFor((int)(ImGui.CalcTextSize(addPluginsText).X + 30 + (ImGuiHelpers.GlobalScale * 5)));
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
wantPluginAddPopup = true;
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
ImGui.TextUnformatted(addPluginsText);
ImGuiHelpers.ScaledDummy(10);
ImGui.EndChild();
}
if (wantPluginAddPopup)
{
this.pickerSearch = string.Empty;
ImGui.OpenPopup(addPluginToProfilePopup);
}
}
private static class Locs
{
public static string TooltipEnableDisable =>
Loc.Localize("ProfileManagerEnableDisableHint", "Enable/Disable this collection");
public static string InstallPlugin => Loc.Localize("ProfileManagerInstall", "Install this plugin");
public static string RemovePlugin =>
Loc.Localize("ProfileManagerRemoveFromProfile", "Remove plugin from this collection");
public static string AddPlugin => Loc.Localize("ProfileManagerAddPlugin", "Add a plugin!");
public static string NoPluginsInProfile =>
Loc.Localize("ProfileManagerNoPluginsInProfile", "Collection has no plugins!");
public static string AlwaysEnableAtBoot =>
Loc.Localize("ProfileManagerAlwaysEnableAtBoot", "Always enable when game starts");
public static string DeleteProfileHint => Loc.Localize("ProfileManagerDeleteProfile", "Delete this collection");
public static string CopyToClipboardHint =>
Loc.Localize("ProfileManagerCopyToClipboard", "Copy collection to clipboard for sharing");
public static string CopyToClipboardNotification =>
Loc.Localize("ProfileManagerCopyToClipboardHint", "Copied to clipboard!");
public static string BackToOverview => Loc.Localize("ProfileManagerBackToOverview", "Back to overview");
public static string SearchHint => Loc.Localize("ProfileManagerSearchHint", "Search...");
public static string AddProfileHint => Loc.Localize("ProfileManagerAddProfileHint", "No collections! Add one!");
public static string CloneProfileHint => Loc.Localize("ProfileManagerCloneProfile", "Clone this collection");
public static string EditProfileHint => Loc.Localize("ProfileManagerEditProfile", "Edit this collection");
public static string ImportProfileHint =>
Loc.Localize("ProfileManagerImportProfile", "Import a shared collection from your clipboard");
public static string AddProfile => Loc.Localize("ProfileManagerAddProfile", "Add a new collection");
public static string NotificationImportSuccess =>
Loc.Localize("ProfileManagerNotificationImportSuccess", "Collection successfully imported!");
public static string NotificationImportError =>
Loc.Localize("ProfileManagerNotificationImportError", "Could not import collection.");
public static string ErrorCouldNotRemove =>
Loc.Localize("ProfileManagerCouldNotRemove", "Could not remove plugin.");
public static string ErrorCouldNotChangeState =>
Loc.Localize("ProfileManagerCouldNotChangeState", "Could not change plugin state.");
public static string NotInstalled(string name) =>
Loc.Localize("ProfileManagerNotInstalled", "{0} (Not Installed)").Format(name);
}
}

View file

@ -23,6 +23,8 @@ namespace Dalamud.Interface.Internal.Windows;
internal class PluginStatWindow : Window
{
private bool showDalamudHooks;
private string drawSearchText = string.Empty;
private string frameworkSearchText = string.Empty;
private string hookSearchText = string.Empty;
/// <summary>
@ -79,6 +81,12 @@ internal class PluginStatWindow : Window
ImGui.SameLine();
ImGuiComponents.TextWithLabel("Collective Average", $"{(loadedPlugins.Any() ? totalAverage / loadedPlugins.Count() / 10000f : 0):F4}ms", "Average of all average draw times");
ImGui.InputTextWithHint(
"###PluginStatWindow_DrawSearch",
"Search",
ref this.drawSearchText,
500);
if (ImGui.BeginTable(
"##PluginStatsDrawTimes",
4,
@ -104,16 +112,22 @@ internal class PluginStatWindow : Window
? loadedPlugins.OrderBy(plugin => plugin.Name)
: loadedPlugins.OrderByDescending(plugin => plugin.Name),
2 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending
? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime)
: loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime),
? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime ?? 0)
: loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.MaxDrawTime ?? 0),
3 => sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending
? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average())
: loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.Average()),
? loadedPlugins.OrderBy(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.DefaultIfEmpty().Average() ?? 0)
: loadedPlugins.OrderByDescending(plugin => plugin.DalamudInterface?.UiBuilder.DrawTimeHistory.DefaultIfEmpty().Average() ?? 0),
_ => loadedPlugins,
};
foreach (var plugin in loadedPlugins)
{
if (!this.drawSearchText.IsNullOrEmpty()
&& !plugin.Manifest.Name.Contains(this.drawSearchText, StringComparison.OrdinalIgnoreCase))
{
continue;
}
ImGui.TableNextRow();
ImGui.TableNextColumn();
@ -168,6 +182,12 @@ internal class PluginStatWindow : Window
ImGui.SameLine();
ImGuiComponents.TextWithLabel("Collective Average", $"{(statsHistory.Any() ? totalAverage / statsHistory.Length : 0):F4}ms", "Average of all average update times");
ImGui.InputTextWithHint(
"###PluginStatWindow_FrameworkSearch",
"Search",
ref this.frameworkSearchText,
500);
if (ImGui.BeginTable(
"##PluginStatsFrameworkTimes",
4,
@ -208,6 +228,13 @@ internal class PluginStatWindow : Window
continue;
}
if (!this.frameworkSearchText.IsNullOrEmpty()
&& handlerHistory.Key != null
&& !handlerHistory.Key.Contains(this.frameworkSearchText, StringComparison.OrdinalIgnoreCase))
{
continue;
}
ImGui.TableNextRow();
ImGui.TableNextColumn();

View file

@ -46,6 +46,14 @@ public class SettingsTabExperimental : SettingsTab
new GapSettingsEntry(5, true),
new ThirdRepoSettingsEntry(),
new GapSettingsEntry(5, true),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingsEnableProfiles", "Enable plugin collections"),
Loc.Localize("DalamudSettingsEnableProfilesHint", "Enables plugin collections, which lets you create toggleable lists of plugins."),
c => c.ProfilesEnabled,
(v, c) => c.ProfilesEnabled = v),
};
public override string Title => Loc.Localize("DalamudSettingsExperimental", "Experimental");

View file

@ -23,8 +23,16 @@ internal sealed class SettingsEntry<T> : SettingsEntry
private object? valueBacking;
private object? fallbackValue;
public SettingsEntry(string name, string description, LoadSettingDelegate load, SaveSettingDelegate save, Action<T?>? change = null, Func<T?, string?>? warning = null, Func<T?, string?>? validity = null, Func<bool>? visibility = null,
object? fallbackValue = null)
public SettingsEntry(
string name,
string description,
LoadSettingDelegate load,
SaveSettingDelegate save,
Action<T?>? change = null,
Func<T?, string?>? warning = null,
Func<T?, string?>? validity = null,
Func<bool>? visibility = null,
object? fallbackValue = null)
{
this.load = load;
this.save = save;

View file

@ -11,6 +11,7 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Serilog;
@ -97,7 +98,7 @@ public class StyleEditorWindow : Window
this.SaveStyle();
var newStyle = StyleModelV1.DalamudStandard;
newStyle.Name = GetRandomName();
newStyle.Name = Util.GetRandomName();
config.SavedStyles.Add(newStyle);
this.currentSel = config.SavedStyles.Count - 1;
@ -167,11 +168,11 @@ public class StyleEditorWindow : Window
{
var newStyle = StyleModel.Deserialize(styleJson);
newStyle.Name ??= GetRandomName();
newStyle.Name ??= Util.GetRandomName();
if (config.SavedStyles.Any(x => x.Name == newStyle.Name))
{
newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)";
newStyle.Name = $"{newStyle.Name} ({Util.GetRandomName()} Mix)";
}
config.SavedStyles.Add(newStyle);
@ -375,15 +376,6 @@ public class StyleEditorWindow : Window
}
}
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)

View file

@ -254,7 +254,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
var initialCursor = ImGui.GetCursorPos();
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value))
{
ImGui.Image(this.shadeTexture.ImGuiHandle, new Vector2(this.shadeTexture.Width * scale, this.shadeTexture.Height * scale));

View file

@ -31,12 +31,12 @@ public sealed class UiBuilder : IDisposable
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get();
private bool hasErrorWindow = false;
private bool lastFrameUiHideState = false;
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
private bool hasErrorWindow = false;
private bool lastFrameUiHideState = false;
/// <summary>
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
/// You do not have to call this manually.