Merge branch 'combining'

This commit is contained in:
Ottermandias 2025-01-15 17:44:58 +01:00
commit d2a8cec01f
13 changed files with 542 additions and 48 deletions

@ -1 +1 @@
Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5
Subproject commit 055f169572223fd1b59389549c88b4c861c94608

View file

@ -0,0 +1,196 @@
using Dalamud.Interface.ImGuiNotification;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Classes;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.String.Classes;
using Penumbra.UI.ModsTab.Groups;
using Penumbra.Util;
namespace Penumbra.Mods.Groups;
/// <summary> Groups that allow all available options to be selected at once. </summary>
public sealed class CombiningModGroup : IModGroup
{
public GroupType Type
=> GroupType.Combining;
public GroupDrawBehaviour Behaviour
=> GroupDrawBehaviour.MultiSelection;
public Mod Mod { get; }
public string Name { get; set; } = "Group";
public string Description { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
public ModPriority Priority { get; set; }
public int Page { get; set; }
public Setting DefaultSettings { get; set; }
public readonly List<CombiningSubMod> OptionData = [];
public List<CombinedDataContainer> Data { get; private set; }
/// <summary> Groups that allow all available options to be selected at once. </summary>
public CombiningModGroup(Mod mod)
{
Mod = mod;
Data = [new CombinedDataContainer(this)];
}
IReadOnlyList<IModOption> IModGroup.Options
=> OptionData;
public IReadOnlyList<IModDataContainer> DataContainers
=> Data;
public bool IsOption
=> OptionData.Count > 0;
public FullPath? FindBestMatch(Utf8GamePath gamePath)
{
foreach (var path in Data.SelectWhere(o
=> (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file)))
return path;
return null;
}
public IModOption? AddOption(string name, string description = "")
{
var groupIdx = Mod.Groups.IndexOf(this);
if (groupIdx < 0)
return null;
var subMod = new CombiningSubMod(this)
{
Name = name,
Description = description,
};
return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions)
? subMod
: null;
}
public static CombiningModGroup? Load(Mod mod, JObject json)
{
var ret = new CombiningModGroup(mod, true);
if (!ModSaveGroup.ReadJsonBase(json, ret))
return null;
var options = json["Options"];
if (options != null)
foreach (var child in options.Children())
{
if (ret.OptionData.Count == IModGroup.MaxCombiningOptions)
{
Penumbra.Messager.NotificationMessage(
$"Combining Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxCombiningOptions} options, ignoring excessive options.",
NotificationType.Warning);
break;
}
var subMod = new CombiningSubMod(ret, child);
ret.OptionData.Add(subMod);
}
var requiredContainers = 1 << ret.OptionData.Count;
var containers = json["Containers"];
if (containers != null)
foreach (var child in containers.Children())
{
if (requiredContainers <= ret.Data.Count)
{
Penumbra.Messager.NotificationMessage(
$"Combining Group {ret.Name} in {mod.Name} has more data containers than it can support with {ret.OptionData.Count} options, ignoring excessive containers.",
NotificationType.Warning);
break;
}
var container = new CombinedDataContainer(ret, child);
ret.Data.Add(container);
}
if (requiredContainers > ret.Data.Count)
{
Penumbra.Messager.NotificationMessage(
$"Combining Group {ret.Name} in {mod.Name} has not enough data containers for its {ret.OptionData.Count} options, filling with empty containers.",
NotificationType.Warning);
ret.Data.EnsureCapacity(requiredContainers);
ret.Data.AddRange(Enumerable.Repeat(0, requiredContainers - ret.Data.Count).Select(_ => new CombinedDataContainer(ret)));
}
ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings);
return ret;
}
public int GetIndex()
=> ModGroup.GetIndex(this);
public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer)
=> new CombiningModGroupEditDrawer(editDrawer, this);
public void AddData(Setting setting, Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
=> Data[setting.AsIndex].AddDataTo(redirections, manipulations);
public void AddChangedItems(ObjectIdentification identifier, IDictionary<string, IIdentifiedObjectData?> changedItems)
{
foreach (var container in DataContainers)
identifier.AddChangedItems(container, changedItems);
}
public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null)
{
ModSaveGroup.WriteJsonBase(jWriter, this);
jWriter.WritePropertyName("Options");
jWriter.WriteStartArray();
foreach (var option in OptionData)
{
jWriter.WriteStartObject();
SubMod.WriteModOption(jWriter, option);
jWriter.WriteEndObject();
}
jWriter.WriteEndArray();
jWriter.WritePropertyName("Containers");
jWriter.WriteStartArray();
foreach (var container in Data)
{
jWriter.WriteStartObject();
if (container.Name.Length > 0)
{
jWriter.WritePropertyName("Name");
jWriter.WriteValue(container.Name);
}
SubMod.WriteModContainer(jWriter, serializer, container, basePath ?? Mod.ModPath);
jWriter.WriteEndObject();
}
jWriter.WriteEndArray();
}
public (int Redirections, int Swaps, int Manips) GetCounts()
=> ModGroup.GetCountsBase(this);
public Setting FixSetting(Setting setting)
=> new(Math.Min(setting.Value, (ulong)(Data.Count - 1)));
/// <summary> Create a group without a mod only for saving it in the creator. </summary>
internal static CombiningModGroup WithoutMod(string name)
=> new(null!)
{
Name = name,
};
/// <summary> For loading when no empty container should be created. </summary>
private CombiningModGroup(Mod mod, bool _)
{
Mod = mod;
Data = [];
}
}

View file

@ -22,7 +22,8 @@ public enum GroupDrawBehaviour
public interface IModGroup
{
public const int MaxMultiOptions = 32;
public const int MaxMultiOptions = 32;
public const int MaxCombiningOptions = 8;
public Mod Mod { get; }
public string Name { get; set; }

View file

@ -0,0 +1,48 @@
using OtterGui;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
namespace Penumbra.Mods.Manager.OptionEditor;
public sealed class CombiningModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
: ModOptionEditor<CombiningModGroup, CombiningSubMod>(communicator, saveService, config), IService
{
protected override CombiningModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync)
=> new(mod)
{
Name = newName,
Priority = priority,
};
protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option)
=> throw new NotImplementedException();
protected override void RemoveOption(CombiningModGroup group, int optionIndex)
{
if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex))
group.DefaultSettings.RemoveBit(optionIndex);
}
protected override bool MoveOption(CombiningModGroup group, int optionIdxFrom, int optionIdxTo)
{
if (!group.OptionData.MoveWithPowerSet(group.Data, ref optionIdxFrom, ref optionIdxTo))
return false;
group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
return true;
}
public void SetDisplayName(CombinedDataContainer container, string name, SaveType saveType = SaveType.Queue)
{
if (container.Name == name)
return;
container.Name = name;
SaveService.Save(saveType, new ModSaveGroup(container.Group, Config.ReplaceNonAsciiOnImport));
Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, container.Group.Mod, container.Group, null, null, -1);
}
}

View file

@ -37,6 +37,7 @@ public class ModGroupEditor(
SingleModGroupEditor singleEditor,
MultiModGroupEditor multiEditor,
ImcModGroupEditor imcEditor,
CombiningModGroupEditor combiningEditor,
CommunicatorService communicator,
SaveService saveService,
Configuration config) : IService
@ -50,6 +51,9 @@ public class ModGroupEditor(
public ImcModGroupEditor ImcEditor
=> imcEditor;
public CombiningModGroupEditor CombiningEditor
=> combiningEditor;
/// <summary> Change the settings stored as default options in a mod.</summary>
public void ChangeModGroupDefaultOption(IModGroup group, Setting defaultOption)
{
@ -223,52 +227,60 @@ public class ModGroupEditor(
case ImcSubMod i:
ImcEditor.DeleteOption(i);
return;
case CombiningSubMod c:
CombiningEditor.DeleteOption(c);
return;
}
}
public IModOption? AddOption(IModGroup group, IModOption option)
=> group switch
{
SingleModGroup s => SingleEditor.AddOption(s, option),
MultiModGroup m => MultiEditor.AddOption(m, option),
ImcModGroup i => ImcEditor.AddOption(i, option),
_ => null,
SingleModGroup s => SingleEditor.AddOption(s, option),
MultiModGroup m => MultiEditor.AddOption(m, option),
ImcModGroup i => ImcEditor.AddOption(i, option),
CombiningModGroup c => CombiningEditor.AddOption(c, option),
_ => null,
};
public IModOption? AddOption(IModGroup group, string newName)
=> group switch
{
SingleModGroup s => SingleEditor.AddOption(s, newName),
MultiModGroup m => MultiEditor.AddOption(m, newName),
ImcModGroup i => ImcEditor.AddOption(i, newName),
_ => null,
SingleModGroup s => SingleEditor.AddOption(s, newName),
MultiModGroup m => MultiEditor.AddOption(m, newName),
ImcModGroup i => ImcEditor.AddOption(i, newName),
CombiningModGroup c => CombiningEditor.AddOption(c, newName),
_ => null,
};
public IModGroup? AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync)
=> type switch
{
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
_ => null,
GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType),
GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType),
GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType),
GroupType.Combining => CombiningEditor.AddModGroup(mod, newName, saveType),
_ => null,
};
public (IModGroup?, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string name, SaveType saveType = SaveType.ImmediateSync)
=> type switch
{
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
_ => (null, -1, false),
GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType),
GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType),
GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType),
GroupType.Combining => CombiningEditor.FindOrAddModGroup(mod, name, saveType),
_ => (null, -1, false),
};
public (IModOption?, int, bool) FindOrAddOption(IModGroup group, string name, SaveType saveType = SaveType.ImmediateSync)
=> group switch
{
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
_ => (null, -1, false),
SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType),
MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType),
ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType),
CombiningModGroup c => CombiningEditor.FindOrAddOption(c, name, saveType),
_ => (null, -1, false),
};
public void MoveOption(IModOption option, int toIdx)
@ -284,6 +296,9 @@ public class ModGroupEditor(
case ImcSubMod i:
ImcEditor.MoveOption(i, toIdx);
return;
case CombiningSubMod c:
CombiningEditor.MoveOption(c, toIdx);
return;
}
}
}

View file

@ -82,13 +82,11 @@ public partial class ModCreator(
if (incorporateMetaChanges)
IncorporateAllMetaChanges(mod, true);
if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges)
{
foreach (var container in mod.AllDataContainers)
{
if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations))
saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport));
}
}
return true;
}
@ -186,7 +184,8 @@ public partial class ModCreator(
/// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod.
/// If delete is true, the files are deleted afterwards.
/// </summary>
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete, bool deleteDefault)
public (bool Changes, List<string> DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete,
bool deleteDefault)
{
var deleteList = new List<string>();
var oldSize = option.Manipulations.Count;
@ -447,9 +446,10 @@ public partial class ModCreator(
var json = JObject.Parse(File.ReadAllText(file.FullName));
switch (json[nameof(Type)]?.ToObject<GroupType>() ?? GroupType.Single)
{
case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json);
case GroupType.Multi: return MultiModGroup.Load(mod, json);
case GroupType.Single: return SingleModGroup.Load(mod, json);
case GroupType.Imc: return ImcModGroup.Load(mod, json);
case GroupType.Combining: return CombiningModGroup.Load(mod, json);
}
}
catch (Exception e)

View file

@ -0,0 +1,72 @@
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups;
using Penumbra.String.Classes;
namespace Penumbra.Mods.SubMods;
public class CombinedDataContainer(IModGroup group) : IModDataContainer
{
public IMod Mod
=> Group.Mod;
public IModGroup Group { get; } = group;
public string Name { get; set; } = string.Empty;
public Dictionary<Utf8GamePath, FullPath> Files { get; set; } = [];
public Dictionary<Utf8GamePath, FullPath> FileSwaps { get; set; } = [];
public MetaDictionary Manipulations { get; set; } = new();
public void AddDataTo(Dictionary<Utf8GamePath, FullPath> redirections, MetaDictionary manipulations)
=> SubMod.AddContainerTo(this, redirections, manipulations);
public string GetName()
{
if (Name.Length > 0)
return Name;
var index = GetDataIndex();
if (index == 0)
return "None";
var sb = new StringBuilder(128);
for (var i = 0; i < IModGroup.MaxCombiningOptions; ++i)
{
if ((index & 1) != 0)
{
sb.Append(Group.Options[i].Name);
sb.Append(' ').Append('+').Append(' ');
}
index >>= 1;
if (index == 0)
break;
}
return sb.ToString(0, sb.Length - 3);
}
public string GetFullName()
=> $"{Group.Name}: {GetName()}";
public (int GroupIndex, int DataIndex) GetDataIndices()
=> (Group.GetIndex(), GetDataIndex());
private int GetDataIndex()
{
var dataIndex = Group.DataContainers.IndexOf(this);
if (dataIndex < 0)
throw new Exception($"Group {Group.Name} from SubMod {Name} does not contain this SubMod.");
return dataIndex;
}
public CombinedDataContainer(CombiningModGroup group, JToken token)
: this(group)
{
SubMod.LoadDataContainer(token, this, group.Mod.ModPath);
Name = token["Name"]?.ToObject<string>() ?? string.Empty;
}
}

View file

@ -0,0 +1,25 @@
using Newtonsoft.Json.Linq;
using Penumbra.Mods.Groups;
namespace Penumbra.Mods.SubMods;
public class CombiningSubMod(IModGroup group) : IModOption
{
public IModGroup Group { get; } = group;
public Mod Mod
=> Group.Mod;
public string Name { get; set; } = "Option";
public string Description { get; set; } = string.Empty;
public string FullName
=> $"{Group.Name}: {Name}";
public int GetIndex()
=> SubMod.GetIndex(this);
public CombiningSubMod(CombiningModGroup group, JToken json)
: this(group)
=> SubMod.LoadOptionData(json, this);
}

View file

@ -81,29 +81,40 @@ public static class SubMod
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath)
{
j.WritePropertyName(nameof(data.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in data.Files)
if (data.Files.Count > 0)
{
if (file.ToRelPath(basePath, out var relPath))
j.WritePropertyName(nameof(data.Files));
j.WriteStartObject();
foreach (var (gamePath, file) in data.Files)
{
if (file.ToRelPath(basePath, out var relPath))
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
}
}
j.WriteEndObject();
}
if (data.FileSwaps.Count > 0)
{
j.WritePropertyName(nameof(data.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in data.FileSwaps)
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(relPath.ToString());
j.WriteValue(file.ToString());
}
j.WriteEndObject();
}
j.WriteEndObject();
j.WritePropertyName(nameof(data.FileSwaps));
j.WriteStartObject();
foreach (var (gamePath, file) in data.FileSwaps)
if (data.Manipulations.Count > 0)
{
j.WritePropertyName(gamePath.ToString());
j.WriteValue(file.ToString());
j.WritePropertyName(nameof(data.Manipulations));
serializer.Serialize(j, data.Manipulations);
}
j.WriteEndObject();
j.WritePropertyName(nameof(data.Manipulations));
serializer.Serialize(j, data.Manipulations);
}
/// <summary> Write the data for a selectable mod option on a JsonWriter. </summary>

View file

@ -452,19 +452,17 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
private bool DrawOptionSelectHeader()
{
const string defaultOption = "Default Option";
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero).Push(ImGuiStyleVar.FrameRounding, 0);
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
var ret = false;
if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
_editor.Option is DefaultSubMod))
if (ImUtf8.ButtonEx("Default Option"u8, "Switch to the default option for the mod.\nThis resets unsaved changes."u8, width, _editor.Option is DefaultSubMod))
{
_editor.LoadOption(-1, 0).Wait();
ret = true;
}
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false))
if (ImUtf8.ButtonEx("Refresh Data"u8, "Refresh data for the current option.\nThis resets unsaved changes."u8, width))
{
_editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx).Wait();
ret = true;
@ -474,7 +472,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
ImGui.SetNextItemWidth(width.X);
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
using var combo = ImRaii.Combo("##optionSelector", _editor.Option!.GetFullName());
using var combo = ImUtf8.Combo("##optionSelector"u8, _editor.Option!.GetFullName());
if (!combo)
return ret;

View file

@ -48,6 +48,7 @@ public class AddGroupDrawer : IUiService
DrawSingleGroupButton(mod, buttonWidth);
ImUtf8.SameLineInner();
DrawMultiGroupButton(mod, buttonWidth);
DrawCombiningGroupButton(mod, buttonWidth);
}
private void DrawSingleGroupButton(Mod mod, Vector2 width)
@ -76,6 +77,18 @@ public class AddGroupDrawer : IUiService
_groupNameValid = false;
}
private void DrawCombiningGroupButton(Mod mod, Vector2 width)
{
if (!ImUtf8.ButtonEx("Add Combining Group"u8, _groupNameValid
? "Add a new combining option group to this mod."u8
: "Can not add a new group of this name."u8,
width, !_groupNameValid))
return;
_modManager.OptionEditor.AddModGroup(mod, GroupType.Combining, _groupName);
_groupName = string.Empty;
_groupNameValid = false;
}
private void DrawImcInput(float width)
{
var change = ImcMetaDrawer.DrawObjectType(ref _imcIdentifier, width);

View file

@ -0,0 +1,111 @@
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.Mods.Groups;
using Penumbra.Mods.SubMods;
namespace Penumbra.UI.ModsTab.Groups;
public readonly struct CombiningModGroupEditDrawer(ModGroupEditDrawer editor, CombiningModGroup group) : IModGroupEditDrawer
{
public void Draw()
{
foreach (var (option, optionIdx) in group.OptionData.WithIndex())
{
using var id = ImUtf8.PushId(optionIdx);
editor.DrawOptionPosition(group, option, optionIdx);
ImUtf8.SameLineInner();
editor.DrawOptionDefaultMultiBehaviour(group, option, optionIdx);
ImUtf8.SameLineInner();
editor.DrawOptionName(option);
ImUtf8.SameLineInner();
editor.DrawOptionDescription(option);
ImUtf8.SameLineInner();
editor.DrawOptionDelete(option);
}
DrawNewOption();
DrawContainerNames();
}
private void DrawNewOption()
{
var count = group.OptionData.Count;
if (count >= IModGroup.MaxCombiningOptions)
return;
var name = editor.DrawNewOptionBase(group, count);
var validName = name.Length > 0;
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
? "Add a new option to this group."u8
: "Please enter a name for the new option."u8, default, !validName))
{
editor.ModManager.OptionEditor.CombiningEditor.AddOption(group, name);
editor.NewOptionName = null;
}
}
private unsafe void DrawContainerNames()
{
if (ImUtf8.ButtonEx("Edit Container Names"u8,
"Add optional names to separate data containers of the combining group.\nThose are just for easier identification while editing the mod, and are not generally displayed to the user."u8,
new Vector2(400 * ImUtf8.GlobalScale, 0)))
ImUtf8.OpenPopup("DataContainerNames"u8);
var sizeX = group.OptionData.Count * (ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight()) + 300 * ImUtf8.GlobalScale;
ImGui.SetNextWindowSize(new Vector2(sizeX, ImGui.GetFrameHeightWithSpacing() * Math.Min(16, group.Data.Count) + 200 * ImUtf8.GlobalScale));
using var popup = ImUtf8.Popup("DataContainerNames"u8);
if (!popup)
return;
foreach (var option in group.OptionData)
{
ImUtf8.RotatedText(option.Name, true);
ImUtf8.SameLineInner();
}
ImGui.NewLine();
ImGui.Separator();
using var child = ImUtf8.Child("##Child"u8, ImGui.GetContentRegionAvail());
ImGuiClip.ClippedDraw(group.Data, DrawRow, ImGui.GetFrameHeightWithSpacing());
}
private void DrawRow(CombinedDataContainer container, int index)
{
using var id = ImUtf8.PushId(index);
using (ImRaii.Disabled())
{
for (var i = 0; i < group.OptionData.Count; ++i)
{
id.Push(i);
var check = (index & (1 << i)) != 0;
ImUtf8.Checkbox(""u8, ref check);
ImUtf8.SameLineInner();
id.Pop();
}
}
var name = editor.CombiningDisplayIndex == index ? editor.CombiningDisplayName ?? container.Name : container.Name;
if (ImUtf8.InputText("##Nothing"u8, ref name, "Optional Display Name..."u8))
{
editor.CombiningDisplayIndex = index;
editor.CombiningDisplayName = name;
}
if (ImGui.IsItemDeactivatedAfterEdit())
editor.ModManager.OptionEditor.CombiningEditor.SetDisplayName(container, name);
if (ImGui.IsItemDeactivated())
{
editor.CombiningDisplayIndex = -1;
editor.CombiningDisplayName = null;
}
}
}

View file

@ -58,6 +58,9 @@ public sealed class ModGroupEditDrawer(
private IModOption? _dragDropOption;
private bool _draggingAcross;
internal string? CombiningDisplayName;
internal int CombiningDisplayIndex;
public void Draw(Mod mod)
{
PrepareStyle();
@ -275,6 +278,7 @@ public sealed class ModGroupEditDrawer(
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal string DrawNewOptionBase(IModGroup group, int count)
{
ImGui.AlignTextToFramePadding();
ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable);
Target(group, count);