Use Luna tabbing.
Some checks failed
.NET Build / build (push) Has been cancelled

This commit is contained in:
Ottermandias 2025-11-03 20:54:54 +01:00
parent 5a2fddab89
commit 9aa1121410
28 changed files with 616 additions and 719 deletions

2
Luna

@ -1 +1 @@
Subproject commit 2e984d9c21370c778d172ab955def18c0dbe8c7d Subproject commit cb294f476476f7a3d8b56a0072dbd300b3d54c4f

View file

@ -10,7 +10,7 @@ public sealed class SelectTab(Logger log) : EventBase<SelectTab.Arguments, Selec
public enum Priority public enum Priority
{ {
/// <seealso cref="UI.Tabs.ConfigTabBar.OnSelectTab"/> /// <seealso cref="UI.Tabs.ConfigTabBar.OnSelectTab"/>
ConfigTabBar = 0, MainTabBar = 0,
} }
/// <summary> The arguments for a SelectTab event. </summary> /// <summary> The arguments for a SelectTab event. </summary>

View file

@ -10,6 +10,7 @@ using Penumbra.UI;
using Penumbra.UI.ResourceWatcher; using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs; using Penumbra.UI.Tabs;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra; namespace Penumbra;

View file

@ -134,7 +134,7 @@ public class Penumbra : IDalamudPlugin
AsyncTask.Run(() => AsyncTask.Run(() =>
{ {
var system = _services.GetService<PenumbraWindowSystem>(); var system = _services.GetService<PenumbraWindowSystem>();
system.Window.Setup(this, _services.GetService<ConfigTabBar>()); system.Window.Setup(this, _services.GetService<MainTabBar>());
_services.GetService<CommandHandler>(); _services.GetService<CommandHandler>();
if (!_disposed) if (!_disposed)
{ {
@ -199,8 +199,12 @@ public class Penumbra : IDalamudPlugin
{ {
ReadOnlySpan<string> relevantPlugins = ReadOnlySpan<string> relevantPlugins =
[ [
"Glamourer", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", "Glamourer", "CustomizePlus", "SimpleHeels",
"IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn", "Ktisis", "Brio",
"heliosphere-plugin", "VfxEditor", "IllusioVitae", "Aetherment",
"DynamicBridge", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn",
"MareSynchronos", "LoporritSync", "KittenSync", "Snowcloak", "LightlessSync", "Sphene", "XivSync", "MareSempiterne" /* PlayerSync */, "AnatoliIliou", "LaciSynchroni"
]; ];
var plugins = _services.GetService<IDalamudPluginInterface>().InstalledPlugins var plugins = _services.GetService<IDalamudPluginInterface>().InstalledPlugins
.GroupBy(p => p.InternalName) .GroupBy(p => p.InternalName)

View file

@ -14,6 +14,7 @@ using Penumbra.UI;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
using Penumbra.UI.ResourceWatcher; using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs; using Penumbra.UI.Tabs;
using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra.Services; namespace Penumbra.Services;

View file

@ -4,7 +4,6 @@ using ImSharp;
using Luna; using Luna;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
@ -25,6 +24,7 @@ using Penumbra.Mods.SubMods;
using Penumbra.Services; using Penumbra.Services;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
using Penumbra.UI.ModsTab; using Penumbra.UI.ModsTab;
using ITab = OtterGui.Widgets.ITab;
using MouseWheelType = OtterGui.Widgets.MouseWheelType; using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Penumbra.UI.AdvancedWindow; namespace Penumbra.UI.AdvancedWindow;

View file

@ -1,10 +1,10 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Penumbra.Api.Enums;
using Penumbra.Services; using Penumbra.Services;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
using Penumbra.UI.Tabs; using Penumbra.UI.Tabs;
using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra.UI; namespace Penumbra.UI;
@ -14,7 +14,7 @@ public sealed class ConfigWindow : Window
private readonly Configuration _config; private readonly Configuration _config;
private readonly ValidityChecker _validityChecker; private readonly ValidityChecker _validityChecker;
private Penumbra? _penumbra; private Penumbra? _penumbra;
private ConfigTabBar _configTabs = null!; private MainTabBar _configTabs = null!;
private string? _lastException; private string? _lastException;
public ConfigWindow(IDalamudPluginInterface pi, Configuration config, ValidityChecker checker, public ConfigWindow(IDalamudPluginInterface pi, Configuration config, ValidityChecker checker,
@ -32,15 +32,15 @@ public sealed class ConfigWindow : Window
public void OpenSettings() public void OpenSettings()
{ {
_configTabs.SelectTab = TabType.Settings; _configTabs.NextTab = TabType.Settings;
IsOpen = true; IsOpen = true;
} }
public void Setup(Penumbra penumbra, ConfigTabBar configTabs) public void Setup(Penumbra penumbra, MainTabBar configTabs)
{ {
_penumbra = penumbra; _penumbra = penumbra;
_configTabs = configTabs; _configTabs = configTabs;
_configTabs.SelectTab = _config.Ephemeral.SelectedTab; _configTabs.NextTab = _config.Ephemeral.SelectedTab;
} }
public override bool DrawConditions() public override bool DrawConditions()
@ -98,12 +98,7 @@ public sealed class ConfigWindow : Window
} }
else else
{ {
var type = _configTabs.Draw(); _configTabs.Draw();
if (type != _config.Ephemeral.SelectedTab)
{
_config.Ephemeral.SelectedTab = type;
_config.Ephemeral.Save();
}
} }
_lastException = null; _lastException = null;

View file

@ -15,18 +15,18 @@ public class IncognitoService(TutorialService tutorial, Configuration config) :
var color = ColorId.FolderExpanded.Value(); var color = ColorId.FolderExpanded.Value();
using (ImStyleBorder.Frame.Push(color)) using (ImStyleBorder.Frame.Push(color))
{ {
var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8; var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8;
var icon = IncognitoMode ? LunaStyle.IncognitoOn : LunaStyle.IncognitoOff; var icon = IncognitoMode ? LunaStyle.IncognitoOn : LunaStyle.IncognitoOff;
if (ImEx.Icon.Button(icon, tt, size: new Vector2(width, Im.Style.FrameHeight), textColor: color) && hold) if (ImEx.Icon.Button(icon, tt, size: new Vector2(width, Im.Style.FrameHeight), textColor: color) && hold)
{ {
config.Ephemeral.IncognitoMode = !IncognitoMode; config.Ephemeral.IncognitoMode = !IncognitoMode;
config.Ephemeral.Save(); config.Ephemeral.Save();
} }
if (!hold)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"\nHold {config.IncognitoModifier} while clicking to toggle.");
} }
if (!hold)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"\nHold {config.IncognitoModifier} while clicking to toggle.");
tutorial.OpenTutorial(BasicTutorialSteps.Incognito); tutorial.OpenTutorial(BasicTutorialSteps.Incognito);
} }
} }

View file

@ -6,7 +6,6 @@ using Luna;
using OtterGui; using OtterGui;
using OtterGui.Services; using OtterGui.Services;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -23,7 +22,7 @@ public class ModPanelChangedItemsTab(
ImGuiCacheService cacheService, ImGuiCacheService cacheService,
Configuration config, Configuration config,
ModDataEditor dataEditor) ModDataEditor dataEditor)
: ITab, Luna.IUiService : ITab<ModPanelTab>
{ {
private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId(); private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId();
@ -209,6 +208,9 @@ public class ModPanelChangedItemsTab(
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Changed Items"u8; => "Changed Items"u8;
public ModPanelTab Identifier
=> ModPanelTab.ChangedItems;
public bool IsVisible public bool IsVisible
=> selector.Selected!.ChangedItems.Count > 0; => selector.Selected!.ChangedItems.Count > 0;

View file

@ -1,9 +1,6 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using ImSharp; using ImSharp;
using OtterGui.Raii; using Luna;
using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.Mods; using Penumbra.Mods;
@ -11,7 +8,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab; namespace Penumbra.UI.ModsTab;
public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab, Luna.IUiService public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab<ModPanelTab>
{ {
private enum ModState private enum ModState
{ {
@ -24,19 +21,22 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Collections"u8; => "Collections"u8;
public ModPanelTab Identifier
=> ModPanelTab.Collections;
public void DrawContent() public void DrawContent()
{ {
var (direct, inherited) = CountUsage(selector.Selected!); var (direct, inherited) = CountUsage(selector.Selected!);
Im.Line.New(); Im.Line.New();
if (direct == 1) switch (direct)
ImUtf8.Text("This Mod is directly configured in 1 collection."u8); {
else if (direct == 0) case 1: Im.Text("This Mod is directly configured in 1 collection."u8); break;
ImUtf8.Text("This mod is entirely unused."u8, Colors.RegexWarningBorder); case 0: Im.Text("This mod is entirely unused."u8, Colors.RegexWarningBorder); break;
else default: Im.Text($"This Mod is directly configured in {direct} collections."); break;
ImUtf8.Text($"This Mod is directly configured in {direct} collections."); }
if (inherited > 0) if (inherited > 0)
ImUtf8.Text($"It is also implicitly used in {inherited} {(inherited == 1 ? "collection" : "collections")} through inheritance."); Im.Text($"It is also implicitly used in {inherited} {(inherited == 1 ? "collection" : "collections")} through inheritance.");
Im.Line.New(); Im.Line.New();
Im.Separator(); Im.Separator();
@ -45,7 +45,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
if (!table) if (!table)
return; return;
var size = ImUtf8.CalcTextSize(ToText(ModState.Unconfigured)).X + 20 * Im.Style.GlobalScale; var size = Im.Font.CalculateSize(ToText(ModState.Unconfigured)).X + 20 * Im.Style.GlobalScale;
var collectionSize = 200 * Im.Style.GlobalScale; var collectionSize = 200 * Im.Style.GlobalScale;
table.SetupColumn("Collection"u8, TableColumnFlags.WidthFixed, collectionSize); table.SetupColumn("Collection"u8, TableColumnFlags.WidthFixed, collectionSize);
table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, size); table.SetupColumn("State"u8, TableColumnFlags.WidthFixed, size);
@ -54,21 +54,21 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
foreach (var (idx, (collection, parent, color, state)) in _cache.Index()) foreach (var (idx, (collection, parent, color, state)) in _cache.Index())
{ {
using var id = ImUtf8.PushId(idx); using var id = Im.Id.Push(idx);
ImUtf8.DrawTableColumn(collection.Identity.Name); table.DrawColumn(collection.Identity.Name);
ImGui.TableNextColumn(); table.NextColumn();
ImUtf8.Text(ToText(state), color); Im.Text(ToText(state), color);
using (var context = ImUtf8.PopupContextItem("Context"u8)) using (var context = Im.Popup.BeginContextItem("Context"u8))
{ {
if (context) if (context)
{ {
ImUtf8.Text(collection.Identity.Name); Im.Text(collection.Identity.Name);
Im.Separator(); Im.Separator();
using (ImRaii.Disabled(state is ModState.Enabled && parent == collection)) using (Im.Disabled(state is ModState.Enabled && parent == collection))
{ {
if (ImUtf8.MenuItem("Enable"u8)) if (Im.Menu.Item("Enable"u8))
{ {
if (parent != collection) if (parent != collection)
manager.Editor.SetModInheritance(collection, selector.Selected!, false); manager.Editor.SetModInheritance(collection, selector.Selected!, false);
@ -76,9 +76,9 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
} }
} }
using (ImRaii.Disabled(state is ModState.Disabled && parent == collection)) using (Im.Disabled(state is ModState.Disabled && parent == collection))
{ {
if (ImUtf8.MenuItem("Disable"u8)) if (Im.Menu.Item("Disable"u8))
{ {
if (parent != collection) if (parent != collection)
manager.Editor.SetModInheritance(collection, selector.Selected!, false); manager.Editor.SetModInheritance(collection, selector.Selected!, false);
@ -86,15 +86,15 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
} }
} }
using (ImRaii.Disabled(parent != collection)) using (Im.Disabled(parent != collection))
{ {
if (ImUtf8.MenuItem("Inherit"u8)) if (Im.Menu.Item("Inherit"u8))
manager.Editor.SetModInheritance(collection, selector.Selected!, true); manager.Editor.SetModInheritance(collection, selector.Selected!, true);
} }
} }
} }
ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Identity.Name); table.DrawColumn(parent == collection ? StringU8.Empty : parent.Identity.Name);
} }
} }

View file

@ -13,11 +13,14 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab; namespace Penumbra.UI.ModsTab;
public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab, IUiService public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab<ModPanelTab>
{ {
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Conflicts"u8; => "Conflicts"u8;
public ModPanelTab Identifier
=> ModPanelTab.Conflicts;
public bool IsVisible public bool IsVisible
=> collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden); => collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden);

View file

@ -1,7 +1,6 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using OtterGui.Raii; using Luna;
using OtterGui;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
@ -12,23 +11,25 @@ public class ModPanelDescriptionTab(
TutorialService tutorial, TutorialService tutorial,
ModManager modManager, ModManager modManager,
PredefinedTagManager predefinedTagsConfig) PredefinedTagManager predefinedTagsConfig)
: ITab, Luna.IUiService : ITab<ModPanelTab>
{ {
private readonly TagButtons _localTags = new(); private readonly TagButtons _localTags = new();
private readonly TagButtons _modTags = new(); private readonly TagButtons _modTags = new();
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Description"u8; => "Description"u8;
public ModPanelTab Identifier
=> ModPanelTab.Description;
public void DrawContent() public void DrawContent()
{ {
using var child = ImRaii.Child("##description"); using var child = Im.Child.Begin("##description"u8);
if (!child) if (!child)
return; return;
ImGui.Dummy(ImEx.ScaledVector(2)); Im.ScaledDummy(2, 2);
Im.ScaledDummy(2, 2);
ImGui.Dummy(ImEx.ScaledVector(2));
var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Enabled var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Enabled
? (true, Im.Style.FrameHeight + Im.Style.WindowPadding.X + (ImGui.GetScrollMaxY() > 0 ? Im.Style.ScrollbarSize : 0)) ? (true, Im.Style.FrameHeight + Im.Style.WindowPadding.X + (ImGui.GetScrollMaxY() > 0 ? Im.Style.ScrollbarSize : 0))
: (false, 0); : (false, 0);
@ -49,9 +50,9 @@ public class ModPanelDescriptionTab(
selector.Selected!.ModTags, out _, false, selector.Selected!.ModTags, out _, false,
ImGui.CalcTextSize("Local ").X - ImGui.CalcTextSize("Mod ").X); ImGui.CalcTextSize("Local ").X - ImGui.CalcTextSize("Mod ").X);
ImGui.Dummy(ImEx.ScaledVector(2)); Im.ScaledDummy(2, 2);
Im.Separator(); Im.Separator();
ImGuiUtil.TextWrapped(selector.Selected!.Description); Im.TextWrapped(selector.Selected!.Description);
} }
} }

View file

@ -1,370 +1,335 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Components; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification; using ImSharp;
using Dalamud.Bindings.ImGui; using Luna;
using ImSharp; using OtterGui.Widgets;
using Luna; using Penumbra.Mods;
using OtterGui; using Penumbra.Mods.Editor;
using OtterGui.Raii; using Penumbra.Mods.Manager;
using OtterGui.Widgets; using Penumbra.Services;
using OtterGui.Text; using Penumbra.UI.ModsTab.Groups;
using Penumbra.Mods;
using Penumbra.Mods.Editor; namespace Penumbra.UI.ModsTab;
using Penumbra.Mods.Manager;
using Penumbra.Services; public class ModPanelEditTab(
using Penumbra.Mods.Settings; ModManager modManager,
using Penumbra.UI.ModsTab.Groups; ModFileSystemSelector selector,
ModFileSystem fileSystem,
namespace Penumbra.UI.ModsTab; Services.MessageService messager,
FilenameService filenames,
public class ModPanelEditTab( ModExportManager modExportManager,
ModManager modManager, Configuration config,
ModFileSystemSelector selector, PredefinedTagManager predefinedTagManager,
ModFileSystem fileSystem, ModGroupEditDrawer groupEditDrawer,
Services.MessageService messager, DescriptionEditPopup descriptionPopup,
FilenameService filenames, AddGroupDrawer addGroupDrawer)
ModExportManager modExportManager, : ITab<ModPanelTab>
Configuration config, {
PredefinedTagManager predefinedTagManager, private readonly TagButtons _modTags = new();
ModGroupEditDrawer groupEditDrawer,
DescriptionEditPopup descriptionPopup, private ModFileSystem.Leaf _leaf = null!;
AddGroupDrawer addGroupDrawer) private Mod _mod = null!;
: ITab, IUiService
{ public ReadOnlySpan<byte> Label
private readonly TagButtons _modTags = new(); => "Edit Mod"u8;
private ModFileSystem.Leaf _leaf = null!; public ModPanelTab Identifier
private Mod _mod = null!; => ModPanelTab.Edit;
public ReadOnlySpan<byte> Label public void DrawContent()
=> "Edit Mod"u8; {
using var child = Im.Child.Begin("##editChild"u8, Im.ContentRegion.Available);
public void DrawContent() if (!child)
{ return;
using var child = ImRaii.Child("##editChild", -Vector2.One);
if (!child) _leaf = selector.SelectedLeaf!;
return; _mod = selector.Selected!;
_leaf = selector.SelectedLeaf!; EditButtons();
_mod = selector.Selected!; EditRegularMeta();
UiHelpers.DefaultLineSpace();
EditButtons(); EditLocalData();
EditRegularMeta(); UiHelpers.DefaultLineSpace();
UiHelpers.DefaultLineSpace();
EditLocalData(); if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullName(), out var newPath, UiHelpers.InputTextWidth.X))
UiHelpers.DefaultLineSpace(); try
{
if (Input.Text("Mod Path", Input.Path, Input.None, _leaf.FullName(), out var newPath, 256, UiHelpers.InputTextWidth.X)) fileSystem.RenameAndMove(_leaf, newPath);
try }
{ catch (Exception e)
fileSystem.RenameAndMove(_leaf, newPath); {
} messager.NotificationMessage(e.Message, NotificationType.Warning, false);
catch (Exception e) }
{
messager.NotificationMessage(e.Message, NotificationType.Warning, false); UiHelpers.DefaultLineSpace();
}
FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X);
UiHelpers.DefaultLineSpace();
UiHelpers.DefaultLineSpace();
FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X); var sharedTagsEnabled = predefinedTagManager.Enabled;
var sharedTagButtonOffset = sharedTagsEnabled ? Im.Style.FrameHeight + Im.Style.FramePadding.X : 0;
UiHelpers.DefaultLineSpace(); var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags,
var sharedTagsEnabled = predefinedTagManager.Enabled; out var editedTag, rightEndOffset: sharedTagButtonOffset);
var sharedTagButtonOffset = sharedTagsEnabled ? Im.Style.FrameHeight + Im.Style.FramePadding.X : 0; if (tagIdx >= 0)
var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags, modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
out var editedTag, rightEndOffset: sharedTagButtonOffset);
if (tagIdx >= 0) if (sharedTagsEnabled)
modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag); predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
selector.Selected!);
if (sharedTagsEnabled)
predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
selector.Selected!); UiHelpers.DefaultLineSpace();
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X);
UiHelpers.DefaultLineSpace();
UiHelpers.DefaultLineSpace();
addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X); groupEditDrawer.Draw(_mod);
UiHelpers.DefaultLineSpace(); descriptionPopup.Draw();
}
groupEditDrawer.Draw(_mod);
descriptionPopup.Draw(); public void Reset()
} {
MoveDirectory.Reset();
public void Reset() Input.Reset();
{ }
MoveDirectory.Reset();
Input.Reset(); /// <summary> The general edit row for non-detailed mod edits. </summary>
} private void EditButtons()
{
/// <summary> The general edit row for non-detailed mod edits. </summary> var buttonSize = new Vector2(150 * Im.Style.GlobalScale, 0);
private void EditButtons() var folderExists = Directory.Exists(_mod.ModPath.FullName);
{ if (ImEx.Button("Open Mod Directory"u8, buttonSize, folderExists
var buttonSize = new Vector2(150 * Im.Style.GlobalScale, 0); ? $"Open \"{_mod.ModPath.FullName}\" in the file explorer of your choice."
var folderExists = Directory.Exists(_mod.ModPath.FullName); : $"Mod directory \"{_mod.ModPath.FullName}\" does not exist.", !folderExists))
var tt = folderExists Process.Start(new ProcessStartInfo(_mod.ModPath.FullName) { UseShellExecute = true });
? $"Open \"{_mod.ModPath.FullName}\" in the file explorer of your choice."
: $"Mod directory \"{_mod.ModPath.FullName}\" does not exist."; Im.Line.Same();
if (ImGuiUtil.DrawDisabledButton("Open Mod Directory", buttonSize, tt, !folderExists)) if (ImEx.Button("Reload Mod"u8, buttonSize, "Reload the current mod from its files.\n"u8
Process.Start(new ProcessStartInfo(_mod.ModPath.FullName) { UseShellExecute = true }); + "If the mod directory or meta file do not exist anymore or if the new mod name is empty, the mod is deleted instead."u8,
false))
Im.Line.Same(); modManager.ReloadMod(_mod);
if (ImGuiUtil.DrawDisabledButton("Reload Mod", buttonSize, "Reload the current mod from its files.\n"
+ "If the mod directory or meta file do not exist anymore or if the new mod name is empty, the mod is deleted instead.", BackupButtons(buttonSize);
false)) MoveDirectory.Draw(modManager, _mod, buttonSize);
modManager.ReloadMod(_mod);
UiHelpers.DefaultLineSpace();
BackupButtons(buttonSize); }
MoveDirectory.Draw(modManager, _mod, buttonSize);
private void BackupButtons(Vector2 buttonSize)
UiHelpers.DefaultLineSpace(); {
} var backup = new ModBackup(modExportManager, _mod);
if (ImEx.Button("Export Mod"u8, buttonSize, ModBackup.CreatingBackup
private void BackupButtons(Vector2 buttonSize) ? "Already exporting a mod."
{ : backup.Exists
var backup = new ModBackup(modExportManager, _mod); ? $"Overwrite current exported mod \"{backup.Name}\" with current mod."
var tt = ModBackup.CreatingBackup : $"Create exported archive of current mod at \"{backup.Name}\".", ModBackup.CreatingBackup))
? "Already exporting a mod." backup.CreateAsync();
: backup.Exists
? $"Overwrite current exported mod \"{backup.Name}\" with current mod." if (Im.Item.RightClicked())
: $"Create exported archive of current mod at \"{backup.Name}\"."; Im.Popup.Open("context"u8);
if (ImUtf8.ButtonEx("Export Mod"u8, tt, buttonSize, ModBackup.CreatingBackup))
backup.CreateAsync(); Im.Line.Same();
if (ImEx.Button("Delete Export"u8, buttonSize, backup.Exists
if (Im.Item.RightClicked()) ? $"Delete existing mod export \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
ImUtf8.OpenPopup("context"u8); : $"Exported mod \"{backup.Name}\" does not exist.", !backup.Exists || !config.DeleteModModifier.IsActive()))
backup.Delete();
Im.Line.Same();
tt = backup.Exists Im.Line.Same();
? $"Delete existing mod export \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)." if (ImEx.Button("Restore From Export"u8, buttonSize, backup.Exists
: $"Exported mod \"{backup.Name}\" does not exist."; ? $"Restore mod from exported file \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)."
if (ImUtf8.ButtonEx("Delete Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive())) : $"Exported mod \"{backup.Name}\" does not exist.", !backup.Exists || !config.DeleteModModifier.IsActive()))
backup.Delete(); backup.Restore(modManager);
if (backup.Exists)
tt = backup.Exists {
? $"Restore mod from exported file \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)." Im.Line.Same();
: $"Exported mod \"{backup.Name}\" does not exist."; ImEx.Icon.Draw(FontAwesomeIcon.CheckCircle.Icon());
Im.Line.Same(); Im.Tooltip.OnHover($"Export exists in \"{backup.Name}\".");
if (ImUtf8.ButtonEx("Restore From Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive())) }
backup.Restore(modManager);
if (backup.Exists) using var context = Im.Popup.Begin("context"u8);
{ if (!context)
Im.Line.Same(); return;
using (ImRaii.PushFont(UiBuilder.IconFont))
{ if (Im.Selectable("Open Backup Directory"u8))
ImUtf8.Text(FontAwesomeIcon.CheckCircle.ToIconString()); Process.Start(new ProcessStartInfo(modExportManager.ExportDirectory.FullName) { UseShellExecute = true });
} }
Im.Tooltip.OnHover($"Export exists in \"{backup.Name}\"."); /// <summary> Anything about editing the regular meta information about the mod. </summary>
} private void EditRegularMeta()
{
using var context = ImUtf8.Popup("context"u8); if (Input.Text("Name"u8, Input.Name, Input.None, _mod.Name, out var newName, UiHelpers.InputTextWidth.X))
if (!context) modManager.DataEditor.ChangeModName(_mod, newName);
return;
if (Input.Text("Author"u8, Input.Author, Input.None, _mod.Author, out var newAuthor, UiHelpers.InputTextWidth.X))
if (ImUtf8.Selectable("Open Backup Directory"u8)) modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
Process.Start(new ProcessStartInfo(modExportManager.ExportDirectory.FullName) { UseShellExecute = true });
} if (Input.Text("Version"u8, Input.Version, Input.None, _mod.Version, out var newVersion,
UiHelpers.InputTextWidth.X))
/// <summary> Anything about editing the regular meta information about the mod. </summary> modManager.DataEditor.ChangeModVersion(_mod, newVersion);
private void EditRegularMeta()
{ if (Input.Text("Website"u8, Input.Website, Input.None, _mod.Website, out var newWebsite,
if (Input.Text("Name", Input.Name, Input.None, _mod.Name, out var newName, 256, UiHelpers.InputTextWidth.X)) UiHelpers.InputTextWidth.X))
modManager.DataEditor.ChangeModName(_mod, newName); modManager.DataEditor.ChangeModWebsite(_mod, newWebsite);
if (Input.Text("Author", Input.Author, Input.None, _mod.Author, out var newAuthor, 256, UiHelpers.InputTextWidth.X)) using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(Im.Style.GlobalScale * 3));
modManager.DataEditor.ChangeModAuthor(_mod, newAuthor);
var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0);
if (Input.Text("Version", Input.Version, Input.None, _mod.Version, out var newVersion, 32, if (Im.Button("Edit Description"u8, reducedSize))
UiHelpers.InputTextWidth.X)) descriptionPopup.Open(_mod);
modManager.DataEditor.ChangeModVersion(_mod, newVersion);
if (Input.Text("Website", Input.Website, Input.None, _mod.Website, out var newWebsite, 256, Im.Line.Same();
UiHelpers.InputTextWidth.X)) var fileExists = File.Exists(filenames.ModMetaPath(_mod));
modManager.DataEditor.ChangeModWebsite(_mod, newWebsite); var tt = fileExists
? "Open the metadata json file in the text editor of your choice."u8
using var style = ImStyleDouble.ItemSpacing.Push(new Vector2(Im.Style.GlobalScale * 3)); : "The metadata json file does not exist."u8;
using (Im.Id.Push("meta"))
var reducedSize = new Vector2(UiHelpers.InputTextMinusButton3, 0); {
if (ImGui.Button("Edit Description", reducedSize)) if (ImEx.Icon.Button(LunaStyle.FileExportIcon, tt, !fileExists))
descriptionPopup.Open(_mod); Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true });
}
Im.Line.Same(); DrawOpenDefaultMod();
var fileExists = File.Exists(filenames.ModMetaPath(_mod)); }
var tt = fileExists
? "Open the metadata json file in the text editor of your choice." private void EditLocalData()
: "The metadata json file does not exist."; {
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##metaFile", UiHelpers.IconButtonSize, tt, DrawImportDate();
!fileExists, true)) DrawOpenLocalData();
Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true }); }
DrawOpenDefaultMod(); private void DrawImportDate()
} {
ImEx.TextFramed($"{DateTimeOffset.FromUnixTimeMilliseconds(_mod.ImportDate).ToLocalTime():yyyy/MM/dd HH:mm}",
private void EditLocalData() new Vector2(UiHelpers.InputTextMinusButton3, 0), ImGuiColor.FrameBackground.Get(0.5f));
{ Im.Line.Same(0, 3 * Im.Style.GlobalScale);
DrawImportDate();
DrawOpenLocalData(); var canRefresh = config.DeleteModModifier.IsActive();
} if (ImEx.Icon.Button(LunaStyle.RefreshIcon, canRefresh
? "Reset the import date to the current date and time."u8
private void DrawImportDate() : $"Reset the import date to the current date and time.\nHold {config.DeleteModModifier} while clicking to refresh.",
{ !canRefresh))
ImEx.TextFramed($"{DateTimeOffset.FromUnixTimeMilliseconds(_mod.ImportDate).ToLocalTime():yyyy/MM/dd HH:mm}", modManager.DataEditor.ResetModImportDate(_mod);
new Vector2(UiHelpers.InputTextMinusButton3, 0), ImGuiColor.FrameBackground.Get(0.5f)); Im.Line.SameInner();
Im.Line.Same(0, 3 * Im.Style.GlobalScale); Im.Text("Import Date"u8);
}
var canRefresh = config.DeleteModModifier.IsActive();
var tt = canRefresh private void DrawOpenLocalData()
? "Reset the import date to the current date and time." {
: $"Reset the import date to the current date and time.\nHold {config.DeleteModModifier} while clicking to refresh."; var file = filenames.LocalDataFile(_mod);
var fileExists = File.Exists(file);
if (ImUtf8.IconButton(FontAwesomeIcon.Sync, tt, disabled: !canRefresh)) var tt = fileExists
modManager.DataEditor.ResetModImportDate(_mod); ? "Open the local mod data file in the text editor of your choice."u8
Im.Line.SameInner(); : "The local mod data file does not exist."u8;
ImUtf8.Text("Import Date"u8); if (ImEx.Button("Open Local Data"u8, UiHelpers.InputTextWidth, tt, !fileExists))
} Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
}
private void DrawOpenLocalData()
{ private void DrawOpenDefaultMod()
var file = filenames.LocalDataFile(_mod); {
var fileExists = File.Exists(file); var file = filenames.OptionGroupFile(_mod, -1, false);
var tt = fileExists var fileExists = File.Exists(file);
? "Open the local mod data file in the text editor of your choice."u8 var tt = fileExists
: "The local mod data file does not exist."u8; ? "Open the default mod data file in the text editor of your choice."u8
if (ImUtf8.ButtonEx("Open Local Data"u8, tt, UiHelpers.InputTextWidth, !fileExists)) : "The default mod data file does not exist."u8;
Process.Start(new ProcessStartInfo(file) { UseShellExecute = true }); if (ImEx.Button("Open Default Data"u8, UiHelpers.InputTextWidth, tt, !fileExists))
} Process.Start(new ProcessStartInfo(file) { UseShellExecute = true });
}
private void DrawOpenDefaultMod()
{
var file = filenames.OptionGroupFile(_mod, -1, false); /// <summary> A text input for the new directory name and a button to apply the move. </summary>
var fileExists = File.Exists(file); private static class MoveDirectory
var tt = fileExists {
? "Open the default mod data file in the text editor of your choice." private static string? _currentModDirectory;
: "The default mod data file does not exist."; private static NewDirectoryState _state = NewDirectoryState.Identical;
if (ImGuiUtil.DrawDisabledButton("Open Default Data", UiHelpers.InputTextWidth, tt, !fileExists))
Process.Start(new ProcessStartInfo(file) { UseShellExecute = true }); public static void Reset()
} {
_currentModDirectory = null;
_state = NewDirectoryState.Identical;
/// <summary> A text input for the new directory name and a button to apply the move. </summary> }
private static class MoveDirectory
{ public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize)
private static string? _currentModDirectory; {
private static NewDirectoryState _state = NewDirectoryState.Identical; Im.Item.SetNextWidth(buttonSize.X * 2 + Im.Style.ItemSpacing.X);
var tmp = _currentModDirectory ?? mod.ModPath.Name;
public static void Reset() if (Im.Input.Text("##newModMove"u8, ref tmp))
{ {
_currentModDirectory = null; _currentModDirectory = tmp;
_state = NewDirectoryState.Identical; _state = modManager.NewDirectoryValid(mod.ModPath.Name, _currentModDirectory, out _);
} }
public static void Draw(ModManager modManager, Mod mod, Vector2 buttonSize) var (disabled, tt) = _state switch
{ {
Im.Item.SetNextWidth(buttonSize.X * 2 + Im.Style.ItemSpacing.X); NewDirectoryState.Identical => (true, "Current directory name is identical to new one."),
var tmp = _currentModDirectory ?? mod.ModPath.Name; NewDirectoryState.Empty => (true, "Please enter a new directory name first."),
if (ImGui.InputText("##newModMove", ref tmp, 64)) NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
{ NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."),
_currentModDirectory = tmp; NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."),
_state = modManager.NewDirectoryValid(mod.ModPath.Name, _currentModDirectory, out _); NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."),
} NewDirectoryState.ContainsInvalidSymbols => (true,
$"{_currentModDirectory} contains invalid symbols for FFXIV."),
var (disabled, tt) = _state switch _ => (true, "Unknown error."),
{ };
NewDirectoryState.Identical => (true, "Current directory name is identical to new one."), Im.Line.Same();
NewDirectoryState.Empty => (true, "Please enter a new directory name first."), if (ImEx.Button("Rename Mod Directory"u8, buttonSize, tt, disabled) && _currentModDirectory is not null)
NewDirectoryState.NonExisting => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."), {
NewDirectoryState.ExistsEmpty => (false, $"Move mod from {mod.ModPath.Name} to {_currentModDirectory}."), modManager.MoveModDirectory(mod, _currentModDirectory);
NewDirectoryState.ExistsNonEmpty => (true, $"{_currentModDirectory} already exists and is not empty."), Reset();
NewDirectoryState.ExistsAsFile => (true, $"{_currentModDirectory} exists as a file."), }
NewDirectoryState.ContainsInvalidSymbols => (true,
$"{_currentModDirectory} contains invalid symbols for FFXIV."), Im.Line.Same();
_ => (true, "Unknown error."), if (LunaStyle.DrawAlignedHelpMarker())
}; Im.Tooltip.Set(
Im.Line.Same(); "The mod directory name is used to correspond stored settings and sort orders, otherwise it has no influence on anything that is displayed.\n"u8
if (ImGuiUtil.DrawDisabledButton("Rename Mod Directory", buttonSize, tt, disabled) && _currentModDirectory != null) + "This can currently not be used on pre-existing folders and does not support merges or overwriting."u8);
{ }
modManager.MoveModDirectory(mod, _currentModDirectory); }
Reset();
} /// <summary> Handles input text and integers in separate fields without buffers for every single one. </summary>
private static class Input
Im.Line.Same(); {
ImGuiComponents.HelpMarker( // Special field indices to reuse the same string buffer.
"The mod directory name is used to correspond stored settings and sort orders, otherwise it has no influence on anything that is displayed.\n" public const int None = -1;
+ "This can currently not be used on pre-existing folders and does not support merges or overwriting."); public const int Name = -2;
} public const int Author = -3;
} public const int Version = -4;
public const int Website = -5;
/// <summary> Handles input text and integers in separate fields without buffers for every single one. </summary> public const int Path = -6;
private static class Input
{ // Temporary strings
// Special field indices to reuse the same string buffer. private static string? _currentEdit;
public const int None = -1; private static int _currentField = None;
public const int Name = -2; private static int _optionIndex = None;
public const int Author = -3;
public const int Version = -4; public static void Reset()
public const int Website = -5; {
public const int Path = -6; _currentEdit = null;
public const int Description = -7; _currentField = None;
_optionIndex = None;
// Temporary strings }
private static string? _currentEdit;
private static ModPriority? _currentGroupPriority; public static bool Text(ReadOnlySpan<byte> label, int field, int option, string oldValue, out string value, float width)
private static int _currentField = None; {
private static int _optionIndex = None; var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue;
Im.Item.SetNextWidth(width);
public static void Reset()
{ if (Im.Input.Text(label, ref tmp))
_currentEdit = null; {
_currentGroupPriority = null; _currentEdit = tmp;
_currentField = None; _optionIndex = option;
_optionIndex = None; _currentField = field;
} }
public static bool Text(string label, int field, int option, string oldValue, out string value, uint maxLength, float width) if (Im.Item.DeactivatedAfterEdit && _currentEdit is not null)
{ {
var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue; var ret = _currentEdit != oldValue;
Im.Item.SetNextWidth(width); value = _currentEdit;
Reset();
if (ImGui.InputText(label, ref tmp)) return ret;
{ }
_currentEdit = tmp;
_optionIndex = option; value = string.Empty;
_currentField = field; return false;
} }
}
if (ImGui.IsItemDeactivatedAfterEdit() && _currentEdit != null) }
{
var ret = _currentEdit != oldValue;
value = _currentEdit;
Reset();
return ret;
}
value = string.Empty;
return false;
}
public static bool Priority(string label, int field, int option, ModPriority oldValue, out ModPriority value, float width)
{
var tmp = (field == _currentField && option == _optionIndex ? _currentGroupPriority ?? oldValue : oldValue).Value;
Im.Item.SetNextWidth(width);
if (ImGui.InputInt(label, ref tmp, 0, 0))
{
_currentGroupPriority = new ModPriority(tmp);
_optionIndex = option;
_currentField = field;
}
if (ImGui.IsItemDeactivatedAfterEdit() && _currentGroupPriority != null)
{
var ret = _currentGroupPriority != oldValue;
value = _currentGroupPriority.Value;
Reset();
return ret;
}
value = ModPriority.Default;
return false;
}
}
}

View file

@ -1,8 +1,8 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using Luna;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.Communication; using Penumbra.Communication;
@ -22,7 +22,7 @@ public class ModPanelSettingsTab(
CommunicatorService communicator, CommunicatorService communicator,
ModGroupDrawer modGroupDrawer, ModGroupDrawer modGroupDrawer,
Configuration config) Configuration config)
: ITab, Luna.IUiService : ITab<ModPanelTab>
{ {
private bool _inherited; private bool _inherited;
private bool _temporary; private bool _temporary;
@ -31,8 +31,11 @@ public class ModPanelSettingsTab(
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Settings"u8; => "Settings"u8;
public ModPanelTab Identifier
=> ModPanelTab.Settings;
public void DrawHeader() public void PostTabButton()
=> tutorial.OpenTutorial(BasicTutorialSteps.ModOptions); => tutorial.OpenTutorial(BasicTutorialSteps.ModOptions);
public void Reset() public void Reset()

View file

@ -1,10 +1,5 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.UI.AdvancedWindow; using Penumbra.UI.AdvancedWindow;
@ -12,138 +7,88 @@ using ImGuiColor = ImSharp.ImGuiColor;
namespace Penumbra.UI.ModsTab; namespace Penumbra.UI.ModsTab;
public class ModPanelTabBar : IUiService public enum ModPanelTab
{ {
private enum ModPanelTabType Description,
{ Settings,
Description, ChangedItems,
Settings, Conflicts,
ChangedItems, Collections,
Conflicts, Edit,
Collections, };
Edit,
};
public readonly ModPanelSettingsTab Settings; public class ModPanelTabBar : TabBar<ModPanelTab>
public readonly ModPanelDescriptionTab Description; {
public readonly ModPanelCollectionsTab Collections; public readonly ModPanelSettingsTab Settings;
public readonly ModPanelConflictsTab Conflicts; public readonly ModPanelEditTab Edit;
public readonly ModPanelChangedItemsTab ChangedItems; private readonly ModManager _modManager;
public readonly ModPanelEditTab Edit; private readonly TutorialService _tutorial;
private readonly ModEditWindowFactory _modEditWindowFactory;
private readonly ModManager _modManager;
private readonly TutorialService _tutorial;
public readonly ITab[] Tabs; private Mod? _lastMod;
private ModPanelTabType _preferredTab = ModPanelTabType.Settings;
private Mod? _lastMod;
public ModPanelTabBar(ModEditWindowFactory modEditWindowFactory, ModPanelSettingsTab settings, ModPanelDescriptionTab description, public ModPanelTabBar(ModEditWindowFactory modEditWindowFactory, ModPanelSettingsTab settings, ModPanelDescriptionTab description,
ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelEditTab edit, ModManager modManager, ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelEditTab edit, ModManager modManager,
TutorialService tutorial, ModPanelCollectionsTab collections) TutorialService tutorial, ModPanelCollectionsTab collections, Logger log)
: base(nameof(ModPanelTabBar), log, settings, description, conflicts, changedItems, collections, edit)
{ {
_modEditWindowFactory = modEditWindowFactory; Flags = TabBarFlags.NoTooltip;
Settings = settings; Settings = settings;
Description = description; Edit = edit;
Conflicts = conflicts; _modManager = modManager;
ChangedItems = changedItems; _tutorial = tutorial;
Edit = edit; Buttons.AddButton(new AdvancedEditingButton(this, modEditWindowFactory), 0);
_modManager = modManager; }
_tutorial = tutorial;
Collections = collections;
Tabs = private sealed class AdvancedEditingButton(ModPanelTabBar parent, ModEditWindowFactory editFactory) : BaseButton
[ {
Settings, public override ReadOnlySpan<byte> Label
Description, => "Advanced Editing"u8;
Conflicts,
ChangedItems, public override void OnClick()
Collections, {
Edit, if (parent._lastMod is { } mod)
]; editFactory.OpenForMod(mod);
}
public override bool HasTooltip
=> true;
public override void DrawTooltip()
=> Im.Text(
"Clicking this will open a new window in which you can\nedit the following things per option for this mod:\n\n"u8
+ "\t\t- file redirections\n"u8
+ "\t\t- file swaps\n"u8
+ "\t\t- metadata manipulations\n"u8
+ "\t\t- model materials\n"u8
+ "\t\t- duplicates\n"u8
+ "\t\t- textures"u8);
} }
public void Draw(Mod mod) public void Draw(Mod mod)
{ {
var tabBarHeight = ImGui.GetCursorPosY(); var tabBarHeight = Im.Cursor.Y;
if (_lastMod != mod) _lastMod = mod;
{ base.Draw();
_lastMod = mod;
TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ToLabel(_preferredTab), out _, () => DrawAdvancedEditingButton(mod), Tabs);
}
else
{
TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ReadOnlySpan<byte>.Empty, out var label, () => DrawAdvancedEditingButton(mod),
Tabs);
_preferredTab = ToType(label);
}
DrawFavoriteButton(mod, tabBarHeight); DrawFavoriteButton(mod, tabBarHeight);
} }
private ReadOnlySpan<byte> ToLabel(ModPanelTabType type)
=> type switch
{
ModPanelTabType.Description => Description.Label,
ModPanelTabType.Settings => Settings.Label,
ModPanelTabType.ChangedItems => ChangedItems.Label,
ModPanelTabType.Conflicts => Conflicts.Label,
ModPanelTabType.Collections => Collections.Label,
ModPanelTabType.Edit => Edit.Label,
_ => ReadOnlySpan<byte>.Empty,
};
private ModPanelTabType ToType(ReadOnlySpan<byte> label)
{
if (label == Description.Label)
return ModPanelTabType.Description;
if (label == Settings.Label)
return ModPanelTabType.Settings;
if (label == ChangedItems.Label)
return ModPanelTabType.ChangedItems;
if (label == Conflicts.Label)
return ModPanelTabType.Conflicts;
if (label == Collections.Label)
return ModPanelTabType.Collections;
if (label == Edit.Label)
return ModPanelTabType.Edit;
return 0;
}
private void DrawAdvancedEditingButton(Mod mod)
{
if (ImGui.TabItemButton("Advanced Editing", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip))
{
_modEditWindowFactory.OpenForMod(mod);
}
ImGuiUtil.HoverTooltip(
"Clicking this will open a new window in which you can\nedit the following things per option for this mod:\n\n"
+ "\t\t- file redirections\n"
+ "\t\t- file swaps\n"
+ "\t\t- metadata manipulations\n"
+ "\t\t- model materials\n"
+ "\t\t- duplicates\n"
+ "\t\t- textures");
}
private void DrawFavoriteButton(Mod mod, float height) private void DrawFavoriteButton(Mod mod, float height)
{ {
var size = ImEx.Icon.CalculateSize(LunaStyle.FavoriteIcon) + Im.Style.FramePadding * 2; var size = ImEx.Icon.CalculateSize(LunaStyle.FavoriteIcon) + Im.Style.FramePadding * 2;
var newPos = new Vector2(ImGui.GetWindowWidth() - size.X - Im.Style.ItemSpacing.X, height); var newPos = new Vector2(Im.Window.Width - size.X - Im.Style.ItemSpacing.X, height);
if (ImGui.GetScrollMaxX() > 0) if (Im.Scroll.MaximumX > 0)
newPos.X += ImGui.GetScrollX(); newPos.X += Im.Scroll.X;
var rectUpper = ImGui.GetWindowPos() + newPos; var rectUpper = Im.Window.Position + newPos;
var color = ImGui.IsMouseHoveringRect(rectUpper, rectUpper + size) ? Im.Style[ImGuiColor.Text] : var color = Im.Mouse.IsHoveringRectangle(rectUpper, rectUpper + size) ? Im.Style[ImGuiColor.Text] :
mod.Favorite ? LunaStyle.FavoriteColor : Im.Style[ImGuiColor.TextDisabled]; mod.Favorite ? LunaStyle.FavoriteColor : Im.Style[ImGuiColor.TextDisabled];
using var c = ImGuiColor.Text.Push(color) using var c = ImGuiColor.Text.Push(color)
.Push(ImGuiColor.Button, Vector4.Zero) .Push(ImGuiColor.Button, Vector4.Zero)
.Push(ImGuiColor.ButtonHovered, Vector4.Zero) .Push(ImGuiColor.ButtonHovered, Vector4.Zero)
.Push(ImGuiColor.ButtonActive, Vector4.Zero); .Push(ImGuiColor.ButtonActive, Vector4.Zero);
ImGui.SetCursorPos(newPos); Im.Cursor.Position = newPos;
if (ImEx.Icon.Button(LunaStyle.FavoriteIcon)) if (ImEx.Icon.Button(LunaStyle.FavoriteIcon))
_modManager.DataEditor.ChangeModFavorite(mod, !mod.Favorite); _modManager.DataEditor.ChangeModFavorite(mod, !mod.Favorite);
@ -151,6 +96,6 @@ public class ModPanelTabBar : IUiService
_tutorial.OpenTutorial(BasicTutorialSteps.Favorites); _tutorial.OpenTutorial(BasicTutorialSteps.Favorites);
if (hovered) if (hovered)
ImGui.SetTooltip("Favorite"); Im.Tooltip.Set("Favorite"u8);
} }
} }

View file

@ -2,7 +2,7 @@ using Dalamud.Bindings.ImGui;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImSharp; using ImSharp;
using OtterGui.Widgets; using Luna;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
@ -16,7 +16,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ResourceWatcher; namespace Penumbra.UI.ResourceWatcher;
public sealed class ResourceWatcher : IDisposable, ITab, Luna.IUiService public sealed class ResourceWatcher : IDisposable, ITab<TabType>
{ {
public const int DefaultMaxEntries = 1024; public const int DefaultMaxEntries = 1024;
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction; public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
@ -96,6 +96,9 @@ public sealed class ResourceWatcher : IDisposable, ITab, Luna.IUiService
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Resource Logger"u8; => "Resource Logger"u8;
public TabType Identifier
=> TabType.ResourceWatcher;
public void DrawContent() public void DrawContent()
{ {
UpdateRecords(); UpdateRecords();

View file

@ -2,10 +2,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using ImSharp; using ImSharp;
using Luna; using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Table; using OtterGui.Table;
using OtterGui.Text;
using Penumbra.Enums; using Penumbra.Enums;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.String; using Penumbra.String;
@ -54,16 +51,16 @@ internal sealed class ResourceWatcherTable : Table<Record>
=> DrawByteString(item.Path, 280 * Im.Style.GlobalScale); => DrawByteString(item.Path, 280 * Im.Style.GlobalScale);
} }
private static unsafe void DrawByteString(CiByteString path, float length) private static void DrawByteString(CiByteString path, float length)
{ {
if (path.IsEmpty) if (path.IsEmpty)
return; return;
var size = ImUtf8.CalcTextSize(path.Span); var size = Im.Font.CalculateSize(path.Span);
var clicked = false; var clicked = false;
if (size.X <= length) if (size.X <= length)
{ {
clicked = ImUtf8.Selectable(path.Span); clicked = Im.Selectable(path.Span);
} }
else else
{ {
@ -71,10 +68,11 @@ internal sealed class ResourceWatcherTable : Table<Record>
using (Im.Group()) using (Im.Group())
{ {
CiByteString shortPath; CiByteString shortPath;
if (fileName != -1) var icon = FontAwesomeIcon.EllipsisH.Icon();
if (fileName is not -1)
{ {
using var font = ImRaii.PushFont(UiBuilder.IconFont); using var font = AwesomeIcon.Font.Push();
clicked = ImUtf8.Selectable(FontAwesomeIcon.EllipsisH.ToIconString()); clicked = Im.Selectable(icon.Span);
Im.Line.SameInner(); Im.Line.SameInner();
shortPath = path.Substring(fileName, path.Length - fileName); shortPath = path.Substring(fileName, path.Length - fileName);
} }
@ -83,14 +81,14 @@ internal sealed class ResourceWatcherTable : Table<Record>
shortPath = path; shortPath = path;
} }
clicked |= ImUtf8.Selectable(shortPath.Span, false, ImGuiSelectableFlags.AllowItemOverlap); clicked |= Im.Selectable(shortPath.Span, false, SelectableFlags.AllowOverlap);
} }
Im.Tooltip.OnHover(path.Span); Im.Tooltip.OnHover(path.Span);
} }
if (clicked) if (clicked)
ImUtf8.SetClipboardText(path.Span); Im.Clipboard.Set(path.Span);
} }
private sealed class RecordTypeColumn : ColumnFlags<RecordType, Record> private sealed class RecordTypeColumn : ColumnFlags<RecordType, Record>
@ -153,13 +151,13 @@ internal sealed class ResourceWatcherTable : Table<Record>
public override float Width public override float Width
=> UiBuilder.MonoFont.GetCharAdvance('0') * 17; => UiBuilder.MonoFont.GetCharAdvance('0') * 17;
public override unsafe string ToName(Record item) public override string ToName(Record item)
=> item.Crc64 != 0 ? $"{item.Crc64:X16}" : string.Empty; => item.Crc64 is not 0 ? $"{item.Crc64:X16}" : string.Empty;
public override unsafe void DrawColumn(Record item, int _) public override unsafe void DrawColumn(Record item, int _)
{ {
using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null); using var font = item.Handle is null ? null : Im.Font.PushMono();
ImUtf8.Text(ToName(item)); Im.Text(ToName(item));
} }
} }
@ -334,17 +332,17 @@ internal sealed class ResourceWatcherTable : Table<Record>
var (icon, color, tt) = item.LoadState switch var (icon, color, tt) = item.LoadState switch
{ {
LoadState.Success => (FontAwesomeIcon.CheckCircle, ColorId.IncreasedMetaValue.Value(), LoadState.Success => (FontAwesomeIcon.CheckCircle, ColorId.IncreasedMetaValue.Value(),
$"Successfully loaded ({(byte)item.LoadState})."), new StringU8($"Successfully loaded ({(byte)item.LoadState}).")),
LoadState.FailedSubResource => (FontAwesomeIcon.ExclamationCircle, ColorId.DecreasedMetaValue.Value(), LoadState.FailedSubResource => (FontAwesomeIcon.ExclamationCircle, ColorId.DecreasedMetaValue.Value(),
$"Dependencies failed to load ({(byte)item.LoadState})."), new StringU8($"Dependencies failed to load ({(byte)item.LoadState}).")),
<= LoadState.Constructed => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(), <= LoadState.Constructed => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(),
$"Not yet loaded ({(byte)item.LoadState})."), new StringU8($"Not yet loaded ({(byte)item.LoadState}).")),
< LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), $"Loading asynchronously ({(byte)item.LoadState})."), < LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), new StringU8($"Loading asynchronously ({(byte)item.LoadState}).")),
> LoadState.Success => (FontAwesomeIcon.Times, ColorId.DecreasedMetaValue.Value(), > LoadState.Success => (FontAwesomeIcon.Times, ColorId.DecreasedMetaValue.Value(),
$"Failed to load ({(byte)item.LoadState})."), new StringU8($"Failed to load ({(byte)item.LoadState}).")),
}; };
ImEx.Icon.Draw(icon.Icon(), color); ImEx.Icon.Draw(icon.Icon(), color);
ImGuiUtil.HoverTooltip(tt); Im.Tooltip.OnHover(tt);
} }
public override int Compare(Record lhs, Record rhs) public override int Compare(Record lhs, Record rhs)
@ -361,8 +359,8 @@ internal sealed class ResourceWatcherTable : Table<Record>
public override unsafe void DrawColumn(Record item, int _) public override unsafe void DrawColumn(Record item, int _)
{ {
using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null); using var font = item.Handle is null ? null : Im.Font.PushMono();
ImGuiUtil.RightAlign(ToName(item)); ImEx.TextRightAligned(ToName(item));
} }
} }
@ -447,7 +445,7 @@ internal sealed class ResourceWatcherTable : Table<Record>
=> 30 * Im.Style.GlobalScale; => 30 * Im.Style.GlobalScale;
public override void DrawColumn(Record item, int _) public override void DrawColumn(Record item, int _)
=> ImGuiUtil.RightAlign(item.RefCount.ToString()); => ImEx.TextRightAligned($"{item.RefCount}");
public override int Compare(Record lhs, Record rhs) public override int Compare(Record lhs, Record rhs)
=> lhs.RefCount.CompareTo(rhs.RefCount); => lhs.RefCount.CompareTo(rhs.RefCount);
@ -462,7 +460,7 @@ internal sealed class ResourceWatcherTable : Table<Record>
=> item.OsThreadId.ToString(); => item.OsThreadId.ToString();
public override void DrawColumn(Record item, int _) public override void DrawColumn(Record item, int _)
=> ImGuiUtil.RightAlign(ToName(item)); => ImEx.TextRightAligned($"{item.OsThreadId}");
public override int Compare(Record lhs, Record rhs) public override int Compare(Record lhs, Record rhs)
=> lhs.OsThreadId.CompareTo(rhs.OsThreadId); => lhs.OsThreadId.CompareTo(rhs.OsThreadId);

