mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 02:07:24 +01:00
Add checking for supported features with the currently new supported features 'Atch', 'Shp' and 'Atr'.
This commit is contained in:
parent
98203e4e8a
commit
318a41fe52
14 changed files with 216 additions and 37 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 1465203967d08519c6716292bc5e5094c7fbcacc
|
||||
Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f
|
||||
|
|
@ -17,7 +17,7 @@ public class PenumbraApi(
|
|||
UiApi ui) : IDisposable, IApiService, IPenumbraApi
|
||||
{
|
||||
public const int BreakingVersion = 5;
|
||||
public const int FeatureVersion = 9;
|
||||
public const int FeatureVersion = 10;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,39 +1,38 @@
|
|||
using System.Collections.Frozen;
|
||||
using Newtonsoft.Json;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class PluginStateApi : IPenumbraApiPluginState, IApiService
|
||||
public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly CommunicatorService _communicator;
|
||||
|
||||
public PluginStateApi(Configuration config, CommunicatorService communicator)
|
||||
{
|
||||
_config = config;
|
||||
_communicator = communicator;
|
||||
}
|
||||
|
||||
public string GetModDirectory()
|
||||
=> _config.ModDirectory;
|
||||
=> config.ModDirectory;
|
||||
|
||||
public string GetConfiguration()
|
||||
=> JsonConvert.SerializeObject(_config, Formatting.Indented);
|
||||
=> JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
|
||||
public event Action<string, bool>? ModDirectoryChanged
|
||||
{
|
||||
add => _communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
|
||||
remove => _communicator.ModDirectoryChanged.Unsubscribe(value!);
|
||||
add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
|
||||
remove => communicator.ModDirectoryChanged.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public bool GetEnabledState()
|
||||
=> _config.EnableMods;
|
||||
=> config.EnableMods;
|
||||
|
||||
public event Action<bool>? EnabledChange
|
||||
{
|
||||
add => _communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
|
||||
remove => _communicator.EnabledChanged.Unsubscribe(value!);
|
||||
add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
|
||||
remove => communicator.EnabledChanged.Unsubscribe(value!);
|
||||
}
|
||||
|
||||
public FrozenSet<string> SupportedFeatures
|
||||
=> FeatureChecker.SupportedFeatures.ToFrozenSet();
|
||||
|
||||
public string[] CheckSupportedFeatures(IEnumerable<string> requiredFeatures)
|
||||
=> requiredFeatures.Where(f => !FeatureChecker.Supported(f)).ToArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ public sealed class IpcProviders : IDisposable, IApiService
|
|||
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.SupportedFeatures.Provider(pi, api.PluginState),
|
||||
IpcSubscribers.CheckSupportedFeatures.Provider(pi, api.PluginState),
|
||||
|
||||
IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
|
||||
IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.Api.IpcSubscribers;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ namespace Penumbra.Api.IpcTester;
|
|||
|
||||
public class PluginStateIpcTester : IUiService, IDisposable
|
||||
{
|
||||
private readonly IDalamudPluginInterface _pi;
|
||||
private readonly IDalamudPluginInterface _pi;
|
||||
public readonly EventSubscriber<string, bool> ModDirectoryChanged;
|
||||
public readonly EventSubscriber Initialized;
|
||||
public readonly EventSubscriber Disposed;
|
||||
|
|
@ -26,6 +27,9 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
|||
private readonly List<DateTimeOffset> _initializedList = [];
|
||||
private readonly List<DateTimeOffset> _disposedList = [];
|
||||
|
||||
private string _requiredFeatureString = string.Empty;
|
||||
private string[] _requiredFeatures = [];
|
||||
|
||||
private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
|
||||
private bool? _lastEnabledValue;
|
||||
|
||||
|
|
@ -48,12 +52,15 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
|||
EnabledChange.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var _ = ImRaii.TreeNode("Plugin State");
|
||||
if (!_)
|
||||
return;
|
||||
|
||||
if (ImUtf8.InputText("Required Features"u8, ref _requiredFeatureString))
|
||||
_requiredFeatures = _requiredFeatureString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
|
@ -71,6 +78,12 @@ public class PluginStateIpcTester : IUiService, IDisposable
|
|||
IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
|
||||
ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
|
||||
|
||||
IpcTester.DrawIntro(SupportedFeatures.Label, "Supported Features");
|
||||
ImUtf8.Text(string.Join(", ", new SupportedFeatures(_pi).Invoke()));
|
||||
|
||||
IpcTester.DrawIntro(CheckSupportedFeatures.Label, "Missing Features");
|
||||
ImUtf8.Text(string.Join(", ", new CheckSupportedFeatures(_pi).Invoke(_requiredFeatures)));
|
||||
|
||||
DrawConfigPopup();
|
||||
IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
|
||||
if (ImGui.Button("Get"))
|
||||
|
|
|
|||
95
Penumbra/Mods/FeatureChecker.cs
Normal file
95
Penumbra/Mods/FeatureChecker.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System.Collections.Frozen;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Text;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.UI.Classes;
|
||||
using Notification = OtterGui.Classes.Notification;
|
||||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
public static class FeatureChecker
|
||||
{
|
||||
/// <summary> Manually setup supported features to exclude None and Invalid and not make something supported too early. </summary>
|
||||
private static readonly FrozenDictionary<string, FeatureFlags> SupportedFlags = new[]
|
||||
{
|
||||
FeatureFlags.Atch,
|
||||
FeatureFlags.Shp,
|
||||
FeatureFlags.Atr,
|
||||
}.ToFrozenDictionary(f => f.ToString(), f => f);
|
||||
|
||||
public static IReadOnlyCollection<string> SupportedFeatures
|
||||
=> SupportedFlags.Keys;
|
||||
|
||||
public static FeatureFlags ParseFlags(string modDirectory, string modName, IEnumerable<string> features)
|
||||
{
|
||||
var featureFlags = FeatureFlags.None;
|
||||
HashSet<string> missingFeatures = [];
|
||||
foreach (var feature in features)
|
||||
{
|
||||
if (SupportedFlags.TryGetValue(feature, out var featureFlag))
|
||||
featureFlags |= featureFlag;
|
||||
else
|
||||
missingFeatures.Add(feature);
|
||||
}
|
||||
|
||||
if (missingFeatures.Count > 0)
|
||||
{
|
||||
Penumbra.Messager.AddMessage(new Notification(
|
||||
$"Please update Penumbra to use the mod {modName}{(modDirectory != modName ? $" at {modDirectory}" : string.Empty)}!\n\nLoading failed because it requires the unsupported feature{(missingFeatures.Count > 1 ? $"s\n\n\t[{string.Join("], [", missingFeatures)}]." : $" [{missingFeatures.First()}].")}",
|
||||
NotificationType.Warning));
|
||||
return FeatureFlags.Invalid;
|
||||
}
|
||||
|
||||
return featureFlags;
|
||||
}
|
||||
|
||||
public static bool Supported(string features)
|
||||
=> SupportedFlags.ContainsKey(features);
|
||||
|
||||
public static void DrawFeatureFlagInput(ModDataEditor editor, Mod mod, float width)
|
||||
{
|
||||
const int numButtons = 5;
|
||||
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing;
|
||||
var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0);
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.TextDisabled);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, innerSpacing)
|
||||
.Push(ImGuiStyleVar.FrameBorderSize, 0);
|
||||
using (var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())
|
||||
.Push(ImGuiCol.Button, buttonColor)
|
||||
.Push(ImGuiCol.Text, textColor))
|
||||
{
|
||||
foreach (var flag in SupportedFlags.Values)
|
||||
{
|
||||
if (mod.RequiredFeatures.HasFlag(flag))
|
||||
{
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale);
|
||||
color.Pop(2);
|
||||
if (ImUtf8.Button($"{flag}", size))
|
||||
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag);
|
||||
color.Push(ImGuiCol.Button, buttonColor)
|
||||
.Push(ImGuiCol.Text, textColor);
|
||||
style.Pop();
|
||||
}
|
||||
else if (ImUtf8.Button($"{flag}", size))
|
||||
{
|
||||
editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImUtf8.ButtonEx("Compute"u8, "Compute the required features automatically from the used features."u8, size))
|
||||
editor.ChangeRequiredFeatures(mod, mod.ComputeRequiredFeatures());
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.ButtonEx("Clear"u8, "Clear all required features."u8, size))
|
||||
editor.ChangeRequiredFeatures(mod, FeatureFlags.None);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImUtf8.Text("Required Features"u8);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ public enum ModDataChangeType : ushort
|
|||
Image = 0x1000,
|
||||
DefaultChangedItems = 0x2000,
|
||||
PreferredChangedItems = 0x4000,
|
||||
RequiredFeatures = 0x8000,
|
||||
}
|
||||
|
||||
public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService, ItemData itemData) : IService
|
||||
|
|
@ -95,6 +96,16 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic
|
|||
mod.Website = newWebsite;
|
||||
saveService.QueueSave(new ModMeta(mod));
|
||||
communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeRequiredFeatures(Mod mod, FeatureFlags flags)
|
||||
{
|
||||
if (mod.RequiredFeatures == flags)
|
||||
return;
|
||||
|
||||
mod.RequiredFeatures = flags;
|
||||
saveService.QueueSave(new ModMeta(mod));
|
||||
communicatorService.ModDataChanged.Invoke(ModDataChangeType.RequiredFeatures, mod, null);
|
||||
}
|
||||
|
||||
public void ChangeModTag(Mod mod, int tagIdx, string newTag)
|
||||
|
|
|
|||
|
|
@ -143,9 +143,10 @@ public sealed class ModManager : ModStorage, IDisposable, IService
|
|||
_communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath);
|
||||
if (!Creator.ReloadMod(mod, true, false, out var metaChange))
|
||||
{
|
||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead."
|
||||
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead.");
|
||||
if (mod.RequiredFeatures is not FeatureFlags.Invalid)
|
||||
Penumbra.Log.Warning(mod.Name.Length == 0
|
||||
? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead."
|
||||
: $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead.");
|
||||
RemoveMod(mod);
|
||||
return;
|
||||
}
|
||||
|
|
@ -251,12 +252,8 @@ public sealed class ModManager : ModStorage, IDisposable, IService
|
|||
{
|
||||
switch (type)
|
||||
{
|
||||
case ModPathChangeType.Added:
|
||||
SetNew(mod);
|
||||
break;
|
||||
case ModPathChangeType.Deleted:
|
||||
SetKnown(mod);
|
||||
break;
|
||||
case ModPathChangeType.Added: SetNew(mod); break;
|
||||
case ModPathChangeType.Deleted: SetKnown(mod); break;
|
||||
case ModPathChangeType.Moved:
|
||||
if (oldDirectory != null && newDirectory != null)
|
||||
DataEditor.MoveDataFile(oldDirectory, newDirectory);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Extensions;
|
||||
using Penumbra.GameData.Data;
|
||||
|
|
@ -12,6 +11,16 @@ using Penumbra.String.Classes;
|
|||
|
||||
namespace Penumbra.Mods;
|
||||
|
||||
[Flags]
|
||||
public enum FeatureFlags : ulong
|
||||
{
|
||||
None = 0,
|
||||
Atch = 1ul << 0,
|
||||
Shp = 1ul << 1,
|
||||
Atr = 1ul << 2,
|
||||
Invalid = 1ul << 62,
|
||||
}
|
||||
|
||||
public sealed class Mod : IMod
|
||||
{
|
||||
public static readonly TemporaryMod ForcedFiles = new()
|
||||
|
|
@ -57,6 +66,7 @@ public sealed class Mod : IMod
|
|||
public string Image { get; internal set; } = string.Empty;
|
||||
public IReadOnlyList<string> ModTags { get; internal set; } = [];
|
||||
public HashSet<CustomItemId> DefaultPreferredItems { get; internal set; } = [];
|
||||
public FeatureFlags RequiredFeatures { get; internal set; } = 0;
|
||||
|
||||
|
||||
// Local Data
|
||||
|
|
@ -70,6 +80,23 @@ public sealed class Mod : IMod
|
|||
public readonly DefaultSubMod Default;
|
||||
public readonly List<IModGroup> Groups = [];
|
||||
|
||||
/// <summary> Compute the required feature flags for this mod. </summary>
|
||||
public FeatureFlags ComputeRequiredFeatures()
|
||||
{
|
||||
var flags = FeatureFlags.None;
|
||||
foreach (var option in AllDataContainers)
|
||||
{
|
||||
if (option.Manipulations.Atch.Count > 0)
|
||||
flags |= FeatureFlags.Atch;
|
||||
if (option.Manipulations.Atr.Count > 0)
|
||||
flags |= FeatureFlags.Atr;
|
||||
if (option.Manipulations.Shp.Count > 0)
|
||||
flags |= FeatureFlags.Shp;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public AppliedModData GetData(ModSettings? settings = null)
|
||||
{
|
||||
if (settings is not { Enabled: true })
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ public partial class ModCreator(
|
|||
MetaFileManager metaFileManager,
|
||||
GamePathParser gamePathParser) : IService
|
||||
{
|
||||
public readonly Configuration Config = config;
|
||||
public const FeatureFlags SupportedFeatures = FeatureFlags.Atch | FeatureFlags.Shp | FeatureFlags.Atr;
|
||||
public readonly Configuration Config = config;
|
||||
|
||||
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
||||
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "", string? author = null)
|
||||
|
|
@ -74,7 +75,7 @@ public partial class ModCreator(
|
|||
return false;
|
||||
|
||||
modDataChange = ModMeta.Load(dataEditor, this, mod);
|
||||
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0)
|
||||
if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0 || mod.RequiredFeatures is FeatureFlags.Invalid)
|
||||
return false;
|
||||
|
||||
modDataChange |= ModLocalData.Load(dataEditor, mod);
|
||||
|
|
@ -82,9 +83,9 @@ public partial class ModCreator(
|
|||
LoadAllGroups(mod);
|
||||
if (incorporateMetaChanges)
|
||||
IncorporateAllMetaChanges(mod, true, deleteDefaultMetaChanges);
|
||||
else if (deleteDefaultMetaChanges)
|
||||
ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, saveService, false);
|
||||
|
||||
else if (deleteDefaultMetaChanges)
|
||||
ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, saveService, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Services;
|
||||
|
||||
|
|
@ -28,6 +27,19 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
|||
{ nameof(Mod.ModTags), JToken.FromObject(mod.ModTags) },
|
||||
{ nameof(Mod.DefaultPreferredItems), JToken.FromObject(mod.DefaultPreferredItems) },
|
||||
};
|
||||
if (mod.RequiredFeatures is not FeatureFlags.None)
|
||||
{
|
||||
var features = mod.RequiredFeatures;
|
||||
var array = new JArray();
|
||||
foreach (var flag in Enum.GetValues<FeatureFlags>())
|
||||
{
|
||||
if ((features & flag) is not FeatureFlags.None)
|
||||
array.Add(flag.ToString());
|
||||
}
|
||||
|
||||
jObject[nameof(Mod.RequiredFeatures)] = array;
|
||||
}
|
||||
|
||||
using var jWriter = new JsonTextWriter(writer);
|
||||
jWriter.Formatting = Formatting.Indented;
|
||||
jObject.WriteTo(jWriter);
|
||||
|
|
@ -60,6 +72,8 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
|||
var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values<string>().OfType<string>();
|
||||
var defaultItems = (json[nameof(Mod.DefaultPreferredItems)] as JArray)?.Values<ulong>().Select(i => (CustomItemId)i).ToHashSet()
|
||||
?? [];
|
||||
var requiredFeatureArray = (json[nameof(Mod.RequiredFeatures)] as JArray)?.Values<string>() ?? [];
|
||||
var requiredFeatures = FeatureChecker.ParseFlags(mod.ModPath.Name, newName.Length > 0 ? newName : mod.Name.Length > 0 ? mod.Name : "Unknown", requiredFeatureArray!);
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.Name != newName)
|
||||
|
|
@ -111,6 +125,13 @@ public readonly struct ModMeta(Mod mod) : ISavable
|
|||
editor.SaveService.ImmediateSave(new ModMeta(mod));
|
||||
}
|
||||
|
||||
// Required features get checked during parsing, in which case the new required features signal invalid.
|
||||
if (requiredFeatures != mod.RequiredFeatures)
|
||||
{
|
||||
changes |= ModDataChangeType.RequiredFeatures;
|
||||
mod.RequiredFeatures = requiredFeatures;
|
||||
}
|
||||
|
||||
changes |= ModLocalData.UpdateTags(mod, modTags, null);
|
||||
|
||||
return changes;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ public class ModPanelEditTab(
|
|||
messager.NotificationMessage(e.Message, NotificationType.Warning, false);
|
||||
}
|
||||
|
||||
UiHelpers.DefaultLineSpace();
|
||||
|
||||
FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X);
|
||||
|
||||
UiHelpers.DefaultLineSpace();
|
||||
var sharedTagsEnabled = predefinedTagManager.Count > 0;
|
||||
var sharedTagButtonOffset = sharedTagsEnabled ? ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X : 0;
|
||||
|
|
@ -76,6 +80,7 @@ public class ModPanelEditTab(
|
|||
predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
|
||||
selector.Selected!);
|
||||
|
||||
|
||||
UiHelpers.DefaultLineSpace();
|
||||
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
|
||||
UiHelpers.DefaultLineSpace();
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
|
|||
if (set.All is { } value)
|
||||
{
|
||||
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value);
|
||||
ImUtf8.Text("All"u8);
|
||||
ImUtf8.Text("All, "u8);
|
||||
ImGui.SameLine(0, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,14 @@
|
|||
"type": "integer"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"RequiredFeatures": {
|
||||
"description": "A list of required features by name.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue