mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-31 12:53:41 +01:00
Merge remote-tracking branch 'origin/master' into new_im_hooks-rollup
This commit is contained in:
commit
6764cac648
23 changed files with 1148 additions and 67 deletions
|
|
@ -40,6 +40,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "Dala
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Common", "Dalamud.Common\Dalamud.Common.csproj", "{F21B13D2-D7D0-4456-B70F-3F8D695064E2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.LocExporter", "tools\Dalamud.LocExporter\Dalamud.LocExporter.csproj", "{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -102,6 +104,10 @@ Global
|
|||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F21B13D2-D7D0-4456-B70F-3F8D695064E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A568929D-6FF6-4DFA-9D14-5D7DC08FA5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
|
|
@ -21,4 +22,9 @@ internal sealed class DevPluginSettings
|
|||
/// Gets or sets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
public Guid WorkingPluginId { get; set; } = Guid.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of validation problems that have been dismissed by the user.
|
||||
/// </summary>
|
||||
public List<string> DismissedValidationProblems { get; set; } = new();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>9.1.0.2</DalamudVersion>
|
||||
<DalamudVersion>9.1.0.5</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
|
|||
96
Dalamud/Interface/Fools24.cs
Normal file
96
Dalamud/Interface/Fools24.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Networking.Http;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "One-off")]
|
||||
internal class Fools24 : IServiceType
|
||||
{
|
||||
private readonly HappyHttpClient httpClient;
|
||||
|
||||
private CancellationTokenSource? cancellation;
|
||||
private Task? horseIconTask = null;
|
||||
|
||||
private string[]? applicableIcons = null;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public Fools24(HappyHttpClient httpClient)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public bool IsWaitingForIconList => this.horseIconTask?.IsCompleted == false;
|
||||
|
||||
public bool Failed { get; private set; }
|
||||
|
||||
public static bool IsDayApplicable()
|
||||
{
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var dateAhead = utcNow.AddHours(14);
|
||||
var dateBehind = utcNow.AddHours(-12);
|
||||
|
||||
return dateAhead is { Day: 1, Month: 4 } || dateBehind is { Day: 1, Month: 4 } || DateTime.Now is { Day: 1, Month: 4 };
|
||||
}
|
||||
|
||||
public string? GetHorseIconLink(string internalName)
|
||||
{
|
||||
if (this.applicableIcons == null || this.applicableIcons.All(x => x != $"{internalName}.png"))
|
||||
return null;
|
||||
|
||||
return $"https://raw.githubusercontent.com/goaaats/horse-icons/main/icons/{internalName}.png";
|
||||
}
|
||||
|
||||
public void NotifyInstallerWindowOpened()
|
||||
{
|
||||
if (!IsDayApplicable())
|
||||
return;
|
||||
|
||||
Service<PluginImageCache>.Get().ClearIconCache();
|
||||
|
||||
if (this.horseIconTask?.IsCompleted == false)
|
||||
return;
|
||||
|
||||
this.Failed = false;
|
||||
try
|
||||
{
|
||||
this.cancellation = new CancellationTokenSource();
|
||||
this.horseIconTask = this.httpClient.SharedHttpClient.GetStringAsync("https://raw.githubusercontent.com/goaaats/horse-icons/main/iconlist.txt", this.cancellation.Token)
|
||||
.ContinueWith(
|
||||
f =>
|
||||
{
|
||||
if (!f.IsCompletedSuccessfully)
|
||||
{
|
||||
this.Failed = true;
|
||||
this.applicableIcons = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (f.Result is not { Length: > 0 })
|
||||
{
|
||||
this.Failed = true;
|
||||
this.applicableIcons = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.applicableIcons = f.Result.Split(
|
||||
'\n',
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
});
|
||||
this.cancellation.CancelAfter(10000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to fetch horse icons");
|
||||
this.Failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ internal static class NotificationConstants
|
|||
public const float ProgressWaveLoopMaxColorTimeRatio = 0.7f;
|
||||
|
||||
/// <summary>Default duration of the notification.</summary>
|
||||
public static readonly TimeSpan DefaultDuration = TimeSpan.FromSeconds(3);
|
||||
public static readonly TimeSpan DefaultDuration = TimeSpan.FromSeconds(7);
|
||||
|
||||
/// <summary>Duration of show animation.</summary>
|
||||
public static readonly TimeSpan ShowAnimationDuration = TimeSpan.FromMilliseconds(300);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Runtime.InteropServices;
|
|||
using System.Text;
|
||||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
|
|
@ -110,6 +111,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
/// <summary>Undo range for modifying the buffer while composition is in progress.</summary>
|
||||
private (int Start, int End, int Cursor)? temporaryUndoSelection;
|
||||
|
||||
private bool hadWantTextInput;
|
||||
private bool updateInputLanguage = true;
|
||||
private bool updateImeStatusAgain;
|
||||
|
||||
|
|
@ -262,20 +264,37 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||
{
|
||||
this.updateInputLanguage = true;
|
||||
this.temporaryUndoSelection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Are we not the target of text input?
|
||||
if (!ImGui.GetIO().WantTextInput)
|
||||
{
|
||||
if (this.hadWantTextInput)
|
||||
{
|
||||
// Force the cancellation of whatever was being input.
|
||||
var hImc2 = ImmGetContext(args.Hwnd);
|
||||
if (hImc2 != 0)
|
||||
{
|
||||
ImmNotifyIME(hImc2, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||
ImmReleaseContext(args.Hwnd, hImc2);
|
||||
}
|
||||
}
|
||||
|
||||
this.hadWantTextInput = false;
|
||||
this.updateInputLanguage = true;
|
||||
this.temporaryUndoSelection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.hadWantTextInput = true;
|
||||
|
||||
var hImc = ImmGetContext(args.Hwnd);
|
||||
if (hImc == nint.Zero)
|
||||
{
|
||||
this.updateInputLanguage = true;
|
||||
this.temporaryUndoSelection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -338,9 +357,11 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
this.updateInputLanguage = false;
|
||||
}
|
||||
|
||||
// Microsoft Korean IME and Google Japanese IME drop notifying us of a candidate list change.
|
||||
// As the candidate list update is already there on the next WndProc call, update the candidate list again
|
||||
// here.
|
||||
if (this.updateImeStatusAgain)
|
||||
{
|
||||
this.ReplaceCompositionString(hImc, false);
|
||||
this.UpdateCandidates(hImc);
|
||||
this.updateImeStatusAgain = false;
|
||||
}
|
||||
|
|
@ -410,7 +431,8 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
or VK.VK_RIGHT
|
||||
or VK.VK_DOWN
|
||||
or VK.VK_RETURN:
|
||||
if (this.candidateStrings.Count != 0)
|
||||
// If key inputs that usually result in focus change, cancel the input process.
|
||||
if (!string.IsNullOrEmpty(ImmGetCompositionString(hImc, GCS.GCS_COMPSTR)))
|
||||
{
|
||||
this.ClearState(hImc);
|
||||
args.WParam = VK.VK_PROCESSKEY;
|
||||
|
|
@ -428,7 +450,15 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
case WM.WM_RBUTTONDOWN:
|
||||
case WM.WM_MBUTTONDOWN:
|
||||
case WM.WM_XBUTTONDOWN:
|
||||
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
||||
// If mouse click happened while IME composition was in progress, force complete the input process.
|
||||
if (!string.IsNullOrEmpty(ImmGetCompositionString(hImc, GCS.GCS_COMPSTR)))
|
||||
{
|
||||
ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
||||
|
||||
// Disable further handling of mouse button down event, or something would lock up the cursor.
|
||||
args.SuppressWithValue(1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -943,7 +943,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
|
||||
if (ImGui.MenuItem("Export localizable"))
|
||||
{
|
||||
localization.ExportLocalizable();
|
||||
localization.ExportLocalizable(true);
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu("Load language..."))
|
||||
|
|
|
|||
|
|
@ -191,6 +191,11 @@ internal class PluginImageCache : IInternalDisposableService
|
|||
{
|
||||
iconTexture = null;
|
||||
loadedSince = null;
|
||||
|
||||
// Wait for the horse icon list to be there, if applicable
|
||||
var fools = Service<Fools24>.Get();
|
||||
if (Fools24.IsDayApplicable() && fools.IsWaitingForIconList && !fools.Failed)
|
||||
return false;
|
||||
|
||||
if (manifest == null || manifest.InternalName == null)
|
||||
{
|
||||
|
|
@ -638,6 +643,14 @@ internal class PluginImageCache : IInternalDisposableService
|
|||
{
|
||||
if (isThirdParty)
|
||||
return manifest.IconUrl;
|
||||
|
||||
var fools = Service<Fools24>.Get();
|
||||
if (Fools24.IsDayApplicable())
|
||||
{
|
||||
var iconLink = fools.GetHorseIconLink(manifest.InternalName);
|
||||
if (iconLink != null)
|
||||
return iconLink;
|
||||
}
|
||||
|
||||
return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,6 +268,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
{
|
||||
Service<Fools24>.Get().NotifyInstallerWindowOpened();
|
||||
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
|
||||
_ = pluginManager.ReloadPluginMastersAsync();
|
||||
|
|
@ -596,6 +598,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
using (ImRaii.Disabled(isProfileManager))
|
||||
{
|
||||
var searchTextChanged = false;
|
||||
var prevSearchText = this.searchText;
|
||||
ImGui.SetNextItemWidth(searchInputWidth);
|
||||
searchTextChanged |= ImGui.InputTextWithHint(
|
||||
"###XlPluginInstaller_Search",
|
||||
|
|
@ -615,7 +618,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
}
|
||||
|
||||
if (searchTextChanged)
|
||||
this.UpdateCategoriesOnSearchChange();
|
||||
this.UpdateCategoriesOnSearchChange(prevSearchText);
|
||||
}
|
||||
|
||||
// Disable sort if changelogs or profile editor
|
||||
|
|
@ -2458,6 +2461,12 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
if (plugin is LocalDevPlugin devPlugin)
|
||||
{
|
||||
this.DrawDevPluginValidationIssues(devPlugin);
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
}
|
||||
|
||||
// Controls
|
||||
this.DrawPluginControlButton(plugin, availablePluginUpdate);
|
||||
this.DrawDevPluginButtons(plugin);
|
||||
|
|
@ -2961,6 +2970,100 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawDevPluginValidationIssues(LocalDevPlugin devPlugin)
|
||||
{
|
||||
if (!devPlugin.IsLoaded)
|
||||
{
|
||||
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "You have to load this plugin to see validation issues.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var problems = PluginValidator.CheckForProblems(devPlugin);
|
||||
if (problems.Count == 0)
|
||||
{
|
||||
ImGui.PushFont(InterfaceManager.IconFont);
|
||||
ImGui.Text(FontAwesomeIcon.Check.ToIconString());
|
||||
ImGui.PopFont();
|
||||
ImGui.SameLine();
|
||||
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.HealerGreen, "No validation issues found in this plugin!");
|
||||
}
|
||||
else
|
||||
{
|
||||
var numValidProblems = problems.Count(
|
||||
problem => devPlugin.DismissedValidationProblems.All(name => name != problem.GetType().Name));
|
||||
var shouldBother = numValidProblems > 0;
|
||||
var validationIssuesText = shouldBother ?
|
||||
$"Found {problems.Count} validation issue{(problems.Count > 1 ? "s" : string.Empty)} in this plugin!" :
|
||||
$"{problems.Count} dismissed validation issue{(problems.Count > 1 ? "s" : string.Empty)} in this plugin.";
|
||||
|
||||
using var col = ImRaii.PushColor(ImGuiCol.Text, shouldBother ? ImGuiColors.DalamudOrange : ImGuiColors.DalamudGrey);
|
||||
using var tree = ImRaii.TreeNode($"{validationIssuesText}###validationIssueCollapsible");
|
||||
if (tree.Success)
|
||||
{
|
||||
foreach (var problem in problems)
|
||||
{
|
||||
var thisProblemIsDismissed = devPlugin.DismissedValidationProblems.Contains(problem.GetType().Name);
|
||||
|
||||
if (!thisProblemIsDismissed)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudWhite))
|
||||
{
|
||||
if (ImGuiComponents.IconButton(
|
||||
$"##dismissValidationIssue{problem.GetType().Name}",
|
||||
FontAwesomeIcon.TimesCircle))
|
||||
{
|
||||
devPlugin.DismissedValidationProblems.Add(problem.GetType().Name);
|
||||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Dismiss this issue");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
var iconColor = problem.Severity switch
|
||||
{
|
||||
PluginValidator.ValidationSeverity.Fatal => ImGuiColors.DalamudRed,
|
||||
PluginValidator.ValidationSeverity.Warning => ImGuiColors.DalamudOrange,
|
||||
PluginValidator.ValidationSeverity.Information => ImGuiColors.TankBlue,
|
||||
_ => ImGuiColors.DalamudGrey,
|
||||
};
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, iconColor))
|
||||
using (ImRaii.PushFont(InterfaceManager.IconFont))
|
||||
{
|
||||
switch (problem.Severity)
|
||||
{
|
||||
case PluginValidator.ValidationSeverity.Fatal:
|
||||
ImGui.Text(FontAwesomeIcon.TimesCircle.ToIconString());
|
||||
break;
|
||||
case PluginValidator.ValidationSeverity.Warning:
|
||||
ImGui.Text(FontAwesomeIcon.ExclamationTriangle.ToIconString());
|
||||
break;
|
||||
case PluginValidator.ValidationSeverity.Information:
|
||||
ImGui.Text(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, thisProblemIsDismissed ? ImGuiColors.DalamudGrey : ImGuiColors.DalamudWhite))
|
||||
{
|
||||
ImGuiHelpers.SafeTextWrapped(problem.GetLocalizedDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDevPluginButtons(LocalPlugin localPlugin)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
|
|
@ -3341,15 +3444,27 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
return this.updateModalTaskCompletionSource.Task;
|
||||
}
|
||||
|
||||
private void UpdateCategoriesOnSearchChange()
|
||||
private void UpdateCategoriesOnSearchChange(string? previousSearchText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.searchText))
|
||||
{
|
||||
this.categoryManager.SetCategoryHighlightsForPlugins(null);
|
||||
|
||||
// Reset here for good measure, as we're returning from a search
|
||||
this.openPluginCollapsibles.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm));
|
||||
var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)).ToArray();
|
||||
|
||||
// Check if the search results are different, and clear the open collapsibles if they are
|
||||
if (previousSearchText != null)
|
||||
{
|
||||
var previousSearchResults = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)).ToArray();
|
||||
if (!previousSearchResults.SequenceEqual(pluginsMatchingSearch))
|
||||
this.openPluginCollapsibles.Clear();
|
||||
}
|
||||
|
||||
this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch);
|
||||
}
|
||||
}
|
||||
|
|
@ -3357,7 +3472,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
private void UpdateCategoriesOnPluginsChange()
|
||||
{
|
||||
this.categoryManager.BuildCategories(this.pluginListAvailable);
|
||||
this.UpdateCategoriesOnSearchChange();
|
||||
this.UpdateCategoriesOnSearchChange(null);
|
||||
}
|
||||
|
||||
private void DrawFontawesomeIconOutlined(FontAwesomeIcon icon, Vector4 outline, Vector4 iconColor)
|
||||
|
|
@ -3760,7 +3875,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
|
||||
public static string FeedbackModal_ContactInformationRequired => Loc.Localize("InstallerFeedbackContactInfoRequired", "Contact information has not been provided. We require contact information to respond to questions, or to request additional information to troubleshoot problems.");
|
||||
|
||||
public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join Goat Place Discord");
|
||||
public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join XIVLauncher & Dalamud Discord");
|
||||
|
||||
public static string FeedbackModal_ContactInformationDiscordUrl => Loc.Localize("ContactInformationDiscordUrl", "https://goat.place/");
|
||||
|
||||
|
|
|
|||
|
|
@ -651,13 +651,13 @@ internal class ProfileManagerWidget
|
|||
Loc.Localize("ProfileManagerTutorialCommands", "You can use the following commands in chat or in macros to manage active collections:");
|
||||
|
||||
public static string TutorialCommandsEnable =>
|
||||
Loc.Localize("ProfileManagerTutorialCommandsEnable", "/xlenableprofile \"Collection Name\" - Enable a collection");
|
||||
Loc.Localize("ProfileManagerTutorialCommandsEnable", "{0} \"Collection Name\" - Enable a collection").Format(ProfileCommandHandler.CommandEnable);
|
||||
|
||||
public static string TutorialCommandsDisable =>
|
||||
Loc.Localize("ProfileManagerTutorialCommandsDisable", "/xldisableprofile \"Collection Name\" - Disable a collection");
|
||||
Loc.Localize("ProfileManagerTutorialCommandsDisable", "{0} \"Collection Name\" - Disable a collection").Format(ProfileCommandHandler.CommandDisable);
|
||||
|
||||
public static string TutorialCommandsToggle =>
|
||||
Loc.Localize("ProfileManagerTutorialCommandsToggle", "/xltoggleprofile \"Collection Name\" - Toggle a collection's state");
|
||||
Loc.Localize("ProfileManagerTutorialCommandsToggle", "{0} \"Collection Name\" - Toggle a collection's state").Format(ProfileCommandHandler.CommandToggle);
|
||||
|
||||
public static string TutorialCommandsEnd =>
|
||||
Loc.Localize("ProfileManagerTutorialCommandsEnd", "If you run multiple of these commands, they will be executed in order.");
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ internal class SettingsWindow : Window
|
|||
{
|
||||
ImGui.SetTooltip(!ImGui.IsKeyDown(ImGuiKey.ModShift)
|
||||
? Loc.Localize("DalamudSettingsSaveAndExit", "Save changes and close")
|
||||
: Loc.Localize("DalamudSettingsSaveAndExit", "Save changes"));
|
||||
: Loc.Localize("DalamudSettingsSave", "Save changes"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public class SettingsTabLook : SettingsTab
|
|||
|
||||
new SettingsEntry<bool>(
|
||||
Loc.Localize("DalamudSettingReducedMotion", "Reduce motions"),
|
||||
Loc.Localize("DalamudSettingReducedMotion", "This will suppress certain animations from Dalamud, such as the notification popup."),
|
||||
Loc.Localize("DalamudSettingReducedMotionHint", "This will suppress certain animations from Dalamud, such as the notification popup."),
|
||||
c => c.ReduceMotions ?? false,
|
||||
(v, c) => c.ReduceMotions = v),
|
||||
};
|
||||
|
|
|
|||
361
Dalamud/Interface/ManagedFontAtlas/FluentGlyphRangeBuilder.cs
Normal file
361
Dalamud/Interface/ManagedFontAtlas/FluentGlyphRangeBuilder.cs
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>A fluent ImGui glyph range builder.</summary>
|
||||
[SuppressMessage(
|
||||
"StyleCop.CSharp.SpacingRules",
|
||||
"SA1010:Opening square brackets should be spaced correctly",
|
||||
Justification = "No")]
|
||||
public struct FluentGlyphRangeBuilder
|
||||
{
|
||||
private const int ImUnicodeCodepointMax = char.MaxValue;
|
||||
|
||||
private BitArray? characters;
|
||||
|
||||
/// <summary>Clears the builder.</summary>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>A builder is in cleared state on first use.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FluentGlyphRangeBuilder Clear()
|
||||
{
|
||||
this.characters?.SetAll(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds a single codepoint to the builder.</summary>
|
||||
/// <param name="codepoint">The codepoint to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FluentGlyphRangeBuilder With(char codepoint) => this.With((int)codepoint);
|
||||
|
||||
/// <inheritdoc cref="With(char)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FluentGlyphRangeBuilder With(uint codepoint) =>
|
||||
codepoint <= char.MaxValue ? this.With((int)codepoint) : this;
|
||||
|
||||
/// <inheritdoc cref="With(char)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FluentGlyphRangeBuilder With(int codepoint)
|
||||
{
|
||||
if (codepoint <= ImUnicodeCodepointMax)
|
||||
this.EnsureCharacters().Set(codepoint, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds a unicode range to the builder.</summary>
|
||||
/// <param name="range">The unicode range to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(UnicodeRange range) =>
|
||||
this.With(range.FirstCodePoint, (range.FirstCodePoint + range.Length) - 1);
|
||||
|
||||
/// <summary>Adds unicode ranges to the builder.</summary>
|
||||
/// <param name="range1">The 1st unicode range to add.</param>
|
||||
/// <param name="range2">The 2st unicode range to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(UnicodeRange range1, UnicodeRange range2) =>
|
||||
this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1)
|
||||
.With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1);
|
||||
|
||||
/// <summary>Adds unicode ranges to the builder.</summary>
|
||||
/// <param name="range1">The 1st unicode range to add.</param>
|
||||
/// <param name="range2">The 2st unicode range to add.</param>
|
||||
/// <param name="range3">The 3rd unicode range to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(UnicodeRange range1, UnicodeRange range2, UnicodeRange range3) =>
|
||||
this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1)
|
||||
.With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1)
|
||||
.With(range3.FirstCodePoint, (range3.FirstCodePoint + range3.Length) - 1);
|
||||
|
||||
/// <summary>Adds unicode ranges to the builder.</summary>
|
||||
/// <param name="range1">The 1st unicode range to add.</param>
|
||||
/// <param name="range2">The 2st unicode range to add.</param>
|
||||
/// <param name="range3">The 3rd unicode range to add.</param>
|
||||
/// <param name="range4">The 4th unicode range to add.</param>
|
||||
/// <param name="evenMoreRanges">Even more unicode ranges to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(
|
||||
UnicodeRange range1,
|
||||
UnicodeRange range2,
|
||||
UnicodeRange range3,
|
||||
UnicodeRange range4,
|
||||
params UnicodeRange[] evenMoreRanges) =>
|
||||
this.With(range1.FirstCodePoint, (range1.FirstCodePoint + range1.Length) - 1)
|
||||
.With(range2.FirstCodePoint, (range2.FirstCodePoint + range2.Length) - 1)
|
||||
.With(range3.FirstCodePoint, (range3.FirstCodePoint + range3.Length) - 1)
|
||||
.With(range4.FirstCodePoint, (range4.FirstCodePoint + range4.Length) - 1)
|
||||
.With(evenMoreRanges);
|
||||
|
||||
/// <summary>Adds unicode ranges to the builder.</summary>
|
||||
/// <param name="ranges">Unicode ranges to add.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(IEnumerable<UnicodeRange> ranges)
|
||||
{
|
||||
foreach (var range in ranges)
|
||||
this.With(range);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds a range of characters to the builder.</summary>
|
||||
/// <param name="from">The first codepoint, inclusive.</param>
|
||||
/// <param name="to">The last codepoint, inclusive.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Unsupported codepoints will be ignored.</para>
|
||||
/// <para>If <paramref name="from"/> is more than <paramref name="to"/>, then they will be swapped.</para>
|
||||
/// </remarks>
|
||||
public FluentGlyphRangeBuilder With(char from, char to) =>
|
||||
this.With(Math.Clamp(from, int.MinValue, int.MaxValue), Math.Clamp(to, int.MinValue, int.MaxValue));
|
||||
|
||||
/// <inheritdoc cref="With(char,char)"/>
|
||||
public FluentGlyphRangeBuilder With(uint from, uint to) =>
|
||||
this.With((int)Math.Min(from, int.MaxValue), (int)Math.Min(to, int.MaxValue));
|
||||
|
||||
/// <inheritdoc cref="With(char,char)"/>
|
||||
public FluentGlyphRangeBuilder With(int from, int to)
|
||||
{
|
||||
from = Math.Clamp(from, 1, ImUnicodeCodepointMax);
|
||||
to = Math.Clamp(to, 1, ImUnicodeCodepointMax);
|
||||
if (from > to)
|
||||
(from, to) = (to, from);
|
||||
|
||||
var bits = this.EnsureCharacters();
|
||||
for (; from <= to; from++)
|
||||
bits.Set(from, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds characters from a UTF-8 character sequence.</summary>
|
||||
/// <param name="utf8Sequence">The sequence.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(ReadOnlySpan<byte> utf8Sequence)
|
||||
{
|
||||
var bits = this.EnsureCharacters();
|
||||
while (!utf8Sequence.IsEmpty)
|
||||
{
|
||||
if (Rune.DecodeFromUtf8(utf8Sequence, out var rune, out var len) == OperationStatus.Done
|
||||
&& rune.Value < ImUnicodeCodepointMax)
|
||||
bits.Set(rune.Value, true);
|
||||
utf8Sequence = utf8Sequence[len..];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds characters from a UTF-8 character sequence.</summary>
|
||||
/// <param name="utf8Sequence">The sequence.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(IEnumerable<byte> utf8Sequence)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[4];
|
||||
var bufp = 0;
|
||||
var bits = this.EnsureCharacters();
|
||||
foreach (var b in utf8Sequence)
|
||||
{
|
||||
buf[bufp++] = b;
|
||||
|
||||
while (Rune.DecodeFromUtf8(buf[..bufp], out var rune, out var len) is var state
|
||||
&& state != OperationStatus.NeedMoreData)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case OperationStatus.Done when rune.Value <= ImUnicodeCodepointMax:
|
||||
bits.Set(rune.Value, true);
|
||||
goto case OperationStatus.InvalidData;
|
||||
|
||||
case OperationStatus.InvalidData:
|
||||
bufp -= len;
|
||||
break;
|
||||
|
||||
case OperationStatus.NeedMoreData:
|
||||
case OperationStatus.DestinationTooSmall:
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected return from {Rune.DecodeFromUtf8}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds characters from a UTF-16 character sequence.</summary>
|
||||
/// <param name="utf16Sequence">The sequence.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(ReadOnlySpan<char> utf16Sequence)
|
||||
{
|
||||
var bits = this.EnsureCharacters();
|
||||
while (!utf16Sequence.IsEmpty)
|
||||
{
|
||||
if (Rune.DecodeFromUtf16(utf16Sequence, out var rune, out var len) == OperationStatus.Done
|
||||
&& rune.Value <= ImUnicodeCodepointMax)
|
||||
bits.Set(rune.Value, true);
|
||||
utf16Sequence = utf16Sequence[len..];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds characters from a UTF-16 character sequence.</summary>
|
||||
/// <param name="utf16Sequence">The sequence.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(IEnumerable<char> utf16Sequence)
|
||||
{
|
||||
var bits = this.EnsureCharacters();
|
||||
foreach (var c in utf16Sequence)
|
||||
{
|
||||
if (!char.IsSurrogate(c))
|
||||
bits.Set(c, true);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Adds characters from a string.</summary>
|
||||
/// <param name="string">The string.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored.</remarks>
|
||||
public FluentGlyphRangeBuilder With(string @string) => this.With(@string.AsSpan());
|
||||
|
||||
/// <summary>Adds glyphs that are likely to be used in the given culture to the builder.</summary>
|
||||
/// <param name="cultureInfo">A culture info.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>Unsupported codepoints will be ignored. Unsupported culture will do nothing.
|
||||
/// Do make a PR if you need more.</remarks>
|
||||
public FluentGlyphRangeBuilder WithLanguage(CultureInfo cultureInfo)
|
||||
{
|
||||
// Call in chunks of three to avoid allocating arrays.
|
||||
// Avoid adding ranges that goes over BMP; that is, ranges that goes over ImUnicodeCodepointMax.
|
||||
switch (cultureInfo.TwoLetterISOLanguageName)
|
||||
{
|
||||
case "ja":
|
||||
// http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml
|
||||
return
|
||||
this
|
||||
.With(
|
||||
UnicodeRanges.CjkSymbolsandPunctuation,
|
||||
UnicodeRanges.Hiragana,
|
||||
UnicodeRanges.Katakana)
|
||||
.With(
|
||||
UnicodeRanges.HalfwidthandFullwidthForms,
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA)
|
||||
// Blame Japanese cell carriers for the below.
|
||||
.With(
|
||||
UnicodeRanges.EnclosedCjkLettersandMonths);
|
||||
case "zh":
|
||||
return
|
||||
this
|
||||
.With(
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA);
|
||||
case "ko":
|
||||
return
|
||||
this
|
||||
.With(
|
||||
UnicodeRanges.HangulJamo,
|
||||
UnicodeRanges.HangulCompatibilityJamo,
|
||||
UnicodeRanges.HangulSyllables)
|
||||
.With(
|
||||
UnicodeRanges.HangulJamoExtendedA,
|
||||
UnicodeRanges.HangulJamoExtendedB);
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds glyphs that are likely to be used in the given culture to the builder.</summary>
|
||||
/// <param name="languageTag">A language tag that will be used to locate the culture info.</param>
|
||||
/// <returns><c>this</c> for method chaining.</returns>
|
||||
/// <remarks>See <see cref="CultureInfo.GetCultureInfo(string)"/> documentation for supported language tags.
|
||||
/// </remarks>
|
||||
public FluentGlyphRangeBuilder WithLanguage(string languageTag) =>
|
||||
this.WithLanguage(CultureInfo.GetCultureInfo(languageTag));
|
||||
|
||||
/// <summary>Builds the accumulated data into an ImGui glyph range.</summary>
|
||||
/// <param name="addFallbackCodepoints">Whether to add the default fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Whether to add the default ellipsis codepoints to the range.</param>
|
||||
/// <returns>The built ImGui glyph ranges.</returns>
|
||||
public ushort[] Build(bool addFallbackCodepoints = true, bool addEllipsisCodepoints = true)
|
||||
{
|
||||
if (addFallbackCodepoints)
|
||||
this.With(FontAtlasFactory.FallbackCodepoints);
|
||||
if (addEllipsisCodepoints)
|
||||
this.With(FontAtlasFactory.EllipsisCodepoints).With('.');
|
||||
return this.BuildExact();
|
||||
}
|
||||
|
||||
/// <summary>Builds the accumulated data into an ImGui glyph range, exactly as specified.</summary>
|
||||
/// <returns>The built ImGui glyph ranges.</returns>
|
||||
public ushort[] BuildExact()
|
||||
{
|
||||
if (this.characters is null)
|
||||
return [0];
|
||||
var bits = this.characters;
|
||||
|
||||
// Count the number of ranges first.
|
||||
var numRanges = 0;
|
||||
var lastCodepoint = -1;
|
||||
for (var i = 1; i <= ImUnicodeCodepointMax; i++)
|
||||
{
|
||||
if (bits.Get(i))
|
||||
{
|
||||
if (lastCodepoint == -1)
|
||||
lastCodepoint = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastCodepoint != -1)
|
||||
{
|
||||
numRanges++;
|
||||
lastCodepoint = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the final range that terminates on the ending boundary.
|
||||
if (lastCodepoint != -1)
|
||||
numRanges++;
|
||||
|
||||
// Allocate the array and build the range.
|
||||
var res = GC.AllocateUninitializedArray<ushort>((numRanges * 2) + 1);
|
||||
var resp = 0;
|
||||
for (var i = 1; i <= ImUnicodeCodepointMax; i++)
|
||||
{
|
||||
if (bits.Get(i) == ((resp & 1) == 0))
|
||||
res[resp++] = unchecked((ushort)i);
|
||||
}
|
||||
|
||||
// Handle the final range that terminates on the ending boundary.
|
||||
if ((resp & 1) == 1)
|
||||
res[resp++] = ImUnicodeCodepointMax;
|
||||
|
||||
// Add the zero terminator.
|
||||
res[resp] = 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>Ensures that <see cref="characters"/> is not null, by creating one as necessary.</summary>
|
||||
/// <returns>An instance of <see cref="BitArray"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private BitArray EnsureCharacters() => this.characters ??= new(ImUnicodeCodepointMax + 1);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
|
|
@ -12,6 +13,34 @@ namespace Dalamud.Interface.ManagedFontAtlas;
|
|||
/// </summary>
|
||||
public static class FontAtlasBuildToolkitUtilities
|
||||
{
|
||||
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
|
||||
/// <param name="chars">The chars.</param>
|
||||
/// <returns>A new range builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FluentGlyphRangeBuilder BeginGlyphRange(this IEnumerable<char> chars) =>
|
||||
default(FluentGlyphRangeBuilder).With(chars);
|
||||
|
||||
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
|
||||
/// <param name="chars">The chars.</param>
|
||||
/// <returns>A new range builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FluentGlyphRangeBuilder BeginGlyphRange(this ReadOnlySpan<char> chars) =>
|
||||
default(FluentGlyphRangeBuilder).With(chars);
|
||||
|
||||
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
|
||||
/// <param name="chars">The chars.</param>
|
||||
/// <returns>A new range builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FluentGlyphRangeBuilder BeginGlyphRange(this string chars) =>
|
||||
default(FluentGlyphRangeBuilder).With(chars);
|
||||
|
||||
/// <summary>Begins building a new array of <see cref="ushort"/> containing ImGui glyph ranges.</summary>
|
||||
/// <param name="range">The unicode range.</param>
|
||||
/// <returns>A new range builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FluentGlyphRangeBuilder BeginGlyphRange(this UnicodeRange range) =>
|
||||
default(FluentGlyphRangeBuilder).With(range);
|
||||
|
||||
/// <summary>
|
||||
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
/// </summary>
|
||||
|
|
@ -19,16 +48,12 @@ public static class FontAtlasBuildToolkitUtilities
|
|||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort[] ToGlyphRange(
|
||||
this IEnumerable<char> enumerable,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true)
|
||||
{
|
||||
using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder);
|
||||
foreach (var c in enumerable)
|
||||
builder.AddChar(c);
|
||||
return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
}
|
||||
bool addEllipsisCodepoints = true) =>
|
||||
enumerable.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
|
||||
/// <summary>
|
||||
/// Compiles given <see cref="char"/>s into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
|
|
@ -37,16 +62,12 @@ public static class FontAtlasBuildToolkitUtilities
|
|||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort[] ToGlyphRange(
|
||||
this ReadOnlySpan<char> span,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true)
|
||||
{
|
||||
using var builderScoped = ImGuiHelpers.NewFontGlyphRangeBuilderPtrScoped(out var builder);
|
||||
foreach (var c in span)
|
||||
builder.AddChar(c);
|
||||
return builder.BuildRangesToArray(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
}
|
||||
bool addEllipsisCodepoints = true) =>
|
||||
span.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
|
||||
/// <summary>
|
||||
/// Compiles given string into an array of <see cref="ushort"/> containing ImGui glyph ranges.
|
||||
|
|
@ -55,11 +76,12 @@ public static class FontAtlasBuildToolkitUtilities
|
|||
/// <param name="addFallbackCodepoints">Add fallback codepoints to the range.</param>
|
||||
/// <param name="addEllipsisCodepoints">Add ellipsis codepoints to the range.</param>
|
||||
/// <returns>The compiled range.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort[] ToGlyphRange(
|
||||
this string @string,
|
||||
bool addFallbackCodepoints = true,
|
||||
bool addEllipsisCodepoints = true) =>
|
||||
@string.AsSpan().ToGlyphRange(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
@string.BeginGlyphRange().Build(addFallbackCodepoints, addEllipsisCodepoints);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the corresponding <see cref="ImFontConfigPtr"/> in
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
|
|
@ -8,6 +9,8 @@ using Dalamud.Utility;
|
|||
|
||||
using ImGuiNET;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -216,10 +219,35 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
|
|||
/// <returns>The added font.</returns>
|
||||
ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont);
|
||||
|
||||
/// <summary>Adds glyphs from the Windows default font for the given culture info into the provided font.</summary>
|
||||
/// <param name="cultureInfo">The culture info.</param>
|
||||
/// <param name="fontConfig">The font config. If <see cref="SafeFontConfig.MergeFont"/> is not set, then
|
||||
/// <see cref="IFontAtlasBuildToolkit.Font"/> will be used as the target. If that is empty too, then it will do
|
||||
/// nothing.</param>
|
||||
/// <param name="weight">The font weight, in range from <c>1</c> to <c>1000</c>. <c>400</c> is regular(normal).
|
||||
/// </param>
|
||||
/// <param name="stretch">The font stretch, in range from <c>1</c> to <c>9</c>. <c>5</c> is medium(normal).
|
||||
/// </param>
|
||||
/// <param name="style">The font style, in range from <c>0</c> to <c>2</c>. <c>0</c> is normal.</param>
|
||||
/// <remarks>
|
||||
/// <para>May do nothing at all if <paramref name="cultureInfo"/> is unsupported by Dalamud font handler.</para>
|
||||
/// <para>See
|
||||
/// <a href="https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts">Microsoft
|
||||
/// Learn</a> for the fonts.</para>
|
||||
/// </remarks>
|
||||
void AttachWindowsDefaultFont(
|
||||
CultureInfo cultureInfo,
|
||||
in SafeFontConfig fontConfig,
|
||||
int weight = (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL,
|
||||
int stretch = (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL,
|
||||
int style = (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL);
|
||||
|
||||
/// <summary>
|
||||
/// Adds glyphs of extra languages into the provided font, depending on Dalamud Configuration.<br />
|
||||
/// <see cref="SafeFontConfig.GlyphRanges"/> will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="fontConfig">The font config.</param>
|
||||
/// <param name="fontConfig">The font config. If <see cref="SafeFontConfig.MergeFont"/> is not set, then
|
||||
/// <see cref="IFontAtlasBuildToolkit.Font"/> will be used as the target. If that is empty too, then it will do
|
||||
/// nothing.</param>
|
||||
void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
|
|
@ -17,6 +17,8 @@ using ImGuiNET;
|
|||
|
||||
using SharpDX.DXGI;
|
||||
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Dalamud.Interface.ManagedFontAtlas.Internals;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -433,9 +435,163 @@ internal sealed partial class FontAtlasFactory
|
|||
public ImFontPtr AddGameGlyphs(GameFontStyle gameFontStyle, ushort[]? glyphRanges, ImFontPtr mergeFont) =>
|
||||
this.gameFontHandleSubstance.AttachGameGlyphs(this, mergeFont, gameFontStyle, glyphRanges);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AttachWindowsDefaultFont(
|
||||
CultureInfo cultureInfo,
|
||||
in SafeFontConfig fontConfig,
|
||||
int weight = (int)DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL,
|
||||
int stretch = (int)DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL,
|
||||
int style = (int)DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL)
|
||||
{
|
||||
var targetFont = fontConfig.MergeFont;
|
||||
if (targetFont.IsNull())
|
||||
targetFont = this.Font;
|
||||
if (targetFont.IsNull())
|
||||
return;
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts
|
||||
var splitTag = cultureInfo.IetfLanguageTag.Split("-");
|
||||
foreach (var test in new[]
|
||||
{
|
||||
cultureInfo.IetfLanguageTag,
|
||||
$"{splitTag[0]}-{splitTag[^1]}",
|
||||
})
|
||||
{
|
||||
var familyName = test switch
|
||||
{
|
||||
"af-ZA" => "Segoe UI",
|
||||
"am-ET" => "Ebrima",
|
||||
"ar-SA" => "Segoe UI",
|
||||
"as-IN" => "Nirmala UI",
|
||||
"az-Latn-AZ" => "Segoe UI",
|
||||
"be-BY" => "Segoe UI",
|
||||
"bg-BG" => "Segoe UI",
|
||||
"bn-BD" => "Nirmala UI",
|
||||
"bn-IN" => "Nirmala UI",
|
||||
"bs-Latn-BA" => "Segoe UI",
|
||||
"ca-ES" => "Segoe UI",
|
||||
"ca-ES-valencia" => "Segoe UI",
|
||||
"chr-CHER-US" => "Gadugi",
|
||||
"cs-CZ" => "Segoe UI",
|
||||
"cy-GB" => "Segoe UI",
|
||||
"da-DK" => "Segoe UI",
|
||||
"de-DE" => "Segoe UI",
|
||||
"el-GR" => "Segoe UI",
|
||||
"en-GB" => "Segoe UI",
|
||||
"es-ES" => "Segoe UI",
|
||||
"et-EE" => "Segoe UI",
|
||||
"eu-ES" => "Segoe UI",
|
||||
"fa-IR" => "Segoe UI",
|
||||
"fi-FI" => "Segoe UI",
|
||||
"fil-PH" => "Segoe UI",
|
||||
"fr-FR" => "Segoe UI",
|
||||
"ga-IE" => "Segoe UI",
|
||||
"gd-GB" => "Segoe UI",
|
||||
"gl-ES" => "Segoe UI",
|
||||
"gu-IN" => "Nirmala UI",
|
||||
"ha-Latn-NG" => "Segoe UI",
|
||||
"he-IL" => "Segoe UI",
|
||||
"hi-IN" => "Nirmala UI",
|
||||
"hr-HR" => "Segoe UI",
|
||||
"hu-HU" => "Segoe UI",
|
||||
"hy-AM" => "Segoe UI",
|
||||
"id-ID" => "Segoe UI",
|
||||
"ig-NG" => "Segoe UI",
|
||||
"is-IS" => "Segoe UI",
|
||||
"it-IT" => "Segoe UI",
|
||||
"ja-JP" => "Yu Gothic UI",
|
||||
"ka-GE" => "Segoe UI",
|
||||
"kk-KZ" => "Segoe UI",
|
||||
"km-KH" => "Leelawadee UI",
|
||||
"kn-IN" => "Nirmala UI",
|
||||
"ko-KR" => "Malgun Gothic",
|
||||
"kok-IN" => "Nirmala UI",
|
||||
"ku-ARAB-IQ" => "Segoe UI",
|
||||
"ky-KG" => "Segoe UI",
|
||||
"lb-LU" => "Segoe UI",
|
||||
"lt-LT" => "Segoe UI",
|
||||
"lv-LV" => "Segoe UI",
|
||||
"mi-NZ" => "Segoe UI",
|
||||
"mk-MK" => "Segoe UI",
|
||||
"ml-IN" => "Nirmala UI",
|
||||
"mn-MN" => "Segoe UI",
|
||||
"mr-IN" => "Nirmala UI",
|
||||
"ms-MY" => "Segoe UI",
|
||||
"mt-MT" => "Segoe UI",
|
||||
"nb-NO" => "Segoe UI",
|
||||
"ne-NP" => "Nirmala UI",
|
||||
"nl-NL" => "Segoe UI",
|
||||
"nn-NO" => "Segoe UI",
|
||||
"nso-ZA" => "Segoe UI",
|
||||
"or-IN" => "Nirmala UI",
|
||||
"pa-Arab-PK" => "Segoe UI",
|
||||
"pa-IN" => "Nirmala UI",
|
||||
"pl-PL" => "Segoe UI",
|
||||
"prs-AF" => "Segoe UI",
|
||||
"pt-BR" => "Segoe UI",
|
||||
"pt-PT" => "Segoe UI",
|
||||
"qut-GT" => "Segoe UI",
|
||||
"quz-PE" => "Segoe UI",
|
||||
"ro-RO" => "Segoe UI",
|
||||
"ru-RU" => "Segoe UI",
|
||||
"rw-RW" => "Segoe UI",
|
||||
"sd-Arab-PK" => "Segoe UI",
|
||||
"si-LK" => "Nirmala UI",
|
||||
"sk-SK" => "Segoe UI",
|
||||
"sl-SI" => "Segoe UI",
|
||||
"sq-AL" => "Segoe UI",
|
||||
"sr-Cyrl-BA" => "Segoe UI",
|
||||
"sr-Cyrl-CS" => "Segoe UI",
|
||||
"sr-Latn-CS" => "Segoe UI",
|
||||
"sv-SE" => "Segoe UI",
|
||||
"sw-KE" => "Segoe UI",
|
||||
"ta-IN" => "Nirmala UI",
|
||||
"te-IN" => "Nirmala UI",
|
||||
"tg-Cyrl-TJ" => "Segoe UI",
|
||||
"th-TH" => "Leelawadee UI",
|
||||
"ti-ET" => "Ebrima",
|
||||
"tk-TM" => "Segoe UI",
|
||||
"tn-ZA" => "Segoe UI",
|
||||
"tr-TR" => "Segoe UI",
|
||||
"tt-RU" => "Segoe UI",
|
||||
"ug-CN" => "Segoe UI",
|
||||
"uk-UA" => "Segoe UI",
|
||||
"ur-PK" => "Segoe UI",
|
||||
"uz-Latn-UZ" => "Segoe UI",
|
||||
"vi-VN" => "Segoe UI",
|
||||
"wo-SN" => "Segoe UI",
|
||||
"xh-ZA" => "Segoe UI",
|
||||
"yo-NG" => "Segoe UI",
|
||||
"zh-CN" => "Microsoft YaHei UI",
|
||||
"zh-HK" => "Microsoft JhengHei UI",
|
||||
"zh-TW" => "Microsoft JhengHei UI",
|
||||
"zh-Hans" => "Microsoft YaHei UI",
|
||||
"zh-Hant" => "Microsoft YaHei UI",
|
||||
"zu-ZA" => "Segoe UI",
|
||||
_ => null,
|
||||
};
|
||||
if (familyName is null)
|
||||
continue;
|
||||
var family = IFontFamilyId
|
||||
.ListSystemFonts(false)
|
||||
.FirstOrDefault(
|
||||
x => x.EnglishName.Equals(familyName, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (family?.Fonts[family.FindBestMatch(weight, stretch, style)] is not { } font)
|
||||
return;
|
||||
font.AddToBuildToolkit(this, fontConfig with { MergeFont = targetFont });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AttachExtraGlyphsForDalamudLanguage(in SafeFontConfig fontConfig)
|
||||
{
|
||||
var targetFont = fontConfig.MergeFont;
|
||||
if (targetFont.IsNull())
|
||||
targetFont = this.Font;
|
||||
if (targetFont.IsNull())
|
||||
return;
|
||||
|
||||
var dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
if (dalamudConfiguration.EffectiveLanguage == "ko"
|
||||
|| Service<DalamudIme>.GetNullable()?.EncounteredHangul is true)
|
||||
|
|
@ -444,41 +600,24 @@ internal sealed partial class FontAtlasFactory
|
|||
DalamudAsset.NotoSansKrRegular,
|
||||
fontConfig with
|
||||
{
|
||||
GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
|
||||
UnicodeRanges.HangulJamo,
|
||||
UnicodeRanges.HangulCompatibilityJamo,
|
||||
UnicodeRanges.HangulSyllables,
|
||||
UnicodeRanges.HangulJamoExtendedA,
|
||||
UnicodeRanges.HangulJamoExtendedB),
|
||||
MergeFont = targetFont,
|
||||
GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("ko-kr").BuildExact(),
|
||||
});
|
||||
}
|
||||
|
||||
var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
var fontPathChs = Path.Combine(windowsDir, "Fonts", "msyh.ttc");
|
||||
if (!File.Exists(fontPathChs))
|
||||
fontPathChs = null;
|
||||
|
||||
var fontPathCht = Path.Combine(windowsDir, "Fonts", "msjh.ttc");
|
||||
if (!File.Exists(fontPathCht))
|
||||
fontPathCht = null;
|
||||
|
||||
if (fontPathCht != null && Service<DalamudConfiguration>.Get().EffectiveLanguage == "tw")
|
||||
if (Service<DalamudConfiguration>.Get().EffectiveLanguage == "tw")
|
||||
{
|
||||
this.AddFontFromFile(fontPathCht, fontConfig with
|
||||
this.AttachWindowsDefaultFont(CultureInfo.GetCultureInfo("zh-hant"), fontConfig with
|
||||
{
|
||||
GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA),
|
||||
GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("zh-hant").BuildExact(),
|
||||
});
|
||||
}
|
||||
else if (fontPathChs != null && (Service<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|
||||
|| Service<DalamudIme>.GetNullable()?.EncounteredHan is true))
|
||||
else if (Service<DalamudConfiguration>.Get().EffectiveLanguage == "zh"
|
||||
|| Service<DalamudIme>.GetNullable()?.EncounteredHan is true)
|
||||
{
|
||||
this.AddFontFromFile(fontPathChs, fontConfig with
|
||||
this.AttachWindowsDefaultFont(CultureInfo.GetCultureInfo("zh-hans"), fontConfig with
|
||||
{
|
||||
GlyphRanges = ImGuiHelpers.CreateImGuiRangesFrom(
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA),
|
||||
GlyphRanges = default(FluentGlyphRangeBuilder).WithLanguage("zh-hans").BuildExact(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,6 +347,12 @@ public sealed class UiBuilder : IDisposable
|
|||
/// </summary>
|
||||
public IFontAtlas FontAtlas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not to use "reduced motion". This usually means that you should use less
|
||||
/// intrusive animations, or disable them entirely.
|
||||
/// </summary>
|
||||
public bool ShouldUseReducedMotion => Service<DalamudConfiguration>.Get().ReduceMotions ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||
/// </summary>
|
||||
|
|
|
|||
194
Dalamud/Plugin/Internal/PluginValidator.cs
Normal file
194
Dalamud/Plugin/Internal/PluginValidator.cs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
|
||||
namespace Dalamud.Plugin.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Class responsible for validating a dev plugin.
|
||||
/// </summary>
|
||||
internal static class PluginValidator
|
||||
{
|
||||
private static readonly char[] LineSeparator = new[] { ' ', '\n', '\r' };
|
||||
|
||||
/// <summary>
|
||||
/// Represents the severity of a validation problem.
|
||||
/// </summary>
|
||||
public enum ValidationSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// The problem is informational.
|
||||
/// </summary>
|
||||
Information,
|
||||
|
||||
/// <summary>
|
||||
/// The problem is a warning.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// The problem is fatal.
|
||||
/// </summary>
|
||||
Fatal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a validation problem.
|
||||
/// </summary>
|
||||
public interface IValidationProblem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the severity of the validation.
|
||||
/// </summary>
|
||||
public ValidationSeverity Severity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Compute the localized description of the problem.
|
||||
/// </summary>
|
||||
/// <returns>Localized string to be shown to the developer.</returns>
|
||||
public string GetLocalizedDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for problems in a plugin.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to validate.</param>
|
||||
/// <returns>An list of problems.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the plugin is not loaded. A plugin must be loaded to validate it.</exception>
|
||||
public static IReadOnlyList<IValidationProblem> CheckForProblems(LocalDevPlugin plugin)
|
||||
{
|
||||
var problems = new List<IValidationProblem>();
|
||||
|
||||
if (!plugin.IsLoaded)
|
||||
throw new InvalidOperationException("Plugin must be loaded to validate.");
|
||||
|
||||
if (!plugin.DalamudInterface!.UiBuilder.HasConfigUi)
|
||||
problems.Add(new NoConfigUiProblem());
|
||||
|
||||
if (!plugin.DalamudInterface.UiBuilder.HasMainUi)
|
||||
problems.Add(new NoMainUiProblem());
|
||||
|
||||
var cmdManager = Service<CommandManager>.Get();
|
||||
foreach (var cmd in cmdManager.Commands.Where(x => x.Value.LoaderAssemblyName == plugin.InternalName && x.Value.ShowInHelp))
|
||||
{
|
||||
if (string.IsNullOrEmpty(cmd.Value.HelpMessage))
|
||||
problems.Add(new CommandWithoutHelpTextProblem(cmd.Key));
|
||||
}
|
||||
|
||||
if (plugin.Manifest.Tags == null || plugin.Manifest.Tags.Count == 0)
|
||||
problems.Add(new NoTagsProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Description) || plugin.Manifest.Description.Split(LineSeparator, StringSplitOptions.RemoveEmptyEntries).Length <= 1)
|
||||
problems.Add(new NoDescriptionProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Punchline))
|
||||
problems.Add(new NoPunchlineProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Name))
|
||||
problems.Add(new NoNameProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Author))
|
||||
problems.Add(new NoAuthorProblem());
|
||||
|
||||
return problems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where the plugin does not have a config UI callback.
|
||||
/// </summary>
|
||||
public class NoConfigUiProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Warning;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "The plugin does not register a config UI callback. If you have a settings window or section, please consider registering UiBuilder.OpenConfigUi to open it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where the plugin does not have a main UI callback.
|
||||
/// </summary>
|
||||
public class NoMainUiProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Warning;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "The plugin does not register a main UI callback. If your plugin has a window that could be considered the main entrypoint to its features, please consider registering UiBuilder.OpenMainUi to open the plugin's main window.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a command does not have a help text.
|
||||
/// </summary>
|
||||
/// <param name="commandName">Name of the command.</param>
|
||||
public class CommandWithoutHelpTextProblem(string commandName) : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => $"The plugin has a command ({commandName}) without a help message. Please consider adding a help message to the command when registering it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin does not have any tags in its manifest.
|
||||
/// </summary>
|
||||
public class NoTagsProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have any tags in its manifest. Please consider adding some to make it easier for users to find your plugin in the installer.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin does not have a description in its manifest.
|
||||
/// </summary>
|
||||
public class NoDescriptionProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a description in its manifest, or it is very terse. Please consider adding one to give users more information about your plugin.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no punchline in its manifest.
|
||||
/// </summary>
|
||||
public class NoPunchlineProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a punchline in its manifest. Please consider adding one to give users a quick overview of what your plugin does.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no name in its manifest.
|
||||
/// </summary>
|
||||
public class NoNameProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a name in its manifest.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no author in its manifest.
|
||||
/// </summary>
|
||||
public class NoAuthorProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have an author in its manifest.";
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,16 @@ namespace Dalamud.Plugin.Internal.Profiles;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal class ProfileCommandHandler : IInternalDisposableService
|
||||
{
|
||||
#pragma warning disable SA1600
|
||||
public const string CommandEnable = "/xlenablecollection";
|
||||
public const string CommandDisable = "/xldisablecollection";
|
||||
public const string CommandToggle = "/xltogglecollection";
|
||||
#pragma warning restore SA1600
|
||||
|
||||
private static readonly string LegacyCommandEnable = CommandEnable.Replace("collection", "profile");
|
||||
private static readonly string LegacyCommandDisable = CommandDisable.Replace("collection", "profile");
|
||||
private static readonly string LegacyCommandToggle = CommandToggle.Replace("collection", "profile");
|
||||
|
||||
private readonly CommandManager cmd;
|
||||
private readonly ProfileManager profileManager;
|
||||
private readonly ChatGui chat;
|
||||
|
|
@ -40,23 +50,38 @@ internal class ProfileCommandHandler : IInternalDisposableService
|
|||
this.chat = chat;
|
||||
this.framework = framework;
|
||||
|
||||
this.cmd.AddHandler("/xlenableprofile", new CommandInfo(this.OnEnableProfile)
|
||||
this.cmd.AddHandler(CommandEnable, new CommandInfo(this.OnEnableProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsEnableHint", "Enable a collection. Usage: /xlenablecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler("/xldisableprofile", new CommandInfo(this.OnDisableProfile)
|
||||
this.cmd.AddHandler(CommandDisable, new CommandInfo(this.OnDisableProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsDisableHint", "Disable a collection. Usage: /xldisablecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler("/xltoggleprofile", new CommandInfo(this.OnToggleProfile)
|
||||
this.cmd.AddHandler(CommandToggle, new CommandInfo(this.OnToggleProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsToggleHint", "Toggle a collection. Usage: /xltogglecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandEnable, new CommandInfo(this.OnEnableProfile)
|
||||
{
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandDisable, new CommandInfo(this.OnDisableProfile)
|
||||
{
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandToggle, new CommandInfo(this.OnToggleProfile)
|
||||
{
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
|
@ -71,9 +96,12 @@ internal class ProfileCommandHandler : IInternalDisposableService
|
|||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.cmd.RemoveHandler("/xlenablecollection");
|
||||
this.cmd.RemoveHandler("/xldisablecollection");
|
||||
this.cmd.RemoveHandler("/xltogglecollection");
|
||||
this.cmd.RemoveHandler(CommandEnable);
|
||||
this.cmd.RemoveHandler(CommandDisable);
|
||||
this.cmd.RemoveHandler(CommandToggle);
|
||||
this.cmd.RemoveHandler(LegacyCommandEnable);
|
||||
this.cmd.RemoveHandler(LegacyCommandDisable);
|
||||
this.cmd.RemoveHandler(LegacyCommandToggle);
|
||||
|
||||
this.framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
|
@ -99,6 +100,11 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
|
|||
/// Gets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
public Guid DevImposedWorkingPluginId => this.devSettings.WorkingPluginId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of validation problems that have been dismissed by the user.
|
||||
/// </summary>
|
||||
public List<string> DismissedValidationProblems => this.devSettings.DismissedValidationProblems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new void Dispose()
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4bfbbe1c9eba5e0f70aa7d119ef6816bd9925988
|
||||
Subproject commit 76f15950d210d5196d942cc8439c2e48bfffd0ee
|
||||
21
tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj
Normal file
21
tools/Dalamud.LocExporter/Dalamud.LocExporter.csproj
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Platforms>x64;AnyCPU</Platforms>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Dalamud\Dalamud.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CheapLoc" Version="1.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
tools/Dalamud.LocExporter/Program.cs
Normal file
10
tools/Dalamud.LocExporter/Program.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
Console.WriteLine("=> Starting loc export...");
|
||||
|
||||
var dalamud = typeof(Dalamud.Localization).Assembly;
|
||||
Loc.ExportLocalizableForAssembly(dalamud, true);
|
||||
|
||||
Console.WriteLine("=> Finished loc export!");
|
||||
Loading…
Add table
Add a link
Reference in a new issue