View file

@ -1,9 +1,9 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using Luna;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.Communication; using Penumbra.Communication;
@ -15,19 +15,22 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class ChangedItemsTab( public sealed class ChangedItemsTab(
CollectionManager collectionManager, CollectionManager collectionManager,
CollectionSelectHeader collectionHeader, CollectionSelectHeader collectionHeader,
ChangedItemDrawer drawer, ChangedItemDrawer drawer,
CommunicatorService communicator) CommunicatorService communicator)
: ITab, Luna.IUiService : ITab<TabType>
{ {
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Changed Items"u8; => "Changed Items"u8;
private string _changedItemFilter = string.Empty; public TabType Identifier
private string _changedItemModFilter = string.Empty; => TabType.ChangedItems;
private Vector2 _buttonSize;
private string _changedItemFilter = string.Empty;
private string _changedItemModFilter = string.Empty;
private Vector2 _buttonSize;
public void DrawContent() public void DrawContent()
{ {
@ -105,7 +108,7 @@ public class ChangedItemsTab(
if (ImUtf8.Selectable(first.Name, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 }) if (ImUtf8.Selectable(first.Name, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 })
&& ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyCtrl
&& first is Mod mod) && first is Mod mod)
communicator.SelectTab.Invoke(new SelectTab.Arguments(TabType.Mods, mod)); communicator.SelectTab.Invoke(new SelectTab.Arguments(Api.Enums.TabType.Mods, mod));
if (!Im.Item.Hovered()) if (!Im.Item.Hovered())
return; return;

View file

@ -2,8 +2,9 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin; using Dalamud.Plugin;
using ImSharp; using ImSharp;
using Luna;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Widgets; using Penumbra.Api.Enums;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
@ -12,7 +13,7 @@ using Penumbra.UI.CollectionTab;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public sealed class CollectionsTab : IDisposable, ITab, Luna.IUiService public sealed class CollectionsTab : ITab<TabType>, IDisposable
{ {
private readonly EphemeralConfig _config; private readonly EphemeralConfig _config;
private readonly CollectionSelector _selector; private readonly CollectionSelector _selector;
@ -38,6 +39,9 @@ public sealed class CollectionsTab : IDisposable, ITab, Luna.IUiService
} }
} }
public TabType Identifier
=> TabType.Collections;
public CollectionsTab(IDalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator, IncognitoService incognito, public CollectionsTab(IDalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator, IncognitoService incognito,
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, SaveService saveService) CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, SaveService saveService)
{ {

View file

@ -1,113 +0,0 @@
using Dalamud.Bindings.ImGui;
using OtterGui.Widgets;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Services;
using Penumbra.UI.Tabs.Debug;
using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher;
namespace Penumbra.UI.Tabs;
public class ConfigTabBar : IDisposable, Luna.IUiService
{
private readonly CommunicatorService _communicator;
public readonly SettingsTab Settings;
public readonly ModsTab Mods;
public readonly CollectionsTab Collections;
public readonly ChangedItemsTab ChangedItems;
public readonly EffectiveTab Effective;
public readonly DebugTab Debug;
public readonly ResourceTab Resource;
public readonly Watcher Watcher;
public readonly OnScreenTab OnScreen;
public readonly MessagesTab Messages;
public readonly ITab[] Tabs;
/// <summary> The tab to select on the next Draw call, if any. </summary>
public TabType SelectTab = TabType.None;
public ConfigTabBar(CommunicatorService communicator, SettingsTab settings, ModsTab mods, CollectionsTab collections,
ChangedItemsTab changedItems, EffectiveTab effective, DebugTab debug, ResourceTab resource, Watcher watcher,
OnScreenTab onScreen, MessagesTab messages)
{
_communicator = communicator;
Settings = settings;
Mods = mods;
Collections = collections;
ChangedItems = changedItems;
Effective = effective;
Debug = debug;
Resource = resource;
Watcher = watcher;
OnScreen = onScreen;
Messages = messages;
Tabs =
[
Settings,
Collections,
Mods,
ChangedItems,
Effective,
OnScreen,
Debug,
Resource,
Watcher,
Messages,
];
_communicator.SelectTab.Subscribe(OnSelectTab, Communication.SelectTab.Priority.ConfigTabBar);
}
public void Dispose()
=> _communicator.SelectTab.Unsubscribe(OnSelectTab);
public TabType Draw()
{
if (TabBar.Draw(string.Empty, ImGuiTabBarFlags.NoTooltip, ToLabel(SelectTab), out var currentLabel, () => { }, Tabs))
SelectTab = TabType.None;
return FromLabel(currentLabel);
}
private ReadOnlySpan<byte> ToLabel(TabType type)
=> type switch
{
TabType.Settings => Settings.Label,
TabType.Mods => Mods.Label,
TabType.Collections => Collections.Label,
TabType.ChangedItems => ChangedItems.Label,
TabType.EffectiveChanges => Effective.Label,
TabType.OnScreen => OnScreen.Label,
TabType.ResourceWatcher => Watcher.Label,
TabType.Debug => Debug.Label,
TabType.ResourceManager => Resource.Label,
TabType.Messages => Messages.Label,
_ => ReadOnlySpan<byte>.Empty,
};
private TabType FromLabel(ReadOnlySpan<byte> label)
{
// @formatter:off
if (label == Mods.Label) return TabType.Mods;
if (label == Collections.Label) return TabType.Collections;
if (label == Settings.Label) return TabType.Settings;
if (label == ChangedItems.Label) return TabType.ChangedItems;
if (label == Effective.Label) return TabType.EffectiveChanges;
if (label == OnScreen.Label) return TabType.OnScreen;
if (label == Messages.Label) return TabType.Messages;
if (label == Watcher.Label) return TabType.ResourceWatcher;
if (label == Debug.Label) return TabType.Debug;
if (label == Resource.Label) return TabType.ResourceManager;
// @formatter:on
return TabType.None;
}
private void OnSelectTab(in SelectTab.Arguments arguments)
{
SelectTab = arguments.Tab;
if (arguments.Mod is not null)
Mods.SelectMod = arguments.Mod;
}
}

View file

@ -14,8 +14,8 @@ using Luna;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OtterGui; using OtterGui;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
@ -67,7 +67,7 @@ public class Diagnostics(ServiceManager provider) : IUiService
} }
} }
public class DebugTab : Window, ITab public sealed class DebugTab : Window, ITab<TabType>
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly CollectionManager _collectionManager; private readonly CollectionManager _collectionManager;
@ -173,6 +173,9 @@ public class DebugTab : Window, ITab
public bool IsVisible public bool IsVisible
=> _config is { DebugMode: true, Ephemeral.DebugSeparateWindow: false }; => _config is { DebugMode: true, Ephemeral.DebugSeparateWindow: false };
public TabType Identifier
=> TabType.Debug;
#if DEBUG #if DEBUG
private const string DebugVersionString = "(Debug)"; private const string DebugVersionString = "(Debug)";
#else #else

View file

@ -1,10 +1,11 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using ImSharp; using ImSharp;
using Luna;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Cache; using Penumbra.Collections.Cache;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
@ -15,12 +16,15 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader) public sealed class EffectiveTab(CollectionManager collectionManager, CollectionSelectHeader collectionHeader)
: ITab, Luna.IUiService : ITab<TabType>
{ {
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Effective Changes"u8; => "Effective Changes"u8;
public TabType Identifier
=> TabType.EffectiveChanges;
public void DrawContent() public void DrawContent()
{ {
SetupEffectiveSizes(); SetupEffectiveSizes();

View file

@ -0,0 +1,55 @@
using Luna;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Services;
using Penumbra.UI.Tabs.Debug;
using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher;
namespace Penumbra.UI.Tabs;
public sealed class MainTabBar : TabBar<TabType>, IDisposable
{
public readonly ModsTab Mods;
private readonly EphemeralConfig _config;
private readonly SelectTab _selectTab;
public MainTabBar(Logger log,
SettingsTab settings,
ModsTab mods,
CollectionsTab collections,
ChangedItemsTab changedItems,
EffectiveTab effectiveChanges,
DebugTab debug,
ResourceTab resources,
Watcher watcher,
OnScreenTab onScreen,
MessagesTab messages, EphemeralConfig config, CommunicatorService communicator)
: base(nameof(MainTabBar), log, settings, collections, mods, changedItems, effectiveChanges, onScreen,
resources, watcher, debug, messages)
{
Mods = mods;
_config = config;
_selectTab = communicator.SelectTab;
_selectTab.Subscribe(OnSelectTab, SelectTab.Priority.MainTabBar);
TabSelected.Subscribe(OnTabSelected, 0);
}
private void OnSelectTab(in SelectTab.Arguments arguments)
{
NextTab = arguments.Tab;
if (arguments.Mod is not null)
Mods.SelectMod = arguments.Mod;
}
public void Dispose()
{
_selectTab.Unsubscribe(OnSelectTab);
}
private void OnTabSelected(in TabType type)
{
_config.SelectedTab = type;
_config.Save();
}
}

View file

@ -1,9 +1,10 @@
using OtterGui.Widgets; using Luna;
using Penumbra.Services; using Penumbra.Api.Enums;
using MessageService = Penumbra.Services.MessageService;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class MessagesTab(MessageService messages) : ITab, Luna.IUiService public sealed class MessagesTab(MessageService messages) : ITab<TabType>
{ {
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Messages"u8; => "Messages"u8;
@ -13,4 +14,7 @@ public class MessagesTab(MessageService messages) : ITab, Luna.IUiService
public void DrawContent() public void DrawContent()
=> messages.DrawNotificationLog(); => messages.DrawNotificationLog();
public TabType Identifier
=> TabType.Messages;
} }

View file

@ -7,7 +7,7 @@ using Dalamud.Interface;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using ImSharp; using ImSharp;
using OtterGui.Widgets; using Luna;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Mods; using Penumbra.Mods;
@ -19,7 +19,7 @@ using Penumbra.GameData.Interop;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class ModsTab( public sealed class ModsTab(
ModManager modManager, ModManager modManager,
CollectionManager collectionManager, CollectionManager collectionManager,
ModFileSystemSelector selector, ModFileSystemSelector selector,
@ -31,7 +31,7 @@ public class ModsTab(
CollectionSelectHeader collectionHeader, CollectionSelectHeader collectionHeader,
ITargetManager targets, ITargetManager targets,
ObjectManager objects) ObjectManager objects)
: ITab, Luna.IUiService : ITab<TabType>
{ {
private readonly ActiveCollections _activeCollections = collectionManager.Active; private readonly ActiveCollections _activeCollections = collectionManager.Active;
@ -41,7 +41,10 @@ public class ModsTab(
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Mods"u8; => "Mods"u8;
public void DrawHeader() public TabType Identifier
=> TabType.Mods;
public void PostTabButton()
=> tutorial.OpenTutorial(BasicTutorialSteps.Mods); => tutorial.OpenTutorial(BasicTutorialSteps.Mods);
public Mod SelectMod public Mod SelectMod
@ -60,7 +63,8 @@ public class ModsTab(
collectionHeader.Draw(false); collectionHeader.Draw(false);
using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero); using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero);
using (var child = ImRaii.Child("##ModsTabMod", Im.ContentRegion.Available with { Y = config.HideRedrawBar ? 0 : -Im.Style.FrameHeight }, using (var child = ImRaii.Child("##ModsTabMod",
Im.ContentRegion.Available with { Y = config.HideRedrawBar ? 0 : -Im.Style.FrameHeight },
true, ImGuiWindowFlags.HorizontalScrollbar)) true, ImGuiWindowFlags.HorizontalScrollbar))
{ {
style.Pop(); style.Pop();

View file

@ -1,9 +1,10 @@
using OtterGui.Widgets; using Luna;
using Penumbra.Api.Enums;
using Penumbra.UI.AdvancedWindow; using Penumbra.UI.AdvancedWindow;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab, Luna.IUiService public sealed class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab<TabType>
{ {
private readonly ResourceTreeViewer _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { }); private readonly ResourceTreeViewer _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
@ -12,4 +13,7 @@ public class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) :
public void DrawContent() public void DrawContent()
=> _viewer.Draw(); => _viewer.Draw();
public TabType Identifier
=> TabType.OnScreen;
} }

View file

@ -4,17 +4,21 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD; using FFXIVClientStructs.STD;
using ImSharp; using ImSharp;
using Luna;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Widgets; using Penumbra.Api.Enums;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.String.Classes; using Penumbra.String.Classes;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner) public sealed class ResourceTab(Configuration config, ResourceManagerService resourceManager, ISigScanner sigScanner)
: ITab, Luna.IUiService : ITab<TabType>
{ {
public TabType Identifier
=> TabType.ResourceManager;
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Resource Manager"u8; => "Resource Manager"u8;
@ -52,7 +56,8 @@ public class ResourceTab(Configuration config, ResourceManagerService resourceMa
private string _resourceManagerFilter = string.Empty; private string _resourceManagerFilter = string.Empty;
/// <summary> Draw a single resource map. </summary> /// <summary> Draw a single resource map. </summary>
private unsafe void DrawResourceMap(ResourceCategory category, uint ext, StdMap<uint, FFXIVClientStructs.Interop.Pointer<ResourceHandle>>* map) private unsafe void DrawResourceMap(ResourceCategory category, uint ext,
StdMap<uint, FFXIVClientStructs.Interop.Pointer<ResourceHandle>>* map)
{ {
if (map == null) if (map == null)
return; return;

View file

@ -1,7 +1,6 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.Utility;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
@ -12,6 +11,7 @@ using OtterGui.Raii;
using OtterGui.Text; using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Interop; using Penumbra.Interop;
using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.PostProcessing;
@ -23,10 +23,13 @@ using Penumbra.UI.ModsTab;
namespace Penumbra.UI.Tabs; namespace Penumbra.UI.Tabs;
public class SettingsTab : ITab, IUiService public sealed class SettingsTab : ITab<TabType>
{ {
public const int RootDirectoryMaxLength = 64; public const int RootDirectoryMaxLength = 64;
public TabType Identifier
=> TabType.Settings;
public ReadOnlySpan<byte> Label public ReadOnlySpan<byte> Label
=> "Settings"u8; => "Settings"u8